@friggframework/core 2.0.0--canary.704.79d9a63.0 → 2.0.0--canary.686.e94de4d.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 +28 -40
- package/README.md +105 -0
- package/application/commands/entity-commands.js +60 -18
- package/application/commands/integration-commands.js +20 -73
- package/application/commands/scheduler-commands.js +11 -7
- package/application/commands/user-commands.js +17 -5
- package/application/index.js +6 -2
- package/core/Worker.js +31 -7
- package/core/create-handler.js +9 -0
- package/credential/repositories/credential-repository-documentdb.js +34 -0
- package/credential/repositories/credential-repository-interface.js +15 -0
- package/credential/repositories/credential-repository-mongo.js +23 -0
- package/credential/repositories/credential-repository-postgres.js +28 -0
- package/credential/use-cases/credential-operations-for-user.js +202 -0
- package/credential/use-cases/reauthorize-credential-for-user.js +73 -0
- package/database/config.js +48 -24
- package/database/documentdb-encryption-service.js +9 -22
- package/database/documentdb-utils.js +2 -0
- package/database/encryption/README.md +76 -14
- package/database/encryption/documentdb-encryption-service.md +78 -1
- package/database/encryption/encryption-config.js +219 -0
- package/database/encryption/encryption-schema-registry.js +93 -12
- package/database/encryption/integration-mapping-encryption.js +1 -1
- package/database/organization-where.js +18 -0
- package/database/prisma.js +15 -38
- package/database/unique-violation.js +25 -0
- package/database/use-cases/check-encryption-health-use-case.js +43 -52
- package/errors/index.js +0 -4
- package/extensions/bootstrap-runner.js +43 -0
- package/extensions/extension-loader.js +187 -0
- package/extensions/index.js +41 -0
- package/extensions/initialize-app.js +123 -0
- package/extensions/route-mounter.js +103 -0
- package/extensions/schema-composer.js +188 -0
- package/generated/prisma-mongodb/edge.js +43 -4
- package/generated/prisma-mongodb/index-browser.js +40 -1
- package/generated/prisma-mongodb/index.d.ts +4761 -133
- package/generated/prisma-mongodb/index.js +43 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +92 -0
- package/generated/prisma-mongodb/wasm.js +43 -4
- package/generated/prisma-postgresql/edge.js +43 -4
- package/generated/prisma-postgresql/index-browser.js +40 -1
- package/generated/prisma-postgresql/index.d.ts +4961 -28
- package/generated/prisma-postgresql/index.js +43 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +87 -0
- package/generated/prisma-postgresql/wasm.js +43 -4
- package/handlers/WEBHOOKS.md +0 -29
- package/handlers/app-definition-loader.js +121 -19
- package/handlers/app-handler-helpers.js +25 -3
- package/handlers/backend-utils.js +31 -25
- package/handlers/middleware/admin-auth.js +14 -1
- package/handlers/routers/auth.js +46 -10
- package/handlers/routers/health.js +13 -4
- package/handlers/routers/integration-defined-routers.js +144 -92
- package/handlers/routers/integration-webhook-routers.js +102 -64
- package/handlers/workers/integration-defined-workers.js +5 -2
- package/index.js +65 -3
- package/infrastructure/scheduler/index.js +19 -6
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
- package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
- package/integrations/index.js +1 -0
- package/integrations/integration-base.js +0 -29
- package/integrations/integration-router.js +131 -96
- 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-not-found.js +14 -0
- package/integrations/repositories/integration-repository-documentdb.js +35 -35
- package/integrations/repositories/integration-repository-interface.js +17 -31
- package/integrations/repositories/integration-repository-mongo.js +60 -129
- package/integrations/repositories/integration-repository-postgres.js +62 -134
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/schedule-config.js +80 -0
- package/integrations/tests/doubles/test-integration-repository.js +12 -1
- package/integrations/use-cases/create-integration.js +72 -75
- package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
- package/integrations/use-cases/get-integrations-for-user.js +80 -40
- package/integrations/use-cases/index.js +7 -1
- package/integrations/use-cases/integration-operations-for-user.js +256 -0
- package/integrations/use-cases/resolve-integration-ownership.js +177 -0
- package/integrations/utils/map-integration-dto.js +43 -14
- package/management-api/create-management-api-router.js +49 -0
- package/management-api/error-response.js +116 -0
- package/management-api/management-api-config.js +151 -0
- package/management-api/meta-router.js +76 -0
- package/management-api/openapi/build-openapi.js +144 -0
- package/management-api/openapi/components.js +408 -0
- package/management-api/openapi/v1-operations.js +83 -0
- package/management-api/openapi/v2-operations.js +264 -0
- package/management-api/route-registry.js +411 -0
- package/management-api/test-support/http-client.js +39 -0
- package/management-api/use-cases/get-management-api-meta.js +57 -0
- package/management-api/v1-deprecation.js +120 -0
- package/management-api/v2/create-v2-router.js +85 -0
- package/management-api/v2/dependencies.js +281 -0
- package/management-api/v2/dto.js +143 -0
- package/management-api/v2/handlers/authorization.js +94 -0
- package/management-api/v2/handlers/credentials.js +33 -0
- package/management-api/v2/handlers/entities.js +77 -0
- package/management-api/v2/handlers/integrations.js +128 -0
- package/management-api/v2/handlers/proxy.js +34 -0
- package/management-api/v2/request.js +85 -0
- package/management-api/v2/test-support/fake-dependencies.js +103 -0
- package/modules/domain/authorization-session.js +144 -0
- package/modules/entity-scope.js +15 -0
- package/modules/entity-type-registry.js +311 -0
- package/modules/index.js +14 -0
- package/modules/module-factory.js +12 -0
- package/modules/module.js +53 -2
- package/modules/proxy/proxy-errors.js +20 -0
- package/modules/proxy/proxy-path.js +179 -0
- package/modules/proxy/proxy-policy.js +248 -0
- package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
- package/modules/repositories/authorization-session-repository-factory.js +31 -0
- package/modules/repositories/authorization-session-repository-interface.js +47 -0
- package/modules/repositories/authorization-session-repository-prisma.js +87 -0
- package/modules/repositories/entity-not-found.js +18 -0
- package/modules/repositories/module-repository-documentdb.js +51 -6
- package/modules/repositories/module-repository-interface.js +19 -2
- package/modules/repositories/module-repository-mongo.js +67 -74
- package/modules/repositories/module-repository-postgres.js +66 -74
- package/modules/repositories/module-repository.js +2 -1
- package/modules/requester/proxy-request.js +244 -0
- package/modules/requester/requester.js +27 -2
- package/modules/scope-config.js +192 -0
- package/modules/scope-policy.js +112 -0
- package/modules/scope-resolution.js +288 -0
- package/modules/scope-visibility.js +247 -0
- package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
- package/modules/tests/doubles/ownership-matching.js +18 -0
- package/modules/use-cases/authorize-entity-for-user.js +61 -0
- package/modules/use-cases/create-entity-from-credential.js +113 -0
- package/modules/use-cases/entity-operations-for-user.js +162 -0
- package/modules/use-cases/entity-types.js +139 -0
- package/modules/use-cases/execute-entity-proxy-request.js +286 -0
- package/modules/use-cases/get-authorization-requirements.js +100 -0
- package/modules/use-cases/get-entities-for-user.js +26 -13
- package/modules/use-cases/get-entity-module-for-user.js +113 -0
- package/modules/use-cases/get-entity-options-by-id.js +10 -4
- package/modules/use-cases/get-entity-options-by-type.js +1 -1
- package/modules/use-cases/get-module-instance-from-type.js +1 -1
- package/modules/use-cases/get-module.js +10 -4
- package/modules/use-cases/manage-access.js +21 -0
- package/modules/use-cases/multi-step-authorization.js +329 -0
- package/modules/use-cases/process-authorization-callback.js +53 -19
- package/modules/use-cases/refresh-entity-options.js +10 -4
- package/modules/use-cases/test-module-auth.js +10 -4
- package/modules/utils/map-module-dto.js +3 -5
- package/package.json +6 -5
- package/prisma-mongodb/schema.prisma +92 -0
- package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
- package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
- package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
- package/prisma-postgresql/schema.prisma +87 -0
- package/providers/index.js +23 -0
- package/providers/resolve-provider.js +93 -0
- package/providers/runtime-detection.js +124 -0
- package/queues/index.js +25 -0
- package/queues/providers/index.js +11 -0
- package/queues/providers/netlify-background-provider.js +150 -0
- package/queues/providers/qstash-queue-provider.js +219 -0
- package/queues/providers/sqs-queue-provider.js +70 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-message-auth.js +193 -0
- package/queues/queue-provider-factory.js +101 -0
- package/queues/queue-provider.js +65 -0
- package/queues/sqs-queue-client.js +81 -0
- package/types/index.d.ts +2 -0
- package/types/modules/index.d.ts +197 -0
- package/types/user/index.d.ts +134 -0
- package/user/adopter-jwt-config.js +177 -0
- package/user/adopter-jwt-errors.js +59 -0
- package/user/adopter-token-verifier.js +122 -0
- package/user/app-user-helpers.js +97 -0
- package/user/create-user-authentication.js +64 -0
- package/user/membership.js +51 -0
- package/user/repositories/organization-membership-record.js +19 -0
- package/user/repositories/organization-membership-repository-documentdb.js +106 -0
- package/user/repositories/organization-membership-repository-factory.js +42 -0
- package/user/repositories/organization-membership-repository-interface.js +57 -0
- package/user/repositories/organization-membership-repository-mongo.js +13 -0
- package/user/repositories/organization-membership-repository-postgres.js +29 -0
- package/user/repositories/organization-membership-repository-prisma.js +81 -0
- package/user/repositories/user-repository-documentdb.js +27 -2
- package/user/repositories/user-repository-interface.js +18 -0
- package/user/repositories/user-repository-mongo.js +24 -0
- package/user/repositories/user-repository-postgres.js +18 -0
- package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
- package/user/tests/doubles/test-user-repository.js +1 -1
- package/user/use-cases/authenticate-user.js +7 -1
- package/user/use-cases/ensure-organization-membership.js +92 -0
- package/user/use-cases/find-individual-users-by-organization.js +54 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
- package/user/use-cases/get-user-from-bearer-token.js +2 -7
- package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
- package/user/user.js +64 -1
- 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
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error `findIntegrationById` throws for an id with no row, and the one
|
|
3
|
+
* predicate that recognises it. The adapters build the error here and the
|
|
4
|
+
* use cases match it here, so the message is written once and a missing
|
|
5
|
+
* integration can be told from any other failure without widening a regex.
|
|
6
|
+
*/
|
|
7
|
+
const MESSAGE = /^Integration with id .+ not found$/;
|
|
8
|
+
|
|
9
|
+
const integrationNotFoundError = (id) =>
|
|
10
|
+
new Error(`Integration with id ${id} not found`);
|
|
11
|
+
|
|
12
|
+
const isIntegrationNotFound = (error) => MESSAGE.test(error?.message || '');
|
|
13
|
+
|
|
14
|
+
module.exports = { integrationNotFoundError, isIntegrationNotFound };
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
IntegrationRepositoryInterface,
|
|
15
15
|
} = require('./integration-repository-interface');
|
|
16
16
|
const { validateConfigPatch } = require('./config-patch-shared');
|
|
17
|
+
const { integrationNotFoundError } = require('./integration-not-found');
|
|
17
18
|
|
|
18
19
|
class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
|
|
19
20
|
constructor() {
|
|
@@ -28,6 +29,28 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
|
|
|
28
29
|
return records.map((doc) => this._mapIntegration(doc));
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Find integrations matching any of the owner/organization pairs, in one
|
|
34
|
+
* query. `organizationId: null` matches rows with no organization (a raw
|
|
35
|
+
* MongoDB `null` also matches a missing field). The caller (a use case)
|
|
36
|
+
* decides which pairs; this only runs them.
|
|
37
|
+
*
|
|
38
|
+
* @param {Array<{userId: string, organizationId: (string|null)}>} clauses
|
|
39
|
+
* @returns {Promise<Array>}
|
|
40
|
+
*/
|
|
41
|
+
async findIntegrationsByOwnership(clauses) {
|
|
42
|
+
if (!Array.isArray(clauses) || clauses.length === 0) return [];
|
|
43
|
+
// Drained: a list can hold more than the first cursor batch (~101).
|
|
44
|
+
const records = await findManyDrained(this.prisma, 'Integration', {
|
|
45
|
+
$or: clauses.map(({ userId, organizationId }) => ({
|
|
46
|
+
userId: toObjectId(userId),
|
|
47
|
+
organizationId:
|
|
48
|
+
organizationId === null ? null : toObjectId(organizationId),
|
|
49
|
+
})),
|
|
50
|
+
});
|
|
51
|
+
return records.map((doc) => this._mapIntegration(doc));
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
async findIntegrations({ type, status } = {}) {
|
|
32
55
|
const filter = {};
|
|
33
56
|
if (type) {
|
|
@@ -68,11 +91,11 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
|
|
|
68
91
|
async findIntegrationById(id) {
|
|
69
92
|
const objectId = toObjectId(id);
|
|
70
93
|
if (!objectId) {
|
|
71
|
-
throw
|
|
94
|
+
throw integrationNotFoundError(id);
|
|
72
95
|
}
|
|
73
96
|
const doc = await findOne(this.prisma, 'Integration', { _id: objectId });
|
|
74
97
|
if (!doc) {
|
|
75
|
-
throw
|
|
98
|
+
throw integrationNotFoundError(id);
|
|
76
99
|
}
|
|
77
100
|
return this._mapIntegration(doc);
|
|
78
101
|
}
|
|
@@ -132,10 +155,18 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
|
|
|
132
155
|
return true;
|
|
133
156
|
}
|
|
134
157
|
|
|
135
|
-
|
|
158
|
+
/**
|
|
159
|
+
* @param {Array<string>} entities - entity ids
|
|
160
|
+
* @param {string} userId - owner (the person, or the organization's user)
|
|
161
|
+
* @param {Object} config - Integration configuration
|
|
162
|
+
* @param {{organizationId?: (string|null)}} [tenancy] - the organization the
|
|
163
|
+
* integration is created in (ADR-056 §3.6)
|
|
164
|
+
*/
|
|
165
|
+
async createIntegration(entities, userId, config, tenancy = {}) {
|
|
136
166
|
const now = new Date();
|
|
137
167
|
const document = {
|
|
138
168
|
userId: toObjectId(userId) || null,
|
|
169
|
+
organizationId: toObjectId(tenancy.organizationId) || null,
|
|
139
170
|
config,
|
|
140
171
|
version: '0.0.0',
|
|
141
172
|
status: 'IN_CREATION',
|
|
@@ -164,38 +195,6 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
|
|
|
164
195
|
return this._mapIntegration(created);
|
|
165
196
|
}
|
|
166
197
|
|
|
167
|
-
async addEntitiesToIntegration(integrationId, entityIds) {
|
|
168
|
-
const objectId = toObjectId(integrationId);
|
|
169
|
-
if (!objectId) return null;
|
|
170
|
-
const result = await updateOne(
|
|
171
|
-
this.prisma,
|
|
172
|
-
'Integration',
|
|
173
|
-
{ _id: objectId },
|
|
174
|
-
{
|
|
175
|
-
$addToSet: { entityIds: { $each: toObjectIdArray(entityIds) } },
|
|
176
|
-
$set: { updatedAt: { $date: new Date().toISOString() } },
|
|
177
|
-
}
|
|
178
|
-
);
|
|
179
|
-
if (!result?.n) return null;
|
|
180
|
-
return this.findIntegrationById(integrationId);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async removeEntitiesFromIntegration(integrationId, entityIds) {
|
|
184
|
-
const objectId = toObjectId(integrationId);
|
|
185
|
-
if (!objectId) return null;
|
|
186
|
-
const result = await updateOne(
|
|
187
|
-
this.prisma,
|
|
188
|
-
'Integration',
|
|
189
|
-
{ _id: objectId },
|
|
190
|
-
{
|
|
191
|
-
$pullAll: { entityIds: toObjectIdArray(entityIds) },
|
|
192
|
-
$set: { updatedAt: { $date: new Date().toISOString() } },
|
|
193
|
-
}
|
|
194
|
-
);
|
|
195
|
-
if (!result?.n) return null;
|
|
196
|
-
return this.findIntegrationById(integrationId);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
198
|
async findIntegrationByUserId(userId) {
|
|
200
199
|
const objectId = toObjectId(userId);
|
|
201
200
|
if (!objectId) return null;
|
|
@@ -330,6 +329,7 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
|
|
|
330
329
|
id: fromObjectId(doc?._id),
|
|
331
330
|
entitiesIds: (doc?.entityIds || []).map((value) => fromObjectId(value)),
|
|
332
331
|
userId: fromObjectId(doc?.userId),
|
|
332
|
+
organizationId: fromObjectId(doc?.organizationId) ?? null,
|
|
333
333
|
config: doc?.config ?? null,
|
|
334
334
|
version: doc?.version ?? null,
|
|
335
335
|
status: doc?.status ?? null,
|
|
@@ -10,6 +10,20 @@
|
|
|
10
10
|
* @abstract
|
|
11
11
|
*/
|
|
12
12
|
class IntegrationRepositoryInterface {
|
|
13
|
+
/**
|
|
14
|
+
* Find integrations matching any of the owner/organization pairs, in one
|
|
15
|
+
* query (ADR-056 §3.6). `organizationId: null` matches rows with no
|
|
16
|
+
* organization. Which pairs is decided by the caller
|
|
17
|
+
* (modules/entity-scope.js), not here.
|
|
18
|
+
*
|
|
19
|
+
* @param {Array<{userId: string|number, organizationId: (string|number|null)}>} clauses
|
|
20
|
+
* @returns {Promise<Array>} Array of integration objects
|
|
21
|
+
* @abstract
|
|
22
|
+
*/
|
|
23
|
+
async findIntegrationsByOwnership(clauses) {
|
|
24
|
+
throw new Error('Method findIntegrationsByOwnership must be implemented by subclass');
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
/**
|
|
14
28
|
* Find all integrations for a user
|
|
15
29
|
*
|
|
@@ -123,10 +137,12 @@ class IntegrationRepositoryInterface {
|
|
|
123
137
|
* @param {Array<string|number>} entities - Array of entity IDs
|
|
124
138
|
* @param {string|number} userId - User ID
|
|
125
139
|
* @param {Object} config - Integration configuration
|
|
140
|
+
* @param {{organizationId?: (string|number|null)}} [tenancy] - the organization the
|
|
141
|
+
* integration is created in (ADR-056 §3.6)
|
|
126
142
|
* @returns {Promise<Object>} Created integration object
|
|
127
143
|
* @abstract
|
|
128
144
|
*/
|
|
129
|
-
async createIntegration(entities, userId, config) {
|
|
145
|
+
async createIntegration(entities, userId, config, tenancy) {
|
|
130
146
|
throw new Error('Method createIntegration must be implemented by subclass');
|
|
131
147
|
}
|
|
132
148
|
|
|
@@ -184,36 +200,6 @@ class IntegrationRepositoryInterface {
|
|
|
184
200
|
'Method findIntegrationsByEntityId must be implemented by subclass'
|
|
185
201
|
);
|
|
186
202
|
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Attach entities to an existing integration. Entities that are already
|
|
190
|
-
* attached are left as they are.
|
|
191
|
-
*
|
|
192
|
-
* @param {string|number} integrationId - Integration ID
|
|
193
|
-
* @param {Array<string|number>} entityIds - Entity IDs to attach
|
|
194
|
-
* @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
|
|
195
|
-
* @abstract
|
|
196
|
-
*/
|
|
197
|
-
async addEntitiesToIntegration(integrationId, entityIds) {
|
|
198
|
-
throw new Error(
|
|
199
|
-
'Method addEntitiesToIntegration must be implemented by subclass'
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Detach entities from an integration. Entities that are not attached
|
|
205
|
-
* are ignored; the entities themselves are not deleted.
|
|
206
|
-
*
|
|
207
|
-
* @param {string|number} integrationId - Integration ID
|
|
208
|
-
* @param {Array<string|number>} entityIds - Entity IDs to detach
|
|
209
|
-
* @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
|
|
210
|
-
* @abstract
|
|
211
|
-
*/
|
|
212
|
-
async removeEntitiesFromIntegration(integrationId, entityIds) {
|
|
213
|
-
throw new Error(
|
|
214
|
-
'Method removeEntitiesFromIntegration must be implemented by subclass'
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
203
|
}
|
|
218
204
|
|
|
219
205
|
module.exports = { IntegrationRepositoryInterface };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const { organizationWhere } = require('../../database/organization-where');
|
|
2
3
|
const {
|
|
3
4
|
IntegrationRepositoryInterface,
|
|
4
5
|
} = require('./integration-repository-interface');
|
|
5
6
|
const { validateConfigPatch } = require('./config-patch-shared');
|
|
7
|
+
const { integrationNotFoundError } = require('./integration-not-found');
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* MongoDB Integration Repository Adapter
|
|
@@ -25,6 +27,25 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
25
27
|
this.prisma = prisma;
|
|
26
28
|
}
|
|
27
29
|
|
|
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
|
+
|
|
28
49
|
/**
|
|
29
50
|
* Find all integrations for a user
|
|
30
51
|
* Replaces: IntegrationModel.find({ user: userId }).populate('entities')
|
|
@@ -41,16 +62,32 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
41
62
|
});
|
|
42
63
|
|
|
43
64
|
// Map to domain objects (maintains same API)
|
|
44
|
-
return integrations.map((integration) => (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
return integrations.map((integration) => (this._mapIntegration(integration)));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Find integrations matching any of the owner/organization pairs, in one
|
|
70
|
+
* query. `organizationId: null` matches rows with no organization. The
|
|
71
|
+
* caller (a use case) decides which pairs; this only runs them.
|
|
72
|
+
*
|
|
73
|
+
* @param {Array<{userId: string, organizationId: (string|null)}>} clauses
|
|
74
|
+
* @returns {Promise<Array>} Array of integration objects
|
|
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)));
|
|
54
91
|
}
|
|
55
92
|
|
|
56
93
|
/**
|
|
@@ -77,16 +114,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
77
114
|
},
|
|
78
115
|
});
|
|
79
116
|
|
|
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
|
-
}));
|
|
117
|
+
return integrations.map((integration) => (this._mapIntegration(integration)));
|
|
90
118
|
}
|
|
91
119
|
|
|
92
120
|
/**
|
|
@@ -165,15 +193,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
165
193
|
throw new Error(`Integration with name ${name} not found`);
|
|
166
194
|
}
|
|
167
195
|
|
|
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
|
-
};
|
|
196
|
+
return this._mapIntegration(integration);
|
|
177
197
|
}
|
|
178
198
|
|
|
179
199
|
/**
|
|
@@ -192,18 +212,10 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
192
212
|
});
|
|
193
213
|
|
|
194
214
|
if (!integration) {
|
|
195
|
-
throw
|
|
215
|
+
throw integrationNotFoundError(id);
|
|
196
216
|
}
|
|
197
217
|
|
|
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
|
-
};
|
|
218
|
+
return this._mapIntegration(integration);
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
/**
|
|
@@ -290,15 +302,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
290
302
|
},
|
|
291
303
|
});
|
|
292
304
|
|
|
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
|
-
}));
|
|
305
|
+
return integrations.map((integration) => (this._mapIntegration(integration)));
|
|
302
306
|
}
|
|
303
307
|
|
|
304
308
|
/**
|
|
@@ -310,13 +314,16 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
310
314
|
* @param {Array<string>} entities - Array of entity IDs (MongoDB ObjectIds)
|
|
311
315
|
* @param {string} userId - User ID (MongoDB ObjectId)
|
|
312
316
|
* @param {Object} config - Integration configuration
|
|
317
|
+
* @param {{organizationId?: (string|null)}} [tenancy] - the organization the
|
|
318
|
+
* integration is created in (ADR-056 §3.6)
|
|
313
319
|
* @returns {Promise<Object>} Created integration object
|
|
314
320
|
*/
|
|
315
|
-
async createIntegration(entities, userId, config) {
|
|
321
|
+
async createIntegration(entities, userId, config, tenancy = {}) {
|
|
316
322
|
const data = {
|
|
317
323
|
config,
|
|
318
324
|
version: '0.0.0',
|
|
319
325
|
userId: userId,
|
|
326
|
+
organizationId: tenancy.organizationId ?? null,
|
|
320
327
|
entityIds: entities,
|
|
321
328
|
};
|
|
322
329
|
|
|
@@ -327,67 +334,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
327
334
|
},
|
|
328
335
|
});
|
|
329
336
|
|
|
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
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Attach entities to an existing integration. Entities already attached
|
|
343
|
-
* are left as they are.
|
|
344
|
-
*
|
|
345
|
-
* @param {string} integrationId - Integration ID
|
|
346
|
-
* @param {Array<string>} entityIds - Entity IDs to attach
|
|
347
|
-
* @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
|
|
348
|
-
*/
|
|
349
|
-
async addEntitiesToIntegration(integrationId, entityIds) {
|
|
350
|
-
|
|
351
|
-
try {
|
|
352
|
-
await this.prisma.integration.update({
|
|
353
|
-
where: { id: integrationId },
|
|
354
|
-
data: {
|
|
355
|
-
entities: {
|
|
356
|
-
connect: entityIds.map((id) => ({ id })),
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
});
|
|
360
|
-
} catch (error) {
|
|
361
|
-
if (error.code === 'P2025') return null;
|
|
362
|
-
throw error;
|
|
363
|
-
}
|
|
364
|
-
return this.findIntegrationById(integrationId);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Detach entities from an integration. Entities that are not attached
|
|
369
|
-
* are ignored. The entities themselves are not deleted.
|
|
370
|
-
*
|
|
371
|
-
* @param {string} integrationId - Integration ID
|
|
372
|
-
* @param {Array<string>} entityIds - Entity IDs to detach
|
|
373
|
-
* @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
|
|
374
|
-
*/
|
|
375
|
-
async removeEntitiesFromIntegration(integrationId, entityIds) {
|
|
376
|
-
|
|
377
|
-
try {
|
|
378
|
-
await this.prisma.integration.update({
|
|
379
|
-
where: { id: integrationId },
|
|
380
|
-
data: {
|
|
381
|
-
entities: {
|
|
382
|
-
disconnect: entityIds.map((id) => ({ id })),
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
} catch (error) {
|
|
387
|
-
if (error.code === 'P2025') return null;
|
|
388
|
-
throw error;
|
|
389
|
-
}
|
|
390
|
-
return this.findIntegrationById(integrationId);
|
|
337
|
+
return this._mapIntegration(integration);
|
|
391
338
|
}
|
|
392
339
|
|
|
393
340
|
/**
|
|
@@ -409,15 +356,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
409
356
|
return null;
|
|
410
357
|
}
|
|
411
358
|
|
|
412
|
-
return
|
|
413
|
-
id: integration.id,
|
|
414
|
-
entitiesIds: integration.entities.map((e) => e.id),
|
|
415
|
-
userId: integration.userId,
|
|
416
|
-
config: integration.config,
|
|
417
|
-
version: integration.version,
|
|
418
|
-
status: integration.status,
|
|
419
|
-
messages: integration.messages,
|
|
420
|
-
};
|
|
359
|
+
return this._mapIntegration(integration);
|
|
421
360
|
}
|
|
422
361
|
|
|
423
362
|
/**
|
|
@@ -441,15 +380,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
441
380
|
},
|
|
442
381
|
});
|
|
443
382
|
|
|
444
|
-
return
|
|
445
|
-
id: integration.id,
|
|
446
|
-
entitiesIds: integration.entities.map((e) => e.id),
|
|
447
|
-
userId: integration.userId,
|
|
448
|
-
config: integration.config,
|
|
449
|
-
version: integration.version,
|
|
450
|
-
status: integration.status,
|
|
451
|
-
messages: integration.messages,
|
|
452
|
-
};
|
|
383
|
+
return this._mapIntegration(integration);
|
|
453
384
|
}
|
|
454
385
|
|
|
455
386
|
/**
|
|
@@ -471,7 +402,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
|
|
|
471
402
|
for (const [key, value] of Object.entries(patch)) {
|
|
472
403
|
$set[`config.${key}`] = value;
|
|
473
404
|
}
|
|
474
|
-
$set.updatedAt =
|
|
405
|
+
$set.updatedAt = new Date();
|
|
475
406
|
|
|
476
407
|
const result = await this.prisma.$runCommandRaw({
|
|
477
408
|
findAndModify: 'Integration',
|