@friggframework/core 2.0.0--canary.663.8de669c.0 → 2.0.0--canary.686.821f8a3.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.
Files changed (188) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CLAUDE.md +28 -0
  3. package/README.md +105 -0
  4. package/application/commands/integration-commands.js +20 -10
  5. package/application/commands/scheduler-commands.js +11 -7
  6. package/application/commands/user-commands.js +17 -5
  7. package/application/index.js +6 -2
  8. package/core/Worker.js +31 -7
  9. package/core/create-handler.js +9 -0
  10. package/credential/repositories/credential-repository-documentdb.js +34 -0
  11. package/credential/repositories/credential-repository-interface.js +15 -0
  12. package/credential/repositories/credential-repository-mongo.js +23 -0
  13. package/credential/repositories/credential-repository-postgres.js +28 -0
  14. package/credential/use-cases/credential-operations-for-user.js +202 -0
  15. package/credential/use-cases/reauthorize-credential-for-user.js +73 -0
  16. package/database/config.js +48 -24
  17. package/database/documentdb-utils.js +6 -1
  18. package/database/encryption/README.md +2 -0
  19. package/database/encryption/encryption-config.js +10 -31
  20. package/database/encryption/encryption-schema-registry.js +121 -24
  21. package/database/encryption/field-encryption-service.js +69 -5
  22. package/database/encryption/test-support/fake-ciphertext.js +15 -0
  23. package/database/organization-where.js +18 -0
  24. package/database/prisma.js +4 -2
  25. package/database/unique-violation.js +25 -0
  26. package/extensions/bootstrap-runner.js +43 -0
  27. package/extensions/extension-loader.js +187 -0
  28. package/extensions/index.js +41 -0
  29. package/extensions/initialize-app.js +123 -0
  30. package/extensions/route-mounter.js +103 -0
  31. package/extensions/schema-composer.js +188 -0
  32. package/generated/prisma-mongodb/edge.js +43 -4
  33. package/generated/prisma-mongodb/index-browser.js +40 -1
  34. package/generated/prisma-mongodb/index.d.ts +4761 -133
  35. package/generated/prisma-mongodb/index.js +43 -4
  36. package/generated/prisma-mongodb/package.json +1 -1
  37. package/generated/prisma-mongodb/schema.prisma +92 -0
  38. package/generated/prisma-mongodb/wasm.js +43 -4
  39. package/generated/prisma-postgresql/edge.js +43 -4
  40. package/generated/prisma-postgresql/index-browser.js +40 -1
  41. package/generated/prisma-postgresql/index.d.ts +4961 -28
  42. package/generated/prisma-postgresql/index.js +43 -4
  43. package/generated/prisma-postgresql/package.json +1 -1
  44. package/generated/prisma-postgresql/schema.prisma +87 -0
  45. package/generated/prisma-postgresql/wasm.js +43 -4
  46. package/handlers/app-definition-loader.js +121 -19
  47. package/handlers/app-handler-helpers.js +25 -3
  48. package/handlers/backend-utils.js +31 -25
  49. package/handlers/middleware/admin-auth.js +14 -1
  50. package/handlers/routers/auth.js +46 -10
  51. package/handlers/routers/health.js +13 -4
  52. package/handlers/routers/integration-defined-routers.js +144 -92
  53. package/handlers/routers/integration-webhook-routers.js +102 -64
  54. package/handlers/workers/integration-defined-workers.js +5 -2
  55. package/index.js +65 -1
  56. package/infrastructure/scheduler/index.js +19 -6
  57. package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
  58. package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
  59. package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
  60. package/integrations/index.js +1 -0
  61. package/integrations/integration-router.js +131 -96
  62. package/integrations/repositories/integration-not-found.js +14 -0
  63. package/integrations/repositories/integration-repository-documentdb.js +35 -3
  64. package/integrations/repositories/integration-repository-interface.js +17 -1
  65. package/integrations/repositories/integration-repository-mongo.js +59 -76
  66. package/integrations/repositories/integration-repository-postgres.js +62 -76
  67. package/integrations/schedule-config.js +80 -0
  68. package/integrations/tests/doubles/test-integration-repository.js +12 -1
  69. package/integrations/use-cases/create-integration.js +55 -20
  70. package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
  71. package/integrations/use-cases/get-integrations-for-user.js +80 -40
  72. package/integrations/use-cases/index.js +7 -1
  73. package/integrations/use-cases/integration-operations-for-user.js +256 -0
  74. package/integrations/use-cases/resolve-integration-ownership.js +177 -0
  75. package/integrations/utils/map-integration-dto.js +43 -14
  76. package/management-api/create-management-api-router.js +49 -0
  77. package/management-api/error-response.js +116 -0
  78. package/management-api/management-api-config.js +151 -0
  79. package/management-api/meta-router.js +76 -0
  80. package/management-api/openapi/build-openapi.js +144 -0
  81. package/management-api/openapi/components.js +408 -0
  82. package/management-api/openapi/v1-operations.js +83 -0
  83. package/management-api/openapi/v2-operations.js +264 -0
  84. package/management-api/route-registry.js +411 -0
  85. package/management-api/test-support/http-client.js +39 -0
  86. package/management-api/use-cases/get-management-api-meta.js +57 -0
  87. package/management-api/v1-deprecation.js +120 -0
  88. package/management-api/v2/create-v2-router.js +85 -0
  89. package/management-api/v2/dependencies.js +281 -0
  90. package/management-api/v2/dto.js +143 -0
  91. package/management-api/v2/handlers/authorization.js +94 -0
  92. package/management-api/v2/handlers/credentials.js +33 -0
  93. package/management-api/v2/handlers/entities.js +77 -0
  94. package/management-api/v2/handlers/integrations.js +128 -0
  95. package/management-api/v2/handlers/proxy.js +34 -0
  96. package/management-api/v2/request.js +85 -0
  97. package/management-api/v2/test-support/fake-dependencies.js +103 -0
  98. package/modules/domain/authorization-session.js +144 -0
  99. package/modules/entity-scope.js +15 -0
  100. package/modules/entity-type-registry.js +311 -0
  101. package/modules/index.js +14 -0
  102. package/modules/module-factory.js +12 -0
  103. package/modules/module.js +53 -2
  104. package/modules/proxy/proxy-errors.js +20 -0
  105. package/modules/proxy/proxy-path.js +179 -0
  106. package/modules/proxy/proxy-policy.js +248 -0
  107. package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
  108. package/modules/repositories/authorization-session-repository-factory.js +31 -0
  109. package/modules/repositories/authorization-session-repository-interface.js +47 -0
  110. package/modules/repositories/authorization-session-repository-prisma.js +87 -0
  111. package/modules/repositories/entity-not-found.js +18 -0
  112. package/modules/repositories/module-repository-documentdb.js +51 -6
  113. package/modules/repositories/module-repository-interface.js +19 -2
  114. package/modules/repositories/module-repository-mongo.js +67 -74
  115. package/modules/repositories/module-repository-postgres.js +66 -74
  116. package/modules/repositories/module-repository.js +2 -1
  117. package/modules/requester/proxy-request.js +244 -0
  118. package/modules/requester/requester.js +27 -2
  119. package/modules/scope-config.js +192 -0
  120. package/modules/scope-policy.js +112 -0
  121. package/modules/scope-resolution.js +288 -0
  122. package/modules/scope-visibility.js +247 -0
  123. package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
  124. package/modules/tests/doubles/ownership-matching.js +18 -0
  125. package/modules/use-cases/authorize-entity-for-user.js +61 -0
  126. package/modules/use-cases/create-entity-from-credential.js +113 -0
  127. package/modules/use-cases/entity-operations-for-user.js +162 -0
  128. package/modules/use-cases/entity-types.js +139 -0
  129. package/modules/use-cases/execute-entity-proxy-request.js +286 -0
  130. package/modules/use-cases/get-authorization-requirements.js +100 -0
  131. package/modules/use-cases/get-entities-for-user.js +26 -13
  132. package/modules/use-cases/get-entity-module-for-user.js +113 -0
  133. package/modules/use-cases/get-entity-options-by-id.js +10 -4
  134. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  135. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  136. package/modules/use-cases/get-module.js +10 -4
  137. package/modules/use-cases/manage-access.js +21 -0
  138. package/modules/use-cases/multi-step-authorization.js +329 -0
  139. package/modules/use-cases/process-authorization-callback.js +53 -19
  140. package/modules/use-cases/refresh-entity-options.js +10 -4
  141. package/modules/use-cases/test-module-auth.js +10 -4
  142. package/package.json +6 -5
  143. package/prisma-mongodb/schema.prisma +92 -0
  144. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
  145. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
  146. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
  147. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
  148. package/prisma-postgresql/schema.prisma +87 -0
  149. package/providers/index.js +23 -0
  150. package/providers/resolve-provider.js +93 -0
  151. package/providers/runtime-detection.js +124 -0
  152. package/queues/index.js +25 -0
  153. package/queues/providers/index.js +11 -0
  154. package/queues/providers/netlify-background-provider.js +150 -0
  155. package/queues/providers/qstash-queue-provider.js +219 -0
  156. package/queues/providers/sqs-queue-provider.js +70 -0
  157. package/queues/queue-client-interface.js +55 -0
  158. package/queues/queue-message-auth.js +193 -0
  159. package/queues/queue-provider-factory.js +101 -0
  160. package/queues/queue-provider.js +65 -0
  161. package/queues/sqs-queue-client.js +81 -0
  162. package/types/index.d.ts +2 -0
  163. package/types/modules/index.d.ts +197 -0
  164. package/types/user/index.d.ts +134 -0
  165. package/user/adopter-jwt-config.js +177 -0
  166. package/user/adopter-jwt-errors.js +59 -0
  167. package/user/adopter-token-verifier.js +122 -0
  168. package/user/app-user-helpers.js +97 -0
  169. package/user/create-user-authentication.js +64 -0
  170. package/user/membership.js +51 -0
  171. package/user/repositories/organization-membership-record.js +19 -0
  172. package/user/repositories/organization-membership-repository-documentdb.js +106 -0
  173. package/user/repositories/organization-membership-repository-factory.js +42 -0
  174. package/user/repositories/organization-membership-repository-interface.js +57 -0
  175. package/user/repositories/organization-membership-repository-mongo.js +13 -0
  176. package/user/repositories/organization-membership-repository-postgres.js +29 -0
  177. package/user/repositories/organization-membership-repository-prisma.js +81 -0
  178. package/user/repositories/user-repository-documentdb.js +27 -2
  179. package/user/repositories/user-repository-interface.js +18 -0
  180. package/user/repositories/user-repository-mongo.js +24 -0
  181. package/user/repositories/user-repository-postgres.js +18 -0
  182. package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
  183. package/user/use-cases/authenticate-user.js +7 -1
  184. package/user/use-cases/ensure-organization-membership.js +92 -0
  185. package/user/use-cases/find-individual-users-by-organization.js +54 -0
  186. package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
  187. package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
  188. package/user/user.js +64 -1
