@friggframework/core 2.0.0--canary.690.08cd07e.0 → 2.0.0--canary.694.f66a64a.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 -28
- package/README.md +0 -105
- package/application/commands/integration-commands.js +10 -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-utils.js +2 -2
- package/database/encryption/encryption-schema-registry.js +12 -93
- package/database/prisma.js +2 -4
- package/errors/index.js +4 -0
- package/errors/mapping-already-exists-error.js +14 -0
- package/generated/prisma-mongodb/edge.js +38 -33
- package/generated/prisma-mongodb/index-browser.js +35 -30
- package/generated/prisma-mongodb/index.d.ts +1880 -3191
- package/generated/prisma-mongodb/index.js +38 -33
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +50 -86
- package/generated/prisma-mongodb/wasm.js +38 -33
- package/generated/prisma-postgresql/edge.js +38 -33
- package/generated/prisma-postgresql/index-browser.js +35 -30
- package/generated/prisma-postgresql/index.d.ts +2050 -3390
- package/generated/prisma-postgresql/index.js +38 -33
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +48 -81
- package/generated/prisma-postgresql/wasm.js +38 -33
- package/handlers/WEBHOOKS.md +29 -0
- package/handlers/app-definition-loader.js +19 -128
- 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/handlers/workers/integration-triggers.js +25 -0
- package/index.js +3 -65
- package/infrastructure/scheduler/index.js +6 -19
- package/infrastructure/scheduler/recurrence.js +204 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
- package/integrations/data-defined/README.md +185 -0
- package/integrations/data-defined/canonical.js +11 -0
- package/integrations/data-defined/capabilities/acmeerp.capabilities.json +608 -0
- package/integrations/data-defined/capabilities/asana.capabilities.json +265 -0
- package/integrations/data-defined/capabilities/calendly.capabilities.json +385 -0
- package/integrations/data-defined/capabilities/docusign.capabilities.json +314 -0
- package/integrations/data-defined/capabilities/fathom.capabilities.json +228 -0
- package/integrations/data-defined/capabilities/github.capabilities.json +563 -0
- package/integrations/data-defined/capabilities/hubspot.capabilities.json +715 -0
- package/integrations/data-defined/capabilities/jira.capabilities.json +376 -0
- package/integrations/data-defined/capabilities/klaviyo.capabilities.json +475 -0
- package/integrations/data-defined/capabilities/linear.capabilities.json +324 -0
- package/integrations/data-defined/capabilities/netsuite.capabilities.json +491 -0
- package/integrations/data-defined/capabilities/notion.capabilities.json +208 -0
- package/integrations/data-defined/capabilities/phone.capabilities.json +470 -0
- package/integrations/data-defined/capabilities/pipedrive.capabilities.json +388 -0
- package/integrations/data-defined/capabilities/quickbooks.capabilities.json +615 -0
- package/integrations/data-defined/capabilities/salesforce.capabilities.json +507 -0
- package/integrations/data-defined/capabilities/shopify.capabilities.json +1082 -0
- package/integrations/data-defined/capabilities/slack.capabilities.json +363 -0
- package/integrations/data-defined/capabilities/stripe.capabilities.json +580 -0
- package/integrations/data-defined/capabilities/webflow.capabilities.json +196 -0
- package/integrations/data-defined/capabilities/zendesk.capabilities.json +409 -0
- package/integrations/data-defined/compile.js +80 -0
- package/integrations/data-defined/config-form.js +137 -0
- package/integrations/data-defined/data-defined-integration.js +417 -0
- package/integrations/data-defined/examples/acme-erp-quickbooks.definition.json +467 -0
- package/integrations/data-defined/examples/asana-slack.definition.json +220 -0
- package/integrations/data-defined/examples/calendly-salesforce.definition.json +459 -0
- package/integrations/data-defined/examples/docusign-salesforce.definition.json +305 -0
- package/integrations/data-defined/examples/fathom-hubspot-notion.definition.json +575 -0
- package/integrations/data-defined/examples/github-linear.definition.json +316 -0
- package/integrations/data-defined/examples/hubspot-slack.definition.json +409 -0
- package/integrations/data-defined/examples/klaviyo-hubspot.definition.json +339 -0
- package/integrations/data-defined/examples/phone-hubspot.definition.json +637 -0
- package/integrations/data-defined/examples/pipedrive-hubspot.definition.json +136 -0
- package/integrations/data-defined/examples/pipedrive-phone.definition.json +340 -0
- package/integrations/data-defined/examples/records/phone-hubspot.definition-version.json +652 -0
- package/integrations/data-defined/examples/records/phone-hubspot.integration.json +86 -0
- package/integrations/data-defined/examples/records/phone-hubspot.mappings.json +39 -0
- package/integrations/data-defined/examples/records/phone-hubspot.workflow-run.json +62 -0
- package/integrations/data-defined/examples/shopify-klaviyo.definition.json +488 -0
- package/integrations/data-defined/examples/shopify-netsuite.definition.json +622 -0
- package/integrations/data-defined/examples/stripe-quickbooks.definition.json +476 -0
- package/integrations/data-defined/examples/webflow-hubspot-slack.definition.json +262 -0
- package/integrations/data-defined/examples/zendesk-jira.definition.json +604 -0
- package/integrations/data-defined/expressions.js +225 -0
- package/integrations/data-defined/index.js +39 -0
- package/integrations/data-defined/interpreter.js +89 -0
- package/integrations/data-defined/manifests.js +125 -0
- package/integrations/data-defined/repositories/definition-version-repository.js +67 -0
- package/integrations/data-defined/repositories/workflow-run-repository.js +59 -0
- package/integrations/data-defined/router.js +97 -0
- package/integrations/data-defined/runtime.js +208 -0
- package/integrations/data-defined/schema/capability-manifest.v0.1.schema.json +377 -0
- package/integrations/data-defined/schema/installation.v0.1.schema.json +130 -0
- package/integrations/data-defined/schema/integration-definition.v0.1.schema.json +902 -0
- package/integrations/data-defined/schema/records.md +98 -0
- package/integrations/data-defined/schema-check.js +58 -0
- package/integrations/data-defined/scripts/validate.js +66 -0
- package/integrations/data-defined/steps.js +89 -0
- package/integrations/data-defined/transforms.js +91 -0
- package/integrations/data-defined/use-cases/definition-use-cases.js +187 -0
- package/integrations/data-defined/validate.js +384 -0
- package/integrations/data-defined/versioning.js +176 -0
- package/integrations/data-defined/webhooks.js +119 -0
- package/integrations/index.js +0 -1
- package/integrations/integration-base.js +117 -0
- package/integrations/integration-router.js +101 -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 +9 -37
- package/integrations/repositories/integration-repository-interface.js +4 -17
- package/integrations/repositories/integration-repository-mongo.js +96 -64
- package/integrations/repositories/integration-repository-postgres.js +81 -63
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/tests/doubles/test-integration-repository.js +1 -12
- package/integrations/triggers.js +244 -0
- 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 -7
- package/integrations/use-cases/run-trigger-tick.js +132 -0
- package/integrations/use-cases/run-trigger.js +77 -0
- package/integrations/utils/map-integration-dto.js +14 -43
- 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 +5 -35
- 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/package.json +6 -6
- package/prisma-mongodb/schema.prisma +50 -86
- package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +66 -0
- package/prisma-postgresql/schema.prisma +48 -81
- package/queues/index.js +0 -25
- package/queues/queuer-util.js +9 -2
- 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/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/credential/use-cases/credential-operations-for-user.js +0 -202
- package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
- 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 -216
- 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 -415
- 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-bases.js +0 -169
- package/modules/proxy/proxy-errors.js +0 -31
- package/modules/proxy/proxy-path.js +0 -183
- package/modules/proxy/proxy-policy.js +0 -277
- 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 -245
- 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 -331
- 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 -150
- 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 -214
- 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
|
@@ -120,7 +120,7 @@ class ProcessRepositoryMongo extends ProcessRepositoryInterface {
|
|
|
120
120
|
if (normalized.newState !== null) {
|
|
121
121
|
$set.state = normalized.newState;
|
|
122
122
|
}
|
|
123
|
-
$set.updatedAt = new Date();
|
|
123
|
+
$set.updatedAt = { $date: new Date().toISOString() };
|
|
124
124
|
update.$set = $set;
|
|
125
125
|
|
|
126
126
|
if (Object.keys(normalized.pushSlice).length > 0) {
|
|
@@ -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) => {
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recurring triggers (ADR-055): what an integration declares, how one installation resolves it, and whether it is
|
|
3
|
+
* due at a tick. Pure; no I/O. The tick itself is `use-cases/run-trigger-tick.js`; the run is
|
|
4
|
+
* `IntegrationBase.onTriggerRun`.
|
|
5
|
+
*
|
|
6
|
+
* static Definition = {
|
|
7
|
+
* name: 'housecallpro',
|
|
8
|
+
* triggers: {
|
|
9
|
+
* pollErp: { every: '15m', handler: 'pollErp' }, // every installation
|
|
10
|
+
* ongoingSync: { every: '1h', configurable: { min: '15m' }, handler: sync }, // an installation may retime it
|
|
11
|
+
* perPlan: { every: (config) => config.pollEvery, when: (config) => config.poll !== false, handler },
|
|
12
|
+
* digest: { cron: '0 8 * * 1-5', timezone: 'America/New_York', configurable: true, handler },
|
|
13
|
+
* nightly: { at: 'cron(0 3 * * ? *)', timezone: 'Europe/London', handler }, // same time for everyone
|
|
14
|
+
* },
|
|
15
|
+
* };
|
|
16
|
+
*
|
|
17
|
+
* Nothing is created per installation. Devtools provisions one function per integration with one schedule per
|
|
18
|
+
* distinct tick and one per `at` trigger (`provisioningOf`). At each tick the function pages through the ENABLED
|
|
19
|
+
* installations and sends `{ event: 'FRIGG_TRIGGER_RUN', data: { integrationId, trigger, scheduledFor } }` for each
|
|
20
|
+
* one that is due. A disabled or deleted installation is simply not due, and a config change applies at the next tick.
|
|
21
|
+
*
|
|
22
|
+
* Two additions to ADR-055 (its "Amendment (ADR-063)"):
|
|
23
|
+
* - `configurable: true | { min }` on an `every` or `cron` trigger lets an installation choose its own timing at
|
|
24
|
+
* `config.triggers.<name>` (`{ every }`, `{ cron, timezone }` or `{ enabled: false }`), never more often than `min`.
|
|
25
|
+
* A choice that breaks the floor, or a trigger that is not configurable, keeps the default (and is reported).
|
|
26
|
+
* - `cron` (five fields, with `timezone`) is a calendar trigger that may differ per installation. It runs off the
|
|
27
|
+
* shared tick: due when a firing falls in (previous tick, this tick]. `at` stays the ADR-055 calendar trigger,
|
|
28
|
+
* the same for every installation, with an exact schedule of its own.
|
|
29
|
+
*/
|
|
30
|
+
const { parseEvery, validateRecurrence, minGapMinutes, lastFiringIn, isTimeZone } = require('../infrastructure/scheduler/recurrence');
|
|
31
|
+
|
|
32
|
+
const TRIGGER_RUN = 'FRIGG_TRIGGER_RUN';
|
|
33
|
+
/** A tick that ran out of time sends this to the integration's queue to carry on from its page cursor. */
|
|
34
|
+
const TRIGGER_TICK = 'FRIGG_TRIGGER_TICK';
|
|
35
|
+
|
|
36
|
+
const DEFAULT_TICK_MINUTES = 5;
|
|
37
|
+
const MAX_EVERY_MINUTES = 1440; // longer cadences use a calendar (`at` / `cron`)
|
|
38
|
+
const AT = /^cron\(\S+ \S+ \S+ \S+ \S+ \S+\)$/;
|
|
39
|
+
const OVERLAP = ['skip', 'allow'];
|
|
40
|
+
|
|
41
|
+
const configurableOf = (c) => (c ? (c === true ? {} : c) : false);
|
|
42
|
+
const kindOf = (t) => (t.at !== undefined ? 'at' : t.cron !== undefined ? 'cron' : 'every');
|
|
43
|
+
const recurrenceOf = (r) => (r?.every !== undefined ? { every: r.every } : r?.cron !== undefined ? { cron: r.cron, ...(r.timezone ? { timezone: r.timezone } : {}) } : null);
|
|
44
|
+
|
|
45
|
+
/** A minutes count as the shortest duration string: 90 -> '90m', 60 -> '1h', 1440 -> '1d'. */
|
|
46
|
+
function durationOf(minutes) {
|
|
47
|
+
if (minutes % 1440 === 0) return `${minutes / 1440}d`;
|
|
48
|
+
if (minutes % 60 === 0) return `${minutes / 60}h`;
|
|
49
|
+
return `${minutes}m`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The tick a trigger runs on, in minutes. `tick` when set. Otherwise: for a constant `every`, that interval; for an
|
|
54
|
+
* `every` given as a function, 5 minutes; for a `cron`, 5 minutes, or less when the cron itself fires more often.
|
|
55
|
+
* A configurable trigger never ticks slower than its floor (or 5 minutes, without one), so the fastest choice it
|
|
56
|
+
* allows can run. `at` triggers have no tick (null): each has its own schedule.
|
|
57
|
+
*/
|
|
58
|
+
function tickMinutesOf(trigger) {
|
|
59
|
+
if (kindOf(trigger) === 'at') return null;
|
|
60
|
+
if (trigger.tick !== undefined) return parseEvery(trigger.tick);
|
|
61
|
+
const configurable = configurableOf(trigger.configurable);
|
|
62
|
+
const floor = configurable ? (configurable.min !== undefined ? parseEvery(configurable.min) : DEFAULT_TICK_MINUTES) : Infinity;
|
|
63
|
+
let natural;
|
|
64
|
+
if (kindOf(trigger) === 'cron') {
|
|
65
|
+
const gap = minGapMinutes({ cron: trigger.cron });
|
|
66
|
+
natural = Math.max(1, Math.min(DEFAULT_TICK_MINUTES, gap ?? DEFAULT_TICK_MINUTES));
|
|
67
|
+
} else {
|
|
68
|
+
natural = typeof trigger.every === 'function' ? DEFAULT_TICK_MINUTES : parseEvery(trigger.every) ?? DEFAULT_TICK_MINUTES;
|
|
69
|
+
}
|
|
70
|
+
return Math.max(1, Math.min(natural, floor ?? DEFAULT_TICK_MINUTES, MAX_EVERY_MINUTES));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Problems with a trigger declaration, found without an installation (definition time and build time).
|
|
75
|
+
* @returns {string[]}
|
|
76
|
+
*/
|
|
77
|
+
function validateTrigger(name, t) {
|
|
78
|
+
const at = (m) => `${name}: ${m}`;
|
|
79
|
+
if (!t || typeof t !== 'object') return [at('A trigger is an object')];
|
|
80
|
+
const problems = [];
|
|
81
|
+
const kinds = ['every', 'at', 'cron'].filter((k) => t[k] !== undefined);
|
|
82
|
+
if (kinds.length !== 1) return [at('Give exactly one of every, at or cron')];
|
|
83
|
+
if (typeof t.handler !== 'function' && typeof t.handler !== 'string') problems.push(at('handler must be a function or a method name'));
|
|
84
|
+
if (t.overlap !== undefined && !OVERLAP.includes(t.overlap)) problems.push(at(`overlap must be ${OVERLAP.join(' or ')}`));
|
|
85
|
+
if (t.when !== undefined && typeof t.when !== 'function') problems.push(at('when must be a function of the installation config'));
|
|
86
|
+
if (t.tick !== undefined) {
|
|
87
|
+
const tick = parseEvery(t.tick);
|
|
88
|
+
if (tick === null || tick > MAX_EVERY_MINUTES) problems.push(at(`tick "${t.tick}" is not a duration between 1m and 1d`));
|
|
89
|
+
}
|
|
90
|
+
const kind = kinds[0];
|
|
91
|
+
if (kind === 'at') {
|
|
92
|
+
if (typeof t.at !== 'string' || !AT.test(t.at)) problems.push(at(`at "${t.at}" must be an EventBridge cron(...) expression with six fields`));
|
|
93
|
+
if (t.timezone !== undefined && !isTimeZone(t.timezone)) problems.push(at(`Unknown time zone "${t.timezone}"`));
|
|
94
|
+
if (t.configurable) problems.push(at('An at trigger runs at one time for every installation; use cron to let installations choose'));
|
|
95
|
+
if (t.tick !== undefined) problems.push(at('An at trigger has its own schedule; tick does not apply'));
|
|
96
|
+
return problems;
|
|
97
|
+
}
|
|
98
|
+
const configurable = configurableOf(t.configurable);
|
|
99
|
+
if (configurable && configurable.min !== undefined && parseEvery(configurable.min) === null) problems.push(at(`configurable.min "${configurable.min}" is not a duration (15m, 2h, 1d)`));
|
|
100
|
+
if (kind === 'every' && typeof t.every === 'function') return problems;
|
|
101
|
+
const recurrence = recurrenceOf(t);
|
|
102
|
+
for (const p of validateRecurrence(recurrence)) problems.push(at(p));
|
|
103
|
+
if (kind === 'every' && parseEvery(t.every) > MAX_EVERY_MINUTES) problems.push(at(`every "${t.every}" is longer than 1d; use at or cron`));
|
|
104
|
+
if (!problems.length && configurable && configurable.min !== undefined) {
|
|
105
|
+
for (const p of validateRecurrence(recurrence, { min: configurable.min })) problems.push(at(`The default breaks its own minimum: ${p}`));
|
|
106
|
+
}
|
|
107
|
+
return problems;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Problems with every trigger a Definition declares. */
|
|
111
|
+
function validateTriggers(triggers = {}) {
|
|
112
|
+
return Object.entries(triggers || {}).flatMap(([name, t]) => validateTrigger(name, t));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* What devtools provisions for an integration class: the distinct ticks (minutes) of its `every`/`cron` triggers,
|
|
117
|
+
* including the tick of the triggers it resolves per installation (`Definition.instanceTriggers`), and one entry per
|
|
118
|
+
* `at` trigger. Throws on an invalid declaration, so a bad trigger fails the build rather than the first tick.
|
|
119
|
+
*
|
|
120
|
+
* @returns {{ ticks: number[], at: Array<{ name, at, timezone? }> }}
|
|
121
|
+
*/
|
|
122
|
+
function provisioningOf(IntegrationClass) {
|
|
123
|
+
const Definition = IntegrationClass?.Definition || {};
|
|
124
|
+
const triggers = Definition.triggers || {};
|
|
125
|
+
const problems = validateTriggers(triggers);
|
|
126
|
+
const instance = Definition.instanceTriggers;
|
|
127
|
+
if (instance && parseEvery(instance.tick) === null) problems.push(`instanceTriggers.tick "${instance.tick}" is not a duration`);
|
|
128
|
+
if (problems.length) throw new Error(`Integration "${Definition.name}" declares invalid triggers: ${problems.join('; ')}`);
|
|
129
|
+
const ticks = new Set();
|
|
130
|
+
const at = [];
|
|
131
|
+
for (const [name, t] of Object.entries(triggers)) {
|
|
132
|
+
if (kindOf(t) === 'at') at.push({ name, at: t.at, ...(t.timezone ? { timezone: t.timezone } : {}) });
|
|
133
|
+
else ticks.add(tickMinutesOf(t));
|
|
134
|
+
}
|
|
135
|
+
if (instance) ticks.add(parseEvery(instance.tick));
|
|
136
|
+
return { ticks: [...ticks].sort((a, b) => a - b), at };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Does this class declare any triggers (fixed or per installation)? */
|
|
140
|
+
function hasTriggers(IntegrationClass) {
|
|
141
|
+
const Definition = IntegrationClass?.Definition || {};
|
|
142
|
+
return Object.keys(Definition.triggers || {}).length > 0 || Boolean(Definition.instanceTriggers);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* One trigger, resolved for one installation.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} name
|
|
149
|
+
* @param {Object} t - the declaration (plus `choice` when the caller found the installation's choice elsewhere)
|
|
150
|
+
* @param {Object} config - the installation's config
|
|
151
|
+
* @returns {{ name, kind, recurrence?, at?, tickMinutes, cadence?, overlap, active: boolean, inactive?: string,
|
|
152
|
+
* source: 'definition'|'installation', problems: string[] }}
|
|
153
|
+
* `active: false` when `when` excludes the installation or it paused a configurable trigger.
|
|
154
|
+
* An invalid choice keeps the default and is listed in `problems`, so a bad edit never stops a trigger.
|
|
155
|
+
*/
|
|
156
|
+
function resolveTrigger(name, t, config = {}) {
|
|
157
|
+
const kind = kindOf(t);
|
|
158
|
+
const out = { name, kind, tickMinutes: tickMinutesOf(t), overlap: t.overlap || 'skip', active: true, source: 'definition', problems: [] };
|
|
159
|
+
if (typeof t.when === 'function' && !t.when(config)) return { ...out, active: false, inactive: 'when' };
|
|
160
|
+
if (kind === 'at') return { ...out, at: t.at, timezone: t.timezone };
|
|
161
|
+
|
|
162
|
+
let recurrence = kind === 'every' ? { every: typeof t.every === 'function' ? t.every(config) : t.every } : recurrenceOf(t);
|
|
163
|
+
const configurable = configurableOf(t.configurable);
|
|
164
|
+
const choice = t.choice !== undefined ? t.choice : config?.triggers?.[name];
|
|
165
|
+
if (choice && typeof choice === 'object' && (recurrenceOf(choice) || choice.enabled !== undefined)) {
|
|
166
|
+
if (!configurable) out.problems.push('This trigger is not configurable');
|
|
167
|
+
else {
|
|
168
|
+
const picked = recurrenceOf(choice);
|
|
169
|
+
const issues = picked ? validateRecurrence(picked, { min: configurable.min }) : [];
|
|
170
|
+
if (picked?.every !== undefined && !issues.length && parseEvery(picked.every) > MAX_EVERY_MINUTES) issues.push(`every "${picked.every}" is longer than 1d`);
|
|
171
|
+
if (issues.length) out.problems.push(...issues);
|
|
172
|
+
else {
|
|
173
|
+
if (picked) { recurrence = picked; out.source = 'installation'; }
|
|
174
|
+
if (choice.enabled === false) return { ...out, recurrence, active: false, inactive: 'paused' };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const own = validateRecurrence(recurrence);
|
|
179
|
+
if (recurrence.every !== undefined && !own.length && parseEvery(recurrence.every) > MAX_EVERY_MINUTES) own.push(`every "${recurrence.every}" is longer than 1d`);
|
|
180
|
+
if (own.length) return { ...out, recurrence, active: false, inactive: 'invalid', problems: [...out.problems, ...own] };
|
|
181
|
+
out.recurrence = recurrence;
|
|
182
|
+
// An interval runs every n ticks (rounded up to a whole number of ticks).
|
|
183
|
+
if (recurrence.every !== undefined) out.cadence = Math.max(1, Math.ceil(parseEvery(recurrence.every) / out.tickMinutes));
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** A stable, evenly spread number for an installation id (FNV-1a, 32 bits). */
|
|
188
|
+
function hashId(value) {
|
|
189
|
+
let h = 0x811c9dc5;
|
|
190
|
+
for (const ch of String(value)) {
|
|
191
|
+
h ^= ch.codePointAt(0);
|
|
192
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
193
|
+
}
|
|
194
|
+
return h;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The start of the tick slot an instant falls in: tick runs are keyed by slot, so a late or repeated tick agrees. */
|
|
198
|
+
function slotStart(timeMs, tickMinutes) {
|
|
199
|
+
const size = tickMinutes * 60000;
|
|
200
|
+
return Math.floor(timeMs / size) * size;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Is a resolved trigger due for this installation at this tick slot?
|
|
205
|
+
*
|
|
206
|
+
* - every: due when `slot mod n == hash(integrationId) mod n` (n = cadence in ticks), so it is stateless and spreads
|
|
207
|
+
* installations evenly across the cadence. scheduledFor is the slot start.
|
|
208
|
+
* - cron: due when it fired in (slot start - tick, slot start]. scheduledFor is that firing, so two ticks in one slot
|
|
209
|
+
* (a redelivery, a retried continuation) name the same run.
|
|
210
|
+
* - at: due whenever its own schedule invoked the tick; scheduledFor is that time, to the minute.
|
|
211
|
+
*
|
|
212
|
+
* @param {Object} resolved - from resolveTrigger
|
|
213
|
+
* @param {{ integrationId, slotMs: number, firing?: (recurrence, from, to) => number|null }} at
|
|
214
|
+
* @returns {{ scheduledFor: string } | null}
|
|
215
|
+
*/
|
|
216
|
+
function dueRun(resolved, { integrationId, slotMs, firing = lastFiringIn }) {
|
|
217
|
+
if (!resolved?.active) return null;
|
|
218
|
+
if (resolved.kind === 'at') return { scheduledFor: new Date(Math.floor(slotMs / 60000) * 60000).toISOString() };
|
|
219
|
+
if (resolved.recurrence.every !== undefined) {
|
|
220
|
+
const n = resolved.cadence;
|
|
221
|
+
const slot = Math.floor(slotMs / (resolved.tickMinutes * 60000));
|
|
222
|
+
return slot % n === hashId(integrationId) % n ? { scheduledFor: new Date(slotMs).toISOString() } : null;
|
|
223
|
+
}
|
|
224
|
+
const fired = firing(resolved.recurrence, slotMs - resolved.tickMinutes * 60000, slotMs);
|
|
225
|
+
return fired === null ? null : { scheduledFor: new Date(fired).toISOString() };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = {
|
|
229
|
+
TRIGGER_RUN,
|
|
230
|
+
TRIGGER_TICK,
|
|
231
|
+
DEFAULT_TICK_MINUTES,
|
|
232
|
+
configurableOf,
|
|
233
|
+
recurrenceOf,
|
|
234
|
+
durationOf,
|
|
235
|
+
tickMinutesOf,
|
|
236
|
+
validateTrigger,
|
|
237
|
+
validateTriggers,
|
|
238
|
+
provisioningOf,
|
|
239
|
+
hasTriggers,
|
|
240
|
+
resolveTrigger,
|
|
241
|
+
hashId,
|
|
242
|
+
slotStart,
|
|
243
|
+
dueRun,
|
|
244
|
+
};
|
|
@@ -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,
|
|
@@ -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
|
};
|