@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -67
- package/CLAUDE.md +148 -124
- package/README.md +165 -130
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
- package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
- package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
- package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
- package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
- package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
- package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/admin-script-commands.js +348 -0
- package/application/commands/integration-commands.js +12 -6
- package/application/index.js +5 -3
- package/assertions/get.js +17 -7
- package/associations/association.js +59 -57
- package/core/CLAUDE.md +222 -156
- package/core/Worker.js +33 -12
- package/credential/credential-router.js +262 -0
- package/credential/repositories/credential-repository-documentdb.js +71 -1
- package/credential/repositories/credential-repository-factory.js +4 -6
- package/credential/repositories/credential-repository-interface.js +8 -2
- package/credential/repositories/credential-repository-mongo.js +45 -4
- package/credential/repositories/credential-repository-postgres.js +44 -3
- package/credential/repositories/credential-repository.js +3 -1
- package/credential/use-cases/delete-credential-for-user.js +50 -0
- package/credential/use-cases/get-credential-for-user.js +21 -3
- package/credential/use-cases/list-credentials-for-user.js +31 -0
- package/credential/use-cases/reauthorize-credential.js +103 -0
- package/credential/use-cases/update-authentication-status.js +5 -2
- package/database/MONGODB_TRANSACTION_FIX.md +72 -52
- package/database/config.js +13 -59
- package/database/documentdb-encryption-service.js +29 -13
- package/database/documentdb-utils.js +21 -12
- package/database/encryption/README.md +53 -36
- package/database/encryption/encryption-schema-registry.js +29 -23
- package/database/encryption/field-encryption-service.js +15 -4
- package/database/encryption/logger.js +3 -2
- package/database/encryption/prisma-encryption-extension.js +5 -8
- package/database/prisma.js +22 -14
- package/database/repositories/health-check-repository-factory.js +7 -5
- package/database/repositories/health-check-repository-interface.js +15 -5
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/use-cases/check-database-health-use-case.js +3 -2
- package/database/use-cases/check-database-state-use-case.js +8 -7
- package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
- package/database/use-cases/get-migration-status-use-case.js +4 -2
- package/database/use-cases/run-database-migration-use-case.js +41 -9
- package/database/use-cases/test-encryption-use-case.js +4 -5
- package/database/use-cases/trigger-database-migration-use-case.js +9 -8
- package/database/utils/mongodb-collection-utils.js +11 -3
- package/database/utils/mongodb-schema-init.js +10 -4
- package/database/utils/prisma-runner.js +144 -74
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
- package/encrypt/Cryptor.js +54 -49
- package/encrypt/aes-encryption-key-provider.js +82 -0
- package/encrypt/encryption-key-provider-interface.js +50 -0
- package/errors/fetch-error.js +1 -1
- package/generated/prisma-mongodb/edge.js +59 -4
- package/generated/prisma-mongodb/index-browser.js +49 -1
- package/generated/prisma-mongodb/index.d.ts +4023 -61
- package/generated/prisma-mongodb/index.js +59 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +87 -0
- package/generated/prisma-mongodb/wasm.js +59 -4
- package/generated/prisma-postgresql/edge.js +58 -4
- package/generated/prisma-postgresql/index-browser.js +48 -1
- package/generated/prisma-postgresql/index.d.ts +4738 -54
- package/generated/prisma-postgresql/index.js +58 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +85 -0
- package/generated/prisma-postgresql/wasm.js +58 -4
- package/handlers/WEBHOOKS.md +104 -81
- package/handlers/app-definition-loader.js +53 -10
- package/handlers/app-handler-helpers.js +14 -3
- package/handlers/backend-utils.js +0 -1
- package/handlers/database-migration-handler.js +64 -45
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +170 -135
- package/handlers/routers/admin.js +474 -0
- package/handlers/routers/auth.js +51 -9
- package/handlers/routers/db-migration.handler.js +2 -3
- package/handlers/routers/db-migration.js +102 -66
- package/handlers/routers/docs.js +180 -0
- package/handlers/routers/health.js +84 -283
- package/handlers/routers/integration-defined-routers.js +44 -33
- package/handlers/routers/integration-webhook-routers.js +73 -52
- package/handlers/routers/middleware/loadUser.js +39 -0
- package/handlers/routers/middleware/requireAdmin.js +43 -0
- package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
- package/handlers/routers/user.js +30 -14
- package/handlers/routers/websocket.js +18 -4
- package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
- package/handlers/workers/db-migration.js +74 -35
- package/handlers/workers/integration-defined-workers.js +31 -20
- package/index.js +50 -16
- package/infrastructure/scheduler/index.js +15 -11
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
- package/integrations/WEBHOOK-QUICKSTART.md +21 -14
- package/integrations/index.js +4 -0
- package/integrations/integration-base.js +9 -6
- package/integrations/integration-router.js +1828 -157
- package/integrations/options.js +47 -20
- package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
- package/integrations/repositories/integration-repository-documentdb.js +53 -24
- package/integrations/repositories/integration-repository-factory.js +6 -6
- package/integrations/repositories/integration-repository-interface.js +27 -9
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +4 -7
- package/integrations/repositories/process-repository-interface.js +9 -5
- package/integrations/repositories/process-repository-mongo.js +7 -3
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +9 -8
- package/integrations/tests/doubles/dummy-integration-class.js +81 -8
- package/integrations/tests/doubles/test-integration-repository.js +51 -11
- package/integrations/use-cases/create-integration.js +36 -32
- package/integrations/use-cases/create-process.js +20 -9
- package/integrations/use-cases/delete-integration-for-user.js +5 -4
- package/integrations/use-cases/execute-proxy-request.js +564 -0
- package/integrations/use-cases/get-integration-for-user.js +25 -10
- package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
- package/integrations/use-cases/get-integrations-for-user.js +4 -1
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +5 -6
- package/integrations/use-cases/index.js +3 -1
- package/integrations/use-cases/load-integration-context.js +1 -5
- package/integrations/use-cases/update-process-state.js +10 -8
- package/integrations/utils/map-integration-dto.js +9 -3
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +32 -30
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/domain/entities/AuthorizationSession.js +131 -0
- package/modules/domain/entities/index.js +10 -0
- package/modules/module.js +4 -9
- package/modules/repositories/authorization-session-repository-factory.js +53 -0
- package/modules/repositories/authorization-session-repository-interface.js +75 -0
- package/modules/repositories/authorization-session-repository-mongo.js +169 -0
- package/modules/repositories/authorization-session-repository-postgres.js +170 -0
- package/modules/repositories/module-repository-documentdb.js +89 -41
- package/modules/repositories/module-repository-factory.js +4 -6
- package/modules/repositories/module-repository-interface.js +13 -0
- package/modules/repositories/module-repository-mongo.js +40 -5
- package/modules/repositories/module-repository-postgres.js +42 -5
- package/modules/repositories/module-repository.js +28 -0
- package/modules/requester/api-key.js +0 -2
- package/modules/requester/basic.js +0 -1
- package/modules/requester/oauth-2.js +3 -14
- package/modules/test/mock-api/definition.js +15 -10
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +10 -4
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/delete-module-entity.js +23 -0
- package/modules/use-cases/get-authorization-requirements.js +110 -0
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +8 -6
- package/modules/use-cases/get-module-entity-by-id.js +16 -0
- package/modules/use-cases/get-module.js +12 -9
- package/modules/use-cases/process-authorization-callback.js +12 -6
- package/modules/use-cases/process-authorization-step.js +160 -0
- package/modules/use-cases/refresh-entity-options.js +8 -6
- package/modules/use-cases/start-authorization-session.js +86 -0
- package/modules/use-cases/test-module-auth.js +8 -6
- package/modules/use-cases/update-module-entity.js +27 -0
- package/modules/utils/map-module-dto.js +2 -2
- package/openapi/openapi-spec-generator.js +370 -0
- package/openapi/openapi-v1.yaml +1360 -0
- package/openapi/openapi-v2.yaml +1535 -0
- package/package.json +7 -9
- package/prisma-mongodb/schema.prisma +87 -0
- package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
- package/prisma-postgresql/schema.prisma +85 -0
- package/providers/index.js +13 -0
- package/providers/resolve-provider.js +93 -0
- package/queues/index.js +19 -0
- package/queues/providers/index.js +7 -0
- package/queues/providers/netlify-background-provider.js +76 -0
- package/queues/providers/qstash-queue-provider.js +122 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-provider-factory.js +93 -0
- package/queues/queue-provider.js +46 -0
- package/queues/queuer-util.js +33 -25
- package/syncs/repositories/sync-repository-documentdb.js +39 -19
- package/syncs/repositories/sync-repository-factory.js +4 -4
- package/syncs/repositories/sync-repository-interface.js +6 -2
- package/syncs/repositories/sync-repository-postgres.js +19 -13
- package/syncs/sync.js +104 -100
- package/token/repositories/token-repository-factory.js +4 -6
- package/types/assertions/index.d.ts +72 -72
- package/types/core/index.d.ts +98 -53
- package/types/errors/index.d.ts +61 -54
- package/types/eslint-config/index.d.ts +40 -40
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/prettier-config/index.d.ts +5 -5
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +95 -93
- package/user/repositories/user-repository-factory.js +4 -4
- package/user/tests/doubles/test-user-repository.js +5 -2
- package/user/use-cases/authenticate-user.js +2 -4
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +8 -7
- package/user/use-cases/create-organization-user.js +1 -1
- package/user/use-cases/create-token-for-user-id.js +1 -1
- package/user/use-cases/delete-user.js +47 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
- package/user/use-cases/get-user-from-bearer-token.js +7 -3
- package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +18 -4
- package/utils/backend-path.js +2 -2
- package/utils/index.js +4 -1
- package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
- package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
- package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
- package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
- package/websocket/repositories/websocket-connection-repository.js +26 -20
- package/websocket/websocket-message-sender-interface.js +38 -0
- package/database/adapters/lambda-invoker.js +0 -97
- package/database/repositories/migration-status-repository-s3.js +0 -137
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
ScriptScheduleRepositoryInterface,
|
|
4
|
+
} = require('./script-schedule-repository-interface');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL Script Schedule Repository Adapter
|
|
8
|
+
* Handles script schedule persistence using Prisma with PostgreSQL
|
|
9
|
+
*
|
|
10
|
+
* PostgreSQL-specific characteristics:
|
|
11
|
+
* - Uses Int IDs with autoincrement
|
|
12
|
+
* - Requires ID conversion: String (app layer) ↔ Int (database)
|
|
13
|
+
* - All returned IDs are converted to strings for application layer consistency
|
|
14
|
+
* - scriptName has unique index
|
|
15
|
+
*/
|
|
16
|
+
class ScriptScheduleRepositoryPostgres extends ScriptScheduleRepositoryInterface {
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this.prisma = prisma;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Convert string ID to integer for PostgreSQL queries
|
|
24
|
+
* @private
|
|
25
|
+
* @param {string|number|null|undefined} id - ID to convert
|
|
26
|
+
* @returns {number|null|undefined} Integer ID or null/undefined
|
|
27
|
+
* @throws {Error} If ID cannot be converted to integer
|
|
28
|
+
*/
|
|
29
|
+
_convertId(id) {
|
|
30
|
+
if (id === null || id === undefined) return id;
|
|
31
|
+
const parsed = Number.parseInt(id, 10);
|
|
32
|
+
if (Number.isNaN(parsed)) {
|
|
33
|
+
throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert schedule object IDs to strings
|
|
40
|
+
* @private
|
|
41
|
+
* @param {Object|null} schedule - Schedule object from database
|
|
42
|
+
* @returns {Object|null} Schedule with string IDs
|
|
43
|
+
*/
|
|
44
|
+
_convertScheduleIds(schedule) {
|
|
45
|
+
if (!schedule) return schedule;
|
|
46
|
+
return {
|
|
47
|
+
...schedule,
|
|
48
|
+
id: schedule.id?.toString(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Find a schedule by script name
|
|
54
|
+
*
|
|
55
|
+
* @param {string} scriptName - The script name
|
|
56
|
+
* @returns {Promise<Object|null>} Schedule record with string ID or null if not found
|
|
57
|
+
*/
|
|
58
|
+
async findScheduleByScriptName(scriptName) {
|
|
59
|
+
const schedule = await this.prisma.scriptSchedule.findUnique({
|
|
60
|
+
where: { scriptName },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return this._convertScheduleIds(schedule);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create or update a schedule (upsert)
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} params - Schedule parameters
|
|
70
|
+
* @param {string} params.scriptName - Name of the script
|
|
71
|
+
* @param {boolean} params.enabled - Whether schedule is enabled
|
|
72
|
+
* @param {string} params.cronExpression - Cron expression
|
|
73
|
+
* @param {string} [params.timezone] - Timezone (default 'UTC')
|
|
74
|
+
* @param {string} [params.externalScheduleId] - External scheduler ID (e.g., AWS ARN)
|
|
75
|
+
* @param {string} [params.externalScheduleName] - External scheduler name
|
|
76
|
+
* @returns {Promise<Object>} Created or updated schedule record with string ID
|
|
77
|
+
*/
|
|
78
|
+
async upsertSchedule({ scriptName, enabled, cronExpression, timezone, externalScheduleId, externalScheduleName }) {
|
|
79
|
+
const data = {
|
|
80
|
+
enabled,
|
|
81
|
+
cronExpression,
|
|
82
|
+
timezone: timezone || 'UTC',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Only set external scheduler fields if provided
|
|
86
|
+
if (externalScheduleId !== undefined) data.externalScheduleId = externalScheduleId;
|
|
87
|
+
if (externalScheduleName !== undefined) data.externalScheduleName = externalScheduleName;
|
|
88
|
+
|
|
89
|
+
const schedule = await this.prisma.scriptSchedule.upsert({
|
|
90
|
+
where: { scriptName },
|
|
91
|
+
update: data,
|
|
92
|
+
create: {
|
|
93
|
+
scriptName,
|
|
94
|
+
...data,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return this._convertScheduleIds(schedule);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Delete a schedule by script name
|
|
103
|
+
*
|
|
104
|
+
* @param {string} scriptName - The script name
|
|
105
|
+
* @returns {Promise<Object>} Deletion result
|
|
106
|
+
*/
|
|
107
|
+
async deleteSchedule(scriptName) {
|
|
108
|
+
try {
|
|
109
|
+
const schedule = await this.prisma.scriptSchedule.delete({
|
|
110
|
+
where: { scriptName },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
acknowledged: true,
|
|
115
|
+
deletedCount: 1,
|
|
116
|
+
deleted: this._convertScheduleIds(schedule),
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// Return 0 count if not found
|
|
120
|
+
if (error.code === 'P2025') {
|
|
121
|
+
return {
|
|
122
|
+
acknowledged: true,
|
|
123
|
+
deletedCount: 0,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Update external scheduler information
|
|
132
|
+
*
|
|
133
|
+
* @param {string} scriptName - The script name
|
|
134
|
+
* @param {Object} externalInfo - External schedule information
|
|
135
|
+
* @param {string} [externalInfo.externalScheduleId] - External scheduler ID (e.g., AWS ARN)
|
|
136
|
+
* @param {string} [externalInfo.externalScheduleName] - External scheduler name
|
|
137
|
+
* @returns {Promise<Object>} Updated schedule record with string ID
|
|
138
|
+
*/
|
|
139
|
+
async updateScheduleExternalInfo(scriptName, { externalScheduleId, externalScheduleName }) {
|
|
140
|
+
const data = {};
|
|
141
|
+
if (externalScheduleId !== undefined) data.externalScheduleId = externalScheduleId;
|
|
142
|
+
if (externalScheduleName !== undefined) data.externalScheduleName = externalScheduleName;
|
|
143
|
+
|
|
144
|
+
const schedule = await this.prisma.scriptSchedule.update({
|
|
145
|
+
where: { scriptName },
|
|
146
|
+
data,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return this._convertScheduleIds(schedule);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Update last triggered timestamp
|
|
154
|
+
*
|
|
155
|
+
* @param {string} scriptName - The script name
|
|
156
|
+
* @param {Date} [timestamp] - Trigger timestamp (default: now)
|
|
157
|
+
* @returns {Promise<Object>} Updated schedule record with string ID
|
|
158
|
+
*/
|
|
159
|
+
async updateScheduleLastTriggered(scriptName, timestamp) {
|
|
160
|
+
const schedule = await this.prisma.scriptSchedule.update({
|
|
161
|
+
where: { scriptName },
|
|
162
|
+
data: {
|
|
163
|
+
lastTriggeredAt: timestamp || new Date(),
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return this._convertScheduleIds(schedule);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Update next trigger timestamp
|
|
172
|
+
*
|
|
173
|
+
* @param {string} scriptName - The script name
|
|
174
|
+
* @param {Date} timestamp - Next trigger timestamp
|
|
175
|
+
* @returns {Promise<Object>} Updated schedule record with string ID
|
|
176
|
+
*/
|
|
177
|
+
async updateScheduleNextTrigger(scriptName, timestamp) {
|
|
178
|
+
const schedule = await this.prisma.scriptSchedule.update({
|
|
179
|
+
where: { scriptName },
|
|
180
|
+
data: {
|
|
181
|
+
nextTriggerAt: timestamp,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return this._convertScheduleIds(schedule);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* List all schedules
|
|
190
|
+
*
|
|
191
|
+
* @param {Object} [options] - Query options
|
|
192
|
+
* @param {boolean} [options.enabledOnly] - Only return enabled schedules
|
|
193
|
+
* @returns {Promise<Array>} Array of schedule records with string IDs
|
|
194
|
+
*/
|
|
195
|
+
async listSchedules(options = {}) {
|
|
196
|
+
const where = {};
|
|
197
|
+
if (options.enabledOnly) {
|
|
198
|
+
where.enabled = true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const schedules = await this.prisma.scriptSchedule.findMany({
|
|
202
|
+
where,
|
|
203
|
+
orderBy: { scriptName: 'asc' },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return schedules.map((schedule) => this._convertScheduleIds(schedule));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = { ScriptScheduleRepositoryPostgres };
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
const ERROR_CODE_MAP = {
|
|
2
|
+
SCRIPT_NOT_FOUND: 404,
|
|
3
|
+
EXECUTION_NOT_FOUND: 404,
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
function mapErrorToResponse(error) {
|
|
7
|
+
const status = ERROR_CODE_MAP[error?.code] || 500;
|
|
8
|
+
return { error: status, reason: error?.message, code: error?.code };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create admin script commands
|
|
13
|
+
* Provides command pattern API for admin script management
|
|
14
|
+
*
|
|
15
|
+
* This follows the Command pattern from integration-commands.js:
|
|
16
|
+
* - Creates repositories via factory functions
|
|
17
|
+
* - Maps errors to HTTP-friendly responses
|
|
18
|
+
* - Returns data or error objects (never throws)
|
|
19
|
+
*
|
|
20
|
+
* WHY SEPARATE FROM integration-commands.js:
|
|
21
|
+
* These commands are intentionally separate because they serve different domains:
|
|
22
|
+
* - integration-commands: User-context operations on integrations
|
|
23
|
+
* - Requires integrationClass constructor parameter
|
|
24
|
+
* - Works with userId, entityIds, integration contexts
|
|
25
|
+
* - Uses IntegrationRepository, ModuleRepository
|
|
26
|
+
* - admin-script-commands: System/admin operations without user context
|
|
27
|
+
* - No user context required
|
|
28
|
+
* - Works with AdminProcess, ScriptSchedule
|
|
29
|
+
* - Uses AdminProcessRepository, ScriptScheduleRepository
|
|
30
|
+
*
|
|
31
|
+
* Merging them would violate SRP and create coupling between
|
|
32
|
+
* user-facing integration code and admin/system code.
|
|
33
|
+
*
|
|
34
|
+
* Authentication:
|
|
35
|
+
* - Uses ENV-based ADMIN_API_KEY (see handlers/middleware/admin-auth.js)
|
|
36
|
+
* - No database-backed API keys (simplified from original design)
|
|
37
|
+
*
|
|
38
|
+
* @returns {Object} Command methods for admin scripts
|
|
39
|
+
*/
|
|
40
|
+
function createAdminScriptCommands() {
|
|
41
|
+
// Lazy-load repository factories to avoid circular dependencies
|
|
42
|
+
const { createAdminProcessRepository } = require('../../admin-scripts/repositories/admin-process-repository-factory');
|
|
43
|
+
const { createScriptScheduleRepository } = require('../../admin-scripts/repositories/script-schedule-repository-factory');
|
|
44
|
+
|
|
45
|
+
const adminProcessRepository = createAdminProcessRepository();
|
|
46
|
+
const scheduleRepository = createScriptScheduleRepository();
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
// ==================== Admin Process Management Commands ====================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a new admin process record
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} params - Process creation parameters
|
|
55
|
+
* @param {string} params.scriptName - Name of script being executed
|
|
56
|
+
* @param {string} [params.scriptVersion] - Script version
|
|
57
|
+
* @param {string} params.trigger - Trigger type ('MANUAL', 'SCHEDULED', 'QUEUE', 'WEBHOOK')
|
|
58
|
+
* @param {string} [params.mode] - Execution mode ('sync' or 'async', default 'async')
|
|
59
|
+
* @param {Object} [params.input] - Input parameters
|
|
60
|
+
* @param {Object} [params.audit] - Audit information (apiKeyName, apiKeyLast4, ipAddress)
|
|
61
|
+
* @returns {Promise<Object>} Created admin process record
|
|
62
|
+
*/
|
|
63
|
+
async createAdminProcess({
|
|
64
|
+
scriptName,
|
|
65
|
+
scriptVersion,
|
|
66
|
+
trigger,
|
|
67
|
+
mode,
|
|
68
|
+
input,
|
|
69
|
+
audit,
|
|
70
|
+
}) {
|
|
71
|
+
try {
|
|
72
|
+
const process = await adminProcessRepository.createProcess({
|
|
73
|
+
name: scriptName,
|
|
74
|
+
type: 'ADMIN_SCRIPT',
|
|
75
|
+
context: {
|
|
76
|
+
scriptVersion,
|
|
77
|
+
trigger,
|
|
78
|
+
mode: mode || 'async',
|
|
79
|
+
input,
|
|
80
|
+
audit,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
return process;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return mapErrorToResponse(error);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Find an admin process by ID
|
|
91
|
+
*
|
|
92
|
+
* @param {string|number} processId - The admin process ID
|
|
93
|
+
* @returns {Promise<Object>} Admin process record or error
|
|
94
|
+
*/
|
|
95
|
+
async findAdminProcessById(processId) {
|
|
96
|
+
try {
|
|
97
|
+
const process = await adminProcessRepository.findProcessById(processId);
|
|
98
|
+
if (!process) {
|
|
99
|
+
const error = new Error(`Execution ${processId} not found`);
|
|
100
|
+
error.code = 'EXECUTION_NOT_FOUND';
|
|
101
|
+
return mapErrorToResponse(error);
|
|
102
|
+
}
|
|
103
|
+
return process;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return mapErrorToResponse(error);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Find all admin processes for a specific script
|
|
111
|
+
*
|
|
112
|
+
* @param {string} scriptName - Script name to filter by
|
|
113
|
+
* @param {Object} [options] - Query options (limit, offset, sortBy, sortOrder)
|
|
114
|
+
* @returns {Promise<Array>} Array of admin process records
|
|
115
|
+
*/
|
|
116
|
+
async findAdminProcessesByName(scriptName, options = {}) {
|
|
117
|
+
try {
|
|
118
|
+
const processes = await adminProcessRepository.findProcessesByName(
|
|
119
|
+
scriptName,
|
|
120
|
+
options
|
|
121
|
+
);
|
|
122
|
+
return processes;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
// Return empty array on error (non-critical)
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Update admin process state
|
|
131
|
+
*
|
|
132
|
+
* @param {string|number} processId - The admin process ID
|
|
133
|
+
* @param {string} state - New state ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED')
|
|
134
|
+
* @returns {Promise<Object>} Updated admin process record
|
|
135
|
+
*/
|
|
136
|
+
async updateAdminProcessState(processId, state) {
|
|
137
|
+
try {
|
|
138
|
+
const updated = await adminProcessRepository.updateProcessState(
|
|
139
|
+
processId,
|
|
140
|
+
state
|
|
141
|
+
);
|
|
142
|
+
return updated;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return mapErrorToResponse(error);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Append a log entry to an admin process's results.logs array
|
|
150
|
+
*
|
|
151
|
+
* @param {string|number} processId - The admin process ID
|
|
152
|
+
* @param {Object} logEntry - Log entry { level, message, data, timestamp }
|
|
153
|
+
* @returns {Promise<Object>} Updated admin process record
|
|
154
|
+
*/
|
|
155
|
+
async appendAdminProcessLog(processId, logEntry) {
|
|
156
|
+
try {
|
|
157
|
+
const updated = await adminProcessRepository.appendProcessLog(
|
|
158
|
+
processId,
|
|
159
|
+
logEntry
|
|
160
|
+
);
|
|
161
|
+
return updated;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return mapErrorToResponse(error);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Complete an admin process
|
|
169
|
+
* Updates state, output, error, and metrics
|
|
170
|
+
*
|
|
171
|
+
* @param {string|number} processId - The admin process ID
|
|
172
|
+
* @param {Object} params - Completion parameters
|
|
173
|
+
* @param {string} [params.state] - Final state ('COMPLETED', 'FAILED')
|
|
174
|
+
* @param {Object} [params.output] - Script output/result (stored in results.output)
|
|
175
|
+
* @param {Object} [params.error] - Error details { name, message, stack } (stored in results.error)
|
|
176
|
+
* @param {Object} [params.metrics] - Performance metrics { startTime, endTime, durationMs } (stored in results.metrics)
|
|
177
|
+
* @returns {Promise<Object>} { success: true } or error
|
|
178
|
+
*/
|
|
179
|
+
async completeAdminProcess(processId, { state, output, error, metrics }) {
|
|
180
|
+
try {
|
|
181
|
+
// Update state if provided
|
|
182
|
+
if (state) {
|
|
183
|
+
await adminProcessRepository.updateProcessState(processId, state);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Build results object from provided fields and merge in one call
|
|
187
|
+
const resultsUpdate = {};
|
|
188
|
+
if (output !== undefined) resultsUpdate.output = output;
|
|
189
|
+
if (error) resultsUpdate.error = error;
|
|
190
|
+
if (metrics) resultsUpdate.metrics = metrics;
|
|
191
|
+
|
|
192
|
+
if (Object.keys(resultsUpdate).length > 0) {
|
|
193
|
+
await adminProcessRepository.updateProcessResults(processId, resultsUpdate);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { success: true };
|
|
197
|
+
} catch (err) {
|
|
198
|
+
return mapErrorToResponse(err);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Find recent admin processes across all scripts
|
|
204
|
+
*
|
|
205
|
+
* @param {Object} [options] - Query options
|
|
206
|
+
* @param {number} [options.limit] - Maximum results (default 20)
|
|
207
|
+
* @param {string} [options.state] - Filter by state
|
|
208
|
+
* @param {Date} [options.since] - Filter by created date
|
|
209
|
+
* @returns {Promise<Array>} Array of recent admin processes
|
|
210
|
+
*/
|
|
211
|
+
async findRecentAdminProcesses(options = {}) {
|
|
212
|
+
try {
|
|
213
|
+
const { limit = 20, state, since } = options;
|
|
214
|
+
|
|
215
|
+
// If state filter provided, use state query
|
|
216
|
+
if (state) {
|
|
217
|
+
return await adminProcessRepository.findProcessesByState(state, {
|
|
218
|
+
limit,
|
|
219
|
+
sortBy: 'createdAt',
|
|
220
|
+
sortOrder: 'desc',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Otherwise, use generic recent query (would need to be added to interface)
|
|
225
|
+
// For now, fall back to empty array if no state filter
|
|
226
|
+
return [];
|
|
227
|
+
} catch (error) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
// ==================== Schedule Management Commands ====================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get schedule by script name
|
|
236
|
+
* Returns database override or null
|
|
237
|
+
*
|
|
238
|
+
* @param {string} scriptName - The script name
|
|
239
|
+
* @returns {Promise<Object|null>} Schedule record or null
|
|
240
|
+
*/
|
|
241
|
+
async getScheduleByScriptName(scriptName) {
|
|
242
|
+
try {
|
|
243
|
+
const schedule = await scheduleRepository.findScheduleByScriptName(scriptName);
|
|
244
|
+
return schedule;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return mapErrorToResponse(error);
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Create or update a schedule (upsert)
|
|
252
|
+
*
|
|
253
|
+
* @param {Object} params - Schedule parameters
|
|
254
|
+
* @param {string} params.scriptName - Name of the script
|
|
255
|
+
* @param {boolean} params.enabled - Whether schedule is enabled
|
|
256
|
+
* @param {string} params.cronExpression - Cron expression
|
|
257
|
+
* @param {string} [params.timezone] - Timezone (default 'UTC')
|
|
258
|
+
* @returns {Promise<Object>} Created or updated schedule
|
|
259
|
+
*/
|
|
260
|
+
async upsertSchedule({ scriptName, enabled, cronExpression, timezone }) {
|
|
261
|
+
try {
|
|
262
|
+
const schedule = await scheduleRepository.upsertSchedule({
|
|
263
|
+
scriptName,
|
|
264
|
+
enabled,
|
|
265
|
+
cronExpression,
|
|
266
|
+
timezone,
|
|
267
|
+
});
|
|
268
|
+
return schedule;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return mapErrorToResponse(error);
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Delete a schedule by script name
|
|
276
|
+
*
|
|
277
|
+
* @param {string} scriptName - The script name
|
|
278
|
+
* @returns {Promise<Object>} Deletion result
|
|
279
|
+
*/
|
|
280
|
+
async deleteSchedule(scriptName) {
|
|
281
|
+
try {
|
|
282
|
+
const result = await scheduleRepository.deleteSchedule(scriptName);
|
|
283
|
+
return result;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return mapErrorToResponse(error);
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Update external scheduler information
|
|
291
|
+
*
|
|
292
|
+
* @param {string} scriptName - The script name
|
|
293
|
+
* @param {Object} externalInfo - External schedule information
|
|
294
|
+
* @param {string} [externalInfo.externalScheduleId] - External scheduler ID (e.g., AWS ARN)
|
|
295
|
+
* @param {string} [externalInfo.externalScheduleName] - External scheduler name
|
|
296
|
+
* @returns {Promise<Object>} Updated schedule
|
|
297
|
+
*/
|
|
298
|
+
async updateScheduleExternalInfo(scriptName, { externalScheduleId, externalScheduleName }) {
|
|
299
|
+
try {
|
|
300
|
+
const schedule = await scheduleRepository.updateScheduleExternalInfo(scriptName, {
|
|
301
|
+
externalScheduleId,
|
|
302
|
+
externalScheduleName,
|
|
303
|
+
});
|
|
304
|
+
return schedule;
|
|
305
|
+
} catch (error) {
|
|
306
|
+
return mapErrorToResponse(error);
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Update last triggered timestamp
|
|
312
|
+
* Called when a schedule triggers
|
|
313
|
+
*
|
|
314
|
+
* @param {string} scriptName - The script name
|
|
315
|
+
* @param {Date} [timestamp] - Trigger timestamp (default: now)
|
|
316
|
+
* @returns {Promise<Object>} Updated schedule
|
|
317
|
+
*/
|
|
318
|
+
async updateScheduleLastTriggered(scriptName, timestamp) {
|
|
319
|
+
try {
|
|
320
|
+
const schedule = await scheduleRepository.updateScheduleLastTriggered(
|
|
321
|
+
scriptName,
|
|
322
|
+
timestamp
|
|
323
|
+
);
|
|
324
|
+
return schedule;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
return mapErrorToResponse(error);
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* List all schedules
|
|
332
|
+
*
|
|
333
|
+
* @param {Object} [options] - Query options
|
|
334
|
+
* @param {boolean} [options.enabledOnly] - Only return enabled schedules
|
|
335
|
+
* @returns {Promise<Array>} Array of schedule records
|
|
336
|
+
*/
|
|
337
|
+
async listSchedules(options = {}) {
|
|
338
|
+
try {
|
|
339
|
+
const schedules = await scheduleRepository.listSchedules(options);
|
|
340
|
+
return schedules;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports = { createAdminScriptCommands };
|
|
@@ -152,10 +152,11 @@ function createIntegrationCommands({ integrationClass }) {
|
|
|
152
152
|
*/
|
|
153
153
|
async updateIntegrationConfig({ integrationId, config }) {
|
|
154
154
|
try {
|
|
155
|
-
const integration =
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
const integration =
|
|
156
|
+
await integrationRepository.updateIntegrationConfig(
|
|
157
|
+
integrationId,
|
|
158
|
+
config
|
|
159
|
+
);
|
|
159
160
|
return integration;
|
|
160
161
|
} catch (error) {
|
|
161
162
|
return mapErrorToResponse(error);
|
|
@@ -175,10 +176,15 @@ function createIntegrationCommands({ integrationClass }) {
|
|
|
175
176
|
throw error;
|
|
176
177
|
}
|
|
177
178
|
|
|
178
|
-
const deleted =
|
|
179
|
+
const deleted =
|
|
180
|
+
await integrationRepository.deleteIntegrationById(
|
|
181
|
+
integrationId
|
|
182
|
+
);
|
|
179
183
|
|
|
180
184
|
if (!deleted) {
|
|
181
|
-
const error = new Error(
|
|
185
|
+
const error = new Error(
|
|
186
|
+
`Integration ${integrationId} not found`
|
|
187
|
+
);
|
|
182
188
|
error.code = 'INTEGRATION_NOT_FOUND';
|
|
183
189
|
return mapErrorToResponse(error);
|
|
184
190
|
}
|
package/application/index.js
CHANGED
|
@@ -4,12 +4,13 @@ const {
|
|
|
4
4
|
} = require('./commands/integration-commands');
|
|
5
5
|
const { createUserCommands } = require('./commands/user-commands');
|
|
6
6
|
const { createEntityCommands } = require('./commands/entity-commands');
|
|
7
|
-
const {
|
|
8
|
-
createCredentialCommands,
|
|
9
|
-
} = require('./commands/credential-commands');
|
|
7
|
+
const { createCredentialCommands } = require('./commands/credential-commands');
|
|
10
8
|
const {
|
|
11
9
|
createSchedulerCommands,
|
|
12
10
|
} = require('./commands/scheduler-commands');
|
|
11
|
+
const {
|
|
12
|
+
createAdminScriptCommands,
|
|
13
|
+
} = require('./commands/admin-script-commands');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Create a unified command factory with all CRUD operations
|
|
@@ -61,6 +62,7 @@ module.exports = {
|
|
|
61
62
|
createEntityCommands,
|
|
62
63
|
createCredentialCommands,
|
|
63
64
|
createSchedulerCommands,
|
|
65
|
+
createAdminScriptCommands,
|
|
64
66
|
|
|
65
67
|
// Legacy standalone function
|
|
66
68
|
findIntegrationContextByExternalEntityId,
|
package/assertions/get.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const { RequiredPropertyError, ParameterTypeError } = require('../errors');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deep property access by dot-separated path string.
|
|
5
|
+
* Replaces lodash.get (deprecated).
|
|
6
|
+
*/
|
|
7
|
+
function deepGet(obj, path, defaultValue) {
|
|
8
|
+
const keys = typeof path === 'string' ? path.split('.') : path;
|
|
9
|
+
let result = obj;
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
if (result == null) return defaultValue;
|
|
12
|
+
result = result[key];
|
|
13
|
+
}
|
|
14
|
+
return result === undefined ? defaultValue : result;
|
|
15
|
+
}
|
|
6
16
|
|
|
7
17
|
const get = (o, key, defaultValue) => {
|
|
8
|
-
const value =
|
|
18
|
+
const value = deepGet(o, key, defaultValue);
|
|
9
19
|
|
|
10
20
|
if (value !== undefined) {
|
|
11
21
|
return value;
|
|
@@ -26,7 +36,7 @@ const getAll = (o, requiredKeys) => {
|
|
|
26
36
|
const returnDict = {};
|
|
27
37
|
|
|
28
38
|
for (const key of requiredKeys) {
|
|
29
|
-
const val =
|
|
39
|
+
const val = deepGet(o, key);
|
|
30
40
|
|
|
31
41
|
if (val) {
|
|
32
42
|
returnDict[key] = val;
|