@@ -27,13 +27,19 @@ 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 user creating the integration.
30
+ * @param {string} userId - ID of the OWNER of the integration (ADR-056 §3):
31
+ * the person or the organization's user, as ResolveIntegrationOwnership decided.
31
32
  * @param {Object} config - Configuration object for the integration.
32
33
  * @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).
33
39
  * @returns {Promise<Object>} The created or reused integration DTO.
34
40
  * @throws {Error} When integration class is not found for the specified type.
35
41
  */
36
- async execute(entities, userId, config) {
42
+ async execute(entities, userId, config, tenancy = {}) {
37
43
  console.log(
38
44
  `[Frigg] Creating ${config?.type} integration for user ${userId} with entities [${entities}]`
39
45
  );
@@ -41,7 +47,8 @@ class CreateIntegration {
41
47
  const existing = await this._findDuplicate(
42
48
  userId,
43
49
  config?.type,
44
- entities
50
+ entities,
51
+ tenancy
45
52
  );
46
53
  if (existing) {
47
54
  console.log(
@@ -50,11 +57,18 @@ class CreateIntegration {
50
57
  return this._reuseExisting(existing, config);
51
58
  }
52
59
 
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
+
53
66
  const integrationRecord =
54
67
  await this.integrationRepository.createIntegration(
55
68
  entities,
56
69
  userId,
57
- config
70
+ config,
71
+ { organizationId: tenancy.organizationId ?? null }
58
72
  );
59
73
  console.log(
60
74
  `[Frigg] Created integration ${integrationRecord.id} for user ${userId}`
@@ -65,7 +79,8 @@ class CreateIntegration {
65
79
  const survivor = await this._findDuplicate(
66
80
  userId,
67
81
  config?.type,
68
- entities
82
+ entities,
83
+ tenancy
69
84
  );
70
85
  if (survivor && String(survivor.id) !== String(integrationRecord.id)) {
71
86
  console.log(
@@ -78,7 +93,8 @@ class CreateIntegration {
78
93
  }
79
94
 
80
95
  const integrationInstance = await this._buildInstance(
81
- integrationRecord
96
+ integrationRecord,
97
+ { integrationClass, modules }
82
98
  );
83
99
  console.log(
84
100
  `[Frigg] Sending ON_CREATE for integration ${integrationRecord.id}`
@@ -135,7 +151,7 @@ class CreateIntegration {
135
151
  return mapIntegrationClassToIntegrationDTO(integrationInstance);
136
152
  }
137
153
 
138
- async _findDuplicate(userId, type, entityIds) {
154
+ async _findDuplicate(userId, type, entityIds, tenancy = {}) {
139
155
  const candidates =
140
156
  await this.integrationRepository.findIntegrationsByUserId(userId);
141
157
  const target = [...(entityIds ?? [])].map(String).sort();
@@ -150,6 +166,7 @@ class CreateIntegration {
150
166
  // matching how teardown failures are already handled here.
151
167
  if (integration.status === 'IN_DELETION') return false;
152
168
  if (integration.config?.type !== type) return false;
169
+ if (!this._sameOrganization(integration, tenancy)) return false;
153
170
  const current = [...(integration.entitiesIds ?? [])]
154
171
  .map(String)
155
172
  .sort();
@@ -162,6 +179,15 @@ class CreateIntegration {
162
179
  return matches.length ? this._pickOldest(matches) : null;
163
180
  }
164
181
 
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
+
165
191
  _pickOldest(records) {
166
192
  return [...records].sort((a, b) => {
167
193
  const diff =
@@ -173,27 +199,36 @@ class CreateIntegration {
173
199
  })[0];
174
200
  }
175
201
 
176
- async _buildInstance(integrationRecord) {
202
+ _requireClass(type) {
177
203
  const integrationClass = this.integrationClasses.find(
178
- (integrationClass) =>
179
- integrationClass.Definition.name ===
180
- integrationRecord.config.type
204
+ (integrationClass) => integrationClass.Definition.name === type
181
205
  );
182
-
183
206
  if (!integrationClass) {
184
- throw new Error(
185
- `No integration class found for type: ${integrationRecord.config.type}`
186
- );
207
+ throw new Error(`No integration class found for type: ${type}`);
187
208
  }
209
+ return integrationClass;
210
+ }
188
211
 
212
+ async _loadModules(entityIds, userId) {
189
213
  const modules = [];
190
- for (const entityId of integrationRecord.entitiesIds) {
191
- const moduleInstance = await this.moduleFactory.getModuleInstance(
192
- entityId,
193
- integrationRecord.userId
214
+ for (const entityId of entityIds) {
215
+ modules.push(
216
+ await this.moduleFactory.getModuleInstance(entityId, userId)
194
217
  );
195
- modules.push(moduleInstance);
196
218
  }
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
+ ));
197
232
 
198
233
  const integrationInstance = new integrationClass({
199
234
  id: integrationRecord.id,
@@ -0,0 +1,100 @@
1
+ const {
2
+ SCHEDULE_SCOPES,
3
+ resolveScheduleConfig,
4
+ } = require('../schedule-config');
5
+
6
+ const ONGOING_SYNC_EVENT = 'ONGOING_SYNC';
7
+ const ACTIVE_STATUS = 'ENABLED';
8
+
9
+ /**
10
+ * Dispatches one cron tick of ONGOING_SYNC to the queue worker, per each
11
+ * integration class's `Definition.schedule.scope` (schedule-config.js):
12
+ *
13
+ * - 'class' (default): one message per class, `data: {}`.
14
+ * - 'installation': one message per ENABLED integration record of the class,
15
+ * `data: { integrationId }`, so the worker hydrates that installation;
16
+ * skipped when the definition's `isDue(integration, { now })` says no.
17
+ *
18
+ * Only classes that implement `startOngoingSync` take part. A failure for one
19
+ * class or installation is counted and logged; the rest still go out.
20
+ */
21
+ class DispatchOngoingSyncs {
22
+ /**
23
+ * @param {Object} params
24
+ * @param {Object} params.integrationRepository - findIntegrations({ type, status })
25
+ * @param {Object} params.queueProvider - send(message, queueId)
26
+ * @param {string} params.queueId - the worker the messages go to
27
+ * @param {() => Date} [params.now]
28
+ * @param {Object} [params.logger=console]
29
+ */
30
+ constructor({ integrationRepository, queueProvider, queueId, now = () => new Date(), logger = console }) {
31
+ this.integrationRepository = integrationRepository;
32
+ this.queueProvider = queueProvider;
33
+ this.queueId = queueId;
34
+ this.now = now;
35
+ this.logger = logger;
36
+ }
37
+
38
+ /**
39
+ * @param {Array<Function>} integrationClasses
40
+ * @returns {Promise<{ dispatched: number, notDue: number, errors: number }>}
41
+ */
42
+ async execute(integrationClasses = []) {
43
+ const result = { dispatched: 0, notDue: 0, errors: 0 };
44
+ const now = this.now();
45
+
46
+ for (const IntegrationClass of integrationClasses) {
47
+ const name = IntegrationClass?.Definition?.name;
48
+ if (!name) continue;
49
+ if (typeof IntegrationClass.prototype?.startOngoingSync !== 'function') continue;
50
+
51
+ try {
52
+ const { scope, isDue } = resolveScheduleConfig(IntegrationClass.Definition);
53
+ if (scope === SCHEDULE_SCOPES.INSTALLATION) {
54
+ await this._dispatchPerInstallation(name, isDue, now, result);
55
+ } else {
56
+ await this._send(name, {});
57
+ result.dispatched++;
58
+ }
59
+ } catch (error) {
60
+ result.errors++;
61
+ this.logger.error(`[ScheduledSync] Failed to dispatch sync for ${name}:`, error.message);
62
+ }
63
+ }
64
+
65
+ return result;
66
+ }
67
+
68
+ async _dispatchPerInstallation(name, isDue, now, result) {
69
+ const integrations = await this.integrationRepository.findIntegrations({
70
+ type: name,
71
+ status: ACTIVE_STATUS,
72
+ });
73
+
74
+ for (const integration of integrations || []) {
75
+ try {
76
+ if (isDue && (await isDue(integration, { now })) !== true) {
77
+ result.notDue++;
78
+ continue;
79
+ }
80
+ await this._send(name, { integrationId: integration.id });
81
+ result.dispatched++;
82
+ } catch (error) {
83
+ result.errors++;
84
+ this.logger.error(
85
+ `[ScheduledSync] Failed to dispatch sync for ${name} integration ${integration?.id}:`,
86
+ error.message
87
+ );
88
+ }
89
+ }
90
+ }
91
+
92
+ _send(integrationName, data) {
93
+ return this.queueProvider.send(
94
+ { integrationName, event: ONGOING_SYNC_EVENT, data },
95
+ this.queueId
96
+ );
97
+ }
98
+ }
99
+
100
+ module.exports = { DispatchOngoingSyncs, ONGOING_SYNC_EVENT };
@@ -2,9 +2,17 @@
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');
5
11
 
6
12
  /**
7
- * Use case for retrieving all integrations for a specific user.
13
+ * Use case for retrieving the integrations a caller may see: the ones they own
14
+ * or their active organization owns, created in that organization (ADR-056
15
+ * §3.6), each with `tenancy` ({scope, owner, organizationId, access}).
8
16
  * @class GetIntegrationsForUser
9
17
  */
10
18
  class GetIntegrationsForUser {
@@ -15,13 +23,16 @@ class GetIntegrationsForUser {
15
23
  * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes.
16
24
  * @param {import('../../modules/module-factory').ModuleFactory} params.moduleFactory - Service for module instantiation and management.
17
25
  * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module and entity data operations.
26
+ * @param {Object} [params.scopePolicy] - from createScopePolicy
18
27
  */
19
28
  constructor({
20
29
  integrationRepository,
21
30
  integrationClasses,
22
31
  moduleFactory,
23
32
  moduleRepository,
33
+ scopePolicy,
24
34
  }) {
35
+ this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
25
36
  /**
26
37
  * @type {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface}
27
38
  */
@@ -32,57 +43,86 @@ class GetIntegrationsForUser {
32
43
  }
33
44
 
34
45
  /**
35
- * Executes the retrieval of all integrations for a user.
36
46
  * @async
37
- * @param {string} userId - ID of the user whose integrations to retrieve.
38
- * @returns {Promise<Object[]>} Array of integration DTOs for the specified user.
47
+ * @param {Object} user - authenticated User
48
+ * @param {{scope?: string, owner?: string}} [filters] - `?scope=` and `?owner=`
49
+ * @returns {Promise<Object[]>} Array of integration DTOs.
39
50
  */
40
- async execute(userId) {
41
- const integrationRecords =
42
- await this.integrationRepository.findIntegrationsByUserId(userId);
51
+ async execute(user, filters = {}) {
52
+ const ctx = this.scopePolicy.accessContext(user);
53
+ const records = await this.integrationRepository.findIntegrationsByOwnership(
54
+ visibilityClauses(ctx, 'integration')
55
+ );
43
56
 
44
57
  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
+ }
45
67
 
46
- for (const integrationRecord of integrationRecords) {
47
- const entities = await this.moduleRepository.findEntitiesByIds(
48
- integrationRecord.entitiesIds
49
- );
68
+ /** Hydrates one stored integration record into its DTO. */
69
+ async toDto(integrationRecord) {
70
+ const entities = await this.moduleRepository.findEntitiesByIds(
71
+ integrationRecord.entitiesIds
72
+ );
73
+
74
+ const integrationClass = this.integrationClasses.find(
75
+ (integrationClass) =>
76
+ integrationClass.Definition.name ===
77
+ integrationRecord.config.type
78
+ );
50
79
 
51
- const integrationClass = this.integrationClasses.find(
52
- (integrationClass) =>
53
- integrationClass.Definition.name ===
54
- integrationRecord.config.type
80
+ const modules = [];
81
+ for (const entity of entities) {
82
+ const moduleInstance = await this.moduleFactory.getModuleInstance(
83
+ entity.id,
84
+ integrationRecord.userId
55
85
  );
86
+ modules.push(moduleInstance);
87
+ }
56
88
 
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
- }
89
+ return mapIntegrationClassToIntegrationDTO({
90
+ id: integrationRecord.id,
91
+ userId: integrationRecord.userId,
92
+ entities: entities,
93
+ config: integrationRecord.config,
94
+ status: integrationRecord.status,
95
+ version: integrationRecord.version,
96
+ messages: integrationRecord.messages || { errors: [], warnings: [] },
97
+ modules,
98
+ options: integrationClass.getOptionDetails(),
99
+ });
100
+ }
101
+ }
66
102
 
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
- };
103
+ /**
104
+ * Every integration one user id owns, with no organization rules: the lookup
105
+ * behind the app-level `friggCommands.findIntegrationsByUserId`, which has a
106
+ * user id and no request.
107
+ */
108
+ class GetIntegrationsByUserId {
109
+ /** @param {ConstructorParameters<typeof GetIntegrationsForUser>[0]} params */
110
+ constructor(params) {
111
+ this.integrationRepository = params.integrationRepository;
112
+ this.hydrator = new GetIntegrationsForUser(params);
113
+ }
78
114
 
79
- integrations.push(
80
- mapIntegrationClassToIntegrationDTO(integrationData)
81
- );
115
+ /** @param {string} userId */
116
+ async execute(userId) {
117
+ const records = await this.integrationRepository.findIntegrationsByUserId(
118
+ userId
119
+ );
120
+ const integrations = [];
121
+ for (const record of records) {
122
+ integrations.push(await this.hydrator.toDto(record));
82
123
  }
83
-
84
124
  return integrations;
85
125
  }
86
126
  }
87
127
 
88
- module.exports = { GetIntegrationsForUser };
128
+ module.exports = { GetIntegrationsForUser, GetIntegrationsByUserId };
@@ -1,4 +1,7 @@
1
- const { GetIntegrationsForUser } = require('./get-integrations-for-user');
1
+ const {
2
+ GetIntegrationsForUser,
3
+ GetIntegrationsByUserId,
4
+ } = require('./get-integrations-for-user');
2
5
  const { DeleteIntegrationForUser } = require('./delete-integration-for-user');
3
6
  const { CreateIntegration } = require('./create-integration');
4
7
  const { GetIntegration } = require('./get-integration');
@@ -6,9 +9,11 @@ const { CreateProcess } = require('./create-process');
6
9
  const { UpdateProcessState } = require('./update-process-state');
7
10
  const { UpdateProcessMetrics } = require('./update-process-metrics');
8
11
  const { GetProcess } = require('./get-process');
12
+ const { DispatchOngoingSyncs } = require('./dispatch-ongoing-syncs');
9
13
 
10
14
  module.exports = {
11
15
  GetIntegrationsForUser,
16
+ GetIntegrationsByUserId,
12
17
  DeleteIntegrationForUser,
13
18
  CreateIntegration,
14
19
  GetIntegration,
@@ -16,4 +21,5 @@ module.exports = {
16
21
  UpdateProcessState,
17
22
  UpdateProcessMetrics,
18
23
  GetProcess,
24
+ DispatchOngoingSyncs,
19
25
  };