@canmingir/link-express 1.6.11 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.ts +29 -0
- package/bin/event-listener.ts +10 -0
- package/index.ts +2 -0
- package/package.json +66 -12
- package/{prepare.js → prepare.ts} +1 -1
- package/src/{authorization.js → authorization.ts} +31 -9
- package/src/config.ts +112 -0
- package/src/dynamodb.ts +24 -0
- package/src/{error.js → error.ts} +30 -11
- package/src/event/client/adapters/KafkaAdapter.ts +140 -0
- package/src/event/client/adapters/SocketAdapter.ts +59 -0
- package/src/event/client/adapters/TxEventQAdapter.ts +232 -0
- package/src/event/client/eventManager.ts +244 -0
- package/src/event/client/index.ts +50 -0
- package/src/event/client/metrics.ts +171 -0
- package/src/event/client/types/types.ts +43 -0
- package/src/event/index.ts +3 -0
- package/src/event/server/server.ts +55 -0
- package/src/event/src/Event.ts +201 -0
- package/src/express.ts +66 -0
- package/src/lib/{settings.js → settings.ts} +16 -4
- package/src/lib/test.ts +68 -0
- package/src/{logger.js → logger.ts} +11 -9
- package/src/metrics/{dbMetrics.js → dbMetrics.ts} +37 -14
- package/src/models/Organization.model.ts +27 -0
- package/src/models/Permission.model.ts +48 -0
- package/src/models/Project.model.ts +50 -0
- package/src/models/Settings.model.ts +31 -0
- package/src/models/{index.js → index.ts} +8 -8
- package/src/platform.ts +55 -0
- package/src/postgres.ts +309 -0
- package/src/routes/index.ts +8 -0
- package/src/routes/metrics.ts +13 -0
- package/src/routes/oauth.ts +267 -0
- package/src/routes/{organizations.js → organizations.ts} +10 -8
- package/src/routes/{permissions.js → permissions.ts} +8 -6
- package/src/routes/{projects.js → projects.ts} +22 -16
- package/src/routes/settings.ts +31 -0
- package/src/schemas/{Organization.js → Organization.ts} +2 -2
- package/src/schemas/{Permission.js → Permission.ts} +2 -2
- package/src/schemas/{Project.js → Project.ts} +2 -2
- package/src/schemas/index.ts +5 -0
- package/src/sequelize.ts +13 -0
- package/src/{test.js → test.ts} +11 -13
- package/src/types/Organization.ts +9 -0
- package/src/types/Permission.ts +13 -0
- package/src/types/Project.ts +14 -0
- package/src/types/index.ts +5 -0
- package/tsconfig.json +32 -0
- package/.eslintrc.js +0 -20
- package/index.js +0 -1
- package/src/config.js +0 -21
- package/src/dynamodb.js +0 -18
- package/src/express.js +0 -58
- package/src/lib/test.js +0 -69
- package/src/models/Organization.js +0 -17
- package/src/models/Permission.js +0 -33
- package/src/models/Project.js +0 -37
- package/src/models/Settings.js +0 -21
- package/src/openapi.js +0 -40
- package/src/platform.js +0 -56
- package/src/postgres.js +0 -308
- package/src/routes/index.js +0 -15
- package/src/routes/metrics.js +0 -12
- package/src/routes/oauth.js +0 -213
- package/src/routes/settings.js +0 -25
- package/src/schemas/index.js +0 -5
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type Callback<T = object> = (payload: T) => void;
|
|
2
|
+
|
|
3
|
+
export interface EventAdapter {
|
|
4
|
+
connect(): Promise<void>;
|
|
5
|
+
disconnect(): Promise<void>;
|
|
6
|
+
publish(type: string, payload: object): Promise<void>;
|
|
7
|
+
subscribe(type: string): Promise<void>;
|
|
8
|
+
unsubscribe(type: string): Promise<void>;
|
|
9
|
+
onMessage(handler: (type: string, payload: object) => void): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BaseInitOptions {
|
|
13
|
+
type: "inMemory" | "kafka" | "txeventq";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface InMemoryOptions extends BaseInitOptions {
|
|
17
|
+
type: "inMemory";
|
|
18
|
+
host: string;
|
|
19
|
+
port?: number;
|
|
20
|
+
protocol: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface KafkaOptions extends BaseInitOptions {
|
|
24
|
+
type: "kafka";
|
|
25
|
+
clientId: string;
|
|
26
|
+
brokers: string[];
|
|
27
|
+
groupId: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TxEventQOptions extends BaseInitOptions {
|
|
31
|
+
type: "txeventq";
|
|
32
|
+
connectString: string;
|
|
33
|
+
user: string;
|
|
34
|
+
password: string;
|
|
35
|
+
instantClientPath?: string;
|
|
36
|
+
walletPath?: string;
|
|
37
|
+
consumerName?: string;
|
|
38
|
+
batchSize?: number;
|
|
39
|
+
waitTime?: number;
|
|
40
|
+
topics?: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type InitOptions = InMemoryOptions | KafkaOptions | TxEventQOptions;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from 'socket.io';
|
|
4
|
+
import { Socket } from 'socket.io';
|
|
5
|
+
import http from 'http';
|
|
6
|
+
|
|
7
|
+
const server = http.createServer();
|
|
8
|
+
const io = new Server(server, {
|
|
9
|
+
cors: { origin: '*' }
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type Subscriptions = Record<string, Set<string>>;
|
|
13
|
+
const subscriptions: Subscriptions = {};
|
|
14
|
+
|
|
15
|
+
io.on('connection', (socket: Socket) => {
|
|
16
|
+
console.log('Client connected:', socket.id);
|
|
17
|
+
|
|
18
|
+
socket.on('subscribe', (type: string) => {
|
|
19
|
+
if (!subscriptions[type]) subscriptions[type] = new Set();
|
|
20
|
+
subscriptions[type].add(socket.id);
|
|
21
|
+
console.log(`Socket ${socket.id} subscribed to ${type}`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
socket.on('unsubscribe', (type: string) => {
|
|
25
|
+
if (subscriptions[type]) {
|
|
26
|
+
subscriptions[type].delete(socket.id);
|
|
27
|
+
if (subscriptions[type].size === 0) delete subscriptions[type];
|
|
28
|
+
console.log(`Socket ${socket.id} unsubscribed from ${type}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
socket.on('publish', ({ type, payload }: { type: string; payload: object }) => {
|
|
33
|
+
console.log(`Publish: ${type}`, payload);
|
|
34
|
+
if (subscriptions[type]) {
|
|
35
|
+
subscriptions[type].forEach((sid) => {
|
|
36
|
+
if (sid !== socket.id) {
|
|
37
|
+
io.to(sid).emit('event', { type, payload });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
socket.on('disconnect', () => {
|
|
44
|
+
Object.keys(subscriptions).forEach((type) => {
|
|
45
|
+
subscriptions[type].delete(socket.id);
|
|
46
|
+
if (subscriptions[type].size === 0) delete subscriptions[type];
|
|
47
|
+
});
|
|
48
|
+
console.log('Client disconnected:', socket.id);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const PORT = process.env.PORT || 8080;
|
|
53
|
+
server.listen(PORT, () => {
|
|
54
|
+
console.log(`Event server listening on port ${PORT}`);
|
|
55
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import client from "prom-client";
|
|
2
|
+
import { v4 as uuid } from "uuid";
|
|
3
|
+
|
|
4
|
+
const subscriptions = {};
|
|
5
|
+
const messages = new Map();
|
|
6
|
+
|
|
7
|
+
const eventPublishCounter = new client.Counter({
|
|
8
|
+
name: "events_published_total",
|
|
9
|
+
help: "Total number of events published",
|
|
10
|
+
labelNames: ["event_type"],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const eventSubscriptionGauge = new client.Gauge({
|
|
14
|
+
name: "active_event_subscriptions",
|
|
15
|
+
help: "Number of active event subscriptions",
|
|
16
|
+
labelNames: ["event_type"],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const eventPublishDuration = new client.Histogram({
|
|
20
|
+
name: "event_publish_duration_seconds",
|
|
21
|
+
help: "Time taken to publish events",
|
|
22
|
+
labelNames: ["event_type"],
|
|
23
|
+
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Track payload size for analysis
|
|
27
|
+
const eventPayloadSize = new client.Histogram({
|
|
28
|
+
name: "event_payload_size_bytes",
|
|
29
|
+
help: "Size of event payloads in bytes",
|
|
30
|
+
labelNames: ["event_type"],
|
|
31
|
+
buckets: [10, 100, 1000, 10000, 100000, 1000000],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Track error rates
|
|
35
|
+
const eventPublishErrors = new client.Counter({
|
|
36
|
+
name: "event_publish_errors_total",
|
|
37
|
+
help: "Total number of event publish errors",
|
|
38
|
+
labelNames: ["event_type", "error_type"],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Track callback processing duration
|
|
42
|
+
const callbackProcessingDuration = new client.Histogram({
|
|
43
|
+
name: "event_callback_duration_seconds",
|
|
44
|
+
help: "Time taken to process event callbacks",
|
|
45
|
+
labelNames: ["event_type"],
|
|
46
|
+
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Track subscription rates
|
|
50
|
+
const subscriptionRate = new client.Counter({
|
|
51
|
+
name: "event_subscriptions_total",
|
|
52
|
+
help: "Total number of event subscriptions created",
|
|
53
|
+
labelNames: ["event_type"],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Track unsubscription rates
|
|
57
|
+
const unsubscriptionRate = new client.Counter({
|
|
58
|
+
name: "event_unsubscriptions_total",
|
|
59
|
+
help: "Total number of event unsubscriptions",
|
|
60
|
+
labelNames: ["event_type"],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Track throughput (events processed per second)
|
|
64
|
+
const eventThroughput = new client.Counter({
|
|
65
|
+
name: "event_callbacks_processed_total",
|
|
66
|
+
help: "Total number of event callbacks processed successfully",
|
|
67
|
+
labelNames: ["event_type"],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const colors = [
|
|
71
|
+
"red",
|
|
72
|
+
"green",
|
|
73
|
+
"yellow",
|
|
74
|
+
"blue",
|
|
75
|
+
"magenta",
|
|
76
|
+
"cyan",
|
|
77
|
+
"white",
|
|
78
|
+
"gray",
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
function typeColor(type) {
|
|
82
|
+
const hash = (str) => {
|
|
83
|
+
let hash = 0;
|
|
84
|
+
for (let i = 0; i < str.length; i++) {
|
|
85
|
+
const char = str.charCodeAt(i);
|
|
86
|
+
hash = (hash << 5) - hash + char;
|
|
87
|
+
hash = hash & hash;
|
|
88
|
+
}
|
|
89
|
+
return Math.abs(hash);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const colorIndex = hash(type) % colors.length;
|
|
93
|
+
return colors[colorIndex];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const subscribe = (...args) => {
|
|
97
|
+
if (args.length < 2) {
|
|
98
|
+
throw new Error("subscribe requires at least 2 arguments");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const callback = args.pop();
|
|
102
|
+
const type = args.join(".");
|
|
103
|
+
const id = uuid();
|
|
104
|
+
|
|
105
|
+
console.debug("node-event", "subscribe", type, id);
|
|
106
|
+
|
|
107
|
+
if (type === "__proto__" || type === "constructor" || type === "prototype") {
|
|
108
|
+
throw new Error("Invalid subscription type");
|
|
109
|
+
}
|
|
110
|
+
if (!subscriptions[type]) {
|
|
111
|
+
subscriptions[type] = {};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const registry = {
|
|
115
|
+
id,
|
|
116
|
+
type,
|
|
117
|
+
callback,
|
|
118
|
+
unsubscribe: () => {
|
|
119
|
+
console.debug("node-event", "unsubscribe", type, id);
|
|
120
|
+
delete subscriptions[type][id];
|
|
121
|
+
|
|
122
|
+
// Track unsubscription
|
|
123
|
+
unsubscriptionRate.labels(type).inc();
|
|
124
|
+
|
|
125
|
+
if (Object.keys(subscriptions[type]).length === 0) {
|
|
126
|
+
delete subscriptions[type];
|
|
127
|
+
eventSubscriptionGauge.labels(type).set(0);
|
|
128
|
+
} else {
|
|
129
|
+
eventSubscriptionGauge
|
|
130
|
+
.labels(type)
|
|
131
|
+
.set(Object.keys(subscriptions[type]).length);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
subscriptions[type][id] = registry;
|
|
137
|
+
|
|
138
|
+
// Update subscription metrics
|
|
139
|
+
subscriptionRate.labels(type).inc();
|
|
140
|
+
eventSubscriptionGauge
|
|
141
|
+
.labels(type)
|
|
142
|
+
.set(Object.keys(subscriptions[type]).length);
|
|
143
|
+
|
|
144
|
+
return registry;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const publish = (...args) => {
|
|
148
|
+
if (args.length < 2) {
|
|
149
|
+
throw new Error("publish requires at least 2 arguments");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const payload = args.pop();
|
|
153
|
+
const type = args.join(".");
|
|
154
|
+
|
|
155
|
+
console.log("node-event", "publish", type, payload);
|
|
156
|
+
messages.set(type, payload);
|
|
157
|
+
|
|
158
|
+
if (type === "__proto__" || type === "constructor" || type === "prototype") {
|
|
159
|
+
throw new Error("Invalid publish type");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Track metrics for event publishing
|
|
163
|
+
const endTimer = eventPublishDuration.labels(type).startTimer();
|
|
164
|
+
eventPublishCounter.labels(type).inc();
|
|
165
|
+
|
|
166
|
+
// Track payload size
|
|
167
|
+
const payloadSize = JSON.stringify(payload).length;
|
|
168
|
+
eventPayloadSize.labels(type).observe(payloadSize);
|
|
169
|
+
|
|
170
|
+
Object.keys(subscriptions[type] || {}).forEach((key) => {
|
|
171
|
+
const registry = subscriptions[type][key];
|
|
172
|
+
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
const callbackTimer = callbackProcessingDuration
|
|
175
|
+
.labels(type)
|
|
176
|
+
.startTimer();
|
|
177
|
+
try {
|
|
178
|
+
registry.callback(payload, registry);
|
|
179
|
+
eventThroughput.labels(type).inc();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error("node-event", "error", type, err);
|
|
182
|
+
const errorName = err instanceof Error ? err.name : "UnknownError";
|
|
183
|
+
eventPublishErrors.labels(type, errorName).inc();
|
|
184
|
+
} finally {
|
|
185
|
+
callbackTimer();
|
|
186
|
+
}
|
|
187
|
+
}, 0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
endTimer();
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
function last(type, init) {
|
|
194
|
+
if (messages.has(type)) {
|
|
195
|
+
return messages.get(type);
|
|
196
|
+
} else {
|
|
197
|
+
return init;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export { subscribe, publish, messages, last, client };
|
package/src/express.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import express, { Request, Response, NextFunction } from "express";
|
|
2
|
+
import "express-async-errors";
|
|
3
|
+
import cors from "cors";
|
|
4
|
+
import morgan from "morgan";
|
|
5
|
+
import helmet from "helmet";
|
|
6
|
+
import * as error from "./error";
|
|
7
|
+
import * as authorization from "./authorization";
|
|
8
|
+
import settings from "./routes/settings";
|
|
9
|
+
import metrics from "./routes/metrics";
|
|
10
|
+
import { getConfig } from "./config";
|
|
11
|
+
|
|
12
|
+
const app = express();
|
|
13
|
+
|
|
14
|
+
const appConfig = getConfig();
|
|
15
|
+
|
|
16
|
+
app.use(helmet());
|
|
17
|
+
app.use(cors());
|
|
18
|
+
app.use(morgan("tiny"));
|
|
19
|
+
|
|
20
|
+
app.use(
|
|
21
|
+
express.json(),
|
|
22
|
+
(err: Error, _req: Request, res: Response, next: NextFunction) =>
|
|
23
|
+
err ? res.status(422).end() : next()
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (appConfig.project) {
|
|
27
|
+
import("./routes/oauth.ts").then((oauthModule) => {
|
|
28
|
+
const oauth = oauthModule.default || oauthModule;
|
|
29
|
+
app.use(
|
|
30
|
+
"/oauth",
|
|
31
|
+
express.urlencoded(),
|
|
32
|
+
(err: Error, _req: Request, res: Response, next: NextFunction) =>
|
|
33
|
+
err ? res.status(422).end() : next(),
|
|
34
|
+
oauth
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
app.use("/metrics", metrics);
|
|
40
|
+
|
|
41
|
+
setImmediate(async () => {
|
|
42
|
+
process.env.PROFILE === "TEST" && app.use(authorization.verify);
|
|
43
|
+
|
|
44
|
+
if (appConfig.project) {
|
|
45
|
+
const [permissionsModule, organizationsModule, projectsModule] =
|
|
46
|
+
await Promise.all([
|
|
47
|
+
import("./routes/permissions.ts"),
|
|
48
|
+
import("./routes/organizations.ts"),
|
|
49
|
+
import("./routes/projects.ts"),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const permissions = permissionsModule.default || permissionsModule;
|
|
53
|
+
const organizations = organizationsModule.default || organizationsModule;
|
|
54
|
+
const projects = projectsModule.default || projectsModule;
|
|
55
|
+
|
|
56
|
+
app.use("/projects", projects);
|
|
57
|
+
app.use("/organizations", organizations);
|
|
58
|
+
app.use("/permissions", permissions);
|
|
59
|
+
app.use("/projects/:projectId/settings", settings);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
app.use((_req: Request, res: Response) => res.status(404).end());
|
|
63
|
+
app.use(error.handle);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export default app;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import Settings from "../models/Settings.model";
|
|
2
2
|
|
|
3
|
-
async function get({
|
|
3
|
+
async function get({
|
|
4
|
+
projectId,
|
|
5
|
+
}: {
|
|
6
|
+
projectId: string;
|
|
7
|
+
}): Promise<Record<string, unknown>> {
|
|
4
8
|
const settingsInstance = await Settings.findOne({
|
|
5
9
|
where: { projectId },
|
|
6
10
|
});
|
|
@@ -12,7 +16,15 @@ async function get({ projectId }) {
|
|
|
12
16
|
}
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
async function upsert(
|
|
19
|
+
async function upsert(
|
|
20
|
+
{
|
|
21
|
+
projectId,
|
|
22
|
+
}: {
|
|
23
|
+
projectId: string;
|
|
24
|
+
},
|
|
25
|
+
settings: Record<string, unknown>,
|
|
26
|
+
_params?: Record<string, unknown>
|
|
27
|
+
): Promise<void> {
|
|
16
28
|
const settingsInstance = await Settings.findOne({
|
|
17
29
|
where: { projectId },
|
|
18
30
|
});
|
|
@@ -33,4 +45,4 @@ async function upsert({ projectId }, settings) {
|
|
|
33
45
|
}
|
|
34
46
|
}
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
export { get, upsert };
|
package/src/lib/test.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import platform from "../platform";
|
|
3
|
+
import { Sequelize } from "sequelize";
|
|
4
|
+
|
|
5
|
+
dotenv.config({ path: ".env.test" });
|
|
6
|
+
|
|
7
|
+
platform.init({
|
|
8
|
+
project: {
|
|
9
|
+
name: "test",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
oauth: {
|
|
12
|
+
jwt: {
|
|
13
|
+
identifier: "email",
|
|
14
|
+
},
|
|
15
|
+
providers: {
|
|
16
|
+
github: {
|
|
17
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
18
|
+
userUrl: "https://api.github.com/user",
|
|
19
|
+
clientId: "0c2844d3d19dc9293fc5",
|
|
20
|
+
redirectUri: "http://localhost:5173/callback",
|
|
21
|
+
userIdentifier: "id",
|
|
22
|
+
userFields: {
|
|
23
|
+
name: "name",
|
|
24
|
+
displayName: "login",
|
|
25
|
+
avatarUrl: "avatar_url",
|
|
26
|
+
email: "email",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
postgres: {
|
|
33
|
+
uri: "sqlite::memory:",
|
|
34
|
+
debug: true,
|
|
35
|
+
sync: false,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
import { init } from "../models";
|
|
40
|
+
|
|
41
|
+
async function reset(): Promise<void> {
|
|
42
|
+
const { sequelize }: { sequelize: Sequelize } = await import("../postgres");
|
|
43
|
+
|
|
44
|
+
if (await init()) {
|
|
45
|
+
await sequelize.sync({ force: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await sequelize.models.Organization.destroy({ truncate: true });
|
|
49
|
+
await sequelize.models.Project.destroy({ truncate: true });
|
|
50
|
+
await sequelize.models.Permission.destroy({ truncate: true });
|
|
51
|
+
await sequelize.models.Settings.destroy({ truncate: true });
|
|
52
|
+
|
|
53
|
+
async function seed(): Promise<void> {
|
|
54
|
+
const { seed: organizations } = await import("../seeds/Organization.json");
|
|
55
|
+
const { seed: permissions } = await import("../seeds/Permission.json");
|
|
56
|
+
const { seed: projects } = await import("../seeds/Project.json");
|
|
57
|
+
const { seed: settings } = await import("../seeds/Settings.json");
|
|
58
|
+
|
|
59
|
+
await sequelize.models.Organization.bulkCreate(organizations);
|
|
60
|
+
await sequelize.models.Project.bulkCreate(projects);
|
|
61
|
+
await sequelize.models.Permission.bulkCreate(permissions);
|
|
62
|
+
await sequelize.models.Settings.bulkCreate(settings);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await seed();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { reset };
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { ecsFormat } from "@elastic/ecs-pino-format";
|
|
2
|
+
import pino from "pino";
|
|
3
|
+
import pinoElastic from "pino-elasticsearch";
|
|
4
|
+
import { getConfig } from "./config";
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
+
const { logger: loggerConfig, project } = getConfig();
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
if (!loggerConfig || !project) {
|
|
9
|
+
throw new Error("Logger and project configuration are required");
|
|
10
|
+
}
|
|
8
11
|
|
|
9
|
-
const streams = [{ stream: process.stdout }];
|
|
12
|
+
const streams: pino.StreamEntry[] = [{ stream: process.stdout }];
|
|
10
13
|
|
|
11
14
|
const streamToElastic = pinoElastic({
|
|
12
15
|
index: loggerConfig.elasticsearch.index,
|
|
13
|
-
consistency: loggerConfig.elasticsearch.consistency || "one",
|
|
14
16
|
node: loggerConfig.elasticsearch.node,
|
|
15
17
|
esVersion: loggerConfig.elasticsearch.esVersion || 8,
|
|
16
18
|
flushBytes: loggerConfig.elasticsearch.flushBytes || 1000,
|
|
17
|
-
});
|
|
19
|
+
} as Parameters<typeof pinoElastic>[0]);
|
|
18
20
|
|
|
19
21
|
streams.push({ stream: streamToElastic });
|
|
20
22
|
|
|
@@ -31,4 +33,4 @@ const logger = pino(
|
|
|
31
33
|
pino.multistream(streams)
|
|
32
34
|
);
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
export default logger;
|
|
@@ -1,6 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
import promClient from "prom-client";
|
|
2
|
+
|
|
3
|
+
interface PushgatewayConfig {
|
|
4
|
+
url: string;
|
|
5
|
+
jobName: string;
|
|
6
|
+
instance?: string;
|
|
7
|
+
interval: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface MetricsConfig {
|
|
11
|
+
url?: string;
|
|
12
|
+
pushGateway: {
|
|
13
|
+
jobName?: string;
|
|
14
|
+
instance?: string;
|
|
15
|
+
};
|
|
16
|
+
interval?: number;
|
|
17
|
+
}
|
|
2
18
|
|
|
3
19
|
class DBMetrics {
|
|
20
|
+
registry: promClient.Registry;
|
|
21
|
+
pushgatewayInterval: NodeJS.Timeout | null;
|
|
22
|
+
pushgatewayConfig: PushgatewayConfig | null;
|
|
23
|
+
dbReadOps: promClient.Counter<string>;
|
|
24
|
+
dbWriteOps: promClient.Counter<string>;
|
|
25
|
+
dbReadLatency: promClient.Histogram<string>;
|
|
26
|
+
dbWriteLatency: promClient.Histogram<string>;
|
|
27
|
+
|
|
4
28
|
constructor() {
|
|
5
29
|
this.registry = new promClient.Registry();
|
|
6
30
|
this.pushgatewayInterval = null;
|
|
@@ -33,17 +57,17 @@ class DBMetrics {
|
|
|
33
57
|
});
|
|
34
58
|
}
|
|
35
59
|
|
|
36
|
-
recordDbRead() {
|
|
60
|
+
recordDbRead(): () => number {
|
|
37
61
|
this.dbReadOps.inc();
|
|
38
62
|
return this.dbReadLatency.startTimer();
|
|
39
63
|
}
|
|
40
64
|
|
|
41
|
-
recordDbWrite() {
|
|
65
|
+
recordDbWrite(): () => number {
|
|
42
66
|
this.dbWriteOps.inc();
|
|
43
67
|
return this.dbWriteLatency.startTimer();
|
|
44
68
|
}
|
|
45
69
|
|
|
46
|
-
startPushgateway(metrics
|
|
70
|
+
startPushgateway(metrics: MetricsConfig): void {
|
|
47
71
|
this.pushgatewayConfig = {
|
|
48
72
|
url: metrics.url || "http://localhost:9091",
|
|
49
73
|
jobName: metrics.pushGateway.jobName || "api",
|
|
@@ -62,19 +86,18 @@ class DBMetrics {
|
|
|
62
86
|
);
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
stopPushgateway() {
|
|
89
|
+
stopPushgateway(): void {
|
|
66
90
|
if (this.pushgatewayInterval) {
|
|
67
91
|
clearInterval(this.pushgatewayInterval);
|
|
68
|
-
this.pushgatewayInterval =
|
|
92
|
+
this.pushgatewayInterval = null;
|
|
69
93
|
console.log("Stopped pushing metrics to Pushgateway");
|
|
70
94
|
}
|
|
71
95
|
}
|
|
72
96
|
|
|
73
|
-
async pushMetricsToGateway() {
|
|
97
|
+
async pushMetricsToGateway(): Promise<void> {
|
|
74
98
|
if (!this.pushgatewayConfig) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
99
|
+
console.error("[DBMetrics] Pushgateway not configured");
|
|
100
|
+
return;
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
try {
|
|
@@ -95,15 +118,15 @@ class DBMetrics {
|
|
|
95
118
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
96
119
|
}
|
|
97
120
|
|
|
98
|
-
console.log("Metrics pushed to Pushgateway successfully");
|
|
121
|
+
console.log("[DBMetrics] Metrics pushed to Pushgateway successfully");
|
|
99
122
|
} catch (err) {
|
|
100
|
-
console.error("Failed to push metrics to Pushgateway:", err);
|
|
123
|
+
console.error("[DBMetrics] Failed to push metrics to Pushgateway:", err);
|
|
101
124
|
}
|
|
102
125
|
}
|
|
103
126
|
|
|
104
|
-
getPushgatewayConfig() {
|
|
127
|
+
getPushgatewayConfig(): PushgatewayConfig | null {
|
|
105
128
|
return this.pushgatewayConfig;
|
|
106
129
|
}
|
|
107
130
|
}
|
|
108
131
|
|
|
109
|
-
|
|
132
|
+
export { DBMetrics };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Table,
|
|
3
|
+
Column,
|
|
4
|
+
Model,
|
|
5
|
+
DataType,
|
|
6
|
+
Default,
|
|
7
|
+
PrimaryKey,
|
|
8
|
+
AllowNull,
|
|
9
|
+
} from "sequelize-typescript";
|
|
10
|
+
|
|
11
|
+
@Table({
|
|
12
|
+
tableName: "Organization",
|
|
13
|
+
timestamps: false,
|
|
14
|
+
underscored: true,
|
|
15
|
+
})
|
|
16
|
+
class Organization extends Model {
|
|
17
|
+
@PrimaryKey
|
|
18
|
+
@Default(DataType.UUIDV4)
|
|
19
|
+
@Column(DataType.UUID)
|
|
20
|
+
declare id: string;
|
|
21
|
+
|
|
22
|
+
@AllowNull(false)
|
|
23
|
+
@Column(DataType.STRING)
|
|
24
|
+
declare name: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default Organization;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Table,
|
|
3
|
+
Column,
|
|
4
|
+
Model,
|
|
5
|
+
DataType,
|
|
6
|
+
Default,
|
|
7
|
+
PrimaryKey,
|
|
8
|
+
AllowNull,
|
|
9
|
+
ForeignKey,
|
|
10
|
+
} from "sequelize-typescript";
|
|
11
|
+
import Organization from "./Organization.model";
|
|
12
|
+
import Project from "./Project.model";
|
|
13
|
+
|
|
14
|
+
@Table({
|
|
15
|
+
tableName: "Permission",
|
|
16
|
+
timestamps: false,
|
|
17
|
+
underscored: true,
|
|
18
|
+
})
|
|
19
|
+
class Permission extends Model {
|
|
20
|
+
@PrimaryKey
|
|
21
|
+
@Default(DataType.UUIDV4)
|
|
22
|
+
@Column(DataType.UUID)
|
|
23
|
+
declare id: string;
|
|
24
|
+
|
|
25
|
+
@AllowNull(false)
|
|
26
|
+
@Column(DataType.UUID)
|
|
27
|
+
declare appId: string;
|
|
28
|
+
|
|
29
|
+
@AllowNull(false)
|
|
30
|
+
@ForeignKey(() => Organization)
|
|
31
|
+
@Column(DataType.UUID)
|
|
32
|
+
declare organizationId: string;
|
|
33
|
+
|
|
34
|
+
@AllowNull(false)
|
|
35
|
+
@ForeignKey(() => Project)
|
|
36
|
+
@Column(DataType.UUID)
|
|
37
|
+
declare projectId: string;
|
|
38
|
+
|
|
39
|
+
@AllowNull(false)
|
|
40
|
+
@Column(DataType.STRING)
|
|
41
|
+
declare userId: string;
|
|
42
|
+
|
|
43
|
+
@AllowNull(false)
|
|
44
|
+
@Column(DataType.STRING)
|
|
45
|
+
declare role: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default Permission;
|