@friggframework/core 2.0.0-next.44 → 2.0.0-next.46
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/README.md +28 -0
- package/application/commands/integration-commands.js +19 -0
- package/core/Worker.js +8 -21
- package/credential/repositories/credential-repository-mongo.js +14 -8
- package/credential/repositories/credential-repository-postgres.js +14 -8
- package/credential/repositories/credential-repository.js +3 -8
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +11 -2
- package/database/models/WebsocketConnection.js +11 -10
- package/database/prisma.js +63 -3
- package/database/repositories/health-check-repository-mongodb.js +3 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +3 -2
- 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/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/encrypt/Cryptor.js +14 -16
- 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 +22897 -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 +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 +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 +25071 -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 +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 +363 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/backend-utils.js +118 -3
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/routers/auth.js +1 -1
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +256 -0
- package/handlers/routers/health.js +41 -6
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
- package/handlers/workers/db-migration.js +352 -0
- package/index.js +28 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/integration-base.js +74 -3
- package/integrations/integration-router.js +60 -70
- package/integrations/repositories/integration-repository-interface.js +12 -0
- package/integrations/repositories/integration-repository-mongo.js +32 -0
- package/integrations/repositories/integration-repository-postgres.js +33 -0
- package/integrations/repositories/process-repository-postgres.js +43 -20
- package/integrations/tests/doubles/dummy-integration-class.js +1 -8
- package/integrations/tests/doubles/test-integration-repository.js +2 -2
- package/logs/logger.js +0 -4
- package/modules/entity.js +0 -1
- package/modules/repositories/module-repository-mongo.js +3 -12
- package/modules/repositories/module-repository-postgres.js +0 -11
- package/modules/repositories/module-repository.js +1 -12
- package/modules/use-cases/get-entity-options-by-id.js +1 -1
- package/modules/use-cases/get-module.js +1 -2
- package/modules/use-cases/refresh-entity-options.js +1 -1
- package/modules/use-cases/test-module-auth.js +1 -1
- package/package.json +82 -66
- package/prisma-mongodb/schema.prisma +21 -21
- package/prisma-postgresql/schema.prisma +15 -15
- package/queues/queuer-util.js +28 -15
- package/types/core/index.d.ts +2 -2
- package/types/module-plugin/index.d.ts +0 -2
- package/user/repositories/user-repository-mongo.js +53 -12
- package/user/repositories/user-repository-postgres.js +53 -14
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
- package/user/use-cases/login-user.js +1 -1
- package/user/user.js +18 -2
- package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
- package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
- package/websocket/repositories/websocket-connection-repository.js +11 -10
- package/application/commands/integration-commands.test.js +0 -123
- package/database/encryption/encryption-integration.test.js +0 -553
- package/database/encryption/encryption-schema-registry.test.js +0 -392
- package/database/encryption/field-encryption-service.test.js +0 -525
- package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
- package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
- package/database/encryption/postgres-relation-decryption.test.js +0 -245
- package/database/encryption/prisma-encryption-extension.test.js +0 -439
- 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/handlers/auth-flow.integration.test.js +0 -147
- package/handlers/integration-event-dispatcher.test.js +0 -141
- package/handlers/routers/health.test.js +0 -210
- package/integrations/tests/use-cases/create-integration.test.js +0 -131
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
- package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
- package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
- package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
- package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
- package/integrations/tests/use-cases/update-integration.test.js +0 -141
- package/integrations/use-cases/create-process.test.js +0 -178
- package/integrations/use-cases/get-process.test.js +0 -190
- package/integrations/use-cases/load-integration-context-full.test.js +0 -329
- package/integrations/use-cases/load-integration-context.test.js +0 -114
- package/integrations/use-cases/update-process-metrics.test.js +0 -308
- package/integrations/use-cases/update-process-state.test.js +0 -256
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/modules/module-hydration.test.js +0 -205
- package/modules/requester/requester.test.js +0 -28
- package/user/tests/use-cases/create-individual-user.test.js +0 -24
- package/user/tests/use-cases/create-organization-user.test.js +0 -28
- package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
- package/user/tests/use-cases/login-user.test.js +0 -140
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
const { Router } = require('express');
|
|
2
|
-
const { moduleFactory, integrationFactory } = require('./../backend-utils');
|
|
3
2
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
3
|
+
const { loadAppDefinition } = require('./../app-definition-loader');
|
|
4
|
+
const { ModuleFactory } = require('../../modules/module-factory');
|
|
5
|
+
const {
|
|
6
|
+
getModulesDefinitionFromIntegrationClasses,
|
|
7
|
+
} = require('../../integrations/utils/map-integration-dto');
|
|
8
|
+
const {
|
|
9
|
+
createModuleRepository,
|
|
10
|
+
} = require('../../modules/repositories/module-repository-factory');
|
|
11
|
+
const {
|
|
12
|
+
createIntegrationRepository,
|
|
13
|
+
} = require('../../integrations/repositories/integration-repository-factory');
|
|
4
14
|
const {
|
|
5
15
|
createHealthCheckRepository,
|
|
6
16
|
} = require('../../database/repositories/health-check-repository-factory');
|
|
@@ -22,6 +32,28 @@ const {
|
|
|
22
32
|
|
|
23
33
|
const router = Router();
|
|
24
34
|
const healthCheckRepository = createHealthCheckRepository();
|
|
35
|
+
|
|
36
|
+
// Load integrations and create factories just like auth router does
|
|
37
|
+
// This verifies the system can properly load integrations
|
|
38
|
+
let moduleFactory, integrationClasses;
|
|
39
|
+
try {
|
|
40
|
+
const appDef = loadAppDefinition();
|
|
41
|
+
integrationClasses = appDef.integrations || [];
|
|
42
|
+
|
|
43
|
+
const moduleRepository = createModuleRepository();
|
|
44
|
+
const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrationClasses);
|
|
45
|
+
|
|
46
|
+
moduleFactory = new ModuleFactory({
|
|
47
|
+
moduleRepository,
|
|
48
|
+
moduleDefinitions,
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to load integrations for health check:', error.message);
|
|
52
|
+
// Factories will be undefined, health check will report unhealthy
|
|
53
|
+
moduleFactory = undefined;
|
|
54
|
+
integrationClasses = [];
|
|
55
|
+
}
|
|
56
|
+
|
|
25
57
|
const testEncryptionUseCase = new TestEncryptionUseCase({
|
|
26
58
|
healthCheckRepository,
|
|
27
59
|
});
|
|
@@ -34,11 +66,11 @@ const checkEncryptionHealthUseCase = new CheckEncryptionHealthUseCase({
|
|
|
34
66
|
const checkExternalApisHealthUseCase = new CheckExternalApisHealthUseCase();
|
|
35
67
|
const checkIntegrationsHealthUseCase = new CheckIntegrationsHealthUseCase({
|
|
36
68
|
moduleFactory,
|
|
37
|
-
|
|
69
|
+
integrationClasses,
|
|
38
70
|
});
|
|
39
71
|
|
|
40
72
|
const validateApiKey = (req, res, next) => {
|
|
41
|
-
const apiKey = req.headers['x-api-key'];
|
|
73
|
+
const apiKey = req.headers['x-frigg-health-api-key'];
|
|
42
74
|
|
|
43
75
|
if (req.path === '/health') {
|
|
44
76
|
return next();
|
|
@@ -48,7 +80,7 @@ const validateApiKey = (req, res, next) => {
|
|
|
48
80
|
console.error('Unauthorized access attempt to health endpoint');
|
|
49
81
|
return res.status(401).json({
|
|
50
82
|
status: 'error',
|
|
51
|
-
message: 'Unauthorized',
|
|
83
|
+
message: 'Unauthorized - x-frigg-health-api-key header required',
|
|
52
84
|
});
|
|
53
85
|
}
|
|
54
86
|
|
|
@@ -141,8 +173,11 @@ const detectVpcConfiguration = async () => {
|
|
|
141
173
|
}
|
|
142
174
|
}
|
|
143
175
|
|
|
144
|
-
|
|
145
|
-
|
|
176
|
+
// Check if Lambda is in VPC using VPC_ENABLED env var set by infrastructure
|
|
177
|
+
results.isInVpc = process.env.VPC_ENABLED === 'true' ||
|
|
178
|
+
(!results.hasInternetAccess && results.canResolvePublicDns) ||
|
|
179
|
+
results.vpcEndpoints.length > 0;
|
|
180
|
+
|
|
146
181
|
results.canConnectToAws =
|
|
147
182
|
results.hasInternetAccess || results.vpcEndpoints.length > 0;
|
|
148
183
|
} catch (error) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const { createAppHandler } = require('./../app-handler-helpers');
|
|
2
|
+
const { loadAppDefinition } = require('../app-definition-loader');
|
|
3
|
+
const { Router } = require('express');
|
|
4
|
+
const { IntegrationEventDispatcher } = require('../integration-event-dispatcher');
|
|
5
|
+
|
|
6
|
+
const handlers = {};
|
|
7
|
+
const { integrations: integrationClasses } = loadAppDefinition();
|
|
8
|
+
|
|
9
|
+
for (const IntegrationClass of integrationClasses) {
|
|
10
|
+
const webhookConfig = IntegrationClass.Definition.webhooks;
|
|
11
|
+
|
|
12
|
+
// Skip if webhooks not enabled
|
|
13
|
+
if (!webhookConfig || (typeof webhookConfig === 'object' && !webhookConfig.enabled)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const router = Router();
|
|
18
|
+
const basePath = `/api/${IntegrationClass.Definition.name}-integration/webhooks`;
|
|
19
|
+
|
|
20
|
+
console.log(`\n│ Configuring webhook routes for ${IntegrationClass.Definition.name}:`);
|
|
21
|
+
|
|
22
|
+
// General webhook route (no integration ID)
|
|
23
|
+
router.post(basePath, async (req, res, next) => {
|
|
24
|
+
try {
|
|
25
|
+
const integrationInstance = new IntegrationClass();
|
|
26
|
+
const dispatcher = new IntegrationEventDispatcher(integrationInstance);
|
|
27
|
+
await dispatcher.dispatchHttp({
|
|
28
|
+
event: 'WEBHOOK_RECEIVED',
|
|
29
|
+
req,
|
|
30
|
+
res,
|
|
31
|
+
next,
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
next(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
console.log(`│ POST ${basePath}`);
|
|
38
|
+
|
|
39
|
+
// Integration-specific webhook route (with integration ID)
|
|
40
|
+
router.post(`${basePath}/:integrationId`, async (req, res, next) => {
|
|
41
|
+
try {
|
|
42
|
+
const integrationInstance = new IntegrationClass();
|
|
43
|
+
const dispatcher = new IntegrationEventDispatcher(integrationInstance);
|
|
44
|
+
await dispatcher.dispatchHttp({
|
|
45
|
+
event: 'WEBHOOK_RECEIVED',
|
|
46
|
+
req,
|
|
47
|
+
res,
|
|
48
|
+
next,
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
next(error);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
console.log(`│ POST ${basePath}/:integrationId`);
|
|
55
|
+
console.log('│');
|
|
56
|
+
|
|
57
|
+
handlers[`${IntegrationClass.Definition.name}Webhook`] = {
|
|
58
|
+
handler: createAppHandler(
|
|
59
|
+
`HTTP Event: ${IntegrationClass.Definition.name} Webhook`,
|
|
60
|
+
router,
|
|
61
|
+
false // shouldUseDatabase = false
|
|
62
|
+
),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { handlers };
|
|
67
|
+
|
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
class CheckIntegrationsHealthUseCase {
|
|
2
|
-
constructor({ moduleFactory,
|
|
2
|
+
constructor({ moduleFactory, integrationClasses }) {
|
|
3
3
|
this.moduleFactory = moduleFactory;
|
|
4
|
-
this.
|
|
4
|
+
this.integrationClasses = integrationClasses;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
execute() {
|
|
8
|
-
const
|
|
9
|
-
? this.moduleFactory.
|
|
8
|
+
const moduleDefinitions = (this.moduleFactory && this.moduleFactory.moduleDefinitions)
|
|
9
|
+
? this.moduleFactory.moduleDefinitions
|
|
10
10
|
: [];
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
this.
|
|
14
|
-
)
|
|
15
|
-
? this.integrationFactory.integrationTypes
|
|
12
|
+
const integrationClasses = Array.isArray(this.integrationClasses)
|
|
13
|
+
? this.integrationClasses
|
|
16
14
|
: [];
|
|
17
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
|
+
|
|
18
30
|
return {
|
|
19
31
|
status: 'healthy',
|
|
20
32
|
modules: {
|
|
@@ -22,8 +34,8 @@ class CheckIntegrationsHealthUseCase {
|
|
|
22
34
|
available: moduleTypes,
|
|
23
35
|
},
|
|
24
36
|
integrations: {
|
|
25
|
-
count:
|
|
26
|
-
available:
|
|
37
|
+
count: integrationNames.length,
|
|
38
|
+
available: integrationNames,
|
|
27
39
|
},
|
|
28
40
|
};
|
|
29
41
|
}
|
|
@@ -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
|
+
};
|
package/index.js
CHANGED
|
@@ -33,6 +33,15 @@ const {
|
|
|
33
33
|
UserRepositoryMongo,
|
|
34
34
|
UserRepositoryPostgres,
|
|
35
35
|
} = require('./user/repositories/user-repository-factory');
|
|
36
|
+
const {
|
|
37
|
+
GetUserFromXFriggHeaders,
|
|
38
|
+
} = require('./user/use-cases/get-user-from-x-frigg-headers');
|
|
39
|
+
const {
|
|
40
|
+
GetUserFromAdopterJwt,
|
|
41
|
+
} = require('./user/use-cases/get-user-from-adopter-jwt');
|
|
42
|
+
const {
|
|
43
|
+
AuthenticateUser,
|
|
44
|
+
} = require('./user/use-cases/authenticate-user');
|
|
36
45
|
|
|
37
46
|
const {
|
|
38
47
|
CredentialRepository,
|
|
@@ -43,6 +52,18 @@ const {
|
|
|
43
52
|
const {
|
|
44
53
|
IntegrationMappingRepository,
|
|
45
54
|
} = require('./integrations/repositories/integration-mapping-repository');
|
|
55
|
+
const {
|
|
56
|
+
CreateProcess,
|
|
57
|
+
} = require('./integrations/use-cases/create-process');
|
|
58
|
+
const {
|
|
59
|
+
UpdateProcessState,
|
|
60
|
+
} = require('./integrations/use-cases/update-process-state');
|
|
61
|
+
const {
|
|
62
|
+
UpdateProcessMetrics,
|
|
63
|
+
} = require('./integrations/use-cases/update-process-metrics');
|
|
64
|
+
const {
|
|
65
|
+
GetProcess,
|
|
66
|
+
} = require('./integrations/use-cases/get-process');
|
|
46
67
|
const { Cryptor } = require('./encrypt');
|
|
47
68
|
const {
|
|
48
69
|
BaseError,
|
|
@@ -111,6 +132,9 @@ module.exports = {
|
|
|
111
132
|
createUserRepository,
|
|
112
133
|
UserRepositoryMongo,
|
|
113
134
|
UserRepositoryPostgres,
|
|
135
|
+
GetUserFromXFriggHeaders,
|
|
136
|
+
GetUserFromAdopterJwt,
|
|
137
|
+
AuthenticateUser,
|
|
114
138
|
CredentialRepository,
|
|
115
139
|
ModuleRepository,
|
|
116
140
|
IntegrationMappingRepository,
|
|
@@ -130,6 +154,10 @@ module.exports = {
|
|
|
130
154
|
createIntegrationRouter,
|
|
131
155
|
getModulesDefinitionFromIntegrationClasses,
|
|
132
156
|
LoadIntegrationContextUseCase,
|
|
157
|
+
CreateProcess,
|
|
158
|
+
UpdateProcessState,
|
|
159
|
+
UpdateProcessMetrics,
|
|
160
|
+
GetProcess,
|
|
133
161
|
|
|
134
162
|
// application - Command factories for integration developers
|
|
135
163
|
application,
|