@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.
- package/credential/credential-repository.js +9 -0
- package/credential/use-cases/get-credential-for-user.js +21 -0
- package/encrypt/encrypt.js +27 -4
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +0 -3
- package/handlers/backend-utils.js +29 -34
- package/handlers/routers/auth.js +14 -11
- package/handlers/routers/integration-defined-routers.js +8 -5
- package/handlers/routers/user.js +25 -5
- package/handlers/workers/integration-defined-workers.js +6 -3
- package/index.js +0 -11
- package/integrations/index.js +0 -5
- package/integrations/integration-base.js +10 -7
- package/integrations/integration-repository.js +44 -0
- package/integrations/integration-router.js +230 -132
- package/integrations/integration.js +233 -0
- package/integrations/options.js +1 -1
- package/integrations/use-cases/create-integration.js +58 -0
- package/integrations/use-cases/delete-integration-for-user.js +53 -0
- package/integrations/use-cases/get-integration-for-user.js +63 -0
- package/integrations/use-cases/get-integration-instance.js +73 -0
- package/integrations/use-cases/get-integrations-for-user.js +64 -0
- package/integrations/use-cases/index.js +11 -0
- package/integrations/use-cases/update-integration.js +81 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/module-plugin/index.js +0 -4
- package/module-plugin/module-factory.js +13 -32
- package/module-plugin/module-repository.js +70 -0
- package/module-plugin/module-service.js +50 -0
- package/module-plugin/{auther.js → module.js} +109 -173
- package/module-plugin/test/mock-api/api.js +8 -3
- package/module-plugin/test/mock-api/definition.js +12 -8
- package/module-plugin/use-cases/get-entities-for-user.js +32 -0
- package/module-plugin/utils/map-module-dto.js +18 -0
- package/package.json +5 -5
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +4 -21
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/tests/use-cases/create-individual-user.test.js +24 -0
- package/user/tests/use-cases/create-organization-user.test.js +28 -0
- package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
- package/user/tests/use-cases/login-user.test.js +140 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user-repository.js +62 -0
- package/user/user.js +77 -0
- package/handlers/routers/HEALTHCHECK.md +0 -240
- package/handlers/routers/health.js +0 -459
- package/handlers/routers/health.test.js +0 -203
- package/handlers/routers/middleware/loadUser.js +0 -15
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-user.js +0 -144
- 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 };
|
package/integrations/options.js
CHANGED
|
@@ -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 };
|