@friggframework/core 2.0.0--canary.684.0da024b.0 → 2.0.0--canary.679.a2bcc2f.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 -2
- package/README.md +0 -105
- package/application/commands/integration-commands.js +2 -2
- package/application/commands/user-commands.js +5 -17
- package/credential/use-cases/credential-operations-for-user.js +25 -110
- package/credential/use-cases/reauthorize-credential-for-user.js +8 -15
- package/database/documentdb-utils.js +1 -6
- package/generated/prisma-mongodb/edge.js +4 -15
- package/generated/prisma-mongodb/index-browser.js +1 -12
- package/generated/prisma-mongodb/index.d.ts +86 -2027
- package/generated/prisma-mongodb/index.js +4 -15
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -47
- package/generated/prisma-mongodb/wasm.js +4 -15
- package/generated/prisma-postgresql/edge.js +4 -15
- package/generated/prisma-postgresql/index-browser.js +1 -12
- package/generated/prisma-postgresql/index.d.ts +63 -2162
- package/generated/prisma-postgresql/index.js +4 -15
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -44
- package/generated/prisma-postgresql/wasm.js +4 -15
- package/handlers/app-definition-loader.js +1 -15
- package/index.js +0 -18
- package/integrations/integration-router.js +73 -115
- package/integrations/repositories/integration-repository-documentdb.js +3 -35
- package/integrations/repositories/integration-repository-interface.js +1 -17
- package/integrations/repositories/integration-repository-mongo.js +76 -59
- package/integrations/repositories/integration-repository-postgres.js +76 -62
- package/integrations/tests/doubles/test-integration-repository.js +1 -12
- package/integrations/use-cases/create-integration.js +20 -55
- package/integrations/use-cases/get-integrations-for-user.js +40 -80
- package/integrations/use-cases/index.js +1 -5
- package/integrations/use-cases/integration-operations-for-user.js +24 -55
- package/management-api/management-api-config.js +0 -3
- package/management-api/openapi/components.js +0 -40
- package/management-api/openapi/v2-operations.js +1 -13
- package/management-api/v2/dependencies.js +15 -40
- package/management-api/v2/dto.js +0 -20
- package/management-api/v2/handlers/authorization.js +0 -1
- package/management-api/v2/handlers/credentials.js +2 -6
- package/management-api/v2/handlers/entities.js +7 -10
- package/management-api/v2/handlers/integrations.js +3 -6
- package/management-api/v2/request.js +1 -32
- package/modules/index.js +0 -8
- package/modules/repositories/module-repository-documentdb.js +4 -48
- package/modules/repositories/module-repository-interface.js +2 -19
- package/modules/repositories/module-repository-mongo.js +73 -65
- package/modules/repositories/module-repository-postgres.js +73 -64
- package/modules/use-cases/entity-operations-for-user.js +13 -50
- package/modules/use-cases/entity-types.js +7 -31
- package/modules/use-cases/execute-entity-proxy-request.js +0 -15
- package/modules/use-cases/get-entities-for-user.js +5 -16
- package/modules/use-cases/get-entity-module-for-user.js +3 -23
- package/modules/use-cases/get-entity-options-by-id.js +3 -9
- package/modules/use-cases/get-module.js +3 -9
- package/modules/use-cases/multi-step-authorization.js +12 -39
- package/modules/use-cases/process-authorization-callback.js +18 -52
- package/modules/use-cases/refresh-entity-options.js +3 -9
- package/modules/use-cases/test-module-auth.js +3 -9
- package/package.json +5 -6
- package/prisma-mongodb/schema.prisma +0 -47
- package/prisma-postgresql/schema.prisma +0 -44
- package/types/index.d.ts +0 -1
- package/types/modules/index.d.ts +0 -87
- package/user/repositories/user-repository-documentdb.js +2 -27
- package/user/repositories/user-repository-interface.js +0 -18
- package/user/repositories/user-repository-mongo.js +0 -24
- package/user/repositories/user-repository-postgres.js +0 -18
- package/user/use-cases/authenticate-user.js +1 -7
- package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
- package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
- package/user/user.js +1 -64
- package/database/organization-where.js +0 -18
- package/database/unique-violation.js +0 -25
- package/integrations/repositories/integration-not-found.js +0 -14
- package/integrations/use-cases/resolve-integration-ownership.js +0 -177
- package/modules/entity-scope.js +0 -15
- package/modules/scope-config.js +0 -192
- package/modules/scope-policy.js +0 -112
- package/modules/scope-resolution.js +0 -288
- package/modules/scope-visibility.js +0 -239
- package/modules/tests/doubles/ownership-matching.js +0 -18
- package/modules/use-cases/authorize-entity-for-user.js +0 -61
- package/modules/use-cases/create-entity-from-credential.js +0 -113
- package/modules/use-cases/manage-access.js +0 -21
- package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
- package/types/user/index.d.ts +0 -134
- package/user/adopter-jwt-config.js +0 -177
- package/user/adopter-jwt-errors.js +0 -59
- package/user/adopter-token-verifier.js +0 -122
- package/user/app-user-helpers.js +0 -97
- package/user/create-user-authentication.js +0 -64
- package/user/membership.js +0 -51
- package/user/repositories/organization-membership-record.js +0 -19
- package/user/repositories/organization-membership-repository-documentdb.js +0 -106
- package/user/repositories/organization-membership-repository-factory.js +0 -42
- package/user/repositories/organization-membership-repository-interface.js +0 -57
- package/user/repositories/organization-membership-repository-mongo.js +0 -13
- package/user/repositories/organization-membership-repository-postgres.js +0 -29
- package/user/repositories/organization-membership-repository-prisma.js +0 -81
- package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
- package/user/use-cases/ensure-organization-membership.js +0 -92
- package/user/use-cases/find-individual-users-by-organization.js +0 -54
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
|
-
const { organizationWhere } = require('../../database/organization-where');
|
|
3
2
|
const {
|
|
4
3
|
IntegrationRepositoryInterface,
|
|
5
4
|
} = require('./integration-repository-interface');
|
|
6
5
|
const { validateConfigPatch } = require('./config-patch-shared');
|
|
7
|
-
const { integrationNotFoundError } = require('./integration-not-found');
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* MongoDB Integration Repository Adapter
|
|
@@ -27,25 +25,6 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
27
25
|
this.prisma = prisma;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
/**
|
|
31
|
-
* The one place a integration integration row becomes the record the
|
|
32
|
-
* application sees (ids of its entities, its organization).
|
|
33
|
-
* @private
|
|
34
|
-
*/
|
|
35
|
-
_mapIntegration(integration) {
|
|
36
|
-
return {
|
|
37
|
-
id: integration.id,
|
|
38
|
-
entitiesIds: integration.entities.map((e) => e.id),
|
|
39
|
-
userId: integration.userId,
|
|
40
|
-
organizationId: integration.organizationId ?? null,
|
|
41
|
-
config: integration.config,
|
|
42
|
-
version: integration.version,
|
|
43
|
-
status: integration.status,
|
|
44
|
-
messages: integration.messages,
|
|
45
|
-
createdAt: integration.createdAt,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
28
|
/**
|
|
50
29
|
* Find all integrations for a user
|
|
51
30
|
* Replaces: IntegrationModel.find({ user: userId }).populate('entities')
|
|
@@ -62,32 +41,16 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
62
41
|
});
|
|
63
42
|
|
|
64
43
|
// Map to domain objects (maintains same API)
|
|
65
|
-
return integrations.map((integration) => (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
*/
|
|
76
|
-
async findIntegrationsByOwnership(clauses) {
|
|
77
|
-
if (!Array.isArray(clauses) || clauses.length === 0) return [];
|
|
78
|
-
const integrations = await this.prisma.integration.findMany({
|
|
79
|
-
where: {
|
|
80
|
-
OR: clauses.map(({ userId, organizationId }) => ({
|
|
81
|
-
userId,
|
|
82
|
-
...organizationWhere(organizationId),
|
|
83
|
-
})),
|
|
84
|
-
},
|
|
85
|
-
include: {
|
|
86
|
-
entities: true,
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return integrations.map((integration) => (this._mapIntegration(integration)));
|
|
44
|
+
return integrations.map((integration) => ({
|
|
45
|
+
id: integration.id,
|
|
46
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
47
|
+
userId: integration.userId,
|
|
48
|
+
config: integration.config,
|
|
49
|
+
version: integration.version,
|
|
50
|
+
status: integration.status,
|
|
51
|
+
messages: integration.messages,
|
|
52
|
+
createdAt: integration.createdAt,
|
|
53
|
+
}));
|
|
91
54
|
}
|
|
92
55
|
|
|
93
56
|
/**
|
|
@@ -114,7 +77,16 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
114
77
|
},
|
|
115
78
|
});
|
|
116
79
|
|
|
117
|
-
return integrations.map((integration) => (
|
|
80
|
+
return integrations.map((integration) => ({
|
|
81
|
+
id: integration.id,
|
|
82
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
83
|
+
userId: integration.userId,
|
|
84
|
+
config: integration.config,
|
|
85
|
+
version: integration.version,
|
|
86
|
+
status: integration.status,
|
|
87
|
+
messages: integration.messages,
|
|
88
|
+
createdAt: integration.createdAt,
|
|
89
|
+
}));
|
|
118
90
|
}
|
|
119
91
|
|
|
120
92
|
/**
|
|
@@ -193,7 +165,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
193
165
|
throw new Error(`Integration with name ${name} not found`);
|
|
194
166
|
}
|
|
195
167
|
|
|
196
|
-
return
|
|
168
|
+
return {
|
|
169
|
+
id: integration.id,
|
|
170
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
171
|
+
userId: integration.userId,
|
|
172
|
+
config: integration.config,
|
|
173
|
+
version: integration.version,
|
|
174
|
+
status: integration.status,
|
|
175
|
+
messages: integration.messages,
|
|
176
|
+
};
|
|
197
177
|
}
|
|
198
178
|
|
|
199
179
|
/**
|
|
@@ -212,10 +192,18 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
212
192
|
});
|
|
213
193
|
|
|
214
194
|
if (!integration) {
|
|
215
|
-
throw
|
|
195
|
+
throw new Error(`Integration with id ${id} not found`);
|
|
216
196
|
}
|
|
217
197
|
|
|
218
|
-
return
|
|
198
|
+
return {
|
|
199
|
+
id: integration.id,
|
|
200
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
201
|
+
userId: integration.userId,
|
|
202
|
+
config: integration.config,
|
|
203
|
+
version: integration.version,
|
|
204
|
+
status: integration.status,
|
|
205
|
+
messages: integration.messages,
|
|
206
|
+
};
|
|
219
207
|
}
|
|
220
208
|
|
|
221
209
|
/**
|
|
@@ -302,7 +290,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
302
290
|
},
|
|
303
291
|
});
|
|
304
292
|
|
|
305
|
-
return integrations.map((integration) => (
|
|
293
|
+
return integrations.map((integration) => ({
|
|
294
|
+
id: integration.id,
|
|
295
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
296
|
+
userId: integration.userId,
|
|
297
|
+
config: integration.config,
|
|
298
|
+
version: integration.version,
|
|
299
|
+
status: integration.status,
|
|
300
|
+
messages: integration.messages,
|
|
301
|
+
}));
|
|
306
302
|
}
|
|
307
303
|
|
|
308
304
|
/**
|
|
@@ -314,16 +310,13 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
314
310
|
* @param {Array<string>} entities - Array of entity IDs (MongoDB ObjectIds)
|
|
315
311
|
* @param {string} userId - User ID (MongoDB ObjectId)
|
|
316
312
|
* @param {Object} config - Integration configuration
|
|
317
|
-
* @param {{organizationId?: (string|null)}} [tenancy] - the organization the
|
|
318
|
-
* integration is created in (ADR-056 §3.6)
|
|
319
313
|
* @returns {Promise<Object>} Created integration object
|
|
320
314
|
*/
|
|
321
|
-
async createIntegration(entities, userId, config
|
|
315
|
+
async createIntegration(entities, userId, config) {
|
|
322
316
|
const data = {
|
|
323
317
|
config,
|
|
324
318
|
version: '0.0.0',
|
|
325
319
|
userId: userId,
|
|
326
|
-
organizationId: tenancy.organizationId ?? null,
|
|
327
320
|
entityIds: entities,
|
|
328
321
|
};
|
|
329
322
|
|
|
@@ -334,7 +327,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
334
327
|
},
|
|
335
328
|
});
|
|
336
329
|
|
|
337
|
-
return
|
|
330
|
+
return {
|
|
331
|
+
id: integration.id,
|
|
332
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
333
|
+
userId: integration.userId,
|
|
334
|
+
config: integration.config,
|
|
335
|
+
version: integration.version,
|
|
336
|
+
status: integration.status,
|
|
337
|
+
messages: integration.messages,
|
|
338
|
+
};
|
|
338
339
|
}
|
|
339
340
|
|
|
340
341
|
/**
|
|
@@ -356,7 +357,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
356
357
|
return null;
|
|
357
358
|
}
|
|
358
359
|
|
|
359
|
-
return
|
|
360
|
+
return {
|
|
361
|
+
id: integration.id,
|
|
362
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
363
|
+
userId: integration.userId,
|
|
364
|
+
config: integration.config,
|
|
365
|
+
version: integration.version,
|
|
366
|
+
status: integration.status,
|
|
367
|
+
messages: integration.messages,
|
|
368
|
+
};
|
|
360
369
|
}
|
|
361
370
|
|
|
362
371
|
/**
|
|
@@ -380,7 +389,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
380
389
|
},
|
|
381
390
|
});
|
|
382
391
|
|
|
383
|
-
return
|
|
392
|
+
return {
|
|
393
|
+
id: integration.id,
|
|
394
|
+
entitiesIds: integration.entities.map((e) => e.id),
|
|
395
|
+
userId: integration.userId,
|
|
396
|
+
config: integration.config,
|
|
397
|
+
version: integration.version,
|
|
398
|
+
status: integration.status,
|
|
399
|
+
messages: integration.messages,
|
|
400
|
+
};
|
|
384
401
|
}
|
|
385
402
|
|
|
386
403
|
/**
|
|
@@ -3,7 +3,6 @@ const {
|
|
|
3
3
|
IntegrationRepositoryInterface,
|
|
4
4
|
} = require('./integration-repository-interface');
|
|
5
5
|
const { validateConfigPatch } = require('./config-patch-shared');
|
|
6
|
-
const { integrationNotFoundError } = require('./integration-not-found');
|
|
7
6
|
const { strictIntId } = require('./report-id');
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -52,7 +51,6 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
52
51
|
...integration,
|
|
53
52
|
id: integration.id?.toString(),
|
|
54
53
|
userId: integration.userId?.toString(),
|
|
55
|
-
organizationId: integration.organizationId?.toString() ?? null,
|
|
56
54
|
entities: integration.entities?.map(e => ({
|
|
57
55
|
...e,
|
|
58
56
|
id: e.id?.toString(),
|
|
@@ -62,25 +60,6 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
62
60
|
};
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
/**
|
|
66
|
-
* The one place a converted integration row becomes the record the
|
|
67
|
-
* application sees (string ids, ids of its entities, its organization).
|
|
68
|
-
* @private
|
|
69
|
-
*/
|
|
70
|
-
_mapIntegration(converted) {
|
|
71
|
-
return {
|
|
72
|
-
id: converted.id,
|
|
73
|
-
entitiesIds: converted.entities.map((e) => e.id),
|
|
74
|
-
userId: converted.userId,
|
|
75
|
-
organizationId: converted.organizationId,
|
|
76
|
-
config: converted.config,
|
|
77
|
-
version: converted.version,
|
|
78
|
-
status: converted.status,
|
|
79
|
-
messages: converted.messages,
|
|
80
|
-
createdAt: converted.createdAt,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
63
|
/**
|
|
85
64
|
* Find all integrations for a user
|
|
86
65
|
*
|
|
@@ -99,35 +78,16 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
99
78
|
// Map to domain objects with string IDs
|
|
100
79
|
return integrations.map((integration) => {
|
|
101
80
|
const converted = this._convertIntegrationIds(integration);
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* @returns {Promise<Array>} Array of integration objects with string IDs
|
|
113
|
-
*/
|
|
114
|
-
async findIntegrationsByOwnership(clauses) {
|
|
115
|
-
if (!Array.isArray(clauses) || clauses.length === 0) return [];
|
|
116
|
-
const integrations = await this.prisma.integration.findMany({
|
|
117
|
-
where: {
|
|
118
|
-
OR: clauses.map(({ userId, organizationId }) => ({
|
|
119
|
-
userId: this._convertId(userId),
|
|
120
|
-
organizationId: this._convertId(organizationId),
|
|
121
|
-
})),
|
|
122
|
-
},
|
|
123
|
-
include: {
|
|
124
|
-
entities: true,
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
return integrations.map((integration) => {
|
|
129
|
-
const converted = this._convertIntegrationIds(integration);
|
|
130
|
-
return this._mapIntegration(converted);
|
|
81
|
+
return {
|
|
82
|
+
id: converted.id,
|
|
83
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
84
|
+
userId: converted.userId,
|
|
85
|
+
config: converted.config,
|
|
86
|
+
version: converted.version,
|
|
87
|
+
status: converted.status,
|
|
88
|
+
messages: converted.messages,
|
|
89
|
+
createdAt: converted.createdAt,
|
|
90
|
+
};
|
|
131
91
|
});
|
|
132
92
|
}
|
|
133
93
|
|
|
@@ -157,7 +117,16 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
157
117
|
|
|
158
118
|
return integrations.map((integration) => {
|
|
159
119
|
const converted = this._convertIntegrationIds(integration);
|
|
160
|
-
return
|
|
120
|
+
return {
|
|
121
|
+
id: converted.id,
|
|
122
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
123
|
+
userId: converted.userId,
|
|
124
|
+
config: converted.config,
|
|
125
|
+
version: converted.version,
|
|
126
|
+
status: converted.status,
|
|
127
|
+
messages: converted.messages,
|
|
128
|
+
createdAt: converted.createdAt,
|
|
129
|
+
};
|
|
161
130
|
});
|
|
162
131
|
}
|
|
163
132
|
|
|
@@ -240,7 +209,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
240
209
|
}
|
|
241
210
|
|
|
242
211
|
const converted = this._convertIntegrationIds(integration);
|
|
243
|
-
return
|
|
212
|
+
return {
|
|
213
|
+
id: converted.id,
|
|
214
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
215
|
+
userId: converted.userId,
|
|
216
|
+
config: converted.config,
|
|
217
|
+
version: converted.version,
|
|
218
|
+
status: converted.status,
|
|
219
|
+
messages: converted.messages,
|
|
220
|
+
};
|
|
244
221
|
}
|
|
245
222
|
|
|
246
223
|
/**
|
|
@@ -259,11 +236,19 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
259
236
|
});
|
|
260
237
|
|
|
261
238
|
if (!integration) {
|
|
262
|
-
throw
|
|
239
|
+
throw new Error(`Integration with id ${id} not found`);
|
|
263
240
|
}
|
|
264
241
|
|
|
265
242
|
const converted = this._convertIntegrationIds(integration);
|
|
266
|
-
return
|
|
243
|
+
return {
|
|
244
|
+
id: converted.id,
|
|
245
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
246
|
+
userId: converted.userId,
|
|
247
|
+
config: converted.config,
|
|
248
|
+
version: converted.version,
|
|
249
|
+
status: converted.status,
|
|
250
|
+
messages: converted.messages,
|
|
251
|
+
};
|
|
267
252
|
}
|
|
268
253
|
|
|
269
254
|
/**
|
|
@@ -343,15 +328,12 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
343
328
|
* @param {Array<string>} entities - Array of entity IDs (strings from application layer)
|
|
344
329
|
* @param {string} userId - User ID (string from application layer)
|
|
345
330
|
* @param {Object} config - Integration configuration
|
|
346
|
-
* @param {{organizationId?: (string|null)}} [tenancy] - the organization the
|
|
347
|
-
* integration is created in (ADR-056 §3.6)
|
|
348
331
|
* @returns {Promise<Object>} Created integration object with string IDs
|
|
349
332
|
*/
|
|
350
|
-
async createIntegration(entities, userId, config
|
|
333
|
+
async createIntegration(entities, userId, config) {
|
|
351
334
|
const data = {
|
|
352
335
|
config,
|
|
353
336
|
version: '0.0.0',
|
|
354
|
-
organizationId: this._convertId(tenancy.organizationId ?? null),
|
|
355
337
|
};
|
|
356
338
|
|
|
357
339
|
// PostgreSQL: use nested relations with ID conversion
|
|
@@ -372,7 +354,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
372
354
|
});
|
|
373
355
|
|
|
374
356
|
const converted = this._convertIntegrationIds(integration);
|
|
375
|
-
return
|
|
357
|
+
return {
|
|
358
|
+
id: converted.id,
|
|
359
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
360
|
+
userId: converted.userId,
|
|
361
|
+
config: converted.config,
|
|
362
|
+
version: converted.version,
|
|
363
|
+
status: converted.status,
|
|
364
|
+
messages: converted.messages,
|
|
365
|
+
};
|
|
376
366
|
}
|
|
377
367
|
|
|
378
368
|
/**
|
|
@@ -395,7 +385,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
395
385
|
}
|
|
396
386
|
|
|
397
387
|
const converted = this._convertIntegrationIds(integration);
|
|
398
|
-
return
|
|
388
|
+
return {
|
|
389
|
+
id: converted.id,
|
|
390
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
391
|
+
userId: converted.userId,
|
|
392
|
+
config: converted.config,
|
|
393
|
+
version: converted.version,
|
|
394
|
+
status: converted.status,
|
|
395
|
+
messages: converted.messages,
|
|
396
|
+
};
|
|
399
397
|
}
|
|
400
398
|
|
|
401
399
|
/**
|
|
@@ -420,7 +418,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
420
418
|
});
|
|
421
419
|
|
|
422
420
|
const converted = this._convertIntegrationIds(integration);
|
|
423
|
-
return
|
|
421
|
+
return {
|
|
422
|
+
id: converted.id,
|
|
423
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
424
|
+
userId: converted.userId,
|
|
425
|
+
config: converted.config,
|
|
426
|
+
version: converted.version,
|
|
427
|
+
status: converted.status,
|
|
428
|
+
messages: converted.messages,
|
|
429
|
+
};
|
|
424
430
|
}
|
|
425
431
|
|
|
426
432
|
/**
|
|
@@ -470,7 +476,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
|
|
|
470
476
|
|
|
471
477
|
return integrations.map((integration) => {
|
|
472
478
|
const converted = this._convertIntegrationIds(integration);
|
|
473
|
-
return
|
|
479
|
+
return {
|
|
480
|
+
id: converted.id,
|
|
481
|
+
entitiesIds: converted.entities.map((e) => e.id),
|
|
482
|
+
userId: converted.userId,
|
|
483
|
+
config: converted.config,
|
|
484
|
+
version: converted.version,
|
|
485
|
+
status: converted.status,
|
|
486
|
+
messages: converted.messages,
|
|
487
|
+
};
|
|
474
488
|
});
|
|
475
489
|
}
|
|
476
490
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const { v4: uuid } = require('uuid');
|
|
2
2
|
const { validateConfigPatch } = require('../../repositories/config-patch-shared');
|
|
3
|
-
const { matchesAnyClause } = require('../../../modules/tests/doubles/ownership-matching');
|
|
4
3
|
|
|
5
4
|
class TestIntegrationRepository {
|
|
6
5
|
constructor() {
|
|
@@ -8,14 +7,13 @@ class TestIntegrationRepository {
|
|
|
8
7
|
this.operationHistory = [];
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
async createIntegration(entities, userId, config
|
|
10
|
+
async createIntegration(entities, userId, config) {
|
|
12
11
|
const id = uuid();
|
|
13
12
|
const record = {
|
|
14
13
|
id,
|
|
15
14
|
_id: id,
|
|
16
15
|
entitiesIds: entities,
|
|
17
16
|
userId: userId,
|
|
18
|
-
organizationId: tenancy.organizationId ?? null,
|
|
19
17
|
config,
|
|
20
18
|
version: '0.0.0',
|
|
21
19
|
status: 'IN_CREATION',
|
|
@@ -40,15 +38,6 @@ class TestIntegrationRepository {
|
|
|
40
38
|
return results;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
// Answers the way a database does for the clauses a use case hands it.
|
|
44
|
-
async findIntegrationsByOwnership(clauses) {
|
|
45
|
-
const results = Array.from(this.store.values()).filter((r) =>
|
|
46
|
-
matchesAnyClause(r, clauses)
|
|
47
|
-
);
|
|
48
|
-
this.operationHistory.push({ operation: 'findByOwnership', clauses, count: results.length });
|
|
49
|
-
return results;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
41
|
async findIntegrations(filter = {}) {
|
|
53
42
|
const { type, status } = filter;
|
|
54
43
|
const results = Array.from(this.store.values()).filter((r) => {
|
|
@@ -27,19 +27,13 @@ class CreateIntegration {
|
|
|
27
27
|
* and entity set, instead of creating a duplicate.
|
|
28
28
|
* @async
|
|
29
29
|
* @param {string[]} entities - Array of entity IDs to associate with the integration.
|
|
30
|
-
* @param {string} userId - ID of the
|
|
31
|
-
* the person or the organization's user, as ResolveIntegrationOwnership decided.
|
|
30
|
+
* @param {string} userId - ID of the user creating the integration.
|
|
32
31
|
* @param {Object} config - Configuration object for the integration.
|
|
33
32
|
* @param {string} config.type - Type of integration to create.
|
|
34
|
-
* @param {{organizationId?: (string|null), adoptUnscoped?: boolean}} [tenancy]
|
|
35
|
-
* the organization it is created in (§3.6). A duplicate is only reused in
|
|
36
|
-
* the same organization, so one person's integration in organization A is
|
|
37
|
-
* not handed back as the one they asked for in B. `adoptUnscoped` also
|
|
38
|
-
* accepts a duplicate written before scoping (no organization).
|
|
39
33
|
* @returns {Promise<Object>} The created or reused integration DTO.
|
|
40
34
|
* @throws {Error} When integration class is not found for the specified type.
|
|
41
35
|
*/
|
|
42
|
-
async execute(entities, userId, config
|
|
36
|
+
async execute(entities, userId, config) {
|
|
43
37
|
console.log(
|
|
44
38
|
`[Frigg] Creating ${config?.type} integration for user ${userId} with entities [${entities}]`
|
|
45
39
|
);
|
|
@@ -47,8 +41,7 @@ class CreateIntegration {
|
|
|
47
41
|
const existing = await this._findDuplicate(
|
|
48
42
|
userId,
|
|
49
43
|
config?.type,
|
|
50
|
-
entities
|
|
51
|
-
tenancy
|
|
44
|
+
entities
|
|
52
45
|
);
|
|
53
46
|
if (existing) {
|
|
54
47
|
console.log(
|
|
@@ -57,18 +50,11 @@ class CreateIntegration {
|
|
|
57
50
|
return this._reuseExisting(existing, config);
|
|
58
51
|
}
|
|
59
52
|
|
|
60
|
-
// Everything that can be refused is refused before a row is written: an
|
|
61
|
-
// unknown type, or an entity the owner cannot load, would otherwise
|
|
62
|
-
// leave an IN_CREATION integration behind that nothing moves on.
|
|
63
|
-
const integrationClass = this._requireClass(config?.type);
|
|
64
|
-
const modules = await this._loadModules(entities, userId);
|
|
65
|
-
|
|
66
53
|
const integrationRecord =
|
|
67
54
|
await this.integrationRepository.createIntegration(
|
|
68
55
|
entities,
|
|
69
56
|
userId,
|
|
70
|
-
config
|
|
71
|
-
{ organizationId: tenancy.organizationId ?? null }
|
|
57
|
+
config
|
|
72
58
|
);
|
|
73
59
|
console.log(
|
|
74
60
|
`[Frigg] Created integration ${integrationRecord.id} for user ${userId}`
|
|
@@ -79,8 +65,7 @@ class CreateIntegration {
|
|
|
79
65
|
const survivor = await this._findDuplicate(
|
|
80
66
|
userId,
|
|
81
67
|
config?.type,
|
|
82
|
-
entities
|
|
83
|
-
tenancy
|
|
68
|
+
entities
|
|
84
69
|
);
|
|
85
70
|
if (survivor && String(survivor.id) !== String(integrationRecord.id)) {
|
|
86
71
|
console.log(
|
|
@@ -93,8 +78,7 @@ class CreateIntegration {
|
|
|
93
78
|
}
|
|
94
79
|
|
|
95
80
|
const integrationInstance = await this._buildInstance(
|
|
96
|
-
integrationRecord
|
|
97
|
-
{ integrationClass, modules }
|
|
81
|
+
integrationRecord
|
|
98
82
|
);
|
|
99
83
|
console.log(
|
|
100
84
|
`[Frigg] Sending ON_CREATE for integration ${integrationRecord.id}`
|
|
@@ -151,7 +135,7 @@ class CreateIntegration {
|
|
|
151
135
|
return mapIntegrationClassToIntegrationDTO(integrationInstance);
|
|
152
136
|
}
|
|
153
137
|
|
|
154
|
-
async _findDuplicate(userId, type, entityIds
|
|
138
|
+
async _findDuplicate(userId, type, entityIds) {
|
|
155
139
|
const candidates =
|
|
156
140
|
await this.integrationRepository.findIntegrationsByUserId(userId);
|
|
157
141
|
const target = [...(entityIds ?? [])].map(String).sort();
|
|
@@ -166,7 +150,6 @@ class CreateIntegration {
|
|
|
166
150
|
// matching how teardown failures are already handled here.
|
|
167
151
|
if (integration.status === 'IN_DELETION') return false;
|
|
168
152
|
if (integration.config?.type !== type) return false;
|
|
169
|
-
if (!this._sameOrganization(integration, tenancy)) return false;
|
|
170
153
|
const current = [...(integration.entitiesIds ?? [])]
|
|
171
154
|
.map(String)
|
|
172
155
|
.sort();
|
|
@@ -179,15 +162,6 @@ class CreateIntegration {
|
|
|
179
162
|
return matches.length ? this._pickOldest(matches) : null;
|
|
180
163
|
}
|
|
181
164
|
|
|
182
|
-
/** Same organization, or a row from before scoping that belongs here. */
|
|
183
|
-
_sameOrganization(integration, tenancy) {
|
|
184
|
-
const wanted = tenancy.organizationId ?? null;
|
|
185
|
-
const has = integration.organizationId ?? null;
|
|
186
|
-
if (wanted === null && has === null) return true;
|
|
187
|
-
if (wanted !== null && has !== null) return String(wanted) === String(has);
|
|
188
|
-
return has === null && tenancy.adoptUnscoped === true;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
165
|
_pickOldest(records) {
|
|
192
166
|
return [...records].sort((a, b) => {
|
|
193
167
|
const diff =
|
|
@@ -199,36 +173,27 @@ class CreateIntegration {
|
|
|
199
173
|
})[0];
|
|
200
174
|
}
|
|
201
175
|
|
|
202
|
-
|
|
176
|
+
async _buildInstance(integrationRecord) {
|
|
203
177
|
const integrationClass = this.integrationClasses.find(
|
|
204
|
-
(integrationClass) =>
|
|
178
|
+
(integrationClass) =>
|
|
179
|
+
integrationClass.Definition.name ===
|
|
180
|
+
integrationRecord.config.type
|
|
205
181
|
);
|
|
182
|
+
|
|
206
183
|
if (!integrationClass) {
|
|
207
|
-
throw new Error(
|
|
184
|
+
throw new Error(
|
|
185
|
+
`No integration class found for type: ${integrationRecord.config.type}`
|
|
186
|
+
);
|
|
208
187
|
}
|
|
209
|
-
return integrationClass;
|
|
210
|
-
}
|
|
211
188
|
|
|
212
|
-
async _loadModules(entityIds, userId) {
|
|
213
189
|
const modules = [];
|
|
214
|
-
for (const entityId of
|
|
215
|
-
|
|
216
|
-
|
|
190
|
+
for (const entityId of integrationRecord.entitiesIds) {
|
|
191
|
+
const moduleInstance = await this.moduleFactory.getModuleInstance(
|
|
192
|
+
entityId,
|
|
193
|
+
integrationRecord.userId
|
|
217
194
|
);
|
|
195
|
+
modules.push(moduleInstance);
|
|
218
196
|
}
|
|
219
|
-
return modules;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async _buildInstance(integrationRecord, preloaded) {
|
|
223
|
-
const integrationClass =
|
|
224
|
-
preloaded?.integrationClass ??
|
|
225
|
-
this._requireClass(integrationRecord.config.type);
|
|
226
|
-
const modules =
|
|
227
|
-
preloaded?.modules ??
|
|
228
|
-
(await this._loadModules(
|
|
229
|
-
integrationRecord.entitiesIds,
|
|
230
|
-
integrationRecord.userId
|
|
231
|
-
));
|
|
232
197
|
|
|
233
198
|
const integrationInstance = new integrationClass({
|
|
234
199
|
id: integrationRecord.id,
|