@friggframework/core 2.0.0--canary.398.bdb6d27.0 → 2.0.0--canary.397.e634da9.0

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 (98) hide show
  1. package/README.md +931 -50
  2. package/core/create-handler.js +1 -0
  3. package/credential/credential-repository.js +42 -0
  4. package/credential/use-cases/get-credential-for-user.js +21 -0
  5. package/credential/use-cases/update-authentication-status.js +15 -0
  6. package/database/models/WebsocketConnection.js +0 -5
  7. package/handlers/app-definition-loader.js +38 -0
  8. package/handlers/app-handler-helpers.js +0 -3
  9. package/handlers/backend-utils.js +42 -44
  10. package/handlers/routers/auth.js +3 -14
  11. package/handlers/routers/integration-defined-routers.js +8 -5
  12. package/handlers/routers/user.js +25 -5
  13. package/handlers/workers/integration-defined-workers.js +6 -3
  14. package/index.js +1 -16
  15. package/integrations/index.js +0 -5
  16. package/integrations/integration-base.js +142 -46
  17. package/integrations/integration-repository.js +80 -0
  18. package/integrations/integration-router.js +301 -178
  19. package/integrations/integration.js +246 -0
  20. package/integrations/options.js +1 -1
  21. package/integrations/tests/doubles/dummy-integration-class.js +90 -0
  22. package/integrations/tests/doubles/test-integration-repository.js +89 -0
  23. package/integrations/tests/use-cases/create-integration.test.js +124 -0
  24. package/integrations/tests/use-cases/delete-integration-for-user.test.js +143 -0
  25. package/integrations/tests/use-cases/get-integration-for-user.test.js +143 -0
  26. package/integrations/tests/use-cases/get-integration-instance.test.js +169 -0
  27. package/integrations/tests/use-cases/get-integrations-for-user.test.js +169 -0
  28. package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
  29. package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
  30. package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
  31. package/integrations/tests/use-cases/update-integration.test.js +134 -0
  32. package/integrations/use-cases/create-integration.js +71 -0
  33. package/integrations/use-cases/delete-integration-for-user.js +72 -0
  34. package/integrations/use-cases/get-integration-for-user.js +78 -0
  35. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  36. package/integrations/use-cases/get-integration-instance.js +82 -0
  37. package/integrations/use-cases/get-integrations-for-user.js +76 -0
  38. package/integrations/use-cases/get-possible-integrations.js +27 -0
  39. package/integrations/use-cases/index.js +11 -0
  40. package/integrations/use-cases/update-integration-messages.js +31 -0
  41. package/integrations/use-cases/update-integration-status.js +28 -0
  42. package/integrations/use-cases/update-integration.js +91 -0
  43. package/integrations/utils/map-integration-dto.js +36 -0
  44. package/{module-plugin → modules}/index.js +0 -8
  45. package/modules/module-factory.js +54 -0
  46. package/modules/module-repository.js +107 -0
  47. package/modules/module.js +221 -0
  48. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  49. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  50. package/modules/tests/doubles/test-module-factory.js +16 -0
  51. package/modules/tests/doubles/test-module-repository.js +19 -0
  52. package/modules/use-cases/get-entities-for-user.js +32 -0
  53. package/modules/use-cases/get-entity-options-by-id.js +58 -0
  54. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  55. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  56. package/modules/use-cases/get-module.js +56 -0
  57. package/modules/use-cases/process-authorization-callback.js +114 -0
  58. package/modules/use-cases/refresh-entity-options.js +58 -0
  59. package/modules/use-cases/test-module-auth.js +54 -0
  60. package/modules/utils/map-module-dto.js +18 -0
  61. package/package.json +5 -5
  62. package/syncs/sync.js +0 -1
  63. package/types/integrations/index.d.ts +2 -6
  64. package/types/module-plugin/index.d.ts +4 -56
  65. package/types/syncs/index.d.ts +0 -2
  66. package/user/tests/doubles/test-user-repository.js +72 -0
  67. package/user/tests/use-cases/create-individual-user.test.js +24 -0
  68. package/user/tests/use-cases/create-organization-user.test.js +28 -0
  69. package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
  70. package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
  71. package/user/tests/use-cases/login-user.test.js +140 -0
  72. package/user/use-cases/create-individual-user.js +61 -0
  73. package/user/use-cases/create-organization-user.js +47 -0
  74. package/user/use-cases/create-token-for-user-id.js +30 -0
  75. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  76. package/user/use-cases/login-user.js +122 -0
  77. package/user/user-repository.js +62 -0
  78. package/user/user.js +77 -0
  79. package/handlers/routers/middleware/loadUser.js +0 -15
  80. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  81. package/integrations/create-frigg-backend.js +0 -31
  82. package/integrations/integration-factory.js +0 -251
  83. package/integrations/integration-user.js +0 -144
  84. package/integrations/test/integration-base.test.js +0 -144
  85. package/module-plugin/auther.js +0 -393
  86. package/module-plugin/entity-manager.js +0 -70
  87. package/module-plugin/manager.js +0 -169
  88. package/module-plugin/module-factory.js +0 -61
  89. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  90. /package/{module-plugin → modules}/credential.js +0 -0
  91. /package/{module-plugin → modules}/entity.js +0 -0
  92. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  93. /package/{module-plugin → modules}/requester/basic.js +0 -0
  94. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  95. /package/{module-plugin → modules}/requester/requester.js +0 -0
  96. /package/{module-plugin → modules}/requester/requester.test.js +0 -0
  97. /package/{module-plugin → modules}/test/auther.test.js +0 -0
  98. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -1,5 +1,9 @@
