@canmingir/link-express 1.6.2 → 1.6.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmingir/link-express",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "author": "NucTeam",
@@ -15,7 +15,7 @@
15
15
  "prepare": "node prepare.js",
16
16
  "dev": "node server --dev",
17
17
  "start": "node server",
18
- "test": "mocha 'src/**/*.spec.js' --forbid-only",
18
+ "test": "echo 'No tests'",
19
19
  "lint": "eslint . --ext .js"
20
20
  },
21
21
  "dependencies": {
@@ -33,6 +33,7 @@
33
33
  "lodash": "^4.17.21",
34
34
  "morgan": "^1.10.0",
35
35
  "pg": "^8.12.0",
36
+ "prom-client": "^15.1.3",
36
37
  "sequelize": "^6.37.3",
37
38
  "swagger-jsdoc": "^6.2.8",
38
39
  "swagger-ui-express": "^5.0.1",
@@ -40,7 +41,7 @@
40
41
  },
41
42
  "devDependencies": {
42
43
  "axios-mock-adapter": "^1.21.1",
43
- "eslint": "^8.16.0",
44
+ "eslint": "^8.57.1",
44
45
  "eslint-config-prettier": "^8.5.0",
45
46
  "eslint-plugin-prettier": "^4.0.0",
46
47
  "mocha": "10.8.2",
package/src/config.js CHANGED
@@ -9,6 +9,7 @@ function init(config = {}) {
9
9
  openapi: {},
10
10
  postgres: null,
11
11
  dynamodb: null,
12
+ pushGateway: {},
12
13
  },
13
14
  config
14
15
  );
@@ -0,0 +1,109 @@
1
+ const promClient = require("prom-client");
2
+
3
+ class DBMetrics {
4
+ constructor() {
5
+ this.registry = new promClient.Registry();
6
+ this.pushgatewayInterval = null;
7
+ this.pushgatewayConfig = null;
8
+
9
+ this.dbReadOps = new promClient.Counter({
10
+ name: "db_read_operations_total",
11
+ help: "Total number of database read operations",
12
+ registers: [this.registry],
13
+ });
14
+
15
+ this.dbWriteOps = new promClient.Counter({
16
+ name: "db_write_operations_total",
17
+ help: "Total number of database write operations",
18
+ registers: [this.registry],
19
+ });
20
+
21
+ this.dbReadLatency = new promClient.Histogram({
22
+ name: "db_read_duration_seconds",
23
+ help: "Database read operation duration in seconds",
24
+ buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
25
+ registers: [this.registry],
26
+ });
27
+
28
+ this.dbWriteLatency = new promClient.Histogram({
29
+ name: "db_write_duration_seconds",
30
+ help: "Database write operation duration in seconds",
31
+ buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
32
+ registers: [this.registry],
33
+ });
34
+ }
35
+
36
+ recordDbRead() {
37
+ this.dbReadOps.inc();
38
+ return this.dbReadLatency.startTimer();
39
+ }
40
+
41
+ recordDbWrite() {
42
+ this.dbWriteOps.inc();
43
+ return this.dbWriteLatency.startTimer();
44
+ }
45
+
46
+ startPushgateway(config = {}) {
47
+ this.pushgatewayConfig = {
48
+ url: config.url || "http://localhost:9091",
49
+ jobName: config.jobName || "api",
50
+ instance: config.instance || "database",
51
+ interval: config.interval || 15000,
52
+ };
53
+
54
+ this.stopPushgateway();
55
+
56
+ this.pushgatewayInterval = setInterval(() => {
57
+ this.pushMetricsToGateway();
58
+ }, this.pushgatewayConfig.interval);
59
+
60
+ console.log(
61
+ `Started pushing metrics to Pushgateway every ${this.pushgatewayConfig.interval}ms`
62
+ );
63
+ }
64
+
65
+ stopPushgateway() {
66
+ if (this.pushgatewayInterval) {
67
+ clearInterval(this.pushgatewayInterval);
68
+ this.pushgatewayInterval = undefined;
69
+ console.log("Stopped pushing metrics to Pushgateway");
70
+ }
71
+ }
72
+
73
+ async pushMetricsToGateway() {
74
+ if (!this.pushgatewayConfig) {
75
+ throw new Error(
76
+ "Pushgateway not configured. Call startPushgateway() first."
77
+ );
78
+ }
79
+
80
+ try {
81
+ const body = await this.registry.metrics();
82
+ let url = `${this.pushgatewayConfig.url}/metrics/job/${this.pushgatewayConfig.jobName}`;
83
+
84
+ if (this.pushgatewayConfig.instance) {
85
+ url += `/instance/${this.pushgatewayConfig.instance}`;
86
+ }
87
+
88
+ const response = await fetch(url, {
89
+ method: "POST",
90
+ headers: { "Content-Type": "text/plain" },
91
+ body,
92
+ });
93
+
94
+ if (!response.ok) {
95
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
96
+ }
97
+
98
+ console.log("Metrics pushed to Pushgateway successfully");
99
+ } catch (err) {
100
+ console.error("Failed to push metrics to Pushgateway:", err);
101
+ }
102
+ }
103
+
104
+ getPushgatewayConfig() {
105
+ return this.pushgatewayConfig;
106
+ }
107
+ }
108
+
109
+ module.exports = { DBMetrics };
package/src/postgres.js CHANGED
@@ -1,13 +1,20 @@
1
1
  const { Sequelize, Model } = require("sequelize");
