@friggframework/core 2.0.0--canary.405.1f6792c.0 → 2.0.0--canary.396.6862738.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 (59) hide show
  1. package/credential/credential-repository.js +9 -0
  2. package/credential/use-cases/get-credential-for-user.js +21 -0
  3. package/encrypt/encrypt.js +27 -4
  4. package/handlers/app-definition-loader.js +38 -0
  5. package/handlers/app-handler-helpers.js +0 -3
  6. package/handlers/backend-utils.js +29 -34
  7. package/handlers/routers/auth.js +14 -11
  8. package/handlers/routers/integration-defined-routers.js +8 -5
  9. package/handlers/routers/user.js +25 -5
  10. package/handlers/workers/integration-defined-workers.js +6 -3
  11. package/index.js +0 -11
  12. package/integrations/index.js +0 -5
  13. package/integrations/integration-base.js +10 -7
  14. package/integrations/integration-repository.js +44 -0
  15. package/integrations/integration-router.js +230 -132
  16. package/integrations/integration.js +233 -0
  17. package/integrations/options.js +1 -1
  18. package/integrations/use-cases/create-integration.js +58 -0
  19. package/integrations/use-cases/delete-integration-for-user.js +53 -0
  20. package/integrations/use-cases/get-integration-for-user.js +63 -0
  21. package/integrations/use-cases/get-integration-instance.js +73 -0
  22. package/integrations/use-cases/get-integrations-for-user.js +64 -0
  23. package/integrations/use-cases/index.js +11 -0
  24. package/integrations/use-cases/update-integration.js +81 -0
  25. package/integrations/utils/map-integration-dto.js +37 -0
  26. package/module-plugin/index.js +0 -4
  27. package/module-plugin/module-factory.js +13 -32
  28. package/module-plugin/module-repository.js +70 -0
  29. package/module-plugin/module-service.js +50 -0
  30. package/module-plugin/{auther.js → module.js} +109 -173
  31. package/module-plugin/test/mock-api/api.js +8 -3
  32. package/module-plugin/test/mock-api/definition.js +12 -8
  33. package/module-plugin/use-cases/get-entities-for-user.js +32 -0
  34. package/module-plugin/utils/map-module-dto.js +18 -0
  35. package/package.json +5 -5
  36. package/types/integrations/index.d.ts +2 -6
  37. package/types/module-plugin/index.d.ts +4 -21
  38. package/user/tests/doubles/test-user-repository.js +72 -0
  39. package/user/tests/use-cases/create-individual-user.test.js +24 -0
  40. package/user/tests/use-cases/create-organization-user.test.js +28 -0
  41. package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
  42. package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
  43. package/user/tests/use-cases/login-user.test.js +140 -0
  44. package/user/use-cases/create-individual-user.js +61 -0
  45. package/user/use-cases/create-organization-user.js +47 -0
  46. package/user/use-cases/create-token-for-user-id.js +30 -0
  47. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  48. package/user/use-cases/login-user.js +122 -0
  49. package/user/user-repository.js +62 -0
  50. package/user/user.js +77 -0
  51. package/handlers/routers/HEALTHCHECK.md +0 -240
  52. package/handlers/routers/health.js +0 -459
  53. package/handlers/routers/health.test.js +0 -203
  54. package/handlers/routers/middleware/loadUser.js +0 -15
  55. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  56. package/integrations/create-frigg-backend.js +0 -31
  57. package/integrations/integration-factory.js +0 -251
  58. package/integrations/integration-user.js +0 -144
  59. package/module-plugin/entity-manager.js +0 -70
