@friggframework/core 2.0.0-next.44 → 2.0.0-next.46

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.
Files changed (166) hide show
  1. package/README.md +28 -0
  2. package/application/commands/integration-commands.js +19 -0
  3. package/core/Worker.js +8 -21
  4. package/credential/repositories/credential-repository-mongo.js +14 -8
  5. package/credential/repositories/credential-repository-postgres.js +14 -8
  6. package/credential/repositories/credential-repository.js +3 -8
  7. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  8. package/database/adapters/lambda-invoker.js +97 -0
  9. package/database/config.js +11 -2
  10. package/database/models/WebsocketConnection.js +11 -10
  11. package/database/prisma.js +63 -3
  12. package/database/repositories/health-check-repository-mongodb.js +3 -0
  13. package/database/repositories/migration-status-repository-s3.js +137 -0
  14. package/database/use-cases/check-database-state-use-case.js +81 -0
  15. package/database/use-cases/check-encryption-health-use-case.js +3 -2
  16. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  17. package/database/use-cases/get-migration-status-use-case.js +93 -0
  18. package/database/use-cases/run-database-migration-use-case.js +137 -0
  19. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  20. package/database/utils/mongodb-collection-utils.js +91 -0
  21. package/database/utils/mongodb-schema-init.js +106 -0
  22. package/database/utils/prisma-runner.js +400 -0
  23. package/database/utils/prisma-schema-parser.js +182 -0
  24. package/encrypt/Cryptor.js +14 -16
  25. package/generated/prisma-mongodb/client.d.ts +1 -0
  26. package/generated/prisma-mongodb/client.js +4 -0
  27. package/generated/prisma-mongodb/default.d.ts +1 -0
  28. package/generated/prisma-mongodb/default.js +4 -0
  29. package/generated/prisma-mongodb/edge.d.ts +1 -0
  30. package/generated/prisma-mongodb/edge.js +334 -0
  31. package/generated/prisma-mongodb/index-browser.js +316 -0
  32. package/generated/prisma-mongodb/index.d.ts +22897 -0
  33. package/generated/prisma-mongodb/index.js +359 -0
  34. package/generated/prisma-mongodb/package.json +183 -0
  35. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  36. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  37. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  38. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  39. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  40. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  41. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  42. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  43. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  44. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  45. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  46. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  47. package/generated/prisma-mongodb/schema.prisma +362 -0
  48. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  49. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  50. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  51. package/generated/prisma-mongodb/wasm.js +341 -0
  52. package/generated/prisma-postgresql/client.d.ts +1 -0
  53. package/generated/prisma-postgresql/client.js +4 -0
  54. package/generated/prisma-postgresql/default.d.ts +1 -0
  55. package/generated/prisma-postgresql/default.js +4 -0
  56. package/generated/prisma-postgresql/edge.d.ts +1 -0
  57. package/generated/prisma-postgresql/edge.js +356 -0
  58. package/generated/prisma-postgresql/index-browser.js +338 -0
  59. package/generated/prisma-postgresql/index.d.ts +25071 -0
  60. package/generated/prisma-postgresql/index.js +381 -0
  61. package/generated/prisma-postgresql/package.json +183 -0
  62. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  63. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  64. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  65. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  66. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  67. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  68. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  69. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  70. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  71. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  72. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  73. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  74. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  75. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  76. package/generated/prisma-postgresql/schema.prisma +345 -0
  77. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  78. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  79. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  80. package/generated/prisma-postgresql/wasm.js +363 -0
  81. package/handlers/WEBHOOKS.md +653 -0
  82. package/handlers/backend-utils.js +118 -3
  83. package/handlers/database-migration-handler.js +227 -0
  84. package/handlers/routers/auth.js +1 -1
  85. package/handlers/routers/db-migration.handler.js +29 -0
  86. package/handlers/routers/db-migration.js +256 -0
  87. package/handlers/routers/health.js +41 -6
  88. package/handlers/routers/integration-webhook-routers.js +67 -0
  89. package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
  90. package/handlers/workers/db-migration.js +352 -0
  91. package/index.js +28 -0
  92. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  93. package/integrations/integration-base.js +74 -3
  94. package/integrations/integration-router.js +60 -70
  95. package/integrations/repositories/integration-repository-interface.js +12 -0
  96. package/integrations/repositories/integration-repository-mongo.js +32 -0
  97. package/integrations/repositories/integration-repository-postgres.js +33 -0
  98. package/integrations/repositories/process-repository-postgres.js +43 -20
  99. package/integrations/tests/doubles/dummy-integration-class.js +1 -8
  100. package/integrations/tests/doubles/test-integration-repository.js +2 -2
  101. package/logs/logger.js +0 -4
  102. package/modules/entity.js +0 -1
  103. package/modules/repositories/module-repository-mongo.js +3 -12
  104. package/modules/repositories/module-repository-postgres.js +0 -11
  105. package/modules/repositories/module-repository.js +1 -12
  106. package/modules/use-cases/get-entity-options-by-id.js +1 -1
  107. package/modules/use-cases/get-module.js +1 -2
  108. package/modules/use-cases/refresh-entity-options.js +1 -1
  109. package/modules/use-cases/test-module-auth.js +1 -1
  110. package/package.json +82 -66
  111. package/prisma-mongodb/schema.prisma +21 -21
  112. package/prisma-postgresql/schema.prisma +15 -15
  113. package/queues/queuer-util.js +28 -15
  114. package/types/core/index.d.ts +2 -2
  115. package/types/module-plugin/index.d.ts +0 -2
  116. package/user/repositories/user-repository-mongo.js +53 -12
  117. package/user/repositories/user-repository-postgres.js +53 -14
  118. package/user/use-cases/authenticate-user.js +127 -0
  119. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  120. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  121. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  122. package/user/use-cases/login-user.js +1 -1
  123. package/user/user.js +18 -2
  124. package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
  125. package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
  126. package/websocket/repositories/websocket-connection-repository.js +11 -10
  127. package/application/commands/integration-commands.test.js +0 -123
  128. package/database/encryption/encryption-integration.test.js +0 -553
  129. package/database/encryption/encryption-schema-registry.test.js +0 -392
  130. package/database/encryption/field-encryption-service.test.js +0 -525
  131. package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
  132. package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
  133. package/database/encryption/postgres-relation-decryption.test.js +0 -245
  134. package/database/encryption/prisma-encryption-extension.test.js +0 -439
  135. package/errors/base-error.test.js +0 -32
  136. package/errors/fetch-error.test.js +0 -79
  137. package/errors/halt-error.test.js +0 -11
  138. package/errors/validation-errors.test.js +0 -120
  139. package/handlers/auth-flow.integration.test.js +0 -147
  140. package/handlers/integration-event-dispatcher.test.js +0 -141
  141. package/handlers/routers/health.test.js +0 -210
  142. package/integrations/tests/use-cases/create-integration.test.js +0 -131
  143. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
  144. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
  145. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
  146. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
  147. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
  148. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
  149. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
  150. package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
  151. package/integrations/tests/use-cases/update-integration.test.js +0 -141
  152. package/integrations/use-cases/create-process.test.js +0 -178
  153. package/integrations/use-cases/get-process.test.js +0 -190
  154. package/integrations/use-cases/load-integration-context-full.test.js +0 -329
  155. package/integrations/use-cases/load-integration-context.test.js +0 -114
  156. package/integrations/use-cases/update-process-metrics.test.js +0 -308
  157. package/integrations/use-cases/update-process-state.test.js +0 -256
  158. package/lambda/TimeoutCatcher.test.js +0 -68
  159. package/logs/logger.test.js +0 -76
  160. package/modules/module-hydration.test.js +0 -205
  161. package/modules/requester/requester.test.js +0 -28
  162. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  163. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  164. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  165. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  166. package/user/tests/use-cases/login-user.test.js +0 -140
