@friggframework/core 2.0.0--canary.395.65f5f64.0 → 2.0.0--canary.398.24926ac.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 (97) hide show
  1. package/README.md +50 -931
  2. package/core/create-handler.js +0 -1
  3. package/database/models/WebsocketConnection.js +5 -0
  4. package/handlers/app-handler-helpers.js +3 -0
  5. package/handlers/backend-utils.js +44 -42
  6. package/handlers/routers/auth.js +14 -3
  7. package/handlers/routers/integration-defined-routers.js +5 -8
  8. package/handlers/routers/middleware/loadUser.js +15 -0
  9. package/handlers/routers/middleware/requireLoggedInUser.js +12 -0
  10. package/handlers/routers/user.js +5 -25
  11. package/handlers/workers/integration-defined-workers.js +3 -6
  12. package/index.js +16 -1
  13. package/integrations/create-frigg-backend.js +31 -0
  14. package/integrations/index.js +5 -0
  15. package/integrations/integration-base.js +46 -142
  16. package/integrations/integration-factory.js +251 -0
  17. package/integrations/integration-router.js +181 -303
  18. package/integrations/integration-user.js +144 -0
  19. package/integrations/options.js +1 -1
  20. package/integrations/test/integration-base.test.js +144 -0
  21. package/module-plugin/auther.js +393 -0
  22. package/module-plugin/entity-manager.js +70 -0
  23. package/{modules → module-plugin}/entity.js +0 -1
  24. package/{modules → module-plugin}/index.js +8 -0
  25. package/module-plugin/manager.js +169 -0
  26. package/module-plugin/module-factory.js +61 -0
  27. package/{modules → module-plugin}/test/mock-api/api.js +3 -8
  28. package/{modules → module-plugin}/test/mock-api/definition.js +8 -12
  29. package/package.json +5 -5
  30. package/syncs/sync.js +1 -0
  31. package/types/integrations/index.d.ts +6 -2
  32. package/types/module-plugin/index.d.ts +56 -4
  33. package/types/syncs/index.d.ts +2 -0
  34. package/credential/credential-repository.js +0 -56
  35. package/credential/use-cases/get-credential-for-user.js +0 -21
  36. package/credential/use-cases/update-authentication-status.js +0 -15
  37. package/handlers/app-definition-loader.js +0 -38
  38. package/integrations/integration-repository.js +0 -80
  39. package/integrations/tests/doubles/dummy-integration-class.js +0 -90
  40. package/integrations/tests/doubles/test-integration-repository.js +0 -89
  41. package/integrations/tests/use-cases/create-integration.test.js +0 -124
  42. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -143
  43. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -143
  44. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -169
  45. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -169
  46. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
  47. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
  48. package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
  49. package/integrations/tests/use-cases/update-integration.test.js +0 -134
  50. package/integrations/use-cases/create-integration.js +0 -71
  51. package/integrations/use-cases/delete-integration-for-user.js +0 -72
  52. package/integrations/use-cases/get-integration-for-user.js +0 -78
  53. package/integrations/use-cases/get-integration-instance-by-definition.js +0 -67
  54. package/integrations/use-cases/get-integration-instance.js +0 -82
  55. package/integrations/use-cases/get-integrations-for-user.js +0 -76
  56. package/integrations/use-cases/get-possible-integrations.js +0 -27
  57. package/integrations/use-cases/index.js +0 -11
  58. package/integrations/use-cases/update-integration-messages.js +0 -31
  59. package/integrations/use-cases/update-integration-status.js +0 -28
  60. package/integrations/use-cases/update-integration.js +0 -91
  61. package/integrations/utils/map-integration-dto.js +0 -36
  62. package/modules/module-factory.js +0 -54
  63. package/modules/module-repository.js +0 -107
  64. package/modules/module.js +0 -218
  65. package/modules/tests/doubles/test-module-factory.js +0 -16
  66. package/modules/tests/doubles/test-module-repository.js +0 -19
  67. package/modules/use-cases/get-entities-for-user.js +0 -32
  68. package/modules/use-cases/get-entity-options-by-id.js +0 -58
  69. package/modules/use-cases/get-entity-options-by-type.js +0 -34
  70. package/modules/use-cases/get-module-instance-from-type.js +0 -31
  71. package/modules/use-cases/get-module.js +0 -56
  72. package/modules/use-cases/process-authorization-callback.js +0 -108
  73. package/modules/use-cases/refresh-entity-options.js +0 -58
  74. package/modules/use-cases/test-module-auth.js +0 -54
  75. package/modules/utils/map-module-dto.js +0 -18
  76. package/user/tests/doubles/test-user-repository.js +0 -72
  77. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  78. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  79. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  80. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  81. package/user/tests/use-cases/login-user.test.js +0 -140
  82. package/user/use-cases/create-individual-user.js +0 -61
  83. package/user/use-cases/create-organization-user.js +0 -47
  84. package/user/use-cases/create-token-for-user-id.js +0 -30
  85. package/user/use-cases/get-user-from-bearer-token.js +0 -77
  86. package/user/use-cases/login-user.js +0 -122
  87. package/user/user-repository.js +0 -62
  88. package/user/user.js +0 -77
  89. /package/{modules → module-plugin}/ModuleConstants.js +0 -0
  90. /package/{modules → module-plugin}/credential.js +0 -0
  91. /package/{modules → module-plugin}/requester/api-key.js +0 -0
  92. /package/{modules → module-plugin}/requester/basic.js +0 -0
  93. /package/{modules → module-plugin}/requester/oauth-2.js +0 -0
  94. /package/{modules → module-plugin}/requester/requester.js +0 -0
  95. /package/{modules → module-plugin}/requester/requester.test.js +0 -0
  96. /package/{modules → module-plugin}/test/auther.test.js +0 -0
  97. /package/{modules → module-plugin}/test/mock-api/mocks/hubspot.js +0 -0