1
1
  const { IntegrationMapping } = require('./integration-mapping');
2
2
  const { Options } = require('./options');
3
+ const { UpdateIntegrationStatus } = require('./use-cases/update-integration-status');
4
+ const { IntegrationRepository } = require('./integration-repository');
5
+ const { UpdateIntegrationMessages } = require('./use-cases/update-integration-messages');
6
+
3
7
  const constantsToBeMigrated = {
4
8
  defaultEvents: {
5
9
  ON_CREATE: 'ON_CREATE',
@@ -19,6 +23,12 @@ const constantsToBeMigrated = {
19
23
  };
20
24
 
21
25
  class IntegrationBase {
26
+
27
+ // todo: maybe we can pass this as Dependency Injection in the sub-class constructor
28
+ integrationRepository = new IntegrationRepository();
29
+ updateIntegrationStatus = new UpdateIntegrationStatus({ integrationRepository: this.integrationRepository });
30
+ updateIntegrationMessages = new UpdateIntegrationMessages({ integrationRepository: this.integrationRepository });
31
+
22
32
  static getOptionDetails() {
23
33
  const options = new Options({
24
34
  module: Object.values(this.Definition.modules)[0], // This is a placeholder until we revamp the frontend
@@ -26,6 +36,7 @@ class IntegrationBase {
26
36
  });
27
37
  return options.get();
28
38
  }
39
+
29
40
  /**
30
41
  * CHILDREN SHOULD SPECIFY A DEFINITION FOR THE INTEGRATION
31
42
  */
@@ -50,21 +61,7 @@ class IntegrationBase {
50
61
  static getCurrentVersion() {
51
62
  return this.Definition.version;
52
63
  }
53
- loadModules() {
54
- // Load all the modules defined in Definition.modules
55
- const moduleNames = Object.keys(this.constructor.Definition.modules);
56
- for (const moduleName of moduleNames) {
57
- const { definition } =
58
- this.constructor.Definition.modules[moduleName];
59
- if (typeof definition.API === 'function') {
60
- this[moduleName] = { api: new definition.API() };
61
- } else {
62
- throw new Error(
63
- `Module ${moduleName} must be a function that extends IntegrationModule`
64
- );
65
- }
66
- }
67
- }
64
+
68
65
  registerEventHandlers() {
69
66
  this.on = {
70
67
  ...this.defaultEvents,
@@ -72,7 +69,31 @@ class IntegrationBase {
72
69
  };
73
70
  }
74
71
 
75
- constructor(params) {
72
+ constructor(params = {}) {
73
+ // Data from database record (when instantiated by use cases)
74
+ this.id = params.id;
75
+ this.userId = params.userId || params.integrationId; // fallback for legacy
76
+ this.entities = params.entities;
77
+ this.config = params.config;
78
+ this.status = params.status;
79
+ this.version = params.version;
80
+ this.messages = params.messages || { errors: [], warnings: [] };
81
+
82
+ // Module instances (injected by factory)
83
+ this.modules = {};
84
+ if (params.modules) {
85
+ for (const mod of params.modules) {
86
+ const key = typeof mod.getName === 'function' ? mod.getName() : mod.name;
87
+ if (key) {
88
+ this.modules[key] = mod;
89
+ this[key] = mod; // Direct access (e.g., this.hubspot)
90
+ }
91
+ }
92
+ }
93
+
94
+ // Initialize events object (will be populated by child classes)
95
+ this.events = this.events || {};
96
+
76
97
  this.defaultEvents = {
77
98
  [constantsToBeMigrated.defaultEvents.ON_CREATE]: {
78
99
  type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
@@ -107,7 +128,6 @@ class IntegrationBase {
107
128
  handler: this.refreshActionOptions,
108
129
  },
109
130
  };
110
- this.loadModules();
111
131
  }
112
132
 
113
133
  async send(event, object) {
@@ -121,7 +141,7 @@ class IntegrationBase {
121
141
 
122
142
  async validateConfig() {
123
143
  const configOptions = await this.getConfigOptions();
124
- const currentConfig = this.record.config;
144
+ const currentConfig = this.getConfig();
125
145
  let needsConfig = false;
126
146
  for (const option of configOptions) {
127
147
  if (option.required) {
@@ -133,56 +153,59 @@ class IntegrationBase {
133
153
  )
134
154
  ) {
135
155
  needsConfig = true;
136
- this.record.messages.warnings.push({
137
- title: 'Config Validation Error',
138
- message: `Missing required field of ${option.label}`,
139
- timestamp: Date.now(),
140
- });
156
+ await this.updateIntegrationMessages.execute(
157
+ this.id,
158
+ 'warnings',
159
+ 'Config Validation Error',
160
+ `Missing required field of ${option.label}`,
161
+ Date.now()
162
+ );
141
163
  }
142
164
  }
143
165
  }
144
166
  if (needsConfig) {
145
- this.record.status = 'NEEDS_CONFIG';
146
- await this.record.save();
167
+ await this.updateIntegrationStatus.execute(this.id, 'NEEDS_CONFIG');
147
168
  }
148
169
  }
149
170
 
150
171
  async testAuth() {
151
172
  let didAuthPass = true;
152
173
 
153
- for (const module of Object.keys(IntegrationBase.Definition.modules)) {
174
+ for (const module of Object.keys(this.constructor.Definition.modules)) {
154
175
  try {
155
176
  await this[module].testAuth();
156
177
  } catch {
157
178
  didAuthPass = false;
158
- this.record.messages.errors.push({
159
- title: 'Authentication Error',
160
- message: `There was an error with your ${this[
179
+ await this.updateIntegrationMessages.execute(
180
+ this.id,
181
+ 'errors',
182
+ 'Authentication Error',
183
+ `There was an error with your ${this[
161
184
  module
162
185
  ].constructor.getName()} Entity.
163
186
  Please reconnect/re-authenticate, or reach out to Support for assistance.`,
164
- timestamp: Date.now(),
165
- });
187
+ Date.now()
188
+ );
166
189
  }
167
190
  }
168
191
 
169
192
  if (!didAuthPass) {
170
- this.record.status = 'ERROR';
171
- this.record.markModified('messages.error');
172
- await this.record.save();
193
+ await this.updateIntegrationStatus.execute(this.id, 'ERROR');
173
194
  }
174
195
  }
175
196
 
176
197
  async getMapping(sourceId) {
177
- return IntegrationMapping.findBy(this.record.id, sourceId);
198
+ // todo: this should be a use case
199
+ return IntegrationMapping.findBy(this.id, sourceId);
178
200
  }
179
201
 
180
202
  async upsertMapping(sourceId, mapping) {
181
203
  if (!sourceId) {
182
204
  throw new Error(`sourceId must be set`);
183
205
  }
206
+ // todo: this should be a use case
184
207
  return await IntegrationMapping.upsert(
185
- this.record.id,
208
+ this.id,
186
209
  sourceId,
187
210
  mapping
188
211
  );
@@ -191,15 +214,13 @@ class IntegrationBase {
191
214
  /**
192
215
  * CHILDREN CAN OVERRIDE THESE CONFIGURATION METHODS
193
216
  */
194
- async onCreate(params) {
195
- this.record.status = 'ENABLED';
196
- await this.record.save();
197
- return this.record;
217
+ async onCreate({ integrationId }) {
218
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
198
219
  }
199
220
 
200
- async onUpdate(params) {}
221
+ async onUpdate(params) { }
201
222
 
202
- async onDelete(params) {}
223
+ async onDelete(params) { }
203
224
 
204
225
  async getConfigOptions() {
205
226
  const options = {
@@ -236,10 +257,10 @@ class IntegrationBase {
236
257
  const dynamicUserActions = await this.loadDynamicUserActions();
237
258
  const filteredDynamicActions = actionType
238
259
  ? Object.fromEntries(
239
- Object.entries(dynamicUserActions).filter(
240
- ([_, event]) => event.userActionType === actionType
241
- )
242
- )
260
+ Object.entries(dynamicUserActions).filter(
261
+ ([_, event]) => event.userActionType === actionType
262
+ )
263
+ )
243
264
  : dynamicUserActions;
244
265
  return { ...userActions, ...filteredDynamicActions };
245
266
  }
@@ -259,6 +280,81 @@ class IntegrationBase {
259
280
  };
260
281
  return options;
261
282
  }
283
+
284
+ // === Domain Methods (moved from Integration.js) ===
285
+
286
+ getConfig() {
287
+ return this.config;
288
+ }
289
+
290
+ getModule(key) {
291
+ return this.modules[key];
292
+ }
293
+
294
+ setModule(key, module) {
295
+ this.modules[key] = module;
296
+ this[key] = module;
297
+ }
298
+
299
+ addError(error) {
300
+ if (!this.messages.errors) {
301
+ this.messages.errors = [];
302
+ }
303
+ this.messages.errors.push(error);
304
+ this.status = 'ERROR';
305
+ }
306
+
307
+ addWarning(warning) {
308
+ if (!this.messages.warnings) {
309
+ this.messages.warnings = [];
310
+ }
311
+ this.messages.warnings.push(warning);
312
+ }
313
+
314
+ isActive() {
315
+ return this.status === 'ENABLED' || this.status === 'ACTIVE';
316
+ }
317
+
318
+ needsConfiguration() {
319
+ return this.status === 'NEEDS_CONFIG';
320
+ }
321
+
322
+ hasErrors() {
323
+ return this.status === 'ERROR';
324
+ }
325
+
326
+ belongsToUser(userId) {
327
+ return this.userId.toString() === userId.toString();
328
+ }
329
+
330
+ async initialize() {
331
+ // Load dynamic user actions
332
+ try {
333
+ const additionalUserActions = await this.loadDynamicUserActions();
334
+ this.events = { ...this.events, ...additionalUserActions };
335
+ } catch (e) {
336
+ this.addError(e);
337
+ }
338
+
339
+ // Register event handlers
340
+ await this.registerEventHandlers();
341
+ }
342
+
343
+ getOptionDetails() {
344
+ const options = new Options({
345
+ module: Object.values(this.constructor.Definition.modules)[0],
346
+ ...this.constructor.Definition,
347
+ });
348
+ return options.get();
349
+ }
350
+
351
+ // Legacy method for backward compatibility
352
+ async loadModules() {
353
+ // This method was used in the old architecture for loading modules
354
+ // In the new architecture, modules are injected via constructor
355
+ // For backward compatibility, this is a no-op
356
+ return;
357
+ }
262
358
  }
263
359
 
264
360
  module.exports = { IntegrationBase };
@@ -0,0 +1,80 @@
1
+ const { IntegrationModel } = require('./integration-model');
2
+
3
+ class IntegrationRepository {
4
+ async findIntegrationsByUserId(userId) {
5
+ const integrationRecords = await IntegrationModel.find({ user: userId }, '', { lean: true }).populate('entities');
6
+ return integrationRecords.map(integrationRecord => ({
7
+ id: integrationRecord._id.toString(),
8
+ entitiesIds: integrationRecord.entities.map(e => e._id),
9
+ userId: integrationRecord.user.toString(),
10
+ config: integrationRecord.config,
11
+ version: integrationRecord.version,
12
+ status: integrationRecord.status,
13
+ messages: integrationRecord.messages,
14
+ }));
15
+ }
16
+
17
+ async deleteIntegrationById(integrationId) {
18
+ return IntegrationModel.deleteOne({ _id: integrationId });
19
+ }
20
+
21
+ async findIntegrationByName(name) {
22
+ const integrationRecord = await IntegrationModel.findOne({ 'config.type': name }, '', { lean: true }).populate('entities');
23
+ return {
24
+ id: integrationRecord._id.toString(),
25
+ entitiesIds: integrationRecord.entities.map(e => e._id),
26
+ userId: integrationRecord.user.toString(),
27
+ config: integrationRecord.config,
28
+ version: integrationRecord.version,
29
+ status: integrationRecord.status,
30
+ messages: integrationRecord.messages,
31
+ };
32
+ }
33
+
34
+ async findIntegrationById(id) {
35
+ const integrationRecord = await IntegrationModel.findById(id, '', { lean: true }).populate('entities');
36
+ return {
37
+ id: integrationRecord._id.toString(),
38
+ entitiesIds: integrationRecord.entities.map(e => e._id),
39
+ userId: integrationRecord.user.toString(),
40
+ config: integrationRecord.config,
41
+ version: integrationRecord.version,
42
+ status: integrationRecord.status,
43
+ messages: integrationRecord.messages,
44
+ }
45
+ }
46
+
47
+ async updateIntegrationStatus(integrationId, status) {
48
+ const integrationRecord = await IntegrationModel.updateOne({ _id: integrationId }, { status });
49
+ return integrationRecord.acknowledged;
50
+ }
51
+
52
+ async updateIntegrationMessages(integrationId, messageType, messageTitle, messageBody, messageTimestamp) {
53
+ const integrationRecord = await IntegrationModel.updateOne(
54
+ { _id: integrationId },
55
+ { $push: { [`messages.${messageType}`]: { title: messageTitle, message: messageBody, timestamp: messageTimestamp } } }
56
+ );
57
+ return integrationRecord.acknowledged;
58
+ }
59
+
60
+ async createIntegration(entities, userId, config) {
61
+ const integrationRecord = await IntegrationModel.create({
62
+ entities: entities,
63
+ user: userId,
64
+ config,
65
+ version: '0.0.0',
66
+ });
67
+
68
+ return {
69
+ id: integrationRecord._id.toString(),
70
+ entitiesIds: integrationRecord.entities.map(e => e._id),
71
+ userId: integrationRecord.user.toString(),
72
+ config: integrationRecord.config,
73
+ version: integrationRecord.version,
74
+ status: integrationRecord.status,
75
+ messages: integrationRecord.messages,
76
+ };
77
+ }
78
+ }
79
+
80
+ module.exports = { IntegrationRepository };