@@ -0,0 +1,151 @@
1
+ # Webhook Quick Start Guide
2
+
3
+ Get webhooks working in your Frigg integration in 3 simple steps.
4
+
5
+ ## Step 1: Enable Webhooks
6
+
7
+ Add `webhooks: true` to your Integration Definition:
8
+
9
+ ```javascript
10
+ class MyIntegration extends IntegrationBase {
11
+ static Definition = {
12
+ name: 'my-integration',
13
+ version: '1.0.0',
14
+ modules: {
15
+ myapi: { definition: MyApiDefinition },
16
+ },
17
+ webhooks: true, // ← Add this line
18
+ };
19
+ }
20
+ ```
21
+
22
+ ## Step 2: Handle Webhook Processing
23
+
24
+ Override the `onWebhook` handler to process webhooks:
25
+
26
+ ```javascript
27
+ class MyIntegration extends IntegrationBase {
28
+ // ... Definition ...
29
+
30
+ async onWebhook({ data }) {
31
+ const { body } = data;
32
+
33
+ // You have full access to:
34
+ // - this.myapi (your API modules)
35
+ // - this.config (integration config)
36
+ // - Database operations
37
+
38
+ if (body.event === 'item.created') {
39
+ await this.myapi.api.createItem(body.data);
40
+ }
41
+
42
+ return { processed: true };
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Step 3: Deploy
48
+
49
+ Deploy your Frigg app - webhook routes are automatically created:
50
+
51
+ ```bash
52
+ POST /api/my-integration-integration/webhooks/:integrationId
53
+ ```
54
+
55
+ ## That's It!
56
+
57
+ The default behavior handles:
58
+ - ✅ Receiving webhooks (instant 200 OK response)
59
+ - ✅ Queuing to SQS
60
+ - ✅ Loading your integration with DB and API modules
61
+ - ✅ Calling your `onWebhook` handler
62
+
63
+ ## Optional: Custom Signature Verification
64
+
65
+ Override `onWebhookReceived` for custom signature checks:
66
+
67
+ ```javascript
68
+ async onWebhookReceived({ req, res }) {
69
+ // Verify signature
70
+ const signature = req.headers['x-webhook-signature'];
71
+ if (!this.verifySignature(req.body, signature)) {
72
+ return res.status(401).json({ error: 'Invalid signature' });
73
+ }
74
+
75
+ // Queue for processing (default behavior)
76
+ await this.queueWebhook({
77
+ integrationId: req.params.integrationId,
78
+ body: req.body,
79
+ });
80
+
81
+ res.status(200).json({ received: true });
82
+ }
83
+ ```
84
+
85
+ ## Two Webhook Routes
86
+
87
+ ### With Integration ID (Recommended)
88
+ ```
89
+ POST /api/{name}-integration/webhooks/:integrationId
90
+ ```
91
+ - Full integration loaded in worker
92
+ - Access to DB, config, and API modules
93
+ - Use `this.myapi`, `this.config`, etc.
94
+
95
+ ### Without Integration ID
96
+ ```
97
+ POST /api/{name}-integration/webhooks
98
+ ```
99
+ - Unhydrated integration
100
+ - Useful for system-wide events
101
+ - Limited context
102
+
103
+ ## Need Help?
104
+
105
+ See full documentation: `packages/core/handlers/WEBHOOKS.md`
106
+
107
+ ## Common Patterns
108
+
109
+ ### Slack
110
+ ```javascript
111
+ async onWebhookReceived({ req, res }) {
112
+ if (req.body.type === 'url_verification') {
113
+ return res.json({ challenge: req.body.challenge });
114
+ }
115
+ // ... verify signature, queue ...
116
+ }
117
+ ```
118
+
119
+ ### Stripe
120
+ ```javascript
121
+ async onWebhookReceived({ req, res }) {
122
+ const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
123
+ const event = stripe.webhooks.constructEvent(
124
+ JSON.stringify(req.body),
125
+ req.headers['stripe-signature'],
126
+ process.env.STRIPE_WEBHOOK_SECRET
127
+ );
128
+ await this.queueWebhook({ body: event });
129
+ res.status(200).json({ received: true });
130
+ }
131
+ ```
132
+
133
+ ### GitHub
134
+ ```javascript
135
+ async onWebhookReceived({ req, res }) {
136
+ const crypto = require('crypto');
137
+ const signature = req.headers['x-hub-signature-256'];
138
+ const hash = crypto
139
+ .createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET)
140
+ .update(JSON.stringify(req.body))
141
+ .digest('hex');
142
+
143
+ if (`sha256=${hash}` !== signature) {
144
+ return res.status(401).json({ error: 'Invalid signature' });
145
+ }
146
+
147
+ await this.queueWebhook({ integrationId: req.params.integrationId, body: req.body });
148
+ res.status(200).json({ received: true });
149
+ }
150
+ ```
151
+
@@ -22,6 +22,8 @@ const constantsToBeMigrated = {
22
22
  GET_USER_ACTIONS: 'GET_USER_ACTIONS',
23
23
  GET_USER_ACTION_OPTIONS: 'GET_USER_ACTION_OPTIONS',
24
24
  REFRESH_USER_ACTION_OPTIONS: 'REFRESH_USER_ACTION_OPTIONS',
25
+ WEBHOOK_RECEIVED: 'WEBHOOK_RECEIVED', // HTTP handler, no DB
26
+ ON_WEBHOOK: 'ON_WEBHOOK', // Queue worker, DB-connected
25
27
  // etc...
26
28
  },
27
29
  types: {
@@ -130,6 +132,14 @@ class IntegrationBase {
130
132
  type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
131
133
  handler: this.refreshActionOptions,
132
134
  },
135
+ [constantsToBeMigrated.defaultEvents.WEBHOOK_RECEIVED]: {
136
+ type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
137
+ handler: this.onWebhookReceived,
138
+ },
139
+ [constantsToBeMigrated.defaultEvents.ON_WEBHOOK]: {
140
+ type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
141
+ handler: this.onWebhook,
142
+ },
133
143
  };
134
144
  }
135
145
 
@@ -294,7 +304,9 @@ class IntegrationBase {
294
304
  await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
295
305
  }
296
306
 
297
- async onUpdate(params) {}
307
+ async onUpdate(params) {
308
+ return this.validateConfig();
309
+ }
298
310
 
299
311
  async onDelete(params) {}
300
312
 
@@ -357,6 +369,50 @@ class IntegrationBase {
357
369
  return options;
358
370
  }
359
371
 
372
+ /**
373
+ * WEBHOOK EVENT HANDLERS
374
+ */
375
+ async onWebhookReceived({ req, res }) {
376
+ // Default: queue webhook for processing
377
+ const body = req.body;
378
+ const integrationId = req.params.integrationId || null;
379
+
380
+ await this.queueWebhook({
381
+ integrationId,
382
+ body,
383
+ headers: req.headers,
384
+ query: req.query,
385
+ });
386
+
387
+ res.status(200).json({ received: true });
388
+ }
389
+
390
+ async onWebhook({ data }) {
391
+ // Default: no-op, integrations override this
392
+ console.log('Webhook received:', data);
393
+ }
394
+
395
+ async queueWebhook(data) {
396
+ const { QueuerUtil } = require('../queues');
397
+
398
+ const queueName = `${this.constructor.Definition.name
399
+ .toUpperCase()
400
+ .replace(/-/g, '_')}_QUEUE_URL`;
401
+ const queueUrl = process.env[queueName];
402
+
403
+ if (!queueUrl) {
404
+ throw new Error(`Queue URL not found for ${queueName}`);
405
+ }
406
+
407
+ return QueuerUtil.send(
408
+ {
409
+ event: 'ON_WEBHOOK',
410
+ data,
411
+ },
412
+ queueUrl
413
+ );
414
+ }
415
+
360
416
  // === Domain Methods (moved from Integration.js) ===
361
417
 
362
418
  getConfig() {
@@ -403,8 +459,14 @@ class IntegrationBase {
403
459
  return this.userId.toString() === userId.toString();
404
460
  }
405
461
 
462
+ registerEventHandlers() {
463
+ this.on = {
464
+ ...this.defaultEvents,
465
+ ...this.events,
466
+ };
467
+ }
468
+
406
469
  async initialize() {
407
- // Load dynamic user actions
408
470
  try {
409
471
  const additionalUserActions = await this.loadDynamicUserActions();
410
472
  this.events = { ...this.events, ...additionalUserActions };
@@ -412,7 +474,16 @@ class IntegrationBase {
412
474
  this.addError(e);
413
475
  }
414
476
 
415
- // Event handlers are no longer registered here - handled by IntegrationEventDispatcher
477
+ this.registerEventHandlers();
478
+ }
479
+
480
+ async send(event, object) {
481
+ if (!this.on[event]) {
482
+ throw new Error(
483
+ `Event ${event} is not defined in the Integration event object`
484
+ );
485
+ }
486
+ return this.on[event].handler.call(this, object);
416
487
  }
417
488
 
418
489
  getOptionDetails() {
@@ -56,6 +56,18 @@ const {
56
56
  const {
57
57
  GetUserFromBearerToken,
58
58
  } = require('../user/use-cases/get-user-from-bearer-token');
59
+ const {
60
+ GetUserFromXFriggHeaders,
61
+ } = require('../user/use-cases/get-user-from-x-frigg-headers');
62
+ const {
63
+ GetUserFromAdopterJwt,
64
+ } = require('../user/use-cases/get-user-from-adopter-jwt');
65
+ const {
66
+ AuthenticateWithSharedSecret,
67
+ } = require('../user/use-cases/authenticate-with-shared-secret');
68
+ const {
69
+ AuthenticateUser,
70
+ } = require('../user/use-cases/authenticate-user');
59
71
  const {
60
72
  ProcessAuthorizationCallback,
61
73
  } = require('../modules/use-cases/process-authorization-callback');
@@ -73,6 +85,26 @@ function createIntegrationRouter() {
73
85
  userConfig,
74
86
  });
75
87
 
88
+ const getUserFromXFriggHeaders = new GetUserFromXFriggHeaders({
89
+ userRepository,
90
+ userConfig,
91
+ });
92
+
93
+ const getUserFromAdopterJwt = new GetUserFromAdopterJwt({
94
+ userRepository,
95
+ userConfig,
96
+ });
97
+
98
+ const authenticateWithSharedSecret = new AuthenticateWithSharedSecret();
99
+
100
+ const authenticateUser = new AuthenticateUser({
101
+ getUserFromBearerToken,
102
+ getUserFromXFriggHeaders,
103
+ getUserFromAdopterJwt,
104
+ authenticateWithSharedSecret,
105
+ userConfig,
106
+ });
107
+
76
108
  const moduleFactory = new ModuleFactory({
77
109
  moduleRepository,
78
110
  moduleDefinitions:
@@ -165,7 +197,7 @@ function createIntegrationRouter() {
165
197
 
166
198
  const router = express();
167
199
 
168
- setIntegrationRoutes(router, getUserFromBearerToken, {
200
+ setIntegrationRoutes(router, authenticateUser, {
169
201
  createIntegration,
170
202
  deleteIntegrationForUser,
171
203
  getIntegrationsForUser,
@@ -174,7 +206,7 @@ function createIntegrationRouter() {
174
206
  updateIntegration,
175
207
  getPossibleIntegrations,
176
208
  });
177
- setEntityRoutes(router, getUserFromBearerToken, {
209
+ setEntityRoutes(router, authenticateUser, {
178
210
  getCredentialForUser,
179
211
  getModuleInstanceFromType,
180
212
  getEntityOptionsByType,
@@ -201,10 +233,8 @@ function checkRequiredParams(params, requiredKeys) {
201
233
 
202
234
  if (missingKeys.length > 0) {
203
235
  throw Boom.badRequest(
204
- `Missing Parameter${
205
- missingKeys.length === 1 ? '' : 's'
206
- }: ${missingKeys.join(', ')} ${
207
- missingKeys.length === 1 ? 'is' : 'are'
236
+ `Missing Parameter${missingKeys.length === 1 ? '' : 's'
237
+ }: ${missingKeys.join(', ')} ${missingKeys.length === 1 ? 'is' : 'are'
208
238
  } required.`
209
239
  );
