@friggframework/core 2.0.0--canary.711.f8b6445.0 → 2.0.0--canary.667.f74cf3b.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/CLAUDE.md +0 -40
- package/application/commands/entity-commands.js +60 -18
- package/application/commands/integration-commands.js +0 -63
- package/database/documentdb-utils.js +1 -4
- package/errors/index.js +0 -4
- package/handlers/WEBHOOKS.md +0 -29
- package/index.js +0 -2
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +13 -67
- package/integrations/integration-base.js +0 -29
- package/integrations/integration-router.js +18 -22
- package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
- package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
- package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
- package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
- package/integrations/repositories/integration-mapping-repository.js +0 -29
- package/integrations/repositories/integration-repository-documentdb.js +0 -32
- package/integrations/repositories/integration-repository-interface.js +0 -30
- package/integrations/repositories/integration-repository-mongo.js +1 -53
- package/integrations/repositories/integration-repository-postgres.js +0 -58
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/use-cases/create-integration.js +19 -57
- package/modules/use-cases/create-entity-from-credential.js +105 -0
- package/modules/use-cases/get-entity-options-by-id.js +7 -5
- package/modules/use-cases/get-module.js +29 -8
- package/modules/use-cases/refresh-entity-options.js +7 -5
- package/modules/use-cases/test-module-auth.js +7 -5
- package/modules/utils/find-module-definition.js +23 -0
- package/modules/utils/map-module-dto.js +3 -5
- package/package.json +5 -6
- package/user/tests/doubles/test-user-repository.js +1 -1
- package/user/use-cases/get-user-from-bearer-token.js +2 -7
- package/errors/mapping-already-exists-error.js +0 -14
- package/integrations/use-cases/add-entities-to-integration.js +0 -92
- package/integrations/use-cases/remove-entities-from-integration.js +0 -70
- package/integrations/utils/assert-entity-ids-input.js +0 -42
- package/modules/utils/to-credential-id.js +0 -14
package/CLAUDE.md
CHANGED
|
@@ -206,51 +206,11 @@ packages/core/
|
|
|
206
206
|
- `delete-integration-for-user.js` - Remove integration
|
|
207
207
|
- `get-integration-instance.js` - Load integration with modules
|
|
208
208
|
- `load-integration-context.js` - Full integration context loading
|
|
209
|
-
- `add-entities-to-integration.js` / `remove-entities-from-integration.js` - Attach or detach entities on an existing integration
|
|
210
209
|
|
|
211
210
|
**Repositories**:
|
|
212
211
|
- `integration-repository-factory.js` - Creates database-specific repositories
|
|
213
212
|
- `integration-repository-mongo.js` - MongoDB implementation
|
|
214
213
|
- `integration-repository-postgres.js` - PostgreSQL implementation
|
|
215
|
-
- `addEntitiesToIntegration(integrationId, entityIds)` /
|
|
216
|
-
`removeEntitiesFromIntegration(integrationId, entityIds)` change the entity
|
|
217
|
-
set of an existing integration (all three adapters; `null` for an unknown
|
|
218
|
-
integration). Reach them through the `addEntitiesToIntegration` /
|
|
219
|
-
`removeEntitiesFromIntegration` commands. The add use case
|
|
220
|
-
(`add-entities-to-integration.js`) rejects unknown entities
|
|
221
|
-
(`ENTITIES_NOT_FOUND`, 404), entities of another user
|
|
222
|
-
(`ENTITY_USER_MISMATCH`, 403) and entities of a module the integration does
|
|
223
|
-
not already hold (`ENTITY_MODULE_MISMATCH`, 400). The remove use case
|
|
224
|
-
(`remove-entities-from-integration.js`) refuses to leave a module with no
|
|
225
|
-
entity (`LAST_ENTITY_OF_MODULE`, 409). Re-adding an attached entity or
|
|
226
|
-
removing a detached one changes nothing; a detached entity is not deleted.
|
|
227
|
-
`integration-repository-entities-parity.test.js` runs every adapter against
|
|
228
|
-
real databases when `INTEGRATION_ENTITIES_PARITY_MONGO_URL` /
|
|
229
|
-
`INTEGRATION_ENTITIES_PARITY_POSTGRES_URL` are set. The DocumentDB adapter
|
|
230
|
-
writes only `Integration.entityIds` (as its `createIntegration` does), not
|
|
231
|
-
`Entity.integrationIds`.
|
|
232
|
-
**One credential, many accounts**: when one credential covers several
|
|
233
|
-
provider accounts, give each account its own entity (externalId = account
|
|
234
|
-
id) and attach them all to one integration; webhooks then route with
|
|
235
|
-
`findIntegrationByEntityExternalId`. Traps:
|
|
236
|
-
(1) `IntegrationBase` keys modules by module name, so `this.<moduleKey>`
|
|
237
|
-
holds only one entity's module, and on the queue/webhook path
|
|
238
|
-
(`get-integration-instance.js`) `this.entities` holds entity **ids**, not
|
|
239
|
-
records. For per-account work (a webhook for one account, teardown of every
|
|
240
|
-
account in `onDelete`), load
|
|
241
|
-
`this.commands.loadIntegrationContextById(this.id)` and use
|
|
242
|
-
`context.modules`: one module per entity, each with its record on
|
|
243
|
-
`module.entity` (pick by `module.entity.externalId`).
|
|
244
|
-
(2) `findIntegrationByEntityExternalId` counts every entity with that
|
|
245
|
-
externalId, attached or not, across users: reuse an existing entity
|
|
246
|
-
(`findEntity`) when an account comes back, or delete it when it is revoked,
|
|
247
|
-
never create a second one.
|
|
248
|
-
(3) `createIntegration` reuses an existing integration of the same user
|
|
249
|
-
and type whose entities include all requested ones (an exact match wins),
|
|
250
|
-
so a reconnect that sends the original entities finds the integration that
|
|
251
|
-
now holds more.
|
|
252
|
-
(4) Every module's delegate is the integration: an auth failure on any
|
|
253
|
-
account marks the shared credential invalid and the integration ERROR.
|
|
254
214
|
- `integration-mapping-repository-*.js` - Mapping data persistence.
|
|
255
215
|
`queryMappings(integrationId, { where, orderBy, skip, take, omit })`
|
|
256
216
|
returns `{ mappings, total }`: one filtered, ordered page of an
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
const {
|
|
2
2
|
createModuleRepository,
|
|
3
3
|
} = require('../../modules/repositories/module-repository-factory');
|
|
4
|
-
const { toCredentialId } = require('../../modules/utils/to-credential-id');
|
|
5
4
|
|
|
6
5
|
const ERROR_CODE_MAP = {
|
|
7
6
|
ENTITY_NOT_FOUND: 404,
|
|
8
7
|
INVALID_ENTITY_DATA: 400,
|
|
9
8
|
};
|
|
10
9
|
|
|
11
|
-
function toEntityDto(entity) {
|
|
12
|
-
return {
|
|
13
|
-
id: entity.id,
|
|
14
|
-
userId: entity.userId,
|
|
15
|
-
externalId: entity.externalId,
|
|
16
|
-
name: entity.name,
|
|
17
|
-
moduleName: entity.moduleName,
|
|
18
|
-
credentialId: toCredentialId(entity.credential),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
10
|
function mapErrorToResponse(error) {
|
|
23
11
|
const status = ERROR_CODE_MAP[error?.code] || 500;
|
|
24
12
|
return {
|
|
@@ -78,7 +66,16 @@ function createEntityCommands() {
|
|
|
78
66
|
|
|
79
67
|
const entity = await moduleRepo.createEntity(entityData);
|
|
80
68
|
|
|
81
|
-
return
|
|
69
|
+
return {
|
|
70
|
+
id: entity.id,
|
|
71
|
+
userId: entity.userId,
|
|
72
|
+
externalId: entity.externalId,
|
|
73
|
+
name: entity.name,
|
|
74
|
+
moduleName: entity.moduleName,
|
|
75
|
+
credentialId: entity.credential?._id
|
|
76
|
+
? entity.credential._id.toString()
|
|
77
|
+
: entity.credential,
|
|
78
|
+
};
|
|
82
79
|
} catch (error) {
|
|
83
80
|
return mapErrorToResponse(error);
|
|
84
81
|
}
|
|
@@ -112,7 +109,16 @@ function createEntityCommands() {
|
|
|
112
109
|
return null;
|
|
113
110
|
}
|
|
114
111
|
|
|
115
|
-
return
|
|
112
|
+
return {
|
|
113
|
+
id: entity.id,
|
|
114
|
+
userId: entity.userId,
|
|
115
|
+
externalId: entity.externalId,
|
|
116
|
+
name: entity.name,
|
|
117
|
+
moduleName: entity.moduleName,
|
|
118
|
+
credentialId: entity.credential?._id
|
|
119
|
+
? entity.credential._id.toString()
|
|
120
|
+
: entity.credential,
|
|
121
|
+
};
|
|
116
122
|
} catch (error) {
|
|
117
123
|
return mapErrorToResponse(error);
|
|
118
124
|
}
|
|
@@ -133,7 +139,16 @@ function createEntityCommands() {
|
|
|
133
139
|
|
|
134
140
|
const entities = await moduleRepo.findEntitiesByUserId(userId);
|
|
135
141
|
|
|
136
|
-
return entities.map((entity) =>
|
|
142
|
+
return entities.map((entity) => ({
|
|
143
|
+
id: entity.id,
|
|
144
|
+
userId: entity.userId,
|
|
145
|
+
externalId: entity.externalId,
|
|
146
|
+
name: entity.name,
|
|
147
|
+
moduleName: entity.moduleName,
|
|
148
|
+
credentialId: entity.credential?._id
|
|
149
|
+
? entity.credential._id.toString()
|
|
150
|
+
: entity.credential,
|
|
151
|
+
}));
|
|
137
152
|
} catch (error) {
|
|
138
153
|
if (error.code) {
|
|
139
154
|
return mapErrorToResponse(error);
|
|
@@ -165,7 +180,16 @@ function createEntityCommands() {
|
|
|
165
180
|
moduleName
|
|
166
181
|
);
|
|
167
182
|
|
|
168
|
-
return entities.map((entity) =>
|
|
183
|
+
return entities.map((entity) => ({
|
|
184
|
+
id: entity.id,
|
|
185
|
+
userId: entity.userId,
|
|
186
|
+
externalId: entity.externalId,
|
|
187
|
+
name: entity.name,
|
|
188
|
+
moduleName: entity.moduleName,
|
|
189
|
+
credentialId: entity.credential?._id
|
|
190
|
+
? entity.credential._id.toString()
|
|
191
|
+
: entity.credential,
|
|
192
|
+
}));
|
|
169
193
|
} catch (error) {
|
|
170
194
|
if (error.code) {
|
|
171
195
|
return mapErrorToResponse(error);
|
|
@@ -189,7 +213,16 @@ function createEntityCommands() {
|
|
|
189
213
|
|
|
190
214
|
const entity = await moduleRepo.findEntityById(entityId);
|
|
191
215
|
|
|
192
|
-
return
|
|
216
|
+
return {
|
|
217
|
+
id: entity.id,
|
|
218
|
+
userId: entity.userId,
|
|
219
|
+
externalId: entity.externalId,
|
|
220
|
+
name: entity.name,
|
|
221
|
+
moduleName: entity.moduleName,
|
|
222
|
+
credentialId: entity.credential?._id
|
|
223
|
+
? entity.credential._id.toString()
|
|
224
|
+
: entity.credential,
|
|
225
|
+
};
|
|
193
226
|
} catch (error) {
|
|
194
227
|
return mapErrorToResponse(error);
|
|
195
228
|
}
|
|
@@ -217,7 +250,16 @@ function createEntityCommands() {
|
|
|
217
250
|
throw error;
|
|
218
251
|
}
|
|
219
252
|
|
|
220
|
-
return
|
|
253
|
+
return {
|
|
254
|
+
id: entity.id,
|
|
255
|
+
userId: entity.userId,
|
|
256
|
+
externalId: entity.externalId,
|
|
257
|
+
name: entity.name,
|
|
258
|
+
moduleName: entity.moduleName,
|
|
259
|
+
credentialId: entity.credential?._id
|
|
260
|
+
? entity.credential._id.toString()
|
|
261
|
+
: entity.credential,
|
|
262
|
+
};
|
|
221
263
|
} catch (error) {
|
|
222
264
|
return mapErrorToResponse(error);
|
|
223
265
|
}
|
|
@@ -29,12 +29,6 @@ const {
|
|
|
29
29
|
const {
|
|
30
30
|
PatchIntegrationConfig,
|
|
31
31
|
} = require('../../integrations/use-cases/patch-integration-config');
|
|
32
|
-
const {
|
|
33
|
-
AddEntitiesToIntegration,
|
|
34
|
-
} = require('../../integrations/use-cases/add-entities-to-integration');
|
|
35
|
-
const {
|
|
36
|
-
RemoveEntitiesFromIntegration,
|
|
37
|
-
} = require('../../integrations/use-cases/remove-entities-from-integration');
|
|
38
32
|
const {
|
|
39
33
|
getModulesDefinitionFromIntegrationClasses,
|
|
40
34
|
} = require('../../integrations/utils/map-integration-dto');
|
|
@@ -46,11 +40,6 @@ const ERROR_CODE_MAP = {
|
|
|
46
40
|
EXTERNAL_ID_REQUIRED: 400,
|
|
47
41
|
TYPE_REQUIRED: 400,
|
|
48
42
|
INTEGRATION_RECORD_NOT_FOUND: 404,
|
|
49
|
-
INVALID_INTEGRATION_DATA: 400,
|
|
50
|
-
ENTITIES_NOT_FOUND: 404,
|
|
51
|
-
ENTITY_USER_MISMATCH: 403,
|
|
52
|
-
ENTITY_MODULE_MISMATCH: 400,
|
|
53
|
-
LAST_ENTITY_OF_MODULE: 409,
|
|
54
43
|
};
|
|
55
44
|
|
|
56
45
|
function mapErrorToResponse(error) {
|
|
@@ -174,17 +163,6 @@ function createIntegrationCommands({ integrationClass } = {}) {
|
|
|
174
163
|
integrationRepository,
|
|
175
164
|
});
|
|
176
165
|
|
|
177
|
-
const addEntitiesToIntegrationUseCase = new AddEntitiesToIntegration({
|
|
178
|
-
integrationRepository,
|
|
179
|
-
moduleRepository,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const removeEntitiesFromIntegrationUseCase =
|
|
183
|
-
new RemoveEntitiesFromIntegration({
|
|
184
|
-
integrationRepository,
|
|
185
|
-
moduleRepository,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
166
|
return {
|
|
189
167
|
findIntegrationById,
|
|
190
168
|
listIntegrations,
|
|
@@ -329,47 +307,6 @@ function createIntegrationCommands({ integrationClass } = {}) {
|
|
|
329
307
|
}
|
|
330
308
|
},
|
|
331
309
|
|
|
332
|
-
/**
|
|
333
|
-
* Attach entities to an existing integration. Every entity must
|
|
334
|
-
* exist, belong to the integration's user, and be of a module the
|
|
335
|
-
* integration already holds; entities that are already attached are
|
|
336
|
-
* left as they are.
|
|
337
|
-
* @param {Object} params
|
|
338
|
-
* @param {string} params.integrationId - Integration ID
|
|
339
|
-
* @param {Array<string>} params.entityIds - Entity IDs to attach
|
|
340
|
-
* @returns {Promise<Object>} Updated integration
|
|
341
|
-
*/
|
|
342
|
-
async addEntitiesToIntegration({ integrationId, entityIds }) {
|
|
343
|
-
try {
|
|
344
|
-
return await addEntitiesToIntegrationUseCase.execute(
|
|
345
|
-
integrationId,
|
|
346
|
-
entityIds
|
|
347
|
-
);
|
|
348
|
-
} catch (error) {
|
|
349
|
-
return mapErrorToResponse(error);
|
|
350
|
-
}
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Detach entities from an integration. Entities that are not
|
|
355
|
-
* attached are ignored; detached entities are not deleted. Each
|
|
356
|
-
* module must keep at least one entity.
|
|
357
|
-
* @param {Object} params
|
|
358
|
-
* @param {string} params.integrationId - Integration ID
|
|
359
|
-
* @param {Array<string>} params.entityIds - Entity IDs to detach
|
|
360
|
-
* @returns {Promise<Object>} Updated integration
|
|
361
|
-
*/
|
|
362
|
-
async removeEntitiesFromIntegration({ integrationId, entityIds }) {
|
|
363
|
-
try {
|
|
364
|
-
return await removeEntitiesFromIntegrationUseCase.execute(
|
|
365
|
-
integrationId,
|
|
366
|
-
entityIds
|
|
367
|
-
);
|
|
368
|
-
} catch (error) {
|
|
369
|
-
return mapErrorToResponse(error);
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
|
|
373
310
|
/**
|
|
374
311
|
* Delete an integration by ID
|
|
375
312
|
* @param {string} integrationId - Integration ID to delete
|
|
@@ -58,10 +58,7 @@ async function insertOne(client, collection, document) {
|
|
|
58
58
|
|
|
59
59
|
// Provide helpful context for common errors
|
|
60
60
|
if (error.code === 11000) {
|
|
61
|
-
throw
|
|
62
|
-
new Error(`${errorMsg} - Duplicate key violation`),
|
|
63
|
-
{ code: 11000 }
|
|
64
|
-
);
|
|
61
|
+
throw new Error(`${errorMsg} - Duplicate key violation`);
|
|
65
62
|
}
|
|
66
63
|
throw new Error(errorMsg);
|
|
67
64
|
}
|
package/errors/index.js
CHANGED
|
@@ -6,9 +6,6 @@ const {
|
|
|
6
6
|
ParameterTypeError,
|
|
7
7
|
} = require('./validation-errors');
|
|
8
8
|
const { ClientSafeError } = require('./client-safe-error');
|
|
9
|
-
const {
|
|
10
|
-
MappingAlreadyExistsError,
|
|
11
|
-
} = require('./mapping-already-exists-error');
|
|
12
9
|
|
|
13
10
|
module.exports = {
|
|
14
11
|
BaseError,
|
|
@@ -17,5 +14,4 @@ module.exports = {
|
|
|
17
14
|
RequiredPropertyError,
|
|
18
15
|
ParameterTypeError,
|
|
19
16
|
ClientSafeError,
|
|
20
|
-
MappingAlreadyExistsError,
|
|
21
17
|
};
|
package/handlers/WEBHOOKS.md
CHANGED
|
@@ -483,35 +483,6 @@ async onWebhook({ data }) {
|
|
|
483
483
|
}
|
|
484
484
|
```
|
|
485
485
|
|
|
486
|
-
`getMapping` followed by `upsertMapping` is not atomic. To claim an event so only one
|
|
487
|
-
concurrent worker proceeds, use the insert-only `createMapping`, which throws
|
|
488
|
-
`MappingAlreadyExistsError` (exported from `@friggframework/core`) when the
|
|
489
|
-
`sourceId` is already taken. `deleteMapping(sourceId)` releases the claim and is a
|
|
490
|
-
no-op when nothing exists.
|
|
491
|
-
|
|
492
|
-
```javascript
|
|
493
|
-
const { MappingAlreadyExistsError } = require('@friggframework/core');
|
|
494
|
-
|
|
495
|
-
async onWebhook({ data }) {
|
|
496
|
-
const claimId = `claim:${data.body.id}`;
|
|
497
|
-
try {
|
|
498
|
-
await this.createMapping(claimId, { claimedAt: new Date() });
|
|
499
|
-
} catch (error) {
|
|
500
|
-
if (error instanceof MappingAlreadyExistsError) {
|
|
501
|
-
return { processed: false, duplicate: true };
|
|
502
|
-
}
|
|
503
|
-
throw error;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
try {
|
|
507
|
-
await this.processEvent(data.body);
|
|
508
|
-
} catch (error) {
|
|
509
|
-
await this.deleteMapping(claimId);
|
|
510
|
-
throw error;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
```
|
|
514
|
-
|
|
515
486
|
### 4. Error Handling
|
|
516
487
|
```javascript
|
|
517
488
|
async onWebhook({ data }) {
|
package/index.js
CHANGED
|
@@ -57,7 +57,6 @@ const {
|
|
|
57
57
|
HaltError,
|
|
58
58
|
RequiredPropertyError,
|
|
59
59
|
ParameterTypeError,
|
|
60
|
-
MappingAlreadyExistsError,
|
|
61
60
|
} = require('./errors/index');
|
|
62
61
|
const {
|
|
63
62
|
IntegrationBase,
|
|
@@ -145,7 +144,6 @@ module.exports = {
|
|
|
145
144
|
HaltError,
|
|
146
145
|
RequiredPropertyError,
|
|
147
146
|
ParameterTypeError,
|
|
148
|
-
MappingAlreadyExistsError,
|
|
149
147
|
|
|
150
148
|
// integrations
|
|
151
149
|
IntegrationBase,
|
|
@@ -11,65 +11,19 @@
|
|
|
11
11
|
* This adapter implements SchedulerServiceInterface for AWS EventBridge Scheduler.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
const {
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const {
|
|
15
|
+
SchedulerClient,
|
|
16
|
+
CreateScheduleCommand,
|
|
17
|
+
DeleteScheduleCommand,
|
|
18
|
+
GetScheduleCommand,
|
|
19
|
+
ResourceNotFoundException,
|
|
20
|
+
} = require('@aws-sdk/client-scheduler');
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
* Lazily load the AWS EventBridge Scheduler SDK.
|
|
20
|
-
*
|
|
21
|
-
* Requiring `@aws-sdk/client-scheduler` at module top level would put the AWS
|
|
22
|
-
* SDK on the load path of every consumer of `@friggframework/core`, including
|
|
23
|
-
* those using the mock scheduler or no scheduler at all. Loading it here, on
|
|
24
|
-
* construction of the EventBridge adapter, keeps it out of the module-load
|
|
25
|
-
* path. The adapter calls it exactly once, in its constructor, and keeps the
|
|
26
|
-
* classes its methods need on the instance.
|
|
27
|
-
*
|
|
28
|
-
* Only a MODULE_NOT_FOUND for the scheduler SDK itself is rewritten into an
|
|
29
|
-
* actionable message; any other failure (including a missing transitive
|
|
30
|
-
* dependency) is rethrown unchanged so the real cause is not masked.
|
|
31
|
-
*
|
|
32
|
-
* @returns {object} The relevant exports from `@aws-sdk/client-scheduler`.
|
|
33
|
-
*/
|
|
34
|
-
function loadSchedulerSdk() {
|
|
35
|
-
try {
|
|
36
|
-
// Keep the literal specifier so bundlers (esbuild) can resolve it.
|
|
37
|
-
return require('@aws-sdk/client-scheduler');
|
|
38
|
-
} catch (error) {
|
|
39
|
-
const isSchedulerSdkMissing =
|
|
40
|
-
error &&
|
|
41
|
-
error.code === 'MODULE_NOT_FOUND' &&
|
|
42
|
-
typeof error.message === 'string' &&
|
|
43
|
-
error.message.includes(SCHEDULER_SDK);
|
|
44
|
-
if (isSchedulerSdkMissing) {
|
|
45
|
-
throw new Error(
|
|
46
|
-
`The EventBridge scheduler requires the "${SCHEDULER_SDK}" package, ` +
|
|
47
|
-
`which is not installed. Install it (\`npm install ${SCHEDULER_SDK}\`) ` +
|
|
48
|
-
'to use the EventBridge scheduler provider, or use the mock scheduler provider ' +
|
|
49
|
-
'(SCHEDULER_PROVIDER=mock) for local development.',
|
|
50
|
-
{ cause: error }
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
22
|
+
const { SchedulerServiceInterface } = require('./scheduler-service-interface');
|
|
56
23
|
|
|
57
24
|
class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
|
|
58
25
|
constructor({ region } = {}) {
|
|
59
26
|
super();
|
|
60
|
-
const {
|
|
61
|
-
SchedulerClient,
|
|
62
|
-
CreateScheduleCommand,
|
|
63
|
-
DeleteScheduleCommand,
|
|
64
|
-
GetScheduleCommand,
|
|
65
|
-
ResourceNotFoundException,
|
|
66
|
-
} = loadSchedulerSdk();
|
|
67
|
-
this.sdk = {
|
|
68
|
-
CreateScheduleCommand,
|
|
69
|
-
DeleteScheduleCommand,
|
|
70
|
-
GetScheduleCommand,
|
|
71
|
-
ResourceNotFoundException,
|
|
72
|
-
};
|
|
73
27
|
this.client = new SchedulerClient({
|
|
74
28
|
region: region || process.env.AWS_REGION || 'us-east-1',
|
|
75
29
|
});
|
|
@@ -88,12 +42,7 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
|
|
|
88
42
|
* @param {Object} params.payload - Message payload
|
|
89
43
|
* @returns {Promise<{scheduledJobId: string, scheduledAt: string}>}
|
|
90
44
|
*/
|
|
91
|
-
async scheduleOneTime({
|
|
92
|
-
scheduleName,
|
|
93
|
-
scheduleAt,
|
|
94
|
-
queueResourceId,
|
|
95
|
-
payload,
|
|
96
|
-
}) {
|
|
45
|
+
async scheduleOneTime({ scheduleName, scheduleAt, queueResourceId, payload }) {
|
|
97
46
|
if (!scheduleName) {
|
|
98
47
|
throw new Error('scheduleName is required');
|
|
99
48
|
}
|
|
@@ -110,11 +59,8 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
|
|
|
110
59
|
}
|
|
111
60
|
|
|
112
61
|
// Format date to AWS schedule expression (at(yyyy-mm-ddThh:mm:ss))
|
|
113
|
-
const scheduleExpression = `at(${scheduleAt
|
|
114
|
-
.toISOString()
|
|
115
|
-
.replace(/\.\d{3}Z$/, '')})`;
|
|
62
|
+
const scheduleExpression = `at(${scheduleAt.toISOString().replace(/\.\d{3}Z$/, '')})`;
|
|
116
63
|
|
|
117
|
-
const { CreateScheduleCommand } = this.sdk;
|
|
118
64
|
const command = new CreateScheduleCommand({
|
|
119
65
|
Name: scheduleName,
|
|
120
66
|
GroupName: this.scheduleGroupName,
|
|
@@ -161,7 +107,6 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
|
|
|
161
107
|
throw new Error('scheduleName is required');
|
|
162
108
|
}
|
|
163
109
|
|
|
164
|
-
const { DeleteScheduleCommand, ResourceNotFoundException } = this.sdk;
|
|
165
110
|
const command = new DeleteScheduleCommand({
|
|
166
111
|
Name: scheduleName,
|
|
167
112
|
GroupName: this.scheduleGroupName,
|
|
@@ -196,7 +141,6 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
|
|
|
196
141
|
throw new Error('scheduleName is required');
|
|
197
142
|
}
|
|
198
143
|
|
|
199
|
-
const { GetScheduleCommand, ResourceNotFoundException } = this.sdk;
|
|
200
144
|
const command = new GetScheduleCommand({
|
|
201
145
|
Name: scheduleName,
|
|
202
146
|
GroupName: this.scheduleGroupName,
|
|
@@ -209,7 +153,9 @@ class EventBridgeSchedulerAdapter extends SchedulerServiceInterface {
|
|
|
209
153
|
// Format: at(yyyy-mm-ddThh:mm:ss)
|
|
210
154
|
let scheduledAt = null;
|
|
211
155
|
if (response.ScheduleExpression) {
|
|
212
|
-
const match = response.ScheduleExpression.match(
|
|
156
|
+
const match = response.ScheduleExpression.match(
|
|
157
|
+
/^at\((.+)\)$/
|
|
158
|
+
);
|
|
213
159
|
if (match) {
|
|
214
160
|
scheduledAt = new Date(match[1] + 'Z').toISOString();
|
|
215
161
|
}
|
|
@@ -457,35 +457,6 @@ class IntegrationBase {
|
|
|
457
457
|
);
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
/**
|
|
461
|
-
* Insert-only mapping write; usable as an atomic claim/lock row.
|
|
462
|
-
* @throws {MappingAlreadyExistsError} If a mapping for sourceId exists
|
|
463
|
-
*/
|
|
464
|
-
async createMapping(sourceId, mapping) {
|
|
465
|
-
if (!sourceId) {
|
|
466
|
-
throw new Error(`sourceId must be set`);
|
|
467
|
-
}
|
|
468
|
-
return await this.integrationMappingRepository.createMapping(
|
|
469
|
-
this.id,
|
|
470
|
-
sourceId,
|
|
471
|
-
mapping
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Deletes the mapping for sourceId. A missing mapping is not an error
|
|
477
|
-
* (resolves with deletedCount 0).
|
|
478
|
-
*/
|
|
479
|
-
async deleteMapping(sourceId) {
|
|
480
|
-
if (!sourceId) {
|
|
481
|
-
throw new Error(`sourceId must be set`);
|
|
482
|
-
}
|
|
483
|
-
return await this.integrationMappingRepository.deleteMapping(
|
|
484
|
-
this.id,
|
|
485
|
-
sourceId
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
460
|
/**
|
|
490
461
|
* CHILDREN CAN OVERRIDE THESE CONFIGURATION METHODS
|
|
491
462
|
*/
|
|
@@ -42,6 +42,9 @@ const {
|
|
|
42
42
|
} = require('../modules/use-cases/get-entity-options-by-type');
|
|
43
43
|
const { TestModuleAuth } = require('../modules/use-cases/test-module-auth');
|
|
44
44
|
const { GetModule } = require('../modules/use-cases/get-module');
|
|
45
|
+
const {
|
|
46
|
+
CreateEntityFromCredential,
|
|
47
|
+
} = require('../modules/use-cases/create-entity-from-credential');
|
|
45
48
|
const {
|
|
46
49
|
GetEntityOptionsById,
|
|
47
50
|
} = require('../modules/use-cases/get-entity-options-by-id');
|
|
@@ -198,6 +201,13 @@ function createIntegrationRouter() {
|
|
|
198
201
|
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
199
202
|
});
|
|
200
203
|
|
|
204
|
+
const createEntityFromCredential = new CreateEntityFromCredential({
|
|
205
|
+
processAuthorizationCallback,
|
|
206
|
+
credentialRepository,
|
|
207
|
+
moduleDefinitions:
|
|
208
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses),
|
|
209
|
+
});
|
|
210
|
+
|
|
201
211
|
const router = express();
|
|
202
212
|
|
|
203
213
|
setIntegrationRoutes(router, authenticateUser, {
|
|
@@ -218,6 +228,7 @@ function createIntegrationRouter() {
|
|
|
218
228
|
getEntityOptionsById,
|
|
219
229
|
refreshEntityOptions,
|
|
220
230
|
processAuthorizationCallback,
|
|
231
|
+
createEntityFromCredential,
|
|
221
232
|
});
|
|
222
233
|
return router;
|
|
223
234
|
}
|
|
@@ -502,6 +513,7 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
502
513
|
getEntityOptionsById,
|
|
503
514
|
refreshEntityOptions,
|
|
504
515
|
processAuthorizationCallback,
|
|
516
|
+
createEntityFromCredential,
|
|
505
517
|
} = useCases;
|
|
506
518
|
|
|
507
519
|
router.route('/api/authorize').get(
|
|
@@ -566,35 +578,19 @@ function setEntityRoutes(router, authenticateUser, useCases) {
|
|
|
566
578
|
router.route('/api/entity').post(
|
|
567
579
|
catchAsyncError(async (req, res) => {
|
|
568
580
|
const user = await authenticateUser.execute(req);
|
|
569
|
-
const userId = user.getId();
|
|
570
581
|
const params = checkRequiredParams(req.body, [
|
|
571
582
|
'entityType',
|
|
572
583
|
'data',
|
|
573
584
|
]);
|
|
574
585
|
checkRequiredParams(req.body.data, ['credential_id']);
|
|
575
586
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (!credential) {
|
|
583
|
-
throw Boom.badRequest('Invalid credential ID');
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const module = await getModuleInstanceFromType.execute(
|
|
587
|
-
userId,
|
|
588
|
-
params.entityType
|
|
587
|
+
res.json(
|
|
588
|
+
await createEntityFromCredential.execute({
|
|
589
|
+
user,
|
|
590
|
+
entityType: params.entityType,
|
|
591
|
+
credentialId: params.data.credential_id,
|
|
592
|
+
})
|
|
589
593
|
);
|
|
590
|
-
const entityDetails = await module.getEntityDetails(
|
|
591
|
-
module.api,
|
|
592
|
-
null,
|
|
593
|
-
null,
|
|
594
|
-
userId
|
|
595
|
-
);
|
|
596
|
-
|
|
597
|
-
res.json(await module.findOrCreateEntity(entityDetails));
|
|
598
594
|
})
|
|
599
595
|
);
|
|
600
596
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
|
-
const { MappingAlreadyExistsError } = require('../../errors');
|
|
3
2
|
const {
|
|
4
3
|
toObjectId,
|
|
5
4
|
fromObjectId,
|
|
@@ -181,55 +180,6 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
|
|
|
181
180
|
return this._mapMapping(decryptedMapping);
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
async createMapping(integrationId, sourceId, mapping) {
|
|
185
|
-
const now = new Date();
|
|
186
|
-
const encryptedDocument = await this.encryptionService.encryptFields(
|
|
187
|
-
'IntegrationMapping',
|
|
188
|
-
{
|
|
189
|
-
integrationId,
|
|
190
|
-
sourceId:
|
|
191
|
-
sourceId === null || sourceId === undefined
|
|
192
|
-
? null
|
|
193
|
-
: String(sourceId),
|
|
194
|
-
mapping,
|
|
195
|
-
createdAt: now,
|
|
196
|
-
updatedAt: now,
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
let insertedId;
|
|
201
|
-
try {
|
|
202
|
-
insertedId = await insertOne(
|
|
203
|
-
this.prisma,
|
|
204
|
-
'IntegrationMapping',
|
|
205
|
-
encryptedDocument
|
|
206
|
-
);
|
|
207
|
-
} catch (error) {
|
|
208
|
-
if (error.code === 11000) {
|
|
209
|
-
throw new MappingAlreadyExistsError(integrationId, sourceId, {
|
|
210
|
-
cause: error,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const created = await findOne(this.prisma, 'IntegrationMapping', {
|
|
217
|
-
_id: insertedId,
|
|
218
|
-
});
|
|
219
|
-
if (!created) {
|
|
220
|
-
throw new Error(
|
|
221
|
-
'Failed to create mapping: Document not found after insert. ' +
|
|
222
|
-
'This indicates a database consistency issue.'
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
return this._mapMapping(
|
|
226
|
-
await this.encryptionService.decryptFields(
|
|
227
|
-
'IntegrationMapping',
|
|
228
|
-
created
|
|
229
|
-
)
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
183
|
async findMappingsByIntegration(integrationId) {
|
|
234
184
|
const filter = {};
|
|
235
185
|
if (integrationId) filter.integrationId = integrationId;
|
|
@@ -39,21 +39,6 @@ class IntegrationMappingRepositoryInterface {
|
|
|
39
39
|
throw new Error('Method upsertMapping must be implemented by subclass');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* Insert a mapping, failing if one already exists for the
|
|
44
|
-
* (integrationId, sourceId) pair. Atomic: usable as a claim/lock row.
|
|
45
|
-
*
|
|
46
|
-
* @param {string|number} integrationId - The integration ID
|
|
47
|
-
* @param {string} sourceId - The source ID for lookup
|
|
48
|
-
* @param {Object} mapping - The mapping data
|
|
49
|
-
* @returns {Promise<Object>} The created mapping document
|
|
50
|
-
* @throws {MappingAlreadyExistsError} If the pair already exists
|
|
51
|
-
* @abstract
|
|
52
|
-
*/
|
|
53
|
-
async createMapping(integrationId, sourceId, mapping) {
|
|
54
|
-
throw new Error('createMapping must be implemented by subclass');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
42
|
/**
|
|
58
43
|
* Find all mappings for an integration
|
|
59
44
|
*
|