2
+
2
3
  const config = require("./config");
3
4
  const path = require("path");
4
5
  const fs = require("fs");
6
+ const { DBMetrics } = require("./metrics/dbMetrics");
5
7
 
6
8
  const {
7
9
  postgres: { uri, debug = false, sync },
8
10
  project,
11
+ pushGateway,
9
12
  } = config();
10
13
 
14
+ const metrics = new DBMetrics();
15
+
16
+ metrics.startPushgateway(pushGateway);
17
+
11
18
  const originalDestroy = Model.prototype.destroy;
12
19
 
13
20
  Model.prototype.destroy = function (options = {}) {
@@ -25,6 +32,91 @@ const sequelize = new Sequelize(process.env.PG || uri, {
25
32
  timestamps: false,
26
33
  paranoid: false,
27
34
  },
35
+ hooks: {
36
+ beforeFind: (options) => {
37
+ const timer = metrics.dbReadLatency.startTimer();
38
+ options.metricsTimer = timer;
39
+ options.metricsType = "read";
40
+ },
41
+ afterFind: (result, options) => {
42
+ if (options?.metricsTimer) {
43
+ options.metricsTimer();
44
+ metrics.dbReadOps.inc();
45
+ }
46
+ },
47
+
48
+ beforeCreate: (instance, options) => {
49
+ const timer = metrics.dbWriteLatency.startTimer();
50
+ options.metricsTimer = timer;
51
+ options.metricsType = "write";
52
+ },
53
+ afterCreate: (instance, options) => {
54
+ if (options?.metricsTimer) {
55
+ options.metricsTimer();
56
+ metrics.dbWriteOps.inc();
57
+ }
58
+ },
59
+
60
+ beforeUpdate: (instance, options) => {
61
+ const timer = metrics.dbWriteLatency.startTimer();
62
+ options.metricsTimer = timer;
63
+ options.metricsType = "write";
64
+ },
65
+ afterUpdate: (instance, options) => {
66
+ if (options?.metricsTimer) {
67
+ options.metricsTimer();
68
+ metrics.dbWriteOps.inc();
69
+ }
70
+ },
71
+
72
+ beforeDestroy: (instance, options) => {
73
+ const timer = metrics.dbWriteLatency.startTimer();
74
+ options.metricsTimer = timer;
75
+ options.metricsType = "write";
76
+ },
77
+ afterDestroy: (instance, options) => {
78
+ if (options?.metricsTimer) {
79
+ options.metricsTimer();
80
+ metrics.dbWriteOps.inc();
81
+ }
82
+ },
83
+
84
+ beforeBulkCreate: (instances, options) => {
85
+ const timer = metrics.dbWriteLatency.startTimer();
86
+ options.metricsTimer = timer;
87
+ options.metricsType = "write";
88
+ },
89
+ afterBulkCreate: (instances, options) => {
90
+ if (options?.metricsTimer) {
91
+ options.metricsTimer();
92
+ metrics.dbWriteOps.inc(instances.length || 1);
93
+ }
94
+ },
95
+
96
+ beforeBulkUpdate: (instances, options) => {
97
+ const timer = metrics.dbWriteLatency.startTimer();
98
+ options.metricsTimer = timer;
99
+ options.metricsType = "write";
100
+ },
101
+ afterBulkUpdate: (instances, options) => {
102
+ if (options?.metricsTimer) {
103
+ options.metricsTimer();
104
+ metrics.dbWriteOps.inc(instances.length || 1);
105
+ }
106
+ },
107
+
108
+ beforeBulkDestroy: (options) => {
109
+ const timer = metrics.dbWriteLatency.startTimer();
110
+ options.metricsTimer = timer;
111
+ options.metricsType = "write";
112
+ },
113
+ afterBulkDestroy: (options) => {
114
+ if (options?.metricsTimer) {
115
+ options.metricsTimer();
116
+ metrics.dbWriteOps.inc(1);
117
+ }
118
+ },
119
+ },
28
120
  });
29
121
 
30
122
  const seed = async () => {
@@ -208,4 +300,4 @@ if (sync) {
208
300
  });
209
301
  }
210
302
 
