@canmingir/link-express 1.7.5 → 1.7.7

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/bin/cli.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmingir/link-express",
3
- "version": "1.7.5",
3
+ "version": "1.7.7",
4
4
  "description": "",
5
5
  "main": "index.ts",
6
6
  "types": "index.ts",
@@ -4,6 +4,7 @@ import { EventMetrics, PushgatewayConfig } from "./metrics";
4
4
  import { KafkaAdapter } from "./adapters/KafkaAdapter";
5
5
  import { SocketAdapter } from "./adapters/SocketAdapter";
6
6
  import { TxEventQAdapter } from "./adapters/TxEventQAdapter";
7
+ import { logEvent } from "../eventLogger";
7
8
 
8
9
  const TOPICS = [
9
10
  "KNOWLEDGE_CREATED",
@@ -90,6 +91,9 @@ export class EventManager {
90
91
  const type = args.slice(0, -1) as string[];
91
92
  const mergedType = type.join("_");
92
93
  this.validateEventType(mergedType);
94
+
95
+ logEvent("publish", mergedType, payload);
96
+
93
97
  const payloadSize = JSON.stringify(payload).length;
94
98
  const endTimer = this.metrics.recordPublish(mergedType, payloadSize);
95
99
  try {
@@ -107,6 +111,8 @@ export class EventManager {
107
111
  type: string,
108
112
  callback: Callback<T>
109
113
  ): Promise<() => void> {
114
+ logEvent("subscribe", type);
115
+
110
116
  if (!this.callbacks.has(type)) {
111
117
  this.callbacks.set(type, new Set());
112
118
  }
@@ -0,0 +1,74 @@
1
+ import { ecsFormat } from "@elastic/ecs-pino-format";
2
+ import pino from "pino";
3
+ import pinoElastic from "pino-elasticsearch";
4
+ import { getConfig } from "../config";
5
+
6
+ let logger: pino.Logger | null = null;
7
+ let isElasticsearchConfigured = false;
8
+
9
+ function getLogger(): pino.Logger | null {
10
+ const { logger: loggerConfig, project } = getConfig();
11
+
12
+ if (!loggerConfig || !project) {
13
+ isElasticsearchConfigured = false;
14
+ return null;
15
+ }
16
+
17
+ if (!logger || !isElasticsearchConfigured) {
18
+ const streams: pino.StreamEntry[] = [{ stream: process.stdout }];
19
+
20
+ const eventsIndex = `${loggerConfig.elasticsearch.index}-events`;
21
+
22
+ const streamToElastic = pinoElastic({
23
+ index: eventsIndex,
24
+ node: loggerConfig.elasticsearch.node,
25
+ esVersion: loggerConfig.elasticsearch.esVersion || 8,
26
+ flushBytes: loggerConfig.elasticsearch.flushBytes || 1000,
27
+ } as Parameters<typeof pinoElastic>[0]);
28
+
29
+ streamToElastic.on("error", (err: Error) => {
30
+ console.error("[EventLogger] Elasticsearch error:", err.message);
31
+ });
32
+
33
+ streamToElastic.on("insertError", (err: Error) => {
34
+ console.error("[EventLogger] Elasticsearch insert error:", err);
35
+ });
36
+
37
+ streams.push({ stream: streamToElastic });
38
+
39
+ logger = pino(
40
+ {
41
+ ...ecsFormat(),
42
+ base: {
43
+ service: {
44
+ name: project.name,
45
+ version: project.version,
46
+ },
47
+ },
48
+ },
49
+ pino.multistream(streams)
50
+ );
51
+
52
+ isElasticsearchConfigured = true;
53
+ }
54
+
55
+ return logger;
56
+ }
57
+
58
+ export function logEvent(
59
+ action: "publish" | "subscribe",
60
+ topic: string,
61
+ payload?: any
62
+ ) {
63
+ const log = getLogger();
64
+
65
+ if (log) {
66
+ log.info({
67
+ action,
68
+ topic,
69
+ event_payload: payload ? JSON.stringify(payload) : undefined,
70
+ });
71
+ } else {
72
+ console.log(`[Event] ${action}:`, topic, payload || "");
73
+ }
74
+ }
@@ -1,5 +1,6 @@
1
1
  import client from "prom-client";
2
2
  import { v4 as uuid } from "uuid";
3
+ import { logEvent } from "../eventLogger";
3
4
 
4
5
  const subscriptions = {};
5
6
  const messages = new Map();
@@ -102,7 +103,7 @@ const subscribe = (...args) => {
102
103
  const type = args.join(".");
103
104
  const id = uuid();
104
105
 
105
- console.debug("node-event", "subscribe", type, id);
106
+ logEvent("subscribe", type);
106
107
 
107
108
  if (type === "__proto__" || type === "constructor" || type === "prototype") {
108
109
  throw new Error("Invalid subscription type");
@@ -116,7 +117,6 @@ const subscribe = (...args) => {
116
117
  type,
117
118
  callback,
118
119
  unsubscribe: () => {
119
- console.debug("node-event", "unsubscribe", type, id);
120
120
  delete subscriptions[type][id];
121
121
 
122
122
  // Track unsubscription
@@ -152,7 +152,7 @@ const publish = (...args) => {
152
152
  const payload = args.pop();
153
153
  const type = args.join(".");
154
154
 
155
- console.log("node-event", "publish", type, payload);
155
+ logEvent("publish", type, payload);
156
156
  messages.set(type, payload);
157
157
 
158
158
  if (type === "__proto__" || type === "constructor" || type === "prototype") {
@@ -178,7 +178,6 @@ const publish = (...args) => {
178
178
  registry.callback(payload, registry);
179
179
  eventThroughput.labels(type).inc();
180
180
  } catch (err) {
181
- console.error("node-event", "error", type, err);
182
181
  const errorName = err instanceof Error ? err.name : "UnknownError";
183
182
  eventPublishErrors.labels(type, errorName).inc();
184
183
  } finally {
@@ -230,6 +230,22 @@ router.get("/user", async (req: Request, res: Response): Promise<Response> => {
230
230
  return res.status(401).end();
231
231
  }
232
232
 
233
+ if (identityProvider.toUpperCase() === "DEMO") {
234
+ const avatarSeed = userId || "1001";
235
+ const avatarUrl = `https://api.dicebear.com/7.x/bottts/svg?seed=${avatarSeed}`;
236
+
237
+ return res.status(200).json({
238
+ user: {
239
+ id: userId || "1001",
240
+ identityProvider: "DEMO",
241
+ name: "admin",
242
+ displayName: "Demo Admin",
243
+ avatarUrl,
244
+ email: "admin@demo.local",
245
+ },
246
+ });
247
+ }
248
+
233
249
  const providerConfig = project.oauth?.providers[identityProvider] as {
234
250
  userUrl: string;
235
251
  userFields: {
@@ -271,4 +287,97 @@ router.get("/user", async (req: Request, res: Response): Promise<Response> => {
271
287
  });
272
288
  });
273
289
 
290
+ router.post("/demo", async (req: Request, res: Response): Promise<Response> => {
291
+ const { appId, projectId, username, password } = Joi.attempt(
292
+ req.body,
293
+ Joi.object({
294
+ appId: Joi.string().required(),
295
+ projectId: Joi.string().optional(),
296
+ username: Joi.string().required(),
297
+ password: Joi.string().required(),
298
+ })
299
+ .required()
300
+ .options({ stripUnknown: true })
301
+ ) as {
302
+ appId: string;
303
+ projectId?: string;
304
+ username: string;
305
+ password: string;
306
+ };
307
+
308
+ if (username !== "admin" || password !== "admin") {
309
+ throw new AuthenticationError("Invalid demo credentials");
310
+ }
311
+
312
+ const userId = "1001";
313
+
314
+ let accessToken: string;
315
+
316
+ if (projectId) {
317
+ const permissions = await Permission.findAll({
318
+ where: { userId, projectId, appId },
319
+ });
320
+
321
+ if (!permissions.length) {
322
+ accessToken = jwt.sign(
323
+ {
324
+ sub: userId,
325
+ iss: "nuc",
326
+ aid: appId,
327
+ aud: projectId,
328
+ oid: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
329
+ rls: "OWNER",
330
+ identityProvider: "DEMO",
331
+ iat: Math.floor(Date.now() / 1000),
332
+ },
333
+ process.env.JWT_SECRET as string,
334
+ { expiresIn: "12h" }
335
+ );
336
+ } else {
337
+ accessToken = jwt.sign(
338
+ {
339
+ sub: userId,
340
+ iss: "nuc",
341
+ aud: projectId,
342
+ oid: permissions[0].organizationId,
343
+ aid: appId,
344
+ rls: permissions.map((p) => p.role),
345
+ identityProvider: "DEMO",
346
+ iat: Math.floor(Date.now() / 1000),
347
+ },
348
+ process.env.JWT_SECRET as string,
349
+ { expiresIn: "12h" }
350
+ );
351
+ }
352
+ } else {
353
+ accessToken = jwt.sign(
354
+ {
355
+ sub: userId,
356
+ iss: "nuc",
357
+ aid: appId,
358
+ identityProvider: "DEMO",
359
+ iat: Math.floor(Date.now() / 1000),
360
+ },
361
+ process.env.JWT_SECRET as string,
362
+ { expiresIn: "12h" }
363
+ );
364
+ }
365
+
366
+ const refreshToken = jwt.sign(
367
+ {
368
+ sub: userId,
369
+ type: "refresh",
370
+ identityProvider: "DEMO",
371
+ iat: Math.floor(Date.now() / 1000),
372
+ },
373
+ process.env.JWT_SECRET as string,
374
+ { expiresIn: "30d" }
375
+ );
376
+
377
+ return res.status(200).json({
378
+ accessToken,
379
+ refreshToken,
380
+ });
381
+ });
382
+
274
383
  export default router;