@friggframework/core 2.0.0-next.7 → 2.0.0-next.71
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 +694 -0
- package/README.md +959 -50
- package/application/commands/README.md +451 -0
- package/application/commands/credential-commands.js +245 -0
- package/application/commands/entity-commands.js +336 -0
- package/application/commands/integration-commands.js +210 -0
- package/application/commands/scheduler-commands.js +263 -0
- package/application/commands/user-commands.js +283 -0
- package/application/index.js +73 -0
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +8 -21
- package/core/create-handler.js +14 -7
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +269 -0
- package/credential/repositories/credential-repository-postgres.js +287 -0
- package/credential/repositories/credential-repository.js +300 -0
- package/credential/use-cases/get-credential-for-user.js +25 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +154 -0
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +839 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +268 -0
- package/database/encryption/field-encryption-service.js +226 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/prisma-encryption-extension.js +222 -0
- package/database/index.js +61 -21
- package/database/models/WebsocketConnection.js +16 -10
- package/database/models/readme.md +1 -0
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +134 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -0
- package/database/repositories/health-check-repository.js +108 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-health-use-case.js +29 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +83 -0
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/run-database-migration-use-case.js +139 -0
- package/database/use-cases/test-encryption-use-case.js +253 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +91 -0
- package/database/utils/mongodb-schema-init.js +106 -0
- package/database/utils/prisma-runner.js +477 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/encrypt/Cryptor.js +34 -168
- package/encrypt/index.js +1 -2
- package/encrypt/test-encrypt.js +0 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +2 -1
- package/errors/index.js +2 -0
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +335 -0
- package/generated/prisma-mongodb/index-browser.js +317 -0
- package/generated/prisma-mongodb/index.d.ts +22955 -0
- package/generated/prisma-mongodb/index.js +360 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
- package/generated/prisma-mongodb/runtime/binary.js +289 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +362 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +342 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +357 -0
- package/generated/prisma-postgresql/index-browser.js +339 -0
- package/generated/prisma-postgresql/index.d.ts +25131 -0
- package/generated/prisma-postgresql/index.js +382 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
- package/generated/prisma-postgresql/runtime/binary.js +289 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +345 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +364 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +57 -0
- package/handlers/backend-utils.js +186 -0
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/integration-event-dispatcher.js +54 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +15 -0
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +326 -0
- package/handlers/routers/health.js +516 -0
- package/handlers/routers/integration-defined-routers.js +45 -0
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/routers/user.js +63 -0
- package/handlers/routers/websocket.js +57 -0
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
- package/handlers/workers/db-migration.js +352 -0
- package/handlers/workers/integration-defined-workers.js +27 -0
- package/index.js +78 -22
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
- package/infrastructure/scheduler/index.js +33 -0
- package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
- package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +326 -55
- package/integrations/integration-router.js +374 -179
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-documentdb.js +210 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +127 -0
- package/integrations/repositories/integration-repository-mongo.js +303 -0
- package/integrations/repositories/integration-repository-postgres.js +352 -0
- package/integrations/repositories/process-repository-documentdb.js +243 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +90 -0
- package/integrations/repositories/process-repository-mongo.js +190 -0
- package/integrations/repositories/process-repository-postgres.js +217 -0
- package/integrations/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +105 -0
- package/integrations/tests/doubles/test-integration-repository.js +99 -0
- package/integrations/use-cases/create-integration.js +83 -0
- package/integrations/use-cases/create-process.js +128 -0
- package/integrations/use-cases/delete-integration-for-user.js +101 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +88 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/get-process.js +87 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +92 -0
- package/integrations/use-cases/update-process-metrics.js +201 -0
- package/integrations/use-cases/update-process-state.js +119 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/logs/logger.js +0 -4
- package/{module-plugin → modules}/entity.js +1 -1
- package/{module-plugin → modules}/index.js +0 -8
- package/modules/module-factory.js +56 -0
- package/modules/module.js +228 -0
- package/modules/repositories/module-repository-documentdb.js +335 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +408 -0
- package/modules/repositories/module-repository-postgres.js +453 -0
- package/modules/repositories/module-repository.js +345 -0
- package/modules/requester/api-key.js +52 -0
- package/modules/requester/oauth-2.js +396 -0
- package/{module-plugin → modules}/requester/requester.js +4 -2
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +71 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +31 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +133 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +82 -50
- package/prisma-mongodb/schema.prisma +362 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +345 -0
- package/queues/queuer-util.js +27 -22
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/core/index.d.ts +2 -2
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +5 -59
- package/types/syncs/index.d.ts +0 -2
- package/user/repositories/user-repository-documentdb.js +441 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +201 -0
- package/user/repositories/user-repository-mongo.js +308 -0
- package/user/repositories/user-repository-postgres.js +360 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
- package/websocket/repositories/websocket-connection-repository.js +161 -0
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/mongo.js +0 -45
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/oauth-2.js +0 -219
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler Commands
|
|
3
|
+
*
|
|
4
|
+
* Application Layer - Command pattern for scheduling operations.
|
|
5
|
+
*
|
|
6
|
+
* Follows hexagonal architecture:
|
|
7
|
+
* - Receives SchedulerServiceInterface via dependency injection
|
|
8
|
+
* - Contains business logic (validation, logging, error mapping)
|
|
9
|
+
* - Protocol-agnostic (doesn't know about HTTP/Lambda)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const schedulerCommands = createSchedulerCommands({ integrationName: 'zoho' });
|
|
13
|
+
* await schedulerCommands.scheduleJob({
|
|
14
|
+
* jobId: 'zoho-notif-renewal-abc123',
|
|
15
|
+
* scheduledAt: new Date(Date.now() + 6 * 24 * 60 * 60 * 1000), // 6 days
|
|
16
|
+
* event: 'REFRESH_WEBHOOK',
|
|
17
|
+
* payload: { integrationId: 'abc123' },
|
|
18
|
+
* queueUrl: process.env.ZOHO_QUEUE_URL,
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const { createSchedulerService } = require('../../infrastructure/scheduler');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Derive SQS ARN from SQS URL
|
|
26
|
+
*
|
|
27
|
+
* SQS URL format: https://sqs.{region}.amazonaws.com/{account-id}/{queue-name}
|
|
28
|
+
* SQS ARN format: arn:aws:sqs:{region}:{account-id}:{queue-name}
|
|
29
|
+
*
|
|
30
|
+
* @param {string} queueUrl - SQS queue URL
|
|
31
|
+
* @returns {string} SQS queue ARN
|
|
32
|
+
*/
|
|
33
|
+
function deriveArnFromQueueUrl(queueUrl) {
|
|
34
|
+
try {
|
|
35
|
+
const url = new URL(queueUrl);
|
|
36
|
+
const region = url.hostname.split('.')[1];
|
|
37
|
+
const pathParts = url.pathname.split('/').filter(Boolean);
|
|
38
|
+
const accountId = pathParts[0];
|
|
39
|
+
const queueName = pathParts[1];
|
|
40
|
+
return `arn:aws:sqs:${region}:${accountId}:${queueName}`;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(`Invalid SQS queue URL: ${queueUrl}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ERROR_CODE_MAP = {
|
|
47
|
+
SCHEDULER_NOT_CONFIGURED: 503,
|
|
48
|
+
INVALID_JOB_DATA: 400,
|
|
49
|
+
SCHEDULE_NOT_FOUND: 404,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function mapErrorToResponse(error) {
|
|
53
|
+
const status = ERROR_CODE_MAP[error?.code] || 500;
|
|
54
|
+
return {
|
|
55
|
+
error: status,
|
|
56
|
+
reason: error?.message,
|
|
57
|
+
code: error?.code,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create scheduler commands for an integration
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} params
|
|
65
|
+
* @param {string} params.integrationName - Name of the integration (used for logging)
|
|
66
|
+
* @param {SchedulerServiceInterface} [params.schedulerService] - Optional injected scheduler service
|
|
67
|
+
* @returns {Object} Scheduler commands object
|
|
68
|
+
*/
|
|
69
|
+
function createSchedulerCommands({ integrationName, schedulerService }) {
|
|
70
|
+
if (!integrationName) {
|
|
71
|
+
throw new Error('integrationName is required');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Support both dependency injection and lazy creation
|
|
75
|
+
// DI is preferred for testability, lazy creation for convenience
|
|
76
|
+
let _schedulerService = schedulerService || null;
|
|
77
|
+
|
|
78
|
+
function getSchedulerService() {
|
|
79
|
+
if (!_schedulerService) {
|
|
80
|
+
try {
|
|
81
|
+
_schedulerService = createSchedulerService();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.warn(
|
|
84
|
+
`[${integrationName}] Scheduler service not available: ${error.message}`
|
|
85
|
+
);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return _schedulerService;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
/**
|
|
94
|
+
* Schedule a one-time job to be executed at a specific time
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} params
|
|
97
|
+
* @param {string} params.jobId - Unique identifier for the job
|
|
98
|
+
* @param {Date} params.scheduledAt - When to execute the job
|
|
99
|
+
* @param {string} params.event - Event name to trigger
|
|
100
|
+
* @param {Object} params.payload - Additional payload data
|
|
101
|
+
* @param {string} params.queueUrl - Target SQS queue URL (ARN is derived internally)
|
|
102
|
+
* @returns {Promise<{jobArn: string, scheduledAt: string} | {error: number, reason: string}>}
|
|
103
|
+
*/
|
|
104
|
+
async scheduleJob({ jobId, scheduledAt, event, payload, queueUrl }) {
|
|
105
|
+
try {
|
|
106
|
+
if (!jobId) {
|
|
107
|
+
const error = new Error('jobId is required');
|
|
108
|
+
error.code = 'INVALID_JOB_DATA';
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!scheduledAt || !(scheduledAt instanceof Date)) {
|
|
113
|
+
const error = new Error('scheduledAt must be a valid Date');
|
|
114
|
+
error.code = 'INVALID_JOB_DATA';
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!event) {
|
|
119
|
+
const error = new Error('event is required');
|
|
120
|
+
error.code = 'INVALID_JOB_DATA';
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!queueUrl) {
|
|
125
|
+
const error = new Error('queueUrl is required');
|
|
126
|
+
error.code = 'INVALID_JOB_DATA';
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Derive ARN from URL (business logic - transformation)
|
|
131
|
+
const queueArn = deriveArnFromQueueUrl(queueUrl);
|
|
132
|
+
|
|
133
|
+
// Get scheduler service (via DI or factory)
|
|
134
|
+
const service = getSchedulerService();
|
|
135
|
+
if (!service) {
|
|
136
|
+
console.warn(
|
|
137
|
+
`[${integrationName}] Scheduler not configured, skipping job schedule`
|
|
138
|
+
);
|
|
139
|
+
return {
|
|
140
|
+
jobId,
|
|
141
|
+
jobArn: null,
|
|
142
|
+
scheduledAt: null,
|
|
143
|
+
warning: 'Scheduler not configured',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Build the SQS message payload (business logic - assembly)
|
|
148
|
+
const sqsPayload = {
|
|
149
|
+
event,
|
|
150
|
+
integrationName,
|
|
151
|
+
data: payload || {},
|
|
152
|
+
scheduledAt: scheduledAt.toISOString(),
|
|
153
|
+
createdAt: new Date().toISOString(),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Delegate to service (Port interface)
|
|
157
|
+
const result = await service.scheduleOneTime({
|
|
158
|
+
scheduleName: jobId,
|
|
159
|
+
scheduleAt: scheduledAt,
|
|
160
|
+
queueResourceId: queueArn,
|
|
161
|
+
payload: sqsPayload,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
console.log(
|
|
165
|
+
`[${integrationName}] Scheduled job ${jobId} for ${result.scheduledAt}`
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
jobId,
|
|
170
|
+
jobArn: result.scheduledJobId,
|
|
171
|
+
scheduledAt: result.scheduledAt,
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(
|
|
175
|
+
`[${integrationName}] Failed to schedule job ${jobId}:`,
|
|
176
|
+
error.message
|
|
177
|
+
);
|
|
178
|
+
return mapErrorToResponse(error);
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Delete a scheduled job
|
|
184
|
+
*
|
|
185
|
+
* @param {string} jobId - Job ID to delete
|
|
186
|
+
* @returns {Promise<{success: boolean, jobId: string} | {error: number, reason: string}>}
|
|
187
|
+
*/
|
|
188
|
+
async deleteJob(jobId) {
|
|
189
|
+
try {
|
|
190
|
+
if (!jobId) {
|
|
191
|
+
const error = new Error('jobId is required');
|
|
192
|
+
error.code = 'INVALID_JOB_DATA';
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const service = getSchedulerService();
|
|
197
|
+
if (!service) {
|
|
198
|
+
console.warn(
|
|
199
|
+
`[${integrationName}] Scheduler not configured, skipping job deletion`
|
|
200
|
+
);
|
|
201
|
+
return {
|
|
202
|
+
success: true,
|
|
203
|
+
jobId,
|
|
204
|
+
warning: 'Scheduler not configured',
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await service.deleteSchedule(jobId);
|
|
209
|
+
|
|
210
|
+
console.log(`[${integrationName}] Deleted scheduled job ${jobId}`);
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
success: true,
|
|
214
|
+
jobId,
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(
|
|
218
|
+
`[${integrationName}] Failed to delete job ${jobId}:`,
|
|
219
|
+
error.message
|
|
220
|
+
);
|
|
221
|
+
return mapErrorToResponse(error);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get the status of a scheduled job
|
|
227
|
+
*
|
|
228
|
+
* @param {string} jobId - Job ID to check
|
|
229
|
+
* @returns {Promise<{exists: boolean, scheduledAt?: string, state?: string} | {error: number, reason: string}>}
|
|
230
|
+
*/
|
|
231
|
+
async getJobStatus(jobId) {
|
|
232
|
+
try {
|
|
233
|
+
if (!jobId) {
|
|
234
|
+
const error = new Error('jobId is required');
|
|
235
|
+
error.code = 'INVALID_JOB_DATA';
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const service = getSchedulerService();
|
|
240
|
+
if (!service) {
|
|
241
|
+
return {
|
|
242
|
+
exists: false,
|
|
243
|
+
warning: 'Scheduler not configured',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const status = await service.getScheduleStatus(jobId);
|
|
248
|
+
|
|
249
|
+
return status;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(
|
|
252
|
+
`[${integrationName}] Failed to get job status ${jobId}:`,
|
|
253
|
+
error.message
|
|
254
|
+
);
|
|
255
|
+
return mapErrorToResponse(error);
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = {
|
|
262
|
+
createSchedulerCommands,
|
|
263
|
+
};
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
const {
|
|
2
|
+
createUserRepository,
|
|
3
|
+
} = require('../../user/repositories/user-repository-factory');
|
|
4
|
+
|
|
5
|
+
const ERROR_CODE_MAP = {
|
|
6
|
+
USER_NOT_FOUND: 404,
|
|
7
|
+
USER_ALREADY_EXISTS: 409,
|
|
8
|
+
INVALID_USER_DATA: 400,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function mapErrorToResponse(error) {
|
|
12
|
+
const status = ERROR_CODE_MAP[error?.code] || 500;
|
|
13
|
+
return {
|
|
14
|
+
error: status,
|
|
15
|
+
reason: error?.message,
|
|
16
|
+
code: error?.code,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create user command factory
|
|
22
|
+
*
|
|
23
|
+
* NOTE: This is an internal API. Integration developers should use createFriggCommands() instead.
|
|
24
|
+
*
|
|
25
|
+
* @returns {Object} User command object with CRUD operations
|
|
26
|
+
*/
|
|
27
|
+
function createUserCommands() {
|
|
28
|
+
const userRepository = createUserRepository();
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
/**
|
|
32
|
+
* Create a new individual user
|
|
33
|
+
* @param {Object} params
|
|
34
|
+
* @param {string} params.username - Username (usually email)
|
|
35
|
+
* @param {string} [params.email] - Email address
|
|
36
|
+
* @param {string} [params.appUserId] - External application user ID
|
|
37
|
+
* @param {string} [params.password] - Password (optional)
|
|
38
|
+
* @returns {Promise<Object>} Created user object
|
|
39
|
+
*/
|
|
40
|
+
async createUser({ username, email, appUserId, password } = {}) {
|
|
41
|
+
try {
|
|
42
|
+
if (!username) {
|
|
43
|
+
const error = new Error('username is required');
|
|
44
|
+
error.code = 'INVALID_USER_DATA';
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const userData = { username };
|
|
49
|
+
if (email) userData.email = email;
|
|
50
|
+
if (appUserId) userData.appUserId = appUserId;
|
|
51
|
+
if (password) userData.password = password;
|
|
52
|
+
|
|
53
|
+
const user = await userRepository.createIndividualUser(
|
|
54
|
+
userData
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
id: user.id,
|
|
59
|
+
username: user.username,
|
|
60
|
+
email: user.email,
|
|
61
|
+
appUserId: user.appUserId,
|
|
62
|
+
};
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.code === 11000) {
|
|
65
|
+
// Duplicate key error
|
|
66
|
+
const duplicateError = new Error(
|
|
67
|
+
`User with username '${username}' already exists`
|
|
68
|
+
);
|
|
69
|
+
duplicateError.code = 'USER_ALREADY_EXISTS';
|
|
70
|
+
return mapErrorToResponse(duplicateError);
|
|
71
|
+
}
|
|
72
|
+
return mapErrorToResponse(error);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Find a user by their application user ID
|
|
78
|
+
* @param {string} appUserId - External application user ID
|
|
79
|
+
* @returns {Promise<Object|null>} User object or null if not found
|
|
80
|
+
*/
|
|
81
|
+
async findUserByAppUserId(appUserId) {
|
|
82
|
+
try {
|
|
83
|
+
if (!appUserId) {
|
|
84
|
+
const error = new Error('appUserId is required');
|
|
85
|
+
error.code = 'INVALID_USER_DATA';
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const user = await userRepository.findIndividualUserByAppUserId(
|
|
90
|
+
appUserId
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (!user) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
id: user.id,
|
|
99
|
+
username: user.username,
|
|
100
|
+
email: user.email,
|
|
101
|
+
appUserId: user.appUserId,
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return mapErrorToResponse(error);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Find a user by their username
|
|
110
|
+
* @param {string} username - Username to search for
|
|
111
|
+
* @returns {Promise<Object|null>} User object or null if not found
|
|
112
|
+
*/
|
|
113
|
+
async findUserByUsername(username) {
|
|
114
|
+
try {
|
|
115
|
+
if (!username) {
|
|
116
|
+
const error = new Error('username is required');
|
|
117
|
+
error.code = 'INVALID_USER_DATA';
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const user = await userRepository.findIndividualUserByUsername(
|
|
122
|
+
username
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!user) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
id: user.id,
|
|
131
|
+
username: user.username,
|
|
132
|
+
email: user.email,
|
|
133
|
+
appUserId: user.appUserId,
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return mapErrorToResponse(error);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Find an individual user by their ID
|
|
142
|
+
* @param {string} userId - Individual user ID to search for
|
|
143
|
+
* @returns {Promise<Object|null>} Individual user object or null if not found
|
|
144
|
+
*/
|
|
145
|
+
async findIndividualUserById(userId) {
|
|
146
|
+
try {
|
|
147
|
+
if (!userId) {
|
|
148
|
+
const error = new Error('userId is required');
|
|
149
|
+
error.code = 'INVALID_USER_DATA';
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const user = await userRepository.findIndividualUserById(
|
|
154
|
+
userId
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (!user) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
id: user._id?.toString() || user.id,
|
|
163
|
+
username: user.username,
|
|
164
|
+
email: user.email,
|
|
165
|
+
appUserId: user.appUserId,
|
|
166
|
+
};
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return mapErrorToResponse(error);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Find an organization user by their ID
|
|
174
|
+
* @param {string} userId - Organization user ID to search for
|
|
175
|
+
* @returns {Promise<Object|null>} Organization user object or null if not found
|
|
176
|
+
*/
|
|
177
|
+
async findOrganizationUserById(userId) {
|
|
178
|
+
try {
|
|
179
|
+
if (!userId) {
|
|
180
|
+
const error = new Error('userId is required');
|
|
181
|
+
error.code = 'INVALID_USER_DATA';
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const user = await userRepository.findOrganizationUserById(
|
|
186
|
+
userId
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (!user) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
id: user.id,
|
|
195
|
+
appOrgId: user.appOrgId,
|
|
196
|
+
name: user.name,
|
|
197
|
+
};
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return mapErrorToResponse(error);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Update a user by ID
|
|
205
|
+
* @param {string} userId - User ID to update
|
|
206
|
+
* @param {Object} updates - Fields to update
|
|
207
|
+
* @returns {Promise<Object>} Updated user object
|
|
208
|
+
*/
|
|
209
|
+
async updateUser(userId, updates) {
|
|
210
|
+
try {
|
|
211
|
+
if (!userId) {
|
|
212
|
+
const error = new Error('userId is required');
|
|
213
|
+
error.code = 'INVALID_USER_DATA';
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const user = await userRepository.IndividualUser.update(
|
|
218
|
+
userId,
|
|
219
|
+
updates
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (!user) {
|
|
223
|
+
const error = new Error(`User ${userId} not found`);
|
|
224
|
+
error.code = 'USER_NOT_FOUND';
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
id: user._id.toString(),
|
|
230
|
+
username: user.username,
|
|
231
|
+
email: user.email,
|
|
232
|
+
appUserId: user.appUserId,
|
|
233
|
+
};
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return mapErrorToResponse(error);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Delete a user by ID
|
|
241
|
+
*
|
|
242
|
+
* IMPORTANT: This does NOT automatically cascade delete related records in MongoDB.
|
|
243
|
+
* Integration developers MUST manually delete related data first:
|
|
244
|
+
* 1. Delete integrations (via deleteIntegrationById)
|
|
245
|
+
* 2. Delete entities (via deleteEntityById)
|
|
246
|
+
* 3. Delete credentials (via deleteCredentialById)
|
|
247
|
+
* 4. Finally delete user (via deleteUserById)
|
|
248
|
+
*
|
|
249
|
+
* @param {string} userId - User ID to delete
|
|
250
|
+
* @returns {Promise<Object>} Deletion result
|
|
251
|
+
*/
|
|
252
|
+
async deleteUserById(userId) {
|
|
253
|
+
try {
|
|
254
|
+
if (!userId) {
|
|
255
|
+
const error = new Error('userId is required');
|
|
256
|
+
error.code = 'INVALID_USER_DATA';
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const deleted = await userRepository.deleteUser(userId);
|
|
261
|
+
|
|
262
|
+
if (!deleted) {
|
|
263
|
+
const error = new Error(`User ${userId} not found`);
|
|
264
|
+
error.code = 'USER_NOT_FOUND';
|
|
265
|
+
return mapErrorToResponse(error);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
success: true,
|
|
270
|
+
userId,
|
|
271
|
+
message: 'User deleted successfully',
|
|
272
|
+
};
|
|
273
|
+
} catch (error) {
|
|
274
|
+
return mapErrorToResponse(error);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = {
|
|
281
|
+
createUserCommands,
|
|
282
|
+
ERROR_CODE_MAP,
|
|
283
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const {
|
|
2
|
+
createIntegrationCommands,
|
|
3
|
+
findIntegrationContextByExternalEntityId,
|
|
4
|
+
} = require('./commands/integration-commands');
|
|
5
|
+
const { createUserCommands } = require('./commands/user-commands');
|
|
6
|
+
const { createEntityCommands } = require('./commands/entity-commands');
|
|
7
|
+
const {
|
|
8
|
+
createCredentialCommands,
|
|
9
|
+
} = require('./commands/credential-commands');
|
|
10
|
+
const {
|
|
11
|
+
createSchedulerCommands,
|
|
12
|
+
} = require('./commands/scheduler-commands');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a unified command factory with all CRUD operations
|
|
16
|
+
*
|
|
17
|
+
* This is the main entry point for integration developers to access all
|
|
18
|
+
* database operations without directly touching Mongoose models.
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} params
|
|
21
|
+
* @param {Object} params.integrationClass - Integration class (required)
|
|
22
|
+
* @returns {Object} Unified commands object with all CRUD operations
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const commands = createFriggCommands({ integrationClass: MyIntegration });
|
|
26
|
+
* const user = await commands.createUser({ username: 'user@example.com' });
|
|
27
|
+
* const credential = await commands.createCredential({ userId: user.id, ... });
|
|
28
|
+
*/
|
|
29
|
+
function createFriggCommands({ integrationClass }) {
|
|
30
|
+
// All commands use Frigg's default repositories and use cases
|
|
31
|
+
const integrationCommands = createIntegrationCommands({ integrationClass });
|
|
32
|
+
|
|
33
|
+
const userCommands = createUserCommands();
|
|
34
|
+
|
|
35
|
+
const entityCommands = createEntityCommands();
|
|
36
|
+
|
|
37
|
+
const credentialCommands = createCredentialCommands();
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
// Integration commands
|
|
41
|
+
...integrationCommands,
|
|
42
|
+
|
|
43
|
+
// User commands
|
|
44
|
+
...userCommands,
|
|
45
|
+
|
|
46
|
+
// Entity commands
|
|
47
|
+
...entityCommands,
|
|
48
|
+
|
|
49
|
+
// Credential commands
|
|
50
|
+
...credentialCommands,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
// Unified factory
|
|
56
|
+
createFriggCommands,
|
|
57
|
+
|
|
58
|
+
// Individual factories
|
|
59
|
+
createIntegrationCommands,
|
|
60
|
+
createUserCommands,
|
|
61
|
+
createEntityCommands,
|
|
62
|
+
createCredentialCommands,
|
|
63
|
+
createSchedulerCommands,
|
|
64
|
+
|
|
65
|
+
// Legacy standalone function
|
|
66
|
+
findIntegrationContextByExternalEntityId,
|
|
67
|
+
|
|
68
|
+
// Deprecated - use createFriggCommands instead
|
|
69
|
+
integrationCommands: {
|
|
70
|
+
create: createIntegrationCommands,
|
|
71
|
+
findIntegrationContextByExternalEntityId,
|
|
72
|
+
},
|
|
73
|
+
};
|