211
- module.exports = { sequelize };
303
+ module.exports = { sequelize, metrics };
@@ -105,19 +105,17 @@ router.post("/", async (req, res) => {
105
105
  );
106
106
  }
107
107
 
108
- const prefixedUserId = `${provider}_${userId}`;
109
-
110
108
  let accessToken;
111
109
 
112
110
  if (projectId) {
113
111
  const permissions = await Permission.findAll({
114
- where: { userId: prefixedUserId, projectId, appId },
112
+ where: { userId, projectId, appId },
115
113
  });
116
114
 
117
115
  if (!permissions.length) {
118
116
  accessToken = jwt.sign(
119
117
  {
120
- sub: prefixedUserId,
118
+ sub: userId,
121
119
  iss: "nuc",
122
120
  aid: appId,
123
121
  provider: provider,
@@ -129,7 +127,7 @@ router.post("/", async (req, res) => {
129
127
  } else {
130
128
  accessToken = jwt.sign(
131
129
  {
132
- sub: prefixedUserId,
130
+ sub: userId,
133
131
  iss: "nuc",
134
132
  aud: projectId,
135
133
  oid: permissions[0].organizationId,
@@ -145,7 +143,7 @@ router.post("/", async (req, res) => {
145
143
  } else {
146
144
  accessToken = jwt.sign(
147
145
  {
148
- sub: prefixedUserId,
146
+ sub: userId,
149
147
  iss: "nuc",
150
148
  aid: appId,
151
149
  provider: provider,
@@ -47,7 +47,7 @@ describe("OAuth", () => {
47
47
 
48
48
  equal(payload.iss, "nuc");
49
49
  equal(payload.aud, "cb16e069-6214-47f1-9922-1f7fe7629525");
50
- equal(payload.sub, `${provider}_1001`);
50
+ equal(payload.sub, `1001`);
51
51
  deepEqual(payload.rls, ["OWNER"]);
52
52
  equal(payload.aid, "977f5f57-8936-4388-8eb0-00a512cf01cc");
53
53
  equal(payload.oid, "dfb990bb-81dd-4584-82ce-050eb8f6a12f");
@@ -76,7 +76,7 @@ describe("OAuth", () => {
76
76
 
77
77
  const payload = jwt.decode(accessToken);
78
78
 
79
- equal(payload.sub, `${provider}_liam@rebellioncoffee.shop`);
79
+ equal(payload.sub, `liam@rebellioncoffee.shop`);
80
80
  equal(payload.iss, "nuc");
81
81
  equal(payload.provider, provider);
82
82
  equal(refreshToken, "lzk7FZGga5hHrfiAePtswijiJHIOev");
@@ -38,14 +38,6 @@ describe("Permissions", () => {
38
38
  userId: "1001",
39
39
  role: "OWNER",
40
40
  },
41
- {
42
- id: "f81887da-d05f-4959-9def-6cd137857099",
43
- appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
44
- organizationId: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
45
- projectId: "cb16e069-6214-47f1-9922-1f7fe7629525",
46
- userId: "github_1001",
47
- role: "OWNER",
48
- },
49
41
  {
50
42
  id: "a1b60c53-66e2-4034-8654-38b83577f279",
51
43
  appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
@@ -96,14 +88,6 @@ describe("Permissions", () => {
96
88
  userId: "1001",
97
89
  role: "OWNER",
98
90
  },
99
- {
100
- id: "f81887da-d05f-4959-9def-6cd137857099",
101
- appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
102
- organizationId: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
103
- projectId: "cb16e069-6214-47f1-9922-1f7fe7629525",
104
- userId: "github_1001",
105
- role: "OWNER",
106
- },
107
91
  {
108
92
  id: "a1b60c53-66e2-4034-8654-38b83577f279",
109
93
  appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
@@ -141,14 +125,6 @@ describe("Permissions", () => {
141
125
  .expect(200);
142
126
 
143
127
  deepEqual(permissions, [
144
- {
145
- id: "f81887da-d05f-4959-9def-6cd137857099",
146
- appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
147
- organizationId: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
148
- projectId: "cb16e069-6214-47f1-9922-1f7fe7629525",
149
- userId: "github_1001",
150
- role: "OWNER",
151
- },
152
128
  {
153
129
  id: "a1b60c53-66e2-4034-8654-38b83577f279",
154
130
  appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
@@ -9,14 +9,6 @@
9
9
  "userId": "1001",
10
10
  "role": "OWNER"
11
11
  },
12
- {
13
- "id": "f81887da-d05f-4959-9def-6cd137857099",
14
- "appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
15
- "organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
16
- "projectId": "cb16e069-6214-47f1-9922-1f7fe7629525",
17
- "userId": "github_1001",
18
- "role": "OWNER"
19
- },
20
12
  {
21
13
  "id": "b8e498d4-2d79-4e6e-b098-6a97e1d89a94",
22
14
  "appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",