@@ -1,9 +1,5 @@
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
-
7
3
  const constantsToBeMigrated = {
8
4
  defaultEvents: {
9
5
  ON_CREATE: 'ON_CREATE',
@@ -23,12 +19,6 @@ const constantsToBeMigrated = {
23
19
  };
24
20
 
25
21
  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
-
32
22
  static getOptionDetails() {
33
23
  const options = new Options({
34
24
  module: Object.values(this.Definition.modules)[0], // This is a placeholder until we revamp the frontend
@@ -36,7 +26,6 @@ class IntegrationBase {
36
26
  });
37
27
  return options.get();
38
28
  }
39
-
40
29
  /**
41
30
  * CHILDREN SHOULD SPECIFY A DEFINITION FOR THE INTEGRATION
42
31
  */
@@ -61,7 +50,21 @@ class IntegrationBase {
61
50
  static getCurrentVersion() {
62
51
  return this.Definition.version;
63
52
  }
64
-
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
+ }
65
68
  registerEventHandlers() {
66
69
  this.on = {
67
70
  ...this.defaultEvents,
@@ -69,31 +72,7 @@ class IntegrationBase {
69
72
  };
70
73
  }
71
74
 
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
-
75
+ constructor(params) {
97
76
  this.defaultEvents = {
98
77
  [constantsToBeMigrated.defaultEvents.ON_CREATE]: {
99
78
  type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
@@ -128,6 +107,7 @@ class IntegrationBase {
128
107
  handler: this.refreshActionOptions,
129
108
  },
130
109
  };
110
+ this.loadModules();
131
111
  }
132
112
 
133
113
  async send(event, object) {
@@ -141,7 +121,7 @@ class IntegrationBase {
141
121
 
142
122
  async validateConfig() {
143
123
  const configOptions = await this.getConfigOptions();
144
- const currentConfig = this.getConfig();
124
+ const currentConfig = this.record.config;
145
125
  let needsConfig = false;
146
126
  for (const option of configOptions) {
147
127
  if (option.required) {
@@ -153,59 +133,56 @@ class IntegrationBase {
153
133
  )
154
134
  ) {
155
135
  needsConfig = true;
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
- );
136
+ this.record.messages.warnings.push({
137
+ title: 'Config Validation Error',
138
+ message: `Missing required field of ${option.label}`,
139
+ timestamp: Date.now(),
140
+ });
163
141
  }
164
142
  }
165
143
  }
166
144
  if (needsConfig) {
167
- await this.updateIntegrationStatus.execute(this.id, 'NEEDS_CONFIG');
145
+ this.record.status = 'NEEDS_CONFIG';
146
+ await this.record.save();
168
147
  }
169
148
  }
170
149
 
171
150
  async testAuth() {
172
151
  let didAuthPass = true;
173
152
 
174
- for (const module of Object.keys(this.constructor.Definition.modules)) {
153
+ for (const module of Object.keys(IntegrationBase.Definition.modules)) {
175
154
  try {
176
155
  await this[module].testAuth();
177
156
  } catch {
178
157
  didAuthPass = false;
179
- await this.updateIntegrationMessages.execute(
180
- this.id,
181
- 'errors',
182
- 'Authentication Error',
183
- `There was an error with your ${this[
158
+ this.record.messages.errors.push({
159
+ title: 'Authentication Error',
160
+ message: `There was an error with your ${this[
184
161
  module
185
162
  ].constructor.getName()} Entity.
186
163
  Please reconnect/re-authenticate, or reach out to Support for assistance.`,
187
- Date.now()
188
- );
164
+ timestamp: Date.now(),
165
+ });
189
166
  }
190
167
  }
191
168
 
192
169
  if (!didAuthPass) {
193
- await this.updateIntegrationStatus.execute(this.id, 'ERROR');
170
+ this.record.status = 'ERROR';
171
+ this.record.markModified('messages.error');
172
+ await this.record.save();
194
173
  }
195
174
  }
196
175
 
197
176
  async getMapping(sourceId) {
198
- // todo: this should be a use case
199
- return IntegrationMapping.findBy(this.id, sourceId);
177
+ return IntegrationMapping.findBy(this.record.id, sourceId);
200
178
  }
201
179
 
202
180
  async upsertMapping(sourceId, mapping) {
203
181
  if (!sourceId) {
204
182
  throw new Error(`sourceId must be set`);
205
183
  }
206
- // todo: this should be a use case
207
184
  return await IntegrationMapping.upsert(
208
- this.id,
185
+ this.record.id,
209
186
  sourceId,
210
187
  mapping
211
188
  );
@@ -214,13 +191,15 @@ class IntegrationBase {
214
191
  /**
215
192
  * CHILDREN CAN OVERRIDE THESE CONFIGURATION METHODS
216
193
  */
217
- async onCreate({ integrationId }) {
218
- await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
194
+ async onCreate(params) {
195
+ this.record.status = 'ENABLED';
196
+ await this.record.save();
197
+ return this.record;
219
198
  }
220
199
 
221
- async onUpdate(params) { }
200
+ async onUpdate(params) {}
222
201
 
223
- async onDelete(params) { }
202
+ async onDelete(params) {}
224
203
 
225
204
  async getConfigOptions() {
226
205
  const options = {
@@ -257,10 +236,10 @@ class IntegrationBase {
257
236
  const dynamicUserActions = await this.loadDynamicUserActions();
258
237
  const filteredDynamicActions = actionType
259
238
  ? Object.fromEntries(
260
- Object.entries(dynamicUserActions).filter(
261
- ([_, event]) => event.userActionType === actionType
262
- )
263
- )
239
+ Object.entries(dynamicUserActions).filter(
240
+ ([_, event]) => event.userActionType === actionType
241
+ )
242
+ )
264
243
  : dynamicUserActions;
265
244
  return { ...userActions, ...filteredDynamicActions };
266
245
  }
@@ -280,81 +259,6 @@ class IntegrationBase {
280
259
  };
281
260
  return options;
282
261
  }
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
- }
358
262
  }
359
263
 
360
264
  module.exports = { IntegrationBase };
@@ -0,0 +1,251 @@
1
+ const { ModuleFactory, Credential, Entity } = require('../module-plugin');
2
+ const { IntegrationModel } = require('./integration-model');
3
+ const _ = require('lodash');
4
+
5
+ class IntegrationFactory {
6
+ constructor(integrationClasses = []) {
7
+ this.integrationClasses = integrationClasses;
8
+ this.moduleFactory = new ModuleFactory(...this.getModules());
9
+ this.integrationTypes = this.integrationClasses.map(
10
+ (IntegrationClass) => IntegrationClass.getName()
11
+ );
12
+ this.getIntegrationDefinitions = this.integrationClasses.map(
13
+ (IntegrationClass) => IntegrationClass.Definition
14
+ );
15
+ }
16
+
17
+ async getIntegrationOptions() {
18
+ const options = this.integrationClasses.map(
19
+ (IntegrationClass) => IntegrationClass
20
+ );
21
+ return {
22
+ entities: {
23
+ options: options.map((IntegrationClass) =>
24
+ IntegrationClass.getOptionDetails()
25
+ ),
26
+ authorized: [],
27
+ },
28
+ integrations: [],
29
+ };
30
+ }
31
+
32
+ getModules() {
33
+ return [
34
+ ...new Set(
35
+ this.integrationClasses
36
+ .map((integration) =>
37
+ Object.values(integration.Definition.modules).map(
38
+ (module) => module.definition
39
+ )
40
+ )
41
+ .flat()
42
+ ),
43
+ ];
44
+ }
45
+
46
+ getIntegrationClassByType(type) {
47
+ const integrationClassIndex = this.integrationTypes.indexOf(type);
48
+ return this.integrationClasses[integrationClassIndex];
49
+ }
50
+ getModuleTypesAndKeys(integrationClass) {
51
+ const moduleTypesAndKeys = {};
52
+ const moduleTypeCount = {};
53
+
54
+ if (integrationClass && integrationClass.Definition.modules) {
55
+ for (const [key, moduleClass] of Object.entries(
56
+ integrationClass.Definition.modules
57
+ )) {
58
+ if (
59
+ moduleClass &&
60
+ typeof moduleClass.definition.getName === 'function'
61
+ ) {
62
+ const moduleType = moduleClass.definition.getName();
63
+
64
+ // Check if this module type has already been seen
65
+ if (moduleType in moduleTypesAndKeys) {
66
+ throw new Error(
67
+ `Duplicate module type "${moduleType}" found in integration class definition.`
68
+ );
69
+ }
70
+
71
+ // Well how baout now
72
+
73
+ moduleTypesAndKeys[moduleType] = key;
74
+ moduleTypeCount[moduleType] =
75
+ (moduleTypeCount[moduleType] || 0) + 1;
76
+ }
77
+ }
78
+ }
79
+
80
+ // Check for any module types with count > 1
81
+ for (const [moduleType, count] of Object.entries(moduleTypeCount)) {
82
+ if (count > 1) {
83
+ throw new Error(
84
+ `Multiple instances of module type "${moduleType}" found in integration class definition.`
85
+ );
86
+ }
87
+ }
88
+
89
+ return moduleTypesAndKeys;
90
+ }
91
+
92
+ async getInstanceFromIntegrationId(params) {
93
+ const integrationRecord = await IntegrationHelper.getIntegrationById(
94
+ params.integrationId
95
+ );
96
+ let { userId } = params;
97
+ if (!integrationRecord) {
98
+ throw new Error(
99
+ `No integration found by the ID of ${params.integrationId}`
100
+ );
101
+ }
102
+
103
+ if (!userId) {
104
+ userId = integrationRecord.user._id.toString();
105
+ } else if (userId.toString() !== integrationRecord.user.toString()) {
106
+ throw new Error(
107
+ `Integration ${
108
+ params.integrationId
109
+ } does not belong to User ${userId}, ${integrationRecord.user.toString()}`
110
+ );
111
+ }
112
+
113
+ const integrationClass = this.getIntegrationClassByType(
114
+ integrationRecord.config.type
115
+ );
116
+
117
+ const instance = new integrationClass({
118
+ userId,
119
+ integrationId: params.integrationId,
120
+ });
121
+
122
+ if (
123
+ integrationRecord.entityReference &&
124
+ Object.keys(integrationRecord.entityReference) > 0
125
+ ) {
126
+ // Use the specified entityReference to find the modules and load them according to their key
127
+ // entityReference will be a map of entityIds with their corresponding desired key
128
+ for (const [entityId, key] of Object.entries(
129
+ integrationRecord.entityReference
130
+ )) {
131
+ const moduleInstance =
132
+ await this.moduleFactory.getModuleInstanceFromEntityId(
133
+ entityId,
134
+ integrationRecord.user
135
+ );
136
+ instance[key] = moduleInstance;
137
+ }
138
+ } else {
139
+ // for each entity, get the moduleinstance and load them according to their keys
140
+ // If it's the first entity, load the moduleinstance into primary as well
141
+ // If it's the second entity, load the moduleinstance into target as well
142
+ const moduleTypesAndKeys =
143
+ this.getModuleTypesAndKeys(integrationClass);
144
+ for (let i = 0; i < integrationRecord.entities.length; i++) {
145
+ const entityId = integrationRecord.entities[i];
146
+ const moduleInstance =
147
+ await this.moduleFactory.getModuleInstanceFromEntityId(
148
+ entityId,
149
+ integrationRecord.user
150
+ );
151
+ const moduleType = moduleInstance.getName();
152
+ const key = moduleTypesAndKeys[moduleType];
153
+ instance[key] = moduleInstance;
154
+ if (i === 0) {
155
+ instance.primary = moduleInstance;
156
+ } else if (i === 1) {
157
+ instance.target = moduleInstance;
158
+ }
159
+ }
160
+ }
161
+ instance.record = integrationRecord;
162
+
163
+ try {
164
+ const additionalUserActions =
165
+ await instance.loadDynamicUserActions();
166
+ instance.events = { ...instance.events, ...additionalUserActions };
167
+ } catch (e) {
168
+ instance.record.status = 'ERROR';
169
+ instance.record.messages.errors.push(e);
170
+ await instance.record.save();
171
+ }
172
+ // Register all of the event handlers
173
+
174
+ await instance.registerEventHandlers();
175
+ return instance;
176
+ }
177
+
178
+ async createIntegration(entities, userId, config) {
179
+ const integrationRecord = await IntegrationModel.create({
180
+ entities: entities,
181
+ user: userId,
182
+ config,
183
+ version: '0.0.0',
184
+ });
185
+ return await this.getInstanceFromIntegrationId({
186
+ integrationId: integrationRecord.id,
187
+ userId,
188
+ });
189
+ }
190
+ }
191
+
192
+ const IntegrationHelper = {
193
+ getFormattedIntegration: async function (integrationRecord) {
194
+ const integrationObj = {
195
+ id: integrationRecord.id,
196
+ status: integrationRecord.status,
197
+ config: integrationRecord.config,
198
+ entities: [],
199
+ version: integrationRecord.version,
200
+ messages: integrationRecord.messages,
201
+ };
202
+ for (const entityId of integrationRecord.entities) {
203
+ // Only return non-internal fields. Leverages "select" and "options" to non-excepted fields and a pure object.
204
+ const entity = await Entity.findById(
205
+ entityId,
206
+ '-createdAt -updatedAt -user -credentials -credential -_id -__t -__v',
207
+ { lean: true }
208
+ );
209
+ integrationObj.entities.push({
210
+ id: entityId,
211
+ ...entity,
212
+ });
213
+ }
214
+ return integrationObj;
215
+ },
216
+
217
+ getIntegrationsForUserId: async function (userId) {
218
+ const integrationList = await IntegrationModel.find({ user: userId });
219
+ return await Promise.all(
220
+ integrationList.map(
221
+ async (integrationRecord) =>
222
+ await IntegrationHelper.getFormattedIntegration(
223
+ integrationRecord
224
+ )
225
+ )
226
+ );
227
+ },
228
+
229
+ deleteIntegrationForUserById: async function (userId, integrationId) {
230
+ const integrationList = await IntegrationModel.find({
231
+ user: userId,
232
+ _id: integrationId,
233
+ });
234
+ if (integrationList.length !== 1) {
235
+ throw new Error(
236
+ `Integration with id of ${integrationId} does not exist for this user`
237
+ );
238
+ }
239
+ await IntegrationModel.deleteOne({ _id: integrationId });
240
+ },
241
+
242
+ getIntegrationById: async function (id) {
243
+ return IntegrationModel.findById(id).populate('entities');
244
+ },
245
+
246
+ listCredentials: async function (options) {
247
+ return Credential.find(options);
248
+ },
249
+ };
250
+
251
+ module.exports = { IntegrationFactory, IntegrationHelper };