@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.
- package/README.md +931 -50
- package/core/create-handler.js +1 -0
- package/credential/credential-repository.js +42 -0
- package/credential/use-cases/get-credential-for-user.js +21 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/models/WebsocketConnection.js +0 -5
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +0 -3
- package/handlers/backend-utils.js +42 -44
- package/handlers/routers/auth.js +3 -14
- 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 +1 -16
- package/integrations/index.js +0 -5
- package/integrations/integration-base.js +142 -46
- package/integrations/integration-repository.js +80 -0
- package/integrations/integration-router.js +301 -178
- package/integrations/integration.js +246 -0
- package/integrations/options.js +1 -1
- package/integrations/tests/doubles/dummy-integration-class.js +90 -0
- package/integrations/tests/doubles/test-integration-repository.js +89 -0
- package/integrations/tests/use-cases/create-integration.test.js +124 -0
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +143 -0
- package/integrations/tests/use-cases/get-integration-for-user.test.js +143 -0
- package/integrations/tests/use-cases/get-integration-instance.test.js +169 -0
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +169 -0
- package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
- package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
- package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
- package/integrations/tests/use-cases/update-integration.test.js +134 -0
- package/integrations/use-cases/create-integration.js +71 -0
- package/integrations/use-cases/delete-integration-for-user.js +72 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +82 -0
- package/integrations/use-cases/get-integrations-for-user.js +76 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/index.js +11 -0
- package/integrations/use-cases/update-integration-messages.js +31 -0
- package/integrations/use-cases/update-integration-status.js +28 -0
- package/integrations/use-cases/update-integration.js +91 -0
- package/integrations/utils/map-integration-dto.js +36 -0
- package/{module-plugin → modules}/index.js +0 -8
- package/modules/module-factory.js +54 -0
- package/modules/module-repository.js +107 -0
- package/modules/module.js +221 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +19 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +58 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +31 -0
- package/modules/use-cases/get-module.js +56 -0
- package/modules/use-cases/process-authorization-callback.js +114 -0
- package/modules/use-cases/refresh-entity-options.js +58 -0
- package/modules/use-cases/test-module-auth.js +54 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +5 -5
- package/syncs/sync.js +0 -1
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +4 -56
- package/types/syncs/index.d.ts +0 -2
- 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/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/integrations/test/integration-base.test.js +0 -144
- package/module-plugin/auther.js +0 -393
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/credential.js +0 -0
- /package/{module-plugin → modules}/entity.js +0 -0
- /package/{module-plugin → modules}/requester/api-key.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/requester/requester.js +0 -0
- /package/{module-plugin → modules}/requester/requester.test.js +0 -0
- /package/{module-plugin → modules}/test/auther.test.js +0 -0
- /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
|
-
|
|
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.
|
|
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.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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.
|
|
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(
|
|
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.
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
165
|
-
|
|
187
|
+
Date.now()
|
|
188
|
+
);
|
|
166
189
|
}
|
|
167
190
|
}
|
|
168
191
|
|
|
169
192
|
if (!didAuthPass) {
|
|
170
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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(
|
|
195
|
-
this.
|
|
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
|
-
|
|
240
|
-
|
|
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 };
|