210
240
  }
@@ -214,10 +244,10 @@ function checkRequiredParams(params, requiredKeys) {
214
244
  /**
215
245
  * Sets up integration-related routes on the provided Express router
216
246
  * @param {express.Router} router - Express router instance to add routes to
217
- * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} getUserFromBearerToken - Use case for retrieving a user from a bearer token
247
+ * @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
218
248
  * @param {Object} useCases - use cases for integration management
219
249
  */
220
- function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
250
+ function setIntegrationRoutes(router, authenticateUser, useCases) {
221
251
  const {
222
252
  createIntegration,
223
253
  deleteIntegrationForUser,
@@ -229,9 +259,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
229
259
  } = useCases;
230
260
  router.route('/api/integrations').get(
231
261
  catchAsyncError(async (req, res) => {
232
- const user = await getUserFromBearerToken.execute(
233
- req.headers.authorization
234
- );
262
+ const user = await authenticateUser.execute(req);
235
263
  const userId = user.getId();
236
264
  const integrations = await getIntegrationsForUser.execute(userId);
237
265
  const results = {
@@ -248,9 +276,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
248
276
 
249
277
  router.route('/api/integrations').post(
250
278
  catchAsyncError(async (req, res) => {
251
- const user = await getUserFromBearerToken.execute(
252
- req.headers.authorization
253
- );
279
+ const user = await authenticateUser.execute(req);
254
280
  const userId = user.getId();
255
281
  const params = checkRequiredParams(req.body, [
256
282
  'entities',
@@ -271,9 +297,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
271
297
 
272
298
  router.route('/api/integrations/:integrationId').patch(
273
299
  catchAsyncError(async (req, res) => {
274
- const user = await getUserFromBearerToken.execute(
275
- req.headers.authorization
276
- );
300
+ const user = await authenticateUser.execute(req);
277
301
  const userId = user.getId();
278
302
  const params = checkRequiredParams(req.body, ['config']);
279
303
 
@@ -288,9 +312,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
288
312
 
289
313
  router.route('/api/integrations/:integrationId').delete(
290
314
  catchAsyncError(async (req, res) => {
291
- const user = await getUserFromBearerToken.execute(
292
- req.headers.authorization
293
- );
315
+ const user = await authenticateUser.execute(req);
294
316
  const params = checkRequiredParams(req.params, ['integrationId']);
295
317
  await deleteIntegrationForUser.execute(
296
318
  params.integrationId,
@@ -302,9 +324,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
302
324
 
303
325
  router.route('/api/integrations/:integrationId/config/options').get(
304
326
  catchAsyncError(async (req, res) => {
305
- const user = await getUserFromBearerToken.execute(
306
- req.headers.authorization
307
- );
327
+ const user = await authenticateUser.execute(req);
308
328
  const params = checkRequiredParams(req.params, ['integrationId']);
309
329
  const integration = await getIntegrationInstance.execute(
310
330
  params.integrationId,
@@ -318,9 +338,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
318
338
  .route('/api/integrations/:integrationId/config/options/refresh')
319
339
  .post(
320
340
  catchAsyncError(async (req, res) => {
321
- const user = await getUserFromBearerToken.execute(
322
- req.headers.authorization
323
- );
341
+ const user = await authenticateUser.execute(req);
324
342
  const params = checkRequiredParams(req.params, [
325
343
  'integrationId',
326
344
  ]);
@@ -336,9 +354,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
336
354
  );
337
355
  router.route('/api/integrations/:integrationId/actions').all(
338
356
  catchAsyncError(async (req, res) => {
339
- const user = await getUserFromBearerToken.execute(
340
- req.headers.authorization
341
- );
357
+ const user = await authenticateUser.execute(req);
342
358
  const params = checkRequiredParams(req.params, ['integrationId']);
343
359
  const integration = await getIntegrationInstance.execute(
344
360
  params.integrationId,
@@ -352,9 +368,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
352
368
  .route('/api/integrations/:integrationId/actions/:actionId/options')
353
369
  .all(
354
370
  catchAsyncError(async (req, res) => {
355
- const user = await getUserFromBearerToken.execute(
356
- req.headers.authorization
357
- );
371
+ const user = await authenticateUser.execute(req);
358
372
  const params = checkRequiredParams(req.params, [
359
373
  'integrationId',
360
374
  'actionId',
@@ -379,9 +393,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
379
393
  )
380
394
  .post(
381
395
  catchAsyncError(async (req, res) => {
382
- const user = await getUserFromBearerToken.execute(
383
- req.headers.authorization
384
- );
396
+ const user = await authenticateUser.execute(req);
385
397
  const params = checkRequiredParams(req.params, [
386
398
  'integrationId',
387
399
  'actionId',
@@ -402,9 +414,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
402
414
 
403
415
  router.route('/api/integrations/:integrationId/actions/:actionId').post(
404
416
  catchAsyncError(async (req, res) => {
405
- const user = await getUserFromBearerToken.execute(
406
- req.headers.authorization
407
- );
417
+ const user = await authenticateUser.execute(req);
408
418
  const params = checkRequiredParams(req.params, [
409
419
  'integrationId',
410
420
  'actionId',
@@ -419,9 +429,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
419
429
 
420
430
  router.route('/api/integrations/:integrationId').get(
421
431
  catchAsyncError(async (req, res) => {
422
- const user = await getUserFromBearerToken.execute(
423
- req.headers.authorization
424
- );
432
+ const user = await authenticateUser.execute(req);
425
433
 
426
434
  if (!user) {
427
435
  throw Boom.forbidden('User not found');
@@ -446,9 +454,7 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
446
454
 
447
455
  router.route('/api/integrations/:integrationId/test-auth').get(
448
456
  catchAsyncError(async (req, res) => {
449
- const user = await getUserFromBearerToken.execute(
450
- req.headers.authorization
451
- );
457
+ const user = await authenticateUser.execute(req);
452
458
  const params = checkRequiredParams(req.params, ['integrationId']);
453
459
  const instance = await getIntegrationInstance.execute(
454
460
  params.integrationId,
@@ -478,9 +484,9 @@ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
478
484
  /**
479
485
  * Sets up entity-related routes for the integration router
480
486
  * @param {Object} router - Express router instance
481
- * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} getUserFromBearerToken - Use case for retrieving a user from a bearer token
487
+ * @param {import('../user/use-cases/authenticate-user').AuthenticateUser} authenticateUser - Use case for multi-mode user authentication
482
488
  */
483
- function setEntityRoutes(router, getUserFromBearerToken, useCases) {
489
+ function setEntityRoutes(router, authenticateUser, useCases) {
484
490
  const {
485
491
  getCredentialForUser,
486
492
  getModuleInstanceFromType,
@@ -494,9 +500,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
494
500
 
495
501
  router.route('/api/authorize').get(
496
502
  catchAsyncError(async (req, res) => {
497
- const user = await getUserFromBearerToken.execute(
498
- req.headers.authorization
499
- );
503
+ const user = await authenticateUser.execute(req);
500
504
  const userId = user.getId();
501
505
  const params = checkRequiredParams(req.query, ['entityType']);
502
506
  const module = await getModuleInstanceFromType.execute(
@@ -517,9 +521,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
517
521
 
518
522
  router.route('/api/authorize').post(
519
523
  catchAsyncError(async (req, res) => {
520
- const user = await getUserFromBearerToken.execute(
521
- req.headers.authorization
522
- );
524
+ const user = await authenticateUser.execute(req);
523
525
  const userId = user.getId();
524
526
  const params = checkRequiredParams(req.body, [
525
527
  'entityType',
@@ -538,9 +540,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
538
540
 
539
541
  router.route('/api/entity').post(
540
542
  catchAsyncError(async (req, res) => {
541
- const user = await getUserFromBearerToken.execute(
542
- req.headers.authorization
543
- );
543
+ const user = await authenticateUser.execute(req);
544
544
  const userId = user.getId();
545
545
  const params = checkRequiredParams(req.body, [
546
546
  'entityType',
@@ -575,9 +575,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
575
575
 
576
576
  router.route('/api/entity/options/:credentialId').get(
577
577
  catchAsyncError(async (req, res) => {
578
- const user = await getUserFromBearerToken.execute(
579
- req.headers.authorization
580
- );
578
+ const user = await authenticateUser.execute(req);
581
579
  const userId = user.getId();
582
580
  // TODO May want to pass along the user ID as well so credential ID's can't be fished???
583
581
  // TODO **flagging this for review** -MW
@@ -601,9 +599,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
601
599
 
602
600
  router.route('/api/entities/:entityId/test-auth').get(
603
601
  catchAsyncError(async (req, res) => {
604
- const user = await getUserFromBearerToken.execute(
605
- req.headers.authorization
606
- );
602
+ const user = await authenticateUser.execute(req);
607
603
  const userId = user.getId();
608
604
  const params = checkRequiredParams(req.params, ['entityId']);
609
605
  const testAuthResponse = await testModuleAuth.execute(
@@ -630,9 +626,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
630
626
 
631
627
  router.route('/api/entities/:entityId').get(
632
628
  catchAsyncError(async (req, res) => {
633
- const user = await getUserFromBearerToken.execute(
634
- req.headers.authorization
635
- );
629
+ const user = await authenticateUser.execute(req);
636
630
  const userId = user.getId();
637
631
  const params = checkRequiredParams(req.params, ['entityId']);
638
632
  const module = await getModule.execute(params.entityId, userId);
@@ -643,9 +637,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
643
637
 
644
638
  router.route('/api/entities/:entityId/options').post(
645
639
  catchAsyncError(async (req, res) => {
646
- const user = await getUserFromBearerToken.execute(
647
- req.headers.authorization
648
- );
640
+ const user = await authenticateUser.execute(req);
649
641
  const userId = user.getId();
650
642
  const params = checkRequiredParams(req.params, ['entityId']);
651
643
 
@@ -660,9 +652,7 @@ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
660
652
 
661
653
  router.route('/api/entities/:entityId/options/refresh').post(
662
654
  catchAsyncError(async (req, res) => {
663
- const user = await getUserFromBearerToken.execute(
664
- req.headers.authorization
665
- );
655
+ const user = await authenticateUser.execute(req);
666
656
  const userId = user.getId();
667
657
  const params = checkRequiredParams(req.params, ['entityId']);
668
658
  const updatedOptions = await refreshEntityOptions.execute(
@@ -110,6 +110,18 @@ class IntegrationRepositoryInterface {
110
110
  async findIntegrationByUserId(userId) {
111
111
  throw new Error('Method findIntegrationByUserId must be implemented by subclass');
112
112
  }
113
+
114
+ /**
115
+ * Update integration configuration
116
+ *
117
+ * @param {string|number} integrationId - Integration ID
118
+ * @param {Object} config - Updated configuration object
119
+ * @returns {Promise<Object>} Updated integration object
120
+ * @abstract
121
+ */
122
+ async updateIntegrationConfig(integrationId, config) {
123
+ throw new Error('Method updateIntegrationConfig must be implemented by subclass');
124
+ }
113
125
  }
114
126
 
115
127
  module.exports = { IntegrationRepositoryInterface };
@@ -266,6 +266,38 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
266
266
  messages: integration.messages,
267
267
  };
268
268
  }
269
+
270
+ /**
271
+ * Update integration configuration
272
+ * Replaces: IntegrationModel.updateOne({ _id: integrationId }, { config })
273
+ *
274
+ * @param {string} integrationId - Integration ID (MongoDB ObjectId as string)
275
+ * @param {Object} config - Updated configuration object
276
+ * @returns {Promise<Object>} Updated integration object
277
+ */
278
+ async updateIntegrationConfig(integrationId, config) {
279
+ if (config === null || config === undefined) {
280
+ throw new Error('Config parameter is required');
281
+ }
282
+
283
+ const integration = await this.prisma.integration.update({
284
+ where: { id: integrationId },
285
+ data: { config },
286
+ include: {
287
+ entities: true,
288
+ },
289
+ });
290
+
291
+ return {
292
+ id: integration.id,
293
+ entitiesIds: integration.entities.map((e) => e.id),
294
+ userId: integration.userId,
295
+ config: integration.config,
296
+ version: integration.version,
297
+ status: integration.status,
298
+ messages: integration.messages,
299
+ };
300
+ }
269
301
  }
270
302
 
271
303
  module.exports = { IntegrationRepositoryMongo };
@@ -314,6 +314,39 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
314
314
  messages: converted.messages,
315
315
  };
316
316
  }
317
+
318
+ /**
319
+ * Update integration configuration
320
+ *
321
+ * @param {string} integrationId - Integration ID (string from application layer)
322
+ * @param {Object} config - Updated configuration object
323
+ * @returns {Promise<Object>} Updated integration object with string IDs
324
+ */
325
+ async updateIntegrationConfig(integrationId, config) {
326
+ if (config === null || config === undefined) {
327
+ throw new Error('Config parameter is required');
328
+ }
329
+
330
+ const intId = this._convertId(integrationId);
331
+ const integration = await this.prisma.integration.update({
332
+ where: { id: intId },
333
+ data: { config },
334
+ include: {
335
+ entities: true,
336
+ },
337
+ });
338
+
339
+ const converted = this._convertIntegrationIds(integration);
340
+ return {
341
+ id: converted.id,
342
+ entitiesIds: converted.entities.map((e) => e.id),
343
+ userId: converted.userId,
344
+ config: converted.config,
345
+ version: converted.version,
346
+ status: converted.status,
347
+ messages: converted.messages,
348
+ };
349
+ }
317
350
  }
318
351
 
319
352
  module.exports = { IntegrationRepositoryPostgres };