@friggframework/core 2.0.0--canary.686.df38e8e.0 → 2.0.0--canary.710.9045bbc.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/CHANGELOG.md +0 -16
- package/CLAUDE.md +41 -29
- package/README.md +0 -105
- package/application/commands/README.md +17 -11
- package/application/commands/entity-commands.js +18 -60
- package/application/commands/integration-commands.js +73 -20
- package/application/commands/scheduler-commands.js +7 -11
- package/application/commands/user-commands.js +5 -17
- package/application/index.js +2 -6
- package/core/Worker.js +7 -31
- package/core/create-handler.js +0 -9
- package/credential/repositories/credential-repository-documentdb.js +0 -34
- package/credential/repositories/credential-repository-interface.js +0 -15
- package/credential/repositories/credential-repository-mongo.js +0 -23
- package/credential/repositories/credential-repository-postgres.js +0 -28
- package/database/config.js +24 -48
- package/database/documentdb-encryption-service.js +22 -9
- package/database/documentdb-utils.js +0 -2
- package/database/encryption/README.md +17 -81
- package/database/encryption/documentdb-encryption-service.md +1 -78
- package/database/encryption/encryption-schema-registry.js +46 -123
- package/database/encryption/field-encryption-service.js +5 -69
- package/database/encryption/integration-mapping-encryption.js +1 -1
- package/database/prisma.js +38 -15
- package/database/use-cases/check-encryption-health-use-case.js +52 -43
- package/errors/index.js +4 -0
- package/errors/mapping-already-exists-error.js +14 -0
- package/generated/prisma-mongodb/edge.js +4 -43
- package/generated/prisma-mongodb/index-browser.js +1 -40
- package/generated/prisma-mongodb/index.d.ts +111 -4739
- package/generated/prisma-mongodb/index.js +4 -43
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -92
- package/generated/prisma-mongodb/wasm.js +4 -43
- package/generated/prisma-postgresql/edge.js +4 -43
- package/generated/prisma-postgresql/index-browser.js +1 -40
- package/generated/prisma-postgresql/index.d.ts +33 -4966
- package/generated/prisma-postgresql/index.js +4 -43
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -87
- package/generated/prisma-postgresql/wasm.js +4 -43
- package/handlers/WEBHOOKS.md +29 -0
- package/handlers/app-definition-loader.js +19 -121
- package/handlers/app-handler-helpers.js +3 -25
- package/handlers/backend-utils.js +25 -31
- package/handlers/middleware/admin-auth.js +1 -14
- package/handlers/routers/auth.js +10 -46
- package/handlers/routers/health.js +4 -13
- package/handlers/routers/integration-defined-routers.js +92 -144
- package/handlers/routers/integration-webhook-routers.js +64 -102
- package/handlers/workers/integration-defined-workers.js +2 -5
- package/index.js +3 -65
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +67 -13
- package/infrastructure/scheduler/index.js +6 -19
- package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
- package/integrations/index.js +0 -1
- package/integrations/integration-base.js +29 -0
- package/integrations/integration-router.js +96 -131
- package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
- package/integrations/repositories/integration-mapping-repository.js +29 -0
- package/integrations/repositories/integration-repository-documentdb.js +35 -35
- package/integrations/repositories/integration-repository-interface.js +31 -17
- package/integrations/repositories/integration-repository-mongo.js +129 -60
- package/integrations/repositories/integration-repository-postgres.js +134 -62
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/tests/doubles/test-integration-repository.js +1 -12
- package/integrations/use-cases/add-entities-to-integration.js +92 -0
- package/integrations/use-cases/create-integration.js +75 -72
- package/integrations/use-cases/get-integrations-for-user.js +40 -80
- package/integrations/use-cases/index.js +1 -7
- package/integrations/use-cases/remove-entities-from-integration.js +70 -0
- package/integrations/utils/assert-entity-ids-input.js +42 -0
- package/integrations/utils/map-integration-dto.js +14 -43
- package/jest-setup.js +13 -2
- package/jest-teardown.js +5 -2
- package/jest.config.js +4 -0
- package/jest.db.config.js +23 -0
- package/modules/index.js +0 -14
- package/modules/module-factory.js +0 -12
- package/modules/module.js +2 -53
- package/modules/repositories/module-repository-documentdb.js +6 -51
- package/modules/repositories/module-repository-interface.js +2 -19
- package/modules/repositories/module-repository-mongo.js +74 -67
- package/modules/repositories/module-repository-postgres.js +74 -66
- package/modules/repositories/module-repository.js +1 -2
- package/modules/requester/requester.js +2 -27
- package/modules/use-cases/get-entities-for-user.js +13 -26
- package/modules/use-cases/get-entity-options-by-id.js +4 -10
- 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 +4 -10
- package/modules/use-cases/process-authorization-callback.js +19 -53
- package/modules/use-cases/refresh-entity-options.js +4 -10
- package/modules/use-cases/test-module-auth.js +4 -10
- package/modules/utils/map-module-dto.js +5 -3
- package/modules/utils/to-credential-id.js +14 -0
- package/package.json +7 -6
- package/prisma-mongodb/schema.prisma +0 -92
- package/prisma-postgresql/schema.prisma +0 -87
- package/queues/index.js +0 -25
- package/queues/queuer-util.js +6 -5
- package/types/index.d.ts +0 -2
- 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/tests/doubles/test-user-repository.js +1 -1
- 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-bearer-token.js +7 -2
- package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
- package/user/user.js +1 -64
- package/credential/use-cases/credential-operations-for-user.js +0 -202
- package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
- package/database/encryption/encryption-config.js +0 -219
- package/database/encryption/test-support/fake-ciphertext.js +0 -15
- package/database/organization-where.js +0 -18
- package/database/unique-violation.js +0 -25
- package/extensions/bootstrap-runner.js +0 -43
- package/extensions/extension-loader.js +0 -187
- package/extensions/index.js +0 -41
- package/extensions/initialize-app.js +0 -123
- package/extensions/route-mounter.js +0 -103
- package/extensions/schema-composer.js +0 -188
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -220
- package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
- package/integrations/repositories/integration-not-found.js +0 -14
- package/integrations/schedule-config.js +0 -80
- package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
- package/integrations/use-cases/integration-operations-for-user.js +0 -256
- package/integrations/use-cases/resolve-integration-ownership.js +0 -177
- package/management-api/create-management-api-router.js +0 -49
- package/management-api/error-response.js +0 -116
- package/management-api/management-api-config.js +0 -151
- package/management-api/meta-router.js +0 -76
- package/management-api/openapi/build-openapi.js +0 -144
- package/management-api/openapi/components.js +0 -408
- package/management-api/openapi/v1-operations.js +0 -83
- package/management-api/openapi/v2-operations.js +0 -264
- package/management-api/route-registry.js +0 -411
- package/management-api/test-support/http-client.js +0 -39
- package/management-api/use-cases/get-management-api-meta.js +0 -57
- package/management-api/v1-deprecation.js +0 -120
- package/management-api/v2/create-v2-router.js +0 -85
- package/management-api/v2/dependencies.js +0 -281
- package/management-api/v2/dto.js +0 -143
- package/management-api/v2/handlers/authorization.js +0 -94
- package/management-api/v2/handlers/credentials.js +0 -33
- package/management-api/v2/handlers/entities.js +0 -77
- package/management-api/v2/handlers/integrations.js +0 -128
- package/management-api/v2/handlers/proxy.js +0 -34
- package/management-api/v2/request.js +0 -85
- package/management-api/v2/test-support/fake-dependencies.js +0 -103
- package/modules/domain/authorization-session.js +0 -144
- package/modules/entity-scope.js +0 -15
- package/modules/entity-type-registry.js +0 -311
- package/modules/proxy/proxy-errors.js +0 -20
- package/modules/proxy/proxy-path.js +0 -179
- package/modules/proxy/proxy-policy.js +0 -248
- package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
- package/modules/repositories/authorization-session-repository-factory.js +0 -31
- package/modules/repositories/authorization-session-repository-interface.js +0 -47
- package/modules/repositories/authorization-session-repository-prisma.js +0 -87
- package/modules/repositories/entity-not-found.js +0 -18
- package/modules/requester/proxy-request.js +0 -244
- 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 -247
- package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
- 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/entity-operations-for-user.js +0 -162
- package/modules/use-cases/entity-types.js +0 -139
- package/modules/use-cases/execute-entity-proxy-request.js +0 -286
- package/modules/use-cases/get-authorization-requirements.js +0 -100
- package/modules/use-cases/get-entity-module-for-user.js +0 -113
- package/modules/use-cases/manage-access.js +0 -21
- package/modules/use-cases/multi-step-authorization.js +0 -329
- package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
- 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/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
- package/providers/index.js +0 -23
- package/providers/resolve-provider.js +0 -93
- package/providers/runtime-detection.js +0 -124
- package/queues/providers/index.js +0 -11
- package/queues/providers/netlify-background-provider.js +0 -187
- package/queues/providers/qstash-queue-provider.js +0 -219
- package/queues/providers/sqs-queue-provider.js +0 -70
- package/queues/queue-client-interface.js +0 -55
- package/queues/queue-message-auth.js +0 -193
- package/queues/queue-provider-factory.js +0 -101
- package/queues/queue-provider.js +0 -65
- package/queues/sqs-queue-client.js +0 -81
- package/types/modules/index.d.ts +0 -197
- 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
|
@@ -2,17 +2,9 @@
|
|
|
2
2
|
const {
|
|
3
3
|
mapIntegrationClassToIntegrationDTO,
|
|
4
4
|
} = require('../utils/map-integration-dto');
|
|
5
|
-
const {
|
|
6
|
-
describeRecord,
|
|
7
|
-
visibilityClauses,
|
|
8
|
-
matchesListFilters,
|
|
9
|
-
} = require('../../modules/scope-visibility');
|
|
10
|
-
const { DEFAULT_SCOPE_POLICY } = require('../../modules/scope-policy');
|
|
11
5
|
|
|
12
6
|
/**
|
|
13
|
-
* Use case for retrieving
|
|
14
|
-
* or their active organization owns, created in that organization (ADR-056
|
|
15
|
-
* §3.6), each with `tenancy` ({scope, owner, organizationId, access}).
|
|
7
|
+
* Use case for retrieving all integrations for a specific user.
|
|
16
8
|
* @class GetIntegrationsForUser
|
|
17
9
|
*/
|
|
18
10
|
class GetIntegrationsForUser {
|
|
@@ -23,16 +15,13 @@ class GetIntegrationsForUser {
|
|
|
23
15
|
* @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes.
|
|
24
16
|
* @param {import('../../modules/module-factory').ModuleFactory} params.moduleFactory - Service for module instantiation and management.
|
|
25
17
|
* @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module and entity data operations.
|
|
26
|
-
* @param {Object} [params.scopePolicy] - from createScopePolicy
|
|
27
18
|
*/
|
|
28
19
|
constructor({
|
|
29
20
|
integrationRepository,
|
|
30
21
|
integrationClasses,
|
|
31
22
|
moduleFactory,
|
|
32
23
|
moduleRepository,
|
|
33
|
-
scopePolicy,
|
|
34
24
|
}) {
|
|
35
|
-
this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
|
|
36
25
|
/**
|
|
37
26
|
* @type {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface}
|
|
38
27
|
*/
|
|
@@ -43,86 +32,57 @@ class GetIntegrationsForUser {
|
|
|
43
32
|
}
|
|
44
33
|
|
|
45
34
|
/**
|
|
35
|
+
* Executes the retrieval of all integrations for a user.
|
|
46
36
|
* @async
|
|
47
|
-
* @param {
|
|
48
|
-
* @
|
|
49
|
-
* @returns {Promise<Object[]>} Array of integration DTOs.
|
|
37
|
+
* @param {string} userId - ID of the user whose integrations to retrieve.
|
|
38
|
+
* @returns {Promise<Object[]>} Array of integration DTOs for the specified user.
|
|
50
39
|
*/
|
|
51
|
-
async execute(
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
visibilityClauses(ctx, 'integration')
|
|
55
|
-
);
|
|
40
|
+
async execute(userId) {
|
|
41
|
+
const integrationRecords =
|
|
42
|
+
await this.integrationRepository.findIntegrationsByUserId(userId);
|
|
56
43
|
|
|
57
44
|
const integrations = [];
|
|
58
|
-
for (const record of records) {
|
|
59
|
-
// The query is the filter; the same rule again means a repository
|
|
60
|
-
// that returns too much cannot widen the list.
|
|
61
|
-
const tenancy = describeRecord(record, ctx, 'integration');
|
|
62
|
-
if (!tenancy || !matchesListFilters(tenancy, filters)) continue;
|
|
63
|
-
integrations.push({ ...(await this.toDto(record)), tenancy });
|
|
64
|
-
}
|
|
65
|
-
return integrations;
|
|
66
|
-
}
|
|
67
45
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
const integrationClass = this.integrationClasses.find(
|
|
75
|
-
(integrationClass) =>
|
|
76
|
-
integrationClass.Definition.name ===
|
|
77
|
-
integrationRecord.config.type
|
|
78
|
-
);
|
|
46
|
+
for (const integrationRecord of integrationRecords) {
|
|
47
|
+
const entities = await this.moduleRepository.findEntitiesByIds(
|
|
48
|
+
integrationRecord.entitiesIds
|
|
49
|
+
);
|
|
79
50
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
integrationRecord.userId
|
|
51
|
+
const integrationClass = this.integrationClasses.find(
|
|
52
|
+
(integrationClass) =>
|
|
53
|
+
integrationClass.Definition.name ===
|
|
54
|
+
integrationRecord.config.type
|
|
85
55
|
);
|
|
86
|
-
modules.push(moduleInstance);
|
|
87
|
-
}
|
|
88
56
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
options: integrationClass.getOptionDetails(),
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
57
|
+
const modules = [];
|
|
58
|
+
for (const entity of entities) {
|
|
59
|
+
const moduleInstance =
|
|
60
|
+
await this.moduleFactory.getModuleInstance(
|
|
61
|
+
entity.id,
|
|
62
|
+
integrationRecord.userId
|
|
63
|
+
);
|
|
64
|
+
modules.push(moduleInstance);
|
|
65
|
+
}
|
|
102
66
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
67
|
+
const integrationData = {
|
|
68
|
+
id: integrationRecord.id,
|
|
69
|
+
userId: integrationRecord.userId,
|
|
70
|
+
entities: entities,
|
|
71
|
+
config: integrationRecord.config,
|
|
72
|
+
status: integrationRecord.status,
|
|
73
|
+
version: integrationRecord.version,
|
|
74
|
+
messages: integrationRecord.messages || { errors: [], warnings: [] },
|
|
75
|
+
modules,
|
|
76
|
+
options: integrationClass.getOptionDetails(),
|
|
77
|
+
};
|
|
114
78
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
userId
|
|
119
|
-
);
|
|
120
|
-
const integrations = [];
|
|
121
|
-
for (const record of records) {
|
|
122
|
-
integrations.push(await this.hydrator.toDto(record));
|
|
79
|
+
integrations.push(
|
|
80
|
+
mapIntegrationClassToIntegrationDTO(integrationData)
|
|
81
|
+
);
|
|
123
82
|
}
|
|
83
|
+
|
|
124
84
|
return integrations;
|
|
125
85
|
}
|
|
126
86
|
}
|
|
127
87
|
|
|
128
|
-
module.exports = { GetIntegrationsForUser
|
|
88
|
+
module.exports = { GetIntegrationsForUser };
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
const {
|
|
2
|
-
GetIntegrationsForUser,
|
|
3
|
-
GetIntegrationsByUserId,
|
|
4
|
-
} = require('./get-integrations-for-user');
|
|
1
|
+
const { GetIntegrationsForUser } = require('./get-integrations-for-user');
|
|
5
2
|
const { DeleteIntegrationForUser } = require('./delete-integration-for-user');
|
|
6
3
|
const { CreateIntegration } = require('./create-integration');
|
|
7
4
|
const { GetIntegration } = require('./get-integration');
|
|
@@ -9,11 +6,9 @@ const { CreateProcess } = require('./create-process');
|
|
|
9
6
|
const { UpdateProcessState } = require('./update-process-state');
|
|
10
7
|
const { UpdateProcessMetrics } = require('./update-process-metrics');
|
|
11
8
|
const { GetProcess } = require('./get-process');
|
|
12
|
-
const { DispatchOngoingSyncs } = require('./dispatch-ongoing-syncs');
|
|
13
9
|
|
|
14
10
|
module.exports = {
|
|
15
11
|
GetIntegrationsForUser,
|
|
16
|
-
GetIntegrationsByUserId,
|
|
17
12
|
DeleteIntegrationForUser,
|
|
18
13
|
CreateIntegration,
|
|
19
14
|
GetIntegration,
|
|
@@ -21,5 +16,4 @@ module.exports = {
|
|
|
21
16
|
UpdateProcessState,
|
|
22
17
|
UpdateProcessMetrics,
|
|
23
18
|
GetProcess,
|
|
24
|
-
DispatchOngoingSyncs,
|
|
25
19
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const {
|
|
2
|
+
assertEntityIdsInput,
|
|
3
|
+
integrationNotFound,
|
|
4
|
+
loadIntegration,
|
|
5
|
+
} = require('../utils/assert-entity-ids-input');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detaches entities from an integration. Entities that are not attached are
|
|
9
|
+
* ignored, and detached entities are not deleted. Every module the
|
|
10
|
+
* integration holds must keep at least one entity, so the integration can
|
|
11
|
+
* still hydrate each of its modules.
|
|
12
|
+
* @class RemoveEntitiesFromIntegration
|
|
13
|
+
*/
|
|
14
|
+
class RemoveEntitiesFromIntegration {
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} params
|
|
17
|
+
* @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository
|
|
18
|
+
* @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
|
|
19
|
+
*/
|
|
20
|
+
constructor({ integrationRepository, moduleRepository }) {
|
|
21
|
+
this.integrationRepository = integrationRepository;
|
|
22
|
+
this.moduleRepository = moduleRepository;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} integrationId
|
|
27
|
+
* @param {string[]} entityIds
|
|
28
|
+
* @returns {Promise<Object>} The updated integration record.
|
|
29
|
+
*/
|
|
30
|
+
async execute(integrationId, entityIds) {
|
|
31
|
+
assertEntityIdsInput(integrationId, entityIds);
|
|
32
|
+
|
|
33
|
+
const integration = await loadIntegration(
|
|
34
|
+
this.integrationRepository,
|
|
35
|
+
integrationId
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const removing = new Set(entityIds.map(String));
|
|
39
|
+
const attached = await this.moduleRepository.findEntitiesByIds(
|
|
40
|
+
integration.entitiesIds ?? []
|
|
41
|
+
);
|
|
42
|
+
const emptied = [...new Set(attached.map((e) => e.moduleName))].filter(
|
|
43
|
+
(moduleName) =>
|
|
44
|
+
!attached.some(
|
|
45
|
+
(e) =>
|
|
46
|
+
e.moduleName === moduleName &&
|
|
47
|
+
!removing.has(String(e.id))
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
if (emptied.length > 0) {
|
|
51
|
+
const error = new Error(
|
|
52
|
+
`Removing these entities would leave no entity for module(s): ${emptied.join(
|
|
53
|
+
', '
|
|
54
|
+
)}`
|
|
55
|
+
);
|
|
56
|
+
error.code = 'LAST_ENTITY_OF_MODULE';
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const updated =
|
|
61
|
+
await this.integrationRepository.removeEntitiesFromIntegration(
|
|
62
|
+
integrationId,
|
|
63
|
+
entityIds
|
|
64
|
+
);
|
|
65
|
+
if (!updated) throw integrationNotFound(integrationId);
|
|
66
|
+
return updated;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { RemoveEntitiesFromIntegration };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws INVALID_INTEGRATION_DATA unless an integration id and a non-empty
|
|
3
|
+
* list of entity ids are given.
|
|
4
|
+
* @param {string} integrationId
|
|
5
|
+
* @param {string[]} entityIds
|
|
6
|
+
*/
|
|
7
|
+
function assertEntityIdsInput(integrationId, entityIds) {
|
|
8
|
+
if (!integrationId || !Array.isArray(entityIds) || entityIds.length === 0) {
|
|
9
|
+
const error = new Error(
|
|
10
|
+
'integrationId and a non-empty entityIds array are required'
|
|
11
|
+
);
|
|
12
|
+
error.code = 'INVALID_INTEGRATION_DATA';
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function integrationNotFound(integrationId) {
|
|
18
|
+
const error = new Error(`Integration ${integrationId} not found`);
|
|
19
|
+
error.code = 'INTEGRATION_NOT_FOUND';
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Loads an integration, mapping the repositories' "not found" error to
|
|
25
|
+
* INTEGRATION_NOT_FOUND. Any other failure (connection, invalid id) is
|
|
26
|
+
* rethrown unchanged.
|
|
27
|
+
* @param {Object} integrationRepository
|
|
28
|
+
* @param {string} integrationId
|
|
29
|
+
* @returns {Promise<Object>} The integration record.
|
|
30
|
+
*/
|
|
31
|
+
async function loadIntegration(integrationRepository, integrationId) {
|
|
32
|
+
try {
|
|
33
|
+
return await integrationRepository.findIntegrationById(integrationId);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (/^Integration with id .* not found$/.test(error?.message)) {
|
|
36
|
+
throw integrationNotFound(integrationId);
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { assertEntityIdsInput, integrationNotFound, loadIntegration };
|
|
@@ -19,48 +19,19 @@ function mapIntegrationClassToIntegrationDTO(integration) {
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let deprecationLogged = false;
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* @param {Array<Function>} integrationClasses - integration classes
|
|
37
|
-
* @param {Object} [entities] - `appDefinition.entities` (ADR-059)
|
|
38
|
-
* @returns {Array<Object>} API module definitions
|
|
39
|
-
*/
|
|
40
|
-
function getModulesDefinitionFromIntegrationClasses(
|
|
41
|
-
integrationClasses,
|
|
42
|
-
entities
|
|
43
|
-
) {
|
|
44
|
-
// Required here, not at the top: modules/ must not load at require time
|
|
45
|
-
// of every integration use case that only wants the DTO mapper.
|
|
46
|
-
const {
|
|
47
|
-
getEntityTypeRegistry,
|
|
48
|
-
getEntityTypeDefinitions,
|
|
49
|
-
} = require('../../modules/entity-type-registry');
|
|
50
|
-
if (!deprecationLogged) {
|
|
51
|
-
deprecationLogged = true;
|
|
52
|
-
const { getLogger } = require('../../logs');
|
|
53
|
-
getLogger('frigg.integrations').warn(
|
|
54
|
-
'getModulesDefinitionFromIntegrationClasses is deprecated; use getEntityTypeRegistry (ADR-059)',
|
|
55
|
-
{ eventName: 'frigg.integrations.deprecated_module_definitions' }
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
return getEntityTypeDefinitions(
|
|
59
|
-
getEntityTypeRegistry({ integrations: integrationClasses, entities })
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
module.exports = {
|
|
64
|
-
mapIntegrationClassToIntegrationDTO,
|
|
65
|
-
getModulesDefinitionFromIntegrationClasses,
|
|
23
|
+
const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
|
|
24
|
+
return [
|
|
25
|
+
...new Set(
|
|
26
|
+
integrationClasses
|
|
27
|
+
.map((integration) =>
|
|
28
|
+
Object.values(integration.Definition.modules).map(
|
|
29
|
+
(module) => module.definition
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
.flat()
|
|
33
|
+
),
|
|
34
|
+
];
|
|
66
35
|
};
|
|
36
|
+
|
|
37
|
+
module.exports = { mapIntegrationClassToIntegrationDTO, getModulesDefinitionFromIntegrationClasses };
|
package/jest-setup.js
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Core's unit suites must not need a database, so this global setup only
|
|
2
|
+
// sets the test environment. It deliberately does not start an in-memory
|
|
3
|
+
// MongoDB (as @friggframework/test's globalSetup does): nothing in the default
|
|
4
|
+
// suite talks to a live server, and a binary that failed to start used to stop
|
|
5
|
+
// every core suite from running. Suites that need a real database are
|
|
6
|
+
// `*.db.test.js` files, run by `npm run test:db` (see jest.db.config.js).
|
|
7
|
+
const { overrideEnvironment } = require('@friggframework/test');
|
|
8
|
+
|
|
9
|
+
module.exports = async function () {
|
|
10
|
+
if (!process.env.STAGE) {
|
|
11
|
+
overrideEnvironment({ STAGE: 'dev' });
|
|
12
|
+
}
|
|
13
|
+
};
|
package/jest-teardown.js
CHANGED
package/jest.config.js
CHANGED
|
@@ -20,4 +20,8 @@ module.exports = {
|
|
|
20
20
|
globalTeardown: './jest-teardown.js',
|
|
21
21
|
|
|
22
22
|
setupFilesAfterEnv: ['./logs/jest-logger-setup.js'],
|
|
23
|
+
|
|
24
|
+
// `*.db.test.js` suites talk to a real MongoDB replica set / PostgreSQL
|
|
25
|
+
// and run separately via `npm run test:db` (jest.db.config.js).
|
|
26
|
+
testPathIgnorePatterns: ['/node_modules/', '\\.db\\.test\\.js$'],
|
|
23
27
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Database integration suites (`*.db.test.js`).
|
|
3
|
+
*
|
|
4
|
+
* These talk to real databases instead of mocking Prisma, so they are kept
|
|
5
|
+
* out of the default `npm test` run and executed with `npm run test:db`.
|
|
6
|
+
* They need generated Prisma clients (`npm run prisma:generate`) and:
|
|
7
|
+
* - a MongoDB replica set (Prisma requires one for MongoDB writes):
|
|
8
|
+
* FRIGG_TEST_MONGODB_URL, default
|
|
9
|
+
* mongodb://localhost:27017/frigg?replicaSet=rs0
|
|
10
|
+
* - PostgreSQL with the prisma-postgresql migrations applied:
|
|
11
|
+
* FRIGG_TEST_POSTGRES_URL, default
|
|
12
|
+
* postgresql://postgres:postgres@localhost:5432/frigg?schema=public
|
|
13
|
+
* The repository parity suites skip themselves unless their own URLs are
|
|
14
|
+
* set (INTEGRATION_ENTITIES_PARITY_* / QUERY_MAPPINGS_PARITY_*).
|
|
15
|
+
*/
|
|
16
|
+
const base = require('./jest.config');
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
...base,
|
|
20
|
+
coverageThreshold: undefined,
|
|
21
|
+
testMatch: ['**/*.db.test.js'],
|
|
22
|
+
testPathIgnorePatterns: ['/node_modules/'],
|
|
23
|
+
};
|
package/modules/index.js
CHANGED
|
@@ -4,15 +4,6 @@ const { OAuth2Requester } = require('./requester/oauth-2');
|
|
|
4
4
|
const { Requester } = require('./requester/requester');
|
|
5
5
|
const { ModuleConstants } = require('./ModuleConstants');
|
|
6
6
|
const { ModuleFactory } = require('./module-factory');
|
|
7
|
-
const {
|
|
8
|
-
getEntityTypeRegistry,
|
|
9
|
-
EntityRegistryError,
|
|
10
|
-
} = require('./entity-type-registry');
|
|
11
|
-
const {
|
|
12
|
-
resolveScope,
|
|
13
|
-
listBindingScopes,
|
|
14
|
-
OwnershipConfigError,
|
|
15
|
-
} = require('./entity-scope');
|
|
16
7
|
|
|
17
8
|
module.exports = {
|
|
18
9
|
ApiKeyRequester,
|
|
@@ -21,9 +12,4 @@ module.exports = {
|
|
|
21
12
|
Requester,
|
|
22
13
|
ModuleConstants,
|
|
23
14
|
ModuleFactory,
|
|
24
|
-
getEntityTypeRegistry,
|
|
25
|
-
EntityRegistryError,
|
|
26
|
-
resolveScope,
|
|
27
|
-
listBindingScopes,
|
|
28
|
-
OwnershipConfigError,
|
|
29
15
|
};
|
|
@@ -45,18 +45,6 @@ class ModuleFactory {
|
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// definition.env may be an async function (e.g. the db-credentials
|
|
49
|
-
// extension's bootstrap loads OAuth app credentials from the
|
|
50
|
-
// database); resolve it first. A static env keeps the plain
|
|
51
|
-
// constructor, exactly as before.
|
|
52
|
-
if (typeof moduleDefinition.env === 'function') {
|
|
53
|
-
return await Module.create({
|
|
54
|
-
userId,
|
|
55
|
-
entity,
|
|
56
|
-
definition: moduleDefinition,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
48
|
return new Module({
|
|
61
49
|
userId,
|
|
62
50
|
entity,
|
package/modules/module.js
CHANGED
|
@@ -21,17 +21,8 @@ class Module extends Delegate {
|
|
|
21
21
|
* @param {string} params.userId The user id
|
|
22
22
|
* @param {Object} params.entity The entity record from the database
|
|
23
23
|
* @param {string} [params.state] Optional OAuth state value forwarded to the API client (round-trips through the OAuth provider).
|
|
24
|
-
* @param {Object} [params.resolvedEnv] definition.env as Module.create() resolved it (may be undefined).
|
|
25
|
-
* @param {boolean} [params.envResolved=false] true when resolvedEnv carries the resolved env, even an undefined one; otherwise the static definition.env is used.
|
|
26
24
|
*/
|
|
27
|
-
constructor({
|
|
28
|
-
definition,
|
|
29
|
-
userId = null,
|
|
30
|
-
entity: entityObj = null,
|
|
31
|
-
state = null,
|
|
32
|
-
resolvedEnv,
|
|
33
|
-
envResolved = false,
|
|
34
|
-
}) {
|
|
25
|
+
constructor({ definition, userId = null, entity: entityObj = null, state = null }) {
|
|
35
26
|
super({ definition, userId, entity: entityObj });
|
|
36
27
|
|
|
37
28
|
this.validateDefinition(definition);
|
|
@@ -63,23 +54,8 @@ class Module extends Delegate {
|
|
|
63
54
|
|
|
64
55
|
Object.assign(this, this.definition.requiredAuthMethods);
|
|
65
56
|
|
|
66
|
-
// A pre-resolved env (Module.create()) supports definition.env as an
|
|
67
|
-
// async function, e.g. OAuth app credentials loaded from the database
|
|
68
|
-
// by the db-credentials extension; otherwise the static object.
|
|
69
|
-
// "Resolved" is an explicit flag, not a non-null value: a function env
|
|
70
|
-
// may legitimately resolve to undefined (no static env, no DB row),
|
|
71
|
-
// and falling back to definition.env would then read the function.
|
|
72
|
-
const envValues = envResolved ? resolvedEnv : this.definition.env;
|
|
73
|
-
if (typeof envValues === 'function') {
|
|
74
|
-
// Spreading a function yields no keys, so the API client would
|
|
75
|
-
// silently get no client_id / client_secret.
|
|
76
|
-
throw new TypeError(
|
|
77
|
-
`Module ${this.name}: definition.env is a function; build the module with await Module.create(...)`
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
57
|
const apiParams = {
|
|
82
|
-
...
|
|
58
|
+
...this.definition.env,
|
|
83
59
|
delegate: this,
|
|
84
60
|
logger: this.logger,
|
|
85
61
|
...(state ? { state } : {}),
|
|
@@ -91,33 +67,6 @@ class Module extends Delegate {
|
|
|
91
67
|
this.api = new this.apiClass(apiParams);
|
|
92
68
|
}
|
|
93
69
|
|
|
94
|
-
/**
|
|
95
|
-
* Resolves definition.env, supporting both a static object and a
|
|
96
|
-
* (possibly async) function.
|
|
97
|
-
*
|
|
98
|
-
* @param {Object} definition - Module definition
|
|
99
|
-
* @returns {Promise<Object|undefined>} Resolved env values
|
|
100
|
-
*/
|
|
101
|
-
static async resolveEnv(definition) {
|
|
102
|
-
if (typeof definition.env === 'function') {
|
|
103
|
-
return await definition.env();
|
|
104
|
-
}
|
|
105
|
-
return definition.env;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Async factory that resolves definition.env first, so a module whose
|
|
110
|
-
* env is a function gets its values. Prefer it over `new Module(...)`
|
|
111
|
-
* wherever the call site can await.
|
|
112
|
-
*
|
|
113
|
-
* @param {Object} params - same as the constructor, minus resolvedEnv
|
|
114
|
-
* @returns {Promise<Module>}
|
|
115
|
-
*/
|
|
116
|
-
static async create({ definition, userId = null, entity = null, state = null }) {
|
|
117
|
-
const resolvedEnv = await Module.resolveEnv(definition);
|
|
118
|
-
return new Module({ definition, userId, entity, state, resolvedEnv, envResolved: true });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
70
|
getName() {
|
|
122
71
|
return this.name;
|
|
123
72
|
}
|