@friggframework/core 2.0.0-next.5 → 2.0.0-next.50
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 +693 -0
- package/README.md +959 -50
- package/application/commands/README.md +421 -0
- package/application/commands/credential-commands.js +224 -0
- package/application/commands/entity-commands.js +315 -0
- package/application/commands/integration-commands.js +179 -0
- package/application/commands/user-commands.js +213 -0
- package/application/index.js +69 -0
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +8 -21
- package/core/create-handler.js +2 -7
- package/credential/repositories/credential-repository-factory.js +47 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +307 -0
- package/credential/repositories/credential-repository-postgres.js +313 -0
- package/credential/repositories/credential-repository.js +302 -0
- package/credential/use-cases/get-credential-for-user.js +21 -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/encryption/README.md +684 -0
- package/database/encryption/encryption-schema-registry.js +141 -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 +25 -12
- package/database/models/WebsocketConnection.js +16 -10
- package/database/models/readme.md +1 -0
- package/database/prisma.js +222 -0
- package/database/repositories/health-check-repository-factory.js +43 -0
- package/database/repositories/health-check-repository-interface.js +87 -0
- package/database/repositories/health-check-repository-mongodb.js +91 -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 +137 -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 +400 -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/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 +334 -0
- package/generated/prisma-mongodb/index-browser.js +316 -0
- package/generated/prisma-mongodb/index.d.ts +22898 -0
- package/generated/prisma-mongodb/index.js +359 -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 +3982 -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 +341 -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 +356 -0
- package/generated/prisma-postgresql/index-browser.js +338 -0
- package/generated/prisma-postgresql/index.d.ts +25072 -0
- package/generated/prisma-postgresql/index.js +381 -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 +3982 -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 +363 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +56 -0
- package/handlers/backend-utils.js +180 -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 +256 -0
- package/handlers/routers/health.js +519 -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 +77 -22
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +296 -54
- package/integrations/integration-router.js +381 -182
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-factory.js +50 -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-factory.js +44 -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-factory.js +46 -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/dummy-integration-class.js +83 -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 +87 -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 +93 -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 +36 -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 +221 -0
- package/modules/repositories/module-repository-factory.js +33 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +377 -0
- package/modules/repositories/module-repository-postgres.js +426 -0
- package/modules/repositories/module-repository.js +316 -0
- package/{module-plugin → modules}/requester/requester.js +1 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
- 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 +59 -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 +55 -0
- package/modules/use-cases/process-authorization-callback.js +122 -0
- package/modules/use-cases/refresh-entity-options.js +59 -0
- package/modules/use-cases/test-module-auth.js +55 -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/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +345 -0
- package/queues/queuer-util.js +28 -15
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-factory.js +38 -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-factory.js +33 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +212 -0
- package/token/repositories/token-repository-postgres.js +257 -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-factory.js +46 -0
- package/user/repositories/user-repository-interface.js +198 -0
- package/user/repositories/user-repository-mongo.js +291 -0
- package/user/repositories/user-repository-postgres.js +350 -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 +106 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +93 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +37 -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/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/api-key.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const { createHandler } = require('@friggframework/core');
|
|
2
|
+
const { createWebsocketConnectionRepository } = require('../../database/websocket-connection-repository-factory');
|
|
3
|
+
|
|
4
|
+
const websocketConnectionRepository = createWebsocketConnectionRepository();
|
|
5
|
+
|
|
6
|
+
const handleWebSocketConnection = async (event, context) => {
|
|
7
|
+
// Handle different WebSocket events
|
|
8
|
+
switch (event.requestContext.eventType) {
|
|
9
|
+
case 'CONNECT':
|
|
10
|
+
// Handle new connection
|
|
11
|
+
try {
|
|
12
|
+
const connectionId = event.requestContext.connectionId;
|
|
13
|
+
await websocketConnectionRepository.createConnection(connectionId);
|
|
14
|
+
console.log(`Stored new connection: ${connectionId}`);
|
|
15
|
+
return { statusCode: 200, body: 'Connected.' };
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error('Error storing connection:', error);
|
|
18
|
+
return { statusCode: 500, body: 'Error connecting.' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
case 'DISCONNECT':
|
|
22
|
+
// Handle disconnection
|
|
23
|
+
try {
|
|
24
|
+
const connectionId = event.requestContext.connectionId;
|
|
25
|
+
await websocketConnectionRepository.deleteConnection(connectionId);
|
|
26
|
+
console.log(`Removed connection: ${connectionId}`);
|
|
27
|
+
return { statusCode: 200, body: 'Disconnected.' };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Error removing connection:', error);
|
|
30
|
+
return { statusCode: 500, body: 'Error disconnecting.' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
case 'MESSAGE':
|
|
34
|
+
// Handle incoming message
|
|
35
|
+
const message = JSON.parse(event.body);
|
|
36
|
+
console.log('Received message:', message);
|
|
37
|
+
|
|
38
|
+
// Process the message and send a response
|
|
39
|
+
const responseMessage = { message: 'Message received' };
|
|
40
|
+
return {
|
|
41
|
+
statusCode: 200,
|
|
42
|
+
body: JSON.stringify(responseMessage),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
default:
|
|
46
|
+
return { statusCode: 400, body: 'Unhandled event type.' };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handler = createHandler({
|
|
51
|
+
eventName: 'WebSocket Event',
|
|
52
|
+
method: handleWebSocketConnection,
|
|
53
|
+
shouldUseDatabase: true, // Set to true as we're using the database
|
|
54
|
+
isUserFacingResponse: true, // This is a server-to-server response
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
module.exports = { handler };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
|
|
4
|
+
class CheckExternalApisHealthUseCase {
|
|
5
|
+
constructor({ apis = null } = {}) {
|
|
6
|
+
this.apis = apis || [
|
|
7
|
+
{ name: 'github', url: 'https://api.github.com/status' },
|
|
8
|
+
{ name: 'npm', url: 'https://registry.npmjs.org' },
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async execute() {
|
|
13
|
+
const results = await Promise.all(
|
|
14
|
+
this.apis.map((api) =>
|
|
15
|
+
this._checkExternalAPI(api.url).then((result) => ({
|
|
16
|
+
name: api.name,
|
|
17
|
+
...result,
|
|
18
|
+
}))
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const apiStatuses = {};
|
|
23
|
+
let allReachable = true;
|
|
24
|
+
|
|
25
|
+
results.forEach(({ name, ...checkResult }) => {
|
|
26
|
+
apiStatuses[name] = checkResult;
|
|
27
|
+
if (!checkResult.reachable) {
|
|
28
|
+
allReachable = false;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return { apiStatuses, allReachable };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_checkExternalAPI(url, timeout = 5000) {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
const protocol = url.startsWith('https:') ? https : http;
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const request = protocol.get(url, { timeout }, (res) => {
|
|
42
|
+
const responseTime = Date.now() - startTime;
|
|
43
|
+
resolve({
|
|
44
|
+
status: 'healthy',
|
|
45
|
+
statusCode: res.statusCode,
|
|
46
|
+
responseTime,
|
|
47
|
+
reachable: res.statusCode < 500,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
request.on('error', (error) => {
|
|
52
|
+
resolve({
|
|
53
|
+
status: 'unhealthy',
|
|
54
|
+
error: error.message,
|
|
55
|
+
responseTime: Date.now() - startTime,
|
|
56
|
+
reachable: false,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
request.on('timeout', () => {
|
|
61
|
+
request.destroy();
|
|
62
|
+
resolve({
|
|
63
|
+
status: 'timeout',
|
|
64
|
+
error: 'Request timeout',
|
|
65
|
+
responseTime: timeout,
|
|
66
|
+
reachable: false,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
resolve({
|
|
71
|
+
status: 'error',
|
|
72
|
+
error: error.message,
|
|
73
|
+
responseTime: Date.now() - startTime,
|
|
74
|
+
reachable: false,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { CheckExternalApisHealthUseCase };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class CheckIntegrationsHealthUseCase {
|
|
2
|
+
constructor({ moduleFactory, integrationClasses }) {
|
|
3
|
+
this.moduleFactory = moduleFactory;
|
|
4
|
+
this.integrationClasses = integrationClasses;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
execute() {
|
|
8
|
+
const moduleDefinitions = (this.moduleFactory && this.moduleFactory.moduleDefinitions)
|
|
9
|
+
? this.moduleFactory.moduleDefinitions
|
|
10
|
+
: [];
|
|
11
|
+
|
|
12
|
+
const integrationClasses = Array.isArray(this.integrationClasses)
|
|
13
|
+
? this.integrationClasses
|
|
14
|
+
: [];
|
|
15
|
+
|
|
16
|
+
// Extract module names from definitions
|
|
17
|
+
const moduleTypes = Array.isArray(moduleDefinitions)
|
|
18
|
+
? moduleDefinitions.map(def => def.moduleName || def.name || def.label || 'Unknown')
|
|
19
|
+
: [];
|
|
20
|
+
|
|
21
|
+
// Extract integration names from classes
|
|
22
|
+
const integrationNames = integrationClasses.map(IntegrationClass => {
|
|
23
|
+
try {
|
|
24
|
+
return IntegrationClass.Definition?.name || IntegrationClass.name || 'Unknown';
|
|
25
|
+
} catch {
|
|
26
|
+
return 'Unknown';
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
status: 'healthy',
|
|
32
|
+
modules: {
|
|
33
|
+
count: moduleTypes.length,
|
|
34
|
+
available: moduleTypes,
|
|
35
|
+
},
|
|
36
|
+
integrations: {
|
|
37
|
+
count: integrationNames.length,
|
|
38
|
+
available: integrationNames,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { CheckIntegrationsHealthUseCase };
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Migration Lambda Handler
|
|
3
|
+
*
|
|
4
|
+
* Lambda function that runs Prisma database migrations from within the VPC,
|
|
5
|
+
* enabling CI/CD pipelines to migrate databases without requiring public access.
|
|
6
|
+
*
|
|
7
|
+
* This handler uses the prisma-runner utilities from @friggframework/core,
|
|
8
|
+
* ensuring consistency with the `frigg db:setup` command.
|
|
9
|
+
*
|
|
10
|
+
* Environment Variables Required:
|
|
11
|
+
* - DATABASE_URL: PostgreSQL connection string (automatically set from Secrets Manager)
|
|
12
|
+
* - DB_TYPE: Database type ('postgresql' or 'mongodb')
|
|
13
|
+
* - STAGE: Deployment stage (determines migration command: 'dev' or 'deploy')
|
|
14
|
+
*
|
|
15
|
+
* Invocation:
|
|
16
|
+
* aws lambda invoke \
|
|
17
|
+
* --function-name my-app-production-dbMigrate \
|
|
18
|
+
* --region us-east-1 \
|
|
19
|
+
* response.json
|
|
20
|
+
*
|
|
21
|
+
* Success Response:
|
|
22
|
+
* {
|
|
23
|
+
* "statusCode": 200,
|
|
24
|
+
* "body": {
|
|
25
|
+
* "success": true,
|
|
26
|
+
* "message": "Database migration completed successfully",
|
|
27
|
+
* "dbType": "postgresql",
|
|
28
|
+
* "stage": "production",
|
|
29
|
+
* "migrationCommand": "deploy"
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* Error Response:
|
|
34
|
+
* {
|
|
35
|
+
* "statusCode": 500,
|
|
36
|
+
* "body": {
|
|
37
|
+
* "success": false,
|
|
38
|
+
* "error": "Migration failed: ...",
|
|
39
|
+
* "stack": "Error: ..."
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
RunDatabaseMigrationUseCase,
|
|
46
|
+
MigrationError,
|
|
47
|
+
ValidationError,
|
|
48
|
+
} = require('../../database/use-cases/run-database-migration-use-case');
|
|
49
|
+
const {
|
|
50
|
+
CheckDatabaseStateUseCase,
|
|
51
|
+
} = require('../../database/use-cases/check-database-state-use-case');
|
|
52
|
+
const {
|
|
53
|
+
MigrationStatusRepositoryS3,
|
|
54
|
+
} = require('../../database/repositories/migration-status-repository-s3');
|
|
55
|
+
|
|
56
|
+
// Inject prisma-runner as dependency
|
|
57
|
+
const prismaRunner = require('../../database/utils/prisma-runner');
|
|
58
|
+
|
|
59
|
+
// Use S3 repository for migration status tracking (no User table dependency)
|
|
60
|
+
const bucketName = process.env.S3_BUCKET_NAME || process.env.MIGRATION_STATUS_BUCKET;
|
|
61
|
+
const migrationStatusRepository = new MigrationStatusRepositoryS3(bucketName);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sanitizes error messages to prevent credential leaks
|
|
65
|
+
* @param {string} errorMessage - Error message that might contain credentials
|
|
66
|
+
* @returns {string} Sanitized error message
|
|
67
|
+
*/
|
|
68
|
+
function sanitizeError(errorMessage) {
|
|
69
|
+
if (!errorMessage) return 'Unknown error';
|
|
70
|
+
|
|
71
|
+
return String(errorMessage)
|
|
72
|
+
// Remove PostgreSQL connection strings
|
|
73
|
+
.replace(/postgresql:\/\/[^@\s]+@[^\s/]+/gi, 'postgresql://***:***@***')
|
|
74
|
+
// Remove MongoDB connection strings
|
|
75
|
+
.replace(/mongodb(\+srv)?:\/\/[^@\s]+@[^\s/]+/gi, 'mongodb$1://***:***@***')
|
|
76
|
+
// Remove password parameters
|
|
77
|
+
.replace(/password[=:]\s*[^\s,;)]+/gi, 'password=***')
|
|
78
|
+
// Remove API keys
|
|
79
|
+
.replace(/apikey[=:]\s*[^\s,;)]+/gi, 'apikey=***')
|
|
80
|
+
.replace(/api[_-]?key[=:]\s*[^\s,;)]+/gi, 'api_key=***')
|
|
81
|
+
// Remove tokens
|
|
82
|
+
.replace(/token[=:]\s*[^\s,;)]+/gi, 'token=***')
|
|
83
|
+
.replace(/bearer\s+[^\s,;)]+/gi, 'bearer ***');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Sanitizes DATABASE_URL for safe logging
|
|
88
|
+
* @param {string} url - Database URL
|
|
89
|
+
* @returns {string} Sanitized URL
|
|
90
|
+
*/
|
|
91
|
+
function sanitizeDatabaseUrl(url) {
|
|
92
|
+
if (!url) return '';
|
|
93
|
+
|
|
94
|
+
// Replace credentials in connection string
|
|
95
|
+
return url.replace(/(:\/\/)([^:]+):([^@]+)@/, '$1***:***@');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Extract migration parameters from SQS event or direct invocation
|
|
100
|
+
* @param {Object} event - Lambda event (SQS or direct)
|
|
101
|
+
* @returns {Object} Extracted parameters { migrationId, dbType, stage }
|
|
102
|
+
*/
|
|
103
|
+
function extractMigrationParams(event) {
|
|
104
|
+
let migrationId = null;
|
|
105
|
+
let stage = null;
|
|
106
|
+
|
|
107
|
+
// Migration infrastructure is PostgreSQL-only, so hardcode dbType
|
|
108
|
+
const dbType = 'postgresql';
|
|
109
|
+
|
|
110
|
+
// Check if this is an SQS event
|
|
111
|
+
if (event.Records && event.Records.length > 0) {
|
|
112
|
+
// SQS event - extract from message body
|
|
113
|
+
const message = JSON.parse(event.Records[0].body);
|
|
114
|
+
migrationId = message.migrationId;
|
|
115
|
+
stage = message.stage;
|
|
116
|
+
|
|
117
|
+
console.log('SQS event detected');
|
|
118
|
+
console.log(` Migration ID: ${migrationId}`);
|
|
119
|
+
console.log(` DB Type: ${dbType} (hardcoded - PostgreSQL-only)`);
|
|
120
|
+
console.log(` Stage: ${stage}`);
|
|
121
|
+
} else {
|
|
122
|
+
// Direct invocation - use event properties or environment variables
|
|
123
|
+
migrationId = event.migrationId || null;
|
|
124
|
+
stage = event.stage || process.env.STAGE || 'production';
|
|
125
|
+
|
|
126
|
+
console.log('Direct invocation detected');
|
|
127
|
+
if (migrationId) {
|
|
128
|
+
console.log(` Migration ID: ${migrationId}`);
|
|
129
|
+
}
|
|
130
|
+
console.log(` DB Type: ${dbType} (hardcoded - PostgreSQL-only)`);
|
|
131
|
+
console.log(` Stage: ${stage}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { migrationId, dbType, stage };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Lambda handler entry point
|
|
139
|
+
* @param {Object} event - Lambda event (SQS message or direct invocation)
|
|
140
|
+
* @param {Object} context - Lambda context (contains AWS request ID, timeout info)
|
|
141
|
+
* @returns {Promise<Object>} Response with statusCode and body
|
|
142
|
+
*/
|
|
143
|
+
exports.handler = async (event, context) => {
|
|
144
|
+
console.log('========================================');
|
|
145
|
+
console.log('Database Migration Lambda Started');
|
|
146
|
+
console.log('========================================');
|
|
147
|
+
console.log('Event:', JSON.stringify(event, null, 2));
|
|
148
|
+
console.log('Context:', JSON.stringify({
|
|
149
|
+
requestId: context.requestId,
|
|
150
|
+
functionName: context.functionName,
|
|
151
|
+
remainingTimeInMillis: context.getRemainingTimeInMillis(),
|
|
152
|
+
}, null, 2));
|
|
153
|
+
|
|
154
|
+
// Extract migration parameters from event
|
|
155
|
+
const { migrationId, dbType, stage } = extractMigrationParams(event);
|
|
156
|
+
|
|
157
|
+
// Check for action parameter (direct invocation for status checks)
|
|
158
|
+
const action = event.action || 'migrate'; // Default to migration
|
|
159
|
+
|
|
160
|
+
// Handle checkStatus action
|
|
161
|
+
if (action === 'checkStatus') {
|
|
162
|
+
console.log(`\n========================================`);
|
|
163
|
+
console.log(`Action: checkStatus (dbType=${dbType}, stage=${stage})`);
|
|
164
|
+
console.log(`========================================`);
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const checkDbStateUseCase = new CheckDatabaseStateUseCase({ prismaRunner });
|
|
168
|
+
const status = await checkDbStateUseCase.execute(dbType, stage);
|
|
169
|
+
|
|
170
|
+
console.log('✓ Database state check completed');
|
|
171
|
+
console.log(` Up to date: ${status.upToDate}`);
|
|
172
|
+
console.log(` Pending migrations: ${status.pendingMigrations}`);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
statusCode: 200,
|
|
176
|
+
body: status,
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('❌ Database state check failed:', error.message);
|
|
180
|
+
return {
|
|
181
|
+
statusCode: 500,
|
|
182
|
+
body: {
|
|
183
|
+
success: false,
|
|
184
|
+
error: sanitizeError(error.message),
|
|
185
|
+
upToDate: false,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Otherwise, handle migration (existing code)
|
|
192
|
+
console.log(`\n========================================`);
|
|
193
|
+
console.log(`Action: migrate (migrationId=${migrationId || 'new'})`);
|
|
194
|
+
console.log(`========================================`);
|
|
195
|
+
|
|
196
|
+
// Get environment variables
|
|
197
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
// Validate DATABASE_URL is set
|
|
201
|
+
if (!databaseUrl) {
|
|
202
|
+
const error = 'DATABASE_URL environment variable is not set';
|
|
203
|
+
console.error('❌ Validation failed:', error);
|
|
204
|
+
return {
|
|
205
|
+
statusCode: 500,
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
success: false,
|
|
208
|
+
error,
|
|
209
|
+
}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log('✓ Environment validated');
|
|
214
|
+
console.log(` Database Type: ${dbType}`);
|
|
215
|
+
console.log(` Stage: ${stage}`);
|
|
216
|
+
console.log(` Database URL: ${sanitizeDatabaseUrl(databaseUrl)}`);
|
|
217
|
+
|
|
218
|
+
// Update migration status to RUNNING (if migrationId provided)
|
|
219
|
+
if (migrationId) {
|
|
220
|
+
console.log(`\n✓ Updating migration status to RUNNING: ${migrationId}`);
|
|
221
|
+
await migrationStatusRepository.update({
|
|
222
|
+
migrationId,
|
|
223
|
+
stage,
|
|
224
|
+
state: 'RUNNING',
|
|
225
|
+
progress: 10,
|
|
226
|
+
startedAt: new Date().toISOString(),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Create use case with dependencies (Dependency Injection)
|
|
231
|
+
const runDatabaseMigrationUseCase = new RunDatabaseMigrationUseCase({
|
|
232
|
+
prismaRunner,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
console.log('\n========================================');
|
|
236
|
+
console.log('Executing Database Migration');
|
|
237
|
+
console.log('========================================');
|
|
238
|
+
|
|
239
|
+
// Execute use case (business logic layer)
|
|
240
|
+
const result = await runDatabaseMigrationUseCase.execute({
|
|
241
|
+
dbType,
|
|
242
|
+
stage,
|
|
243
|
+
verbose: true, // Enable verbose output for Lambda CloudWatch logs
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
console.log('✓ Database migration completed successfully');
|
|
247
|
+
console.log('\n========================================');
|
|
248
|
+
console.log('Migration Summary');
|
|
249
|
+
console.log('========================================');
|
|
250
|
+
console.log(` Status: Success`);
|
|
251
|
+
console.log(` Database: ${result.dbType}`);
|
|
252
|
+
console.log(` Stage: ${result.stage}`);
|
|
253
|
+
console.log(` Command: ${result.command}`);
|
|
254
|
+
console.log('========================================');
|
|
255
|
+
|
|
256
|
+
// Update migration status to COMPLETED (if migrationId provided)
|
|
257
|
+
if (migrationId) {
|
|
258
|
+
console.log(`\n✓ Updating migration status to COMPLETED: ${migrationId}`);
|
|
259
|
+
await migrationStatusRepository.update({
|
|
260
|
+
migrationId,
|
|
261
|
+
stage,
|
|
262
|
+
state: 'COMPLETED',
|
|
263
|
+
progress: 100,
|
|
264
|
+
completedAt: new Date().toISOString(),
|
|
265
|
+
migrationCommand: result.command,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Return success response (adapter layer - HTTP mapping)
|
|
270
|
+
const responseBody = {
|
|
271
|
+
success: true,
|
|
272
|
+
message: result.message,
|
|
273
|
+
dbType: result.dbType,
|
|
274
|
+
stage: result.stage,
|
|
275
|
+
migrationCommand: result.command,
|
|
276
|
+
timestamp: new Date().toISOString(),
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
if (migrationId) {
|
|
280
|
+
responseBody.migrationId = migrationId;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
statusCode: 200,
|
|
285
|
+
body: JSON.stringify(responseBody),
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error('\n========================================');
|
|
290
|
+
console.error('Migration Failed');
|
|
291
|
+
console.error('========================================');
|
|
292
|
+
console.error('Error:', error.name, error.message);
|
|
293
|
+
|
|
294
|
+
// Log full stack trace to CloudWatch (only visible to developers)
|
|
295
|
+
if (error.stack) {
|
|
296
|
+
console.error('Stack:', error.stack);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Log context if available (from MigrationError)
|
|
300
|
+
if (error.context) {
|
|
301
|
+
console.error('Context:', JSON.stringify(error.context, null, 2));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Map domain errors to HTTP status codes (adapter layer)
|
|
305
|
+
let statusCode = 500;
|
|
306
|
+
let errorMessage = error.message || 'Unknown error occurred';
|
|
307
|
+
|
|
308
|
+
if (error instanceof ValidationError) {
|
|
309
|
+
statusCode = 400; // Bad Request for validation errors
|
|
310
|
+
} else if (error instanceof MigrationError) {
|
|
311
|
+
statusCode = 500; // Internal Server Error for migration failures
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Sanitize error message before returning
|
|
315
|
+
const sanitizedError = sanitizeError(errorMessage);
|
|
316
|
+
|
|
317
|
+
// Update migration status to FAILED (if migrationId provided)
|
|
318
|
+
if (migrationId) {
|
|
319
|
+
try {
|
|
320
|
+
console.log(`\n✓ Updating migration status to FAILED: ${migrationId}`);
|
|
321
|
+
await migrationStatusRepository.update({
|
|
322
|
+
migrationId,
|
|
323
|
+
stage,
|
|
324
|
+
state: 'FAILED',
|
|
325
|
+
progress: 0,
|
|
326
|
+
error: sanitizedError,
|
|
327
|
+
failedAt: new Date().toISOString(),
|
|
328
|
+
});
|
|
329
|
+
} catch (updateError) {
|
|
330
|
+
console.error('Failed to update migration status:', updateError.message);
|
|
331
|
+
// Continue - don't let status update failure block error response
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const errorBody = {
|
|
336
|
+
success: false,
|
|
337
|
+
error: sanitizedError,
|
|
338
|
+
errorType: error.name || 'Error',
|
|
339
|
+
// Only include stack traces in development environments
|
|
340
|
+
...(stage === 'dev' || stage === 'local' || stage === 'test' ? { stack: error.stack } : {}),
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
if (migrationId) {
|
|
344
|
+
errorBody.migrationId = migrationId;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
statusCode,
|
|
349
|
+
body: JSON.stringify(errorBody),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { createHandler } = require('@friggframework/core');
|
|
2
|
+
const { loadAppDefinition } = require('../app-definition-loader');
|
|
3
|
+
const { createQueueWorker } = require('../backend-utils');
|
|
4
|
+
|
|
5
|
+
const handlers = {};
|
|
6
|
+
const { integrations: integrationClasses } = loadAppDefinition();
|
|
7
|
+
|
|
8
|
+
integrationClasses.forEach((IntegrationClass) => {
|
|
9
|
+
const defaultQueueWorker = createQueueWorker(IntegrationClass);
|
|
10
|
+
|
|
11
|
+
handlers[`${IntegrationClass.Definition.name}`] = {
|
|
12
|
+
queueWorker: createHandler({
|
|
13
|
+
eventName: `Queue Worker for ${IntegrationClass.Definition.name}`,
|
|
14
|
+
isUserFacingResponse: false,
|
|
15
|
+
method: async (event, context) => {
|
|
16
|
+
const worker = new defaultQueueWorker();
|
|
17
|
+
await worker.run(event, context);
|
|
18
|
+
return {
|
|
19
|
+
message: 'Successfully processed the Generic Queue Worker',
|
|
20
|
+
input: event,
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
module.exports = { handlers };
|