@@ -0,0 +1,233 @@
1
+ const { Options } = require('./options');
2
+
3
+ /**
4
+ * Integration (Domain Aggregate-Root)
5
+ * ----------------------------------
6
+ * This class represents a *configured* integration instance at runtime. It is
7
+ * deliberately split into **two layers**:
8
+ * 1. A *data snapshot* of the persisted record (id, userId, config, etc.).
9
+ * 2. A *behaviour* object: a concrete class supplied by the app developer
10
+ * that extends `IntegrationBase` and implements event handlers, user
11
+ * actions, custom routes, etc.
12
+ *
13
+ * The two layers are glued together via a **JavaScript `Proxy`**. When a
14
+ * property is requested on an `Integration` instance we:
15
+ * • Check if the property exists on the wrapper itself (data-layer).
16
+ * • Fallback to the behaviour instance (logic-layer).
17
+ * • If the value is a function we `.bind(this)` so that the function's
18
+ * `this` always points to the *wrapper* – giving it access to both data
19
+ * and behaviour transparently.
20
+ *
21
+ * This means you can treat a hydrated Integration as if it *were* the custom
22
+ * class:
23
+ *
24
+ * ```js
25
+ * const integration = await getIntegration.execute(id, userId);
26
+ * // `send` actually lives on IntegrationBase but is accessible here
27
+ * const actions = await integration.send('GET_USER_ACTIONS');
28
+ * ```
29
+ *
30
+ * A corollary benefit is that **circular references stay internal**: the heavy
31
+ * `Module → Api → delegate` graph is never exposed when we later serialise the
32
+ * object to JSON – we map it to a DTO first.
33
+ */
34
+
35
+ /**
36
+ * Integration Domain Entity
37
+ * Represents a configured integration with its data and behavior
38
+ * Uses the strategy pattern to delegate behavior to the integration class
39
+ * This is the main class that is used to interact with integrations
40
+ */
41
+ class Integration {
42
+ constructor({
43
+ id,
44
+ userId,
45
+ entities,
46
+ config,
47
+ status,
48
+ version,
49
+ messages,
50
+ integrationClass,
51
+ modules = {}
52
+ }) {
53
+ // Data from record
54
+ this.id = id;
55
+ this.userId = userId;
56
+ this.entities = entities;
57
+ this.config = config;
58
+ this.status = status;
59
+ this.version = version;
60
+ this.messages = messages;
61
+
62
+ // Integration behavior (strategy pattern)
63
+ this.integrationClass = integrationClass;
64
+
65
+ // Loaded modules
66
+ this.modules = modules;
67
+
68
+ // Initialize basic behavior (sync parts only)
69
+ this._initializeBasicBehavior();
70
+
71
+ // --- Behaviour delegation via Proxy --------------------------------
72
+ // The Proxy merges the *data layer* (this wrapper) with the *behaviour
73
+ // layer* (custom IntegrationBase subclass). Consumers don't have to
74
+ // know (or care) where a method/property is defined.
75
+ return new Proxy(this, {
76
+ get(target, prop) {
77
+ // First, check if property exists on Integration entity
78
+ if (prop in target) {
79
+ return target[prop];
80
+ }
81
+
82
+ // Then, check if it exists on the behavior instance
83
+ if (target.behavior && prop in target.behavior) {
84
+ const value = target.behavior[prop];
85
+
86
+ // If it's a function, bind the context to the Integration entity
87
+ if (typeof value === 'function') {
88
+ return value.bind(target);
89
+ }
90
+
91
+ return value;
92
+ }
93
+
94
+ // Return undefined for non-existent properties
95
+ return undefined;
96
+ }
97
+ });
98
+ }
99
+
100
+ _initializeBasicBehavior() {
101
+ // Initialize basic behavior (sync parts only)
102
+ if (this.integrationClass) {
103
+ // Create instance for behavior delegation
104
+ this.behavior = new this.integrationClass({
105
+ userId: this.userId,
106
+ integrationId: this.id
107
+ });
108
+
109
+ // Copy events
110
+ this.events = this.behavior.events || {};
111
+ this.defaultEvents = this.behavior.defaultEvents || {};
112
+
113
+
114
+ // Expose behaviour instance methods directly on the wrapper so that
115
+ // early-bound handlers (created before behaviour existed) can still
116
+ // access them without falling back through the Proxy. This prevents
117
+ // `undefined` errors for methods like `loadDynamicUserActions` that
118
+ // may be invoked inside default event-handlers.
119
+ const proto = Object.getPrototypeOf(this.behavior);
120
+ for (const key of Object.getOwnPropertyNames(proto)) {
121
+ if (key === 'constructor') continue;
122
+ if (typeof proto[key] === 'function' && this[key] === undefined) {
123
+ // Bind to behaviour so internal `this` remains correct.
124
+ this[key] = proto[key].bind(this.behavior);
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ async initialize() {
131
+ // Complete async initialization
132
+ if (this.behavior) {
133
+ // Load dynamic user actions
134
+ try {
135
+ const additionalUserActions = await this.loadDynamicUserActions();
136
+ this.events = { ...this.events, ...additionalUserActions };
137
+ } catch (e) {
138
+ this.addError(e);
139
+ }
140
+
141
+ // Register event handlers
142
+ await this.registerEventHandlers();
143
+ }
144
+ }
145
+
146
+ // Core methods that should always be on Integration entity
147
+ // These override any behavior methods with the same name
148
+
149
+ // Module access helpers
150
+ getModule(key) {
151
+ return this.modules[key];
152
+ }
153
+
154
+ setModule(key, module) {
155
+ this.modules[key] = module;
156
+ // Also set on behavior for backward compatibility
157
+ if (this.behavior) {
158
+ this.behavior[key] = module;
159
+ }
160
+ }
161
+
162
+ // State management
163
+ addError(error) {
164
+ if (!this.messages.errors) {
165
+ this.messages.errors = [];
166
+ }
167
+ this.messages.errors.push(error);
168
+ this.status = 'ERROR';
169
+ }
170
+
171
+ addWarning(warning) {
172
+ if (!this.messages.warnings) {
173
+ this.messages.warnings = [];
174
+ }
175
+ this.messages.warnings.push(warning);
176
+ }
177
+
178
+ // Domain methods
179
+ isActive() {
180
+ return this.status === 'ENABLED' || this.status === 'ACTIVE';
181
+ }
182
+
183
+ needsConfiguration() {
184
+ return this.status === 'NEEDS_CONFIG';
185
+ }
186
+
187
+ hasErrors() {
188
+ return this.status === 'ERROR';
189
+ }
190
+
191
+ belongsToUser(userId) {
192
+ return this.userId.toString() === userId.toString();
193
+ }
194
+
195
+ // Get the underlying behavior instance (useful for debugging or special cases)
196
+ getBehavior() {
197
+ return this.behavior;
198
+ }
199
+
200
+ // Check if a method exists (either on entity or behavior)
201
+ hasMethod(methodName) {
202
+ return methodName in this || (this.behavior && methodName in this.behavior);
203
+ }
204
+
205
+ getOptionDetails() {
206
+ const options = new Options({
207
+ module: Object.values(this.integrationClass.Definition.modules)[0], // This is a placeholder until we revamp the frontend
208
+ ...this.integrationClass.Definition,
209
+ });
210
+ return options.get();
211
+ }
212
+
213
+ /**
214
+ * Custom JSON serializer to prevent circular references (e.g. Module → Api → delegate)
215
+ * and to keep API responses lightweight.
216
+ * Only primitive, serialisable data needed by clients is returned.
217
+ */
218
+ toJSON() {
219
+ return {
220
+ id: this.id,
221
+ userId: this.userId,
222
+ entities: this.entities,
223
+ config: this.config,
224
+ status: this.status,
225
+ version: this.version,
226
+ messages: this.messages,
227
+ // Expose userActions if they were loaded/attached elsewhere
228
+ userActions: this.userActions,
229
+ };
230
+ }
231
+ }
232
+
233
+ module.exports = { Integration };
@@ -1,5 +1,5 @@
1
1
  const { RequiredPropertyError } = require('../errors');
2
- const { get, getAndVerifyType } = require('../assertions');
2
+ const { get } = require('../assertions');
3
3
 
4
4
  class Options {
5
5
  constructor(params) {
@@ -0,0 +1,58 @@
1
+ const { Integration } = require('../integration');
2
+ const { mapIntegrationClassToIntegrationDTO } = require('../utils/map-integration-dto');
3
+
4
+ class CreateIntegration {
5
+ /**
6
+ * @param {Object} params
7
+ * @param {import('../integration-repository').IntegrationRepository} params.integrationRepository
8
+ * @param {import('../integration-classes').IntegrationClasses} params.integrationClasses
9
+ * @param {import('../../module-plugin/module-service').ModuleService} params.moduleService
10
+ */
11
+ constructor({ integrationRepository, integrationClasses, moduleService }) {
12
+ this.integrationRepository = integrationRepository;
13
+ this.integrationClasses = integrationClasses;
14
+ this.moduleService = moduleService;
15
+ }
16
+
17
+ async execute(entities, userId, config) {
18
+ const integrationRecord = await this.integrationRepository.createIntegration(entities, userId, config);
19
+
20
+
21
+ // 2. Get the correct Integration class by type
22
+ const integrationClass = this.integrationClasses.find(
23
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
24
+ );
25
+
26
+ if (!integrationClass) {
27
+ throw new Error(`No integration class found for type: ${integrationRecord.config.type}`);
28
+ }
29
+
30
+ const modules = [];
31
+ for (const entityId of integrationRecord.entitiesIds) {
32
+ const moduleInstance = await this.moduleService.getModuleInstance(
33
+ entityId,
34
+ integrationRecord.userId
35
+ );
36
+ modules.push(moduleInstance);
37
+ }
38
+
39
+ const integrationInstance = new Integration({
40
+ id: integrationRecord.id,
41
+ userId: integrationRecord.userId,
42
+ entities: integrationRecord.entitiesIds,
43
+ config: integrationRecord.config,
44
+ status: integrationRecord.status,
45
+ version: integrationRecord.version,
46
+ messages: integrationRecord.messages,
47
+ integrationClass: integrationClass,
48
+ modules
49
+ });
50
+
51
+ await integrationInstance.initialize();
52
+ await integrationInstance.send('ON_CREATE', {});
53
+
54
+ return mapIntegrationClassToIntegrationDTO(integrationInstance);
55
+ }
56
+ }
57
+
58
+ module.exports = { CreateIntegration };
@@ -0,0 +1,53 @@
1
+ const Boom = require('@hapi/boom');
2
+
3
+ class DeleteIntegrationForUser {
4
+ constructor({ integrationRepository, integrationClasses }) {
5
+
6
+ /**
7
+ * @type {import('../integration-repository').IntegrationRepository}
8
+ */
9
+ this.integrationRepository = integrationRepository;
10
+ this.integrationClasses = integrationClasses;
11
+ }
12
+
13
+ async execute(integrationId, userId) {
14
+ const integrationRecord = await this.integrationRepository.findIntegrationById(integrationId);
15
+
16
+ if (!integrationRecord) {
17
+ throw Boom.notFound(
18
+ `Integration with id of ${integrationId} does not exist`
19
+ );
20
+ }
21
+
22
+ const integrationClass = this.integrationClasses.find(
23
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
24
+ );
25
+
26
+ if (integrationRecord.userId !== userId) {
27
+ throw new Error(
28
+ `Integration ${integrationId} does not belong to User ${userId}`
29
+ );
30
+ }
31
+
32
+ const integrationInstance = new Integration({
33
+ id: integrationRecord.id,
34
+ userId: integrationRecord.userId,
35
+ entities: integrationRecord.entitiesIds,
36
+ config: integrationRecord.config,
37
+ status: integrationRecord.status,
38
+ version: integrationRecord.version,
39
+ messages: integrationRecord.messages,
40
+ integrationClass: integrationClass,
41
+ modules: [],
42
+ });
43
+
44
+ // 6. Complete async initialization (load dynamic actions, register handlers)
45
+ await integrationInstance.initialize();
46
+ await integrationInstance.send('ON_DELETE');
47
+
48
+ await this.integrationRepository.deleteIntegrationById(integrationId);
49
+
50
+ }
51
+ }
52
+
53
+ module.exports = { DeleteIntegrationForUser };
@@ -0,0 +1,63 @@
1
+ const { Integration } = require('../integration');
2
+ const { mapIntegrationClassToIntegrationDTO } = require('../utils/map-integration-dto');
3
+
4
+ class GetIntegrationForUser {
5
+ constructor({ integrationRepository, integrationClasses, moduleService, moduleRepository }) {
6
+
7
+ /**
8
+ * @type {import('../integration-repository').IntegrationRepository}
9
+ */
10
+ this.integrationRepository = integrationRepository;
11
+ this.integrationClasses = integrationClasses;
12
+ this.moduleService = moduleService;
13
+ this.moduleRepository = moduleRepository;
14
+ }
15
+
16
+ /**
17
+ * @param {string} integrationId
18
+ * @param {string} userId
19
+ * @returns {Promise<Integration>}
20
+ */
21
+ async execute(integrationId, userId) {
22
+ const integrationRecord = await this.integrationRepository.findIntegrationById(integrationId);
23
+ const entities = await this.moduleRepository.findEntitiesByIds(integrationRecord.entitiesIds);
24
+
25
+ if (!integrationRecord) {
26
+ throw Boom.notFound(`Integration with id of ${integrationId} does not exist`);
27
+ }
28
+
29
+ if (integrationRecord.user.toString() !== userId.toString()) {
30
+ throw Boom.forbidden('User does not have access to this integration');
31
+ }
32
+
33
+ const integrationClass = this.integrationClasses.find(
34
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
35
+ );
36
+
37
+ const modules = [];
38
+ for (const entity of entities) {
39
+ const moduleInstance = await this.moduleService.getModuleInstance(
40
+ entity._id,
41
+ integrationRecord.user
42
+ );
43
+ modules.push(moduleInstance);
44
+ }
45
+
46
+ const integrationInstance = new Integration({
47
+ id: integrationRecord._id,
48
+ userId: integrationRecord.user,
49
+ entities: entities,
50
+ config: integrationRecord.config,
51
+ status: integrationRecord.status,
52
+ version: integrationRecord.version,
53
+ messages: integrationRecord.messages,
54
+ entityReference: integrationRecord.entityReference,
55
+ integrationClass: integrationClass,
56
+ modules
57
+ });
58
+
59
+ return mapIntegrationClassToIntegrationDTO(integrationInstance);
60
+ }
61
+ }
62
+
63
+ module.exports = { GetIntegrationForUser };
@@ -0,0 +1,73 @@
1
+ const { Integration } = require('../integration');
2
+
3
+ class GetIntegrationInstance {
4
+
5
+ /**
6
+ * @class GetIntegrationInstance
7
+ * @description Use case for retrieving a single integration instance by ID and user.
8
+ * @param {Object} params
9
+ * @param {import('../integration-repository').IntegrationRepository} params.integrationRepository - Repository for integration data access
10
+ * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes
11
+ * @param {import('../module-plugin/module-service').ModuleService} params.moduleService - Service for module instantiation and management
12
+ */
13
+ constructor({
14
+ integrationRepository,
15
+ integrationClasses,
16
+ moduleService,
17
+ }) {
18
+ this.integrationRepository = integrationRepository;
19
+ this.integrationClasses = integrationClasses;
20
+ this.moduleService = moduleService;
21
+ }
22
+
23
+ async execute(integrationId, userId) {
24
+ const integrationRecord = await this.integrationRepository.findIntegrationById(integrationId);
25
+
26
+ if (!integrationRecord) {
27
+ throw new Error(`No integration found by the ID of ${integrationId}`);
28
+ }
29
+
30
+ const integrationClass = this.integrationClasses.find(
31
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
32
+ );
33
+
34
+ if (!integrationClass) {
35
+ throw new Error(`No integration class found for type: ${integrationRecord.config.type}`);
36
+ }
37
+
38
+ if (integrationRecord.userId !== userId) {
39
+ throw new Error(
40
+ `Integration ${integrationId} does not belong to User ${userId}`
41
+ );
42
+ }
43
+
44
+
45
+ const modules = [];
46
+ for (const entityId of integrationRecord.entitiesIds) {
47
+ const moduleInstance = await this.moduleService.getModuleInstance(
48
+ entityId,
49
+ integrationRecord.userId
50
+ );
51
+ modules.push(moduleInstance);
52
+ }
53
+
54
+ const integrationInstance = new Integration({
55
+ id: integrationRecord.id,
56
+ userId: integrationRecord.userId,
57
+ entities: integrationRecord.entitiesIds,
58
+ config: integrationRecord.config,
59
+ status: integrationRecord.status,
60
+ version: integrationRecord.version,
61
+ messages: integrationRecord.messages,
62
+ integrationClass: integrationClass,
63
+ modules
64
+ });
65
+
66
+
67
+ await integrationInstance.initialize();
68
+
69
+ return integrationInstance;
70
+ }
71
+ }
72
+
73
+ module.exports = { GetIntegrationInstance };
@@ -0,0 +1,64 @@
1
+ const { Integration } = require('../integration');
2
+ const { mapIntegrationClassToIntegrationDTO } = require('../utils/map-integration-dto');
3
+
4
+ class GetIntegrationsForUser {
5
+ constructor({ integrationRepository, integrationClasses, moduleService, moduleRepository }) {
6
+
7
+ /**
8
+ * @type {import('../integration-repository').IntegrationRepository}
9
+ */
10
+ this.integrationRepository = integrationRepository;
11
+ this.integrationClasses = integrationClasses;
12
+ this.moduleService = moduleService;
13
+ this.moduleRepository = moduleRepository;
14
+ }
15
+
16
+ /**
17
+ * @param {string} userId
18
+ * @returns {Promise<Integration[]>}
19
+ */
20
+ async execute(userId) {
21
+ const integrationRecords = await this.integrationRepository.findIntegrationsByUserId(userId);
22
+
23
+ const integrations = []
24
+
25
+ for (const integrationRecord of integrationRecords) {
26
+ const entities = await this.moduleRepository.findEntitiesByIds(integrationRecord.entitiesIds);
27
+
28
+ const integrationClass = this.integrationClasses.find(
29
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
30
+ );
31
+
32
+ const modules = [];
33
+ for (const entity of entities) {
34
+ const moduleInstance = await this.moduleService.getModuleInstance(
35
+ entity.id,
36
+ integrationRecord.userId
37
+ );
38
+ modules.push(moduleInstance);
39
+ }
40
+
41
+ const integrationInstance = new Integration({
42
+ id: integrationRecord.id,
43
+ userId: integrationRecord.user,
44
+ entities: entities,
45
+ config: integrationRecord.config,
46
+ status: integrationRecord.status,
47
+ version: integrationRecord.version,
48
+ messages: integrationRecord.messages,
49
+ entityReference: integrationRecord.entityReference,
50
+ integrationClass: integrationClass,
51
+ modules
52
+ });
53
+
54
+ integrations.push(
55
+ mapIntegrationClassToIntegrationDTO(integrationInstance)
56
+ );
57
+
58
+ }
59
+
60
+ return integrations;
61
+ }
62
+ }
63
+
64
+ module.exports = { GetIntegrationsForUser };
@@ -0,0 +1,11 @@
1
+ const { GetIntegrationsForUser } = require('./get-integrations-for-user');
2
+ const { DeleteIntegrationForUser } = require('./delete-integration-for-user');
3
+ const { CreateIntegration } = require('./create-integration');
4
+ const { GetIntegration } = require('./get-integration');
5
+
6
+ module.exports = {
7
+ GetIntegrationsForUser,
8
+ DeleteIntegrationForUser,
9
+ CreateIntegration,
10
+ GetIntegration,
11
+ };
@@ -0,0 +1,81 @@
1
+ const { Integration } = require('../integration');
2
+ const { mapIntegrationClassToIntegrationDTO } = require('../utils/map-integration-dto');
3
+
4
+
5
+ class UpdateIntegration {
6
+
7
+ /**
8
+ * @class UpdateIntegration
9
+ * @description Use case for updating a single integration by ID and user.
10
+ * @param {Object} params
11
+ * @param {import('../integration-repository').IntegrationRepository} params.integrationRepository - Repository for integration data access
12
+ * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes
13
+ * @param {import('../module-plugin/module-service').ModuleService} params.moduleService - Service for module instantiation and management
14
+ */
15
+ constructor({
16
+ integrationRepository,
17
+ integrationClasses,
18
+ moduleService,
19
+ }) {
20
+ this.integrationRepository = integrationRepository;
21
+ this.integrationClasses = integrationClasses;
22
+ this.moduleService = moduleService;
23
+ }
24
+
25
+ async execute(integrationId, userId, config) {
26
+ // 1. Get integration record from repository
27
+ const integrationRecord = await this.integrationRepository.findIntegrationById(integrationId);
28
+
29
+ if (!integrationRecord) {
30
+ throw new Error(`No integration found by the ID of ${integrationId}`);
31
+ }
32
+
33
+ // 2. Get the correct Integration class by type
34
+ const integrationClass = this.integrationClasses.find(
35
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
36
+ );
37
+
38
+ if (!integrationClass) {
39
+ throw new Error(`No integration class found for type: ${integrationRecord.config.type}`);
40
+ }
41
+
42
+ if (integrationRecord.userId !== userId) {
43
+ throw new Error(
44
+ `Integration ${integrationId} does not belong to User ${userId}`
45
+ );
46
+ }
47
+
48
+
49
+ // 3. Load modules based on entity references
50
+ const modules = [];
51
+ for (const entityId of integrationRecord.entitiesIds) {
52
+ const moduleInstance = await this.moduleService.getModuleInstance(
53
+ entityId,
54
+ integrationRecord.userId
55
+ );
56
+ modules.push(moduleInstance);
57
+ }
58
+
59
+ // 4. Create the Integration domain entity with modules
60
+ const integrationInstance = new Integration({
61
+ id: integrationRecord.id,
62
+ userId: integrationRecord.userId,
63
+ entities: integrationRecord.entitiesIds,
64
+ config: integrationRecord.config,
65
+ status: integrationRecord.status,
66
+ version: integrationRecord.version,
67
+ messages: integrationRecord.messages,
68
+ integrationClass: integrationClass,
69
+ modules
70
+ });
71
+
72
+
73
+ // 6. Complete async initialization (load dynamic actions, register handlers)
74
+ await integrationInstance.initialize();
75
+ await integrationInstance.send('ON_UPDATE', { config });
76
+
77
+ return mapIntegrationClassToIntegrationDTO(integrationInstance);
78
+ }
79
+ }
80
+
81
+ module.exports = { UpdateIntegration };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @param {import('../integration').Integration} integration
3
+ * Convert an Integration domain instance to a plain DTO suitable for JSON responses.
4
+ */
5
+ function mapIntegrationClassToIntegrationDTO(integration) {
6
+ if (!integration) return null;
7
+
8
+ return {
9
+ id: integration.id,
10
+ userId: integration.userId,
11
+ entities: integration.entities,
12
+ config: integration.config,
13
+ status: integration.status,
14
+ version: integration.version,
15
+ messages: integration.messages,
16
+ entityReference: integration.entityReference,
17
+ userActions: integration.userActions,
18
+ options: integration.getOptionDetails(),
19
+ };
20
+ }
21
+
22
+
23
+ const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
24
+ return [
25
+ ...new Set(
26
+ integrationClasses
27
+ .map((integration) =>
28
+ Object.values(integration.Definition.modules).map(
29
+ (module) => module.definition
30
+ )
31
+ )
32
+ .flat()
33
+ ),
34
+ ];
35
+ };
36
+
37
+ module.exports = { mapIntegrationClassToIntegrationDTO, getModulesDefinitionFromIntegrationClasses };