@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
|
@@ -3,6 +3,22 @@ const { Worker } = require('@friggframework/core');
|
|
|
3
3
|
const {
|
|
4
4
|
IntegrationEventDispatcher,
|
|
5
5
|
} = require('./integration-event-dispatcher');
|
|
6
|
+
const {
|
|
7
|
+
GetIntegrationInstance,
|
|
8
|
+
} = require('../integrations/use-cases/get-integration-instance');
|
|
9
|
+
const { ModuleFactory } = require('../modules/module-factory');
|
|
10
|
+
const {
|
|
11
|
+
createProcessRepository,
|
|
12
|
+
} = require('../integrations/repositories/process-repository-factory');
|
|
13
|
+
const {
|
|
14
|
+
createIntegrationRepository,
|
|
15
|
+
} = require('../integrations/repositories/integration-repository-factory');
|
|
16
|
+
const {
|
|
17
|
+
createModuleRepository,
|
|
18
|
+
} = require('../modules/repositories/module-repository-factory');
|
|
19
|
+
const {
|
|
20
|
+
getModulesDefinitionFromIntegrationClasses,
|
|
21
|
+
} = require('../integrations/utils/map-integration-dto');
|
|
6
22
|
|
|
7
23
|
const loadRouterFromObject = (IntegrationClass, routerObject) => {
|
|
8
24
|
const router = Router();
|
|
@@ -33,20 +49,119 @@ const loadRouterFromObject = (IntegrationClass, routerObject) => {
|
|
|
33
49
|
return router;
|
|
34
50
|
};
|
|
35
51
|
|
|
52
|
+
const initializeRepositories = () => {
|
|
53
|
+
const processRepository = createProcessRepository();
|
|
54
|
+
const integrationRepository = createIntegrationRepository();
|
|
55
|
+
const moduleRepository = createModuleRepository();
|
|
56
|
+
|
|
57
|
+
return { processRepository, integrationRepository, moduleRepository };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const createModuleFactoryWithDefinitions = (
|
|
61
|
+
moduleRepository,
|
|
62
|
+
integrationClasses
|
|
63
|
+
) => {
|
|
64
|
+
const moduleDefinitions =
|
|
65
|
+
getModulesDefinitionFromIntegrationClasses(integrationClasses);
|
|
66
|
+
|
|
67
|
+
return new ModuleFactory({
|
|
68
|
+
moduleRepository,
|
|
69
|
+
moduleDefinitions,
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const loadIntegrationForWebhook = async (integrationId) => {
|
|
74
|
+
const { loadAppDefinition } = require('./app-definition-loader');
|
|
75
|
+
const { integrations: integrationClasses } = loadAppDefinition();
|
|
76
|
+
|
|
77
|
+
const { integrationRepository, moduleRepository } =
|
|
78
|
+
initializeRepositories();
|
|
79
|
+
|
|
80
|
+
const moduleFactory = createModuleFactoryWithDefinitions(
|
|
81
|
+
moduleRepository,
|
|
82
|
+
integrationClasses
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const getIntegrationInstance = new GetIntegrationInstance({
|
|
86
|
+
integrationRepository,
|
|
87
|
+
integrationClasses,
|
|
88
|
+
moduleFactory,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const integrationRecord = await integrationRepository.findIntegrationById(
|
|
92
|
+
integrationId
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return await getIntegrationInstance.execute(
|
|
96
|
+
integrationId,
|
|
97
|
+
integrationRecord.userId
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const loadIntegrationForProcess = async (processId, integrationClass) => {
|
|
102
|
+
const { processRepository, integrationRepository, moduleRepository } =
|
|
103
|
+
initializeRepositories();
|
|
104
|
+
|
|
105
|
+
const moduleFactory = createModuleFactoryWithDefinitions(moduleRepository, [
|
|
106
|
+
integrationClass,
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
const getIntegrationInstance = new GetIntegrationInstance({
|
|
110
|
+
integrationRepository,
|
|
111
|
+
integrationClasses: [integrationClass],
|
|
112
|
+
moduleFactory,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!processId) {
|
|
116
|
+
throw new Error('processId is required in queue message data');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const process = await processRepository.findById(processId);
|
|
120
|
+
|
|
121
|
+
if (!process) {
|
|
122
|
+
throw new Error(`Process not found: ${processId}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return await getIntegrationInstance.execute(
|
|
126
|
+
process.integrationId,
|
|
127
|
+
process.userId
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
36
131
|
const createQueueWorker = (integrationClass) => {
|
|
37
132
|
class QueueWorker extends Worker {
|
|
38
133
|
async _run(params, context) {
|
|
39
134
|
try {
|
|
40
|
-
|
|
135
|
+
let integrationInstance;
|
|
136
|
+
if (
|
|
137
|
+
params.event === 'ON_WEBHOOK' &&
|
|
138
|
+
params.data?.integrationId
|
|
139
|
+
) {
|
|
140
|
+
integrationInstance = await loadIntegrationForWebhook(
|
|
141
|
+
params.data.integrationId
|
|
142
|
+
);
|
|
143
|
+
} else if (params.data?.processId) {
|
|
144
|
+
integrationInstance = await loadIntegrationForProcess(
|
|
145
|
+
params.data.processId,
|
|
146
|
+
integrationClass
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
// Instantiates a DRY integration class without database records.
|
|
150
|
+
// There will be cases where we need to use helpers that the api modules can export.
|
|
151
|
+
// Like for HubSpot, the answer is to do a reverse lookup for the integration by the entity external ID (HubSpot Portal ID),
|
|
152
|
+
// and then you'll have the integration ID available to hydrate from.
|
|
153
|
+
integrationInstance = new integrationClass();
|
|
154
|
+
}
|
|
155
|
+
|
|
41
156
|
const dispatcher = new IntegrationEventDispatcher(
|
|
42
157
|
integrationInstance
|
|
43
158
|
);
|
|
44
|
-
|
|
159
|
+
|
|
160
|
+
return await dispatcher.dispatchJob({
|
|
45
161
|
event: params.event,
|
|
46
162
|
data: params.data,
|
|
47
163
|
context: context,
|
|
48
164
|
});
|
|
49
|
-
return res;
|
|
50
165
|
} catch (error) {
|
|
51
166
|
console.error(
|
|
52
167
|
`Error in ${params.event} for ${integrationClass.Definition.name}:`,
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Migration Handler for AWS Lambda
|
|
3
|
+
*
|
|
4
|
+
* Executes Prisma migrations in a Lambda environment.
|
|
5
|
+
* Based on AWS best practices for running migrations in serverless environments.
|
|
6
|
+
*
|
|
7
|
+
* Supported Commands:
|
|
8
|
+
* - deploy: Apply pending migrations to the database (production-safe)
|
|
9
|
+
* - reset: Reset database and apply all migrations (DANGEROUS - dev only)
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* // Via Lambda invoke
|
|
13
|
+
* {
|
|
14
|
+
* "command": "deploy" // or "reset"
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Requirements:
|
|
18
|
+
* - Prisma CLI must be included in deployment or Lambda layer
|
|
19
|
+
* - DATABASE_URL environment variable must be set
|
|
20
|
+
* - VPC configuration for Aurora access
|
|
21
|
+
*
|
|
22
|
+
* Reference: https://www.prisma.io/docs/guides/deployment/deployment-guides/deploying-to-aws-lambda
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const { execFile } = require('child_process');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute Prisma migration command
|
|
30
|
+
*
|
|
31
|
+
* @param {string} command - Migration command ('deploy' or 'reset')
|
|
32
|
+
* @param {string} schemaPath - Path to Prisma schema file
|
|
33
|
+
* @returns {Promise<number>} Exit code
|
|
34
|
+
*/
|
|
35
|
+
async function executePrismaMigration(command, schemaPath) {
|
|
36
|
+
console.log(`Executing Prisma migration: ${command}`);
|
|
37
|
+
console.log(`Schema path: ${schemaPath}`);
|
|
38
|
+
console.log(`Database URL: ${process.env.DATABASE_URL ? '[SET]' : '[NOT SET]'}`);
|
|
39
|
+
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
// Build command arguments
|
|
42
|
+
const args = ['migrate', command];
|
|
43
|
+
|
|
44
|
+
// Add command-specific options
|
|
45
|
+
if (command === 'reset') {
|
|
46
|
+
args.push('--force'); // Skip confirmation prompt
|
|
47
|
+
args.push('--skip-generate'); // Skip client generation (already done in layer)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add schema path if provided
|
|
51
|
+
if (schemaPath) {
|
|
52
|
+
args.push('--schema', schemaPath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`Running: prisma ${args.join(' ')}`);
|
|
56
|
+
|
|
57
|
+
// Execute Prisma CLI
|
|
58
|
+
execFile(
|
|
59
|
+
path.resolve('./node_modules/prisma/build/index.js'),
|
|
60
|
+
args,
|
|
61
|
+
{
|
|
62
|
+
env: {
|
|
63
|
+
...process.env,
|
|
64
|
+
// Ensure Prisma uses the correct binary target
|
|
65
|
+
PRISMA_CLI_BINARY_TARGETS: 'rhel-openssl-3.0.x',
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
(error, stdout, stderr) => {
|
|
69
|
+
// Log all output
|
|
70
|
+
if (stdout) {
|
|
71
|
+
console.log('STDOUT:', stdout);
|
|
72
|
+
}
|
|
73
|
+
if (stderr) {
|
|
74
|
+
console.error('STDERR:', stderr);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (error) {
|
|
78
|
+
console.error(`Migration ${command} exited with error:`, error.message);
|
|
79
|
+
console.error(`Exit code: ${error.code || 1}`);
|
|
80
|
+
resolve(error.code || 1);
|
|
81
|
+
} else {
|
|
82
|
+
console.log(`Migration ${command} completed successfully`);
|
|
83
|
+
resolve(0);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validate migration command
|
|
92
|
+
*/
|
|
93
|
+
function validateCommand(command) {
|
|
94
|
+
const validCommands = ['deploy', 'reset'];
|
|
95
|
+
|
|
96
|
+
if (!validCommands.includes(command)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Invalid migration command: "${command}". ` +
|
|
99
|
+
`Valid commands are: ${validCommands.join(', ')}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Extra validation for dangerous commands
|
|
104
|
+
if (command === 'reset') {
|
|
105
|
+
const stage = process.env.STAGE || process.env.NODE_ENV;
|
|
106
|
+
if (stage === 'production' || stage === 'prod') {
|
|
107
|
+
throw new Error(
|
|
108
|
+
'BLOCKED: "reset" command is not allowed in production environment. ' +
|
|
109
|
+
'This command would delete all data. Use "deploy" instead.'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
console.warn('⚠️ WARNING: "reset" will DELETE all data and reset the database!');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Determine which Prisma schema to use based on database type
|
|
118
|
+
*/
|
|
119
|
+
function getSchemaPath() {
|
|
120
|
+
// In Lambda, schemas are in @friggframework/core/generated/
|
|
121
|
+
const baseSchemaPath = './node_modules/@friggframework/core/generated';
|
|
122
|
+
|
|
123
|
+
// Check if Postgres is enabled
|
|
124
|
+
if (process.env.DATABASE_URL?.includes('postgresql') || process.env.DATABASE_URL?.includes('postgres')) {
|
|
125
|
+
const schemaPath = `${baseSchemaPath}/prisma-postgresql/schema.prisma`;
|
|
126
|
+
console.log(`Using PostgreSQL schema: ${schemaPath}`);
|
|
127
|
+
return schemaPath;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check if MongoDB is enabled
|
|
131
|
+
if (process.env.DATABASE_URL?.includes('mongodb')) {
|
|
132
|
+
const schemaPath = `${baseSchemaPath}/prisma-mongodb/schema.prisma`;
|
|
133
|
+
console.log(`Using MongoDB schema: ${schemaPath}`);
|
|
134
|
+
return schemaPath;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Default to PostgreSQL
|
|
138
|
+
console.log('DATABASE_URL not set or database type unknown, defaulting to PostgreSQL');
|
|
139
|
+
return `${baseSchemaPath}/prisma-postgresql/schema.prisma`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Lambda handler for database migrations
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} event - Lambda event
|
|
146
|
+
* @param {string} event.command - Migration command ('deploy' or 'reset')
|
|
147
|
+
* @param {Object} context - Lambda context
|
|
148
|
+
* @returns {Promise<Object>} Migration result
|
|
149
|
+
*/
|
|
150
|
+
exports.handler = async (event, context) => {
|
|
151
|
+
const startTime = Date.now();
|
|
152
|
+
|
|
153
|
+
console.log('='.repeat(60));
|
|
154
|
+
console.log('Database Migration Handler');
|
|
155
|
+
console.log('='.repeat(60));
|
|
156
|
+
console.log('Event:', JSON.stringify(event, null, 2));
|
|
157
|
+
console.log('Context:', JSON.stringify({
|
|
158
|
+
functionName: context.functionName,
|
|
159
|
+
functionVersion: context.functionVersion,
|
|
160
|
+
memoryLimitInMB: context.memoryLimitInMB,
|
|
161
|
+
logGroupName: context.logGroupName,
|
|
162
|
+
}, null, 2));
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Get migration command (default to 'deploy')
|
|
166
|
+
const command = event.command || 'deploy';
|
|
167
|
+
|
|
168
|
+
// Validate command
|
|
169
|
+
validateCommand(command);
|
|
170
|
+
|
|
171
|
+
// Check required environment variables
|
|
172
|
+
if (!process.env.DATABASE_URL) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
'DATABASE_URL environment variable is not set. ' +
|
|
175
|
+
'Cannot connect to database for migrations.'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Determine schema path
|
|
180
|
+
const schemaPath = getSchemaPath();
|
|
181
|
+
|
|
182
|
+
// Execute migration
|
|
183
|
+
const exitCode = await executePrismaMigration(command, schemaPath);
|
|
184
|
+
|
|
185
|
+
const duration = Date.now() - startTime;
|
|
186
|
+
|
|
187
|
+
if (exitCode === 0) {
|
|
188
|
+
const result = {
|
|
189
|
+
success: true,
|
|
190
|
+
command,
|
|
191
|
+
message: `Migration ${command} completed successfully`,
|
|
192
|
+
duration: `${duration}ms`,
|
|
193
|
+
timestamp: new Date().toISOString(),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
console.log('='.repeat(60));
|
|
197
|
+
console.log('Migration completed successfully');
|
|
198
|
+
console.log(JSON.stringify(result, null, 2));
|
|
199
|
+
console.log('='.repeat(60));
|
|
200
|
+
|
|
201
|
+
return result;
|
|
202
|
+
} else {
|
|
203
|
+
throw new Error(`Migration ${command} failed with exit code ${exitCode}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const duration = Date.now() - startTime;
|
|
208
|
+
|
|
209
|
+
console.error('='.repeat(60));
|
|
210
|
+
console.error('Migration failed');
|
|
211
|
+
console.error('Error:', error.message);
|
|
212
|
+
console.error('Stack:', error.stack);
|
|
213
|
+
console.error('='.repeat(60));
|
|
214
|
+
|
|
215
|
+
const errorResult = {
|
|
216
|
+
success: false,
|
|
217
|
+
command: event.command || 'unknown',
|
|
218
|
+
error: error.message,
|
|
219
|
+
duration: `${duration}ms`,
|
|
220
|
+
timestamp: new Date().toISOString(),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Return error (don't throw) so Lambda doesn't retry
|
|
224
|
+
return errorResult;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
package/handlers/routers/auth.js
CHANGED
|
@@ -3,7 +3,7 @@ const { createAppHandler } = require('./../app-handler-helpers');
|
|
|
3
3
|
|
|
4
4
|
const router = createIntegrationRouter();
|
|
5
5
|
|
|
6
|
-
router.route('/redirect/:appId').get((req, res) => {
|
|
6
|
+
router.route('/api/integrations/redirect/:appId').get((req, res) => {
|
|
7
7
|
res.redirect(
|
|
8
8
|
`${process.env.FRONTEND_URI}/redirect/${req.params.appId
|
|
9
9
|
}?${new URLSearchParams(req.query)}`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Migration Router Lambda Handler
|
|
3
|
+
*
|
|
4
|
+
* Minimal Lambda wrapper that avoids loading core/index.js
|
|
5
|
+
* (which would try to load user/** modules excluded from migration packages)
|
|
6
|
+
*
|
|
7
|
+
* This handler is intentionally simpler than health.handler.js to avoid dependencies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const serverlessHttp = require('serverless-http');
|
|
11
|
+
const express = require('express');
|
|
12
|
+
const cors = require('cors');
|
|
13
|
+
const dbMigrationRouter = require('./db-migration');
|
|
14
|
+
|
|
15
|
+
// Create minimal Express app
|
|
16
|
+
const app = express();
|
|
17
|
+
app.use(cors());
|
|
18
|
+
app.use(express.json());
|
|
19
|
+
app.use(dbMigrationRouter);
|
|
20
|
+
|
|
21
|
+
// Error handler
|
|
22
|
+
app.use((err, req, res, next) => {
|
|
23
|
+
console.error('Error:', err);
|
|
24
|
+
res.status(500).json({ message: 'Internal Server Error' });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Export as .handler property (Lambda config: db-migration.handler)
|
|
28
|
+
module.exports.handler = serverlessHttp(app);
|
|
29
|
+
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Migration Router
|
|
3
|
+
*
|
|
4
|
+
* HTTP API for triggering and monitoring database migrations.
|
|
5
|
+
*
|
|
6
|
+
* Endpoints:
|
|
7
|
+
* - GET /db-migrate/status - Check if migrations are pending
|
|
8
|
+
* - POST /db-migrate - Trigger async migration (queues job)
|
|
9
|
+
* - GET /db-migrate/:processId - Check migration status
|
|
10
|
+
*
|
|
11
|
+
* Security:
|
|
12
|
+
* - Requires ADMIN_API_KEY header for all requests
|
|
13
|
+
*
|
|
14
|
+
* Architecture:
|
|
15
|
+
* - Router (Adapter Layer) → Use Cases (Domain) → Repositories (Infrastructure)
|
|
16
|
+
* - Follows DDD/Hexagonal architecture
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { Router } = require('express');
|
|
20
|
+
const catchAsyncError = require('express-async-handler');
|
|
21
|
+
const { MigrationStatusRepositoryS3 } = require('../../database/repositories/migration-status-repository-s3');
|
|
22
|
+
const {
|
|
23
|
+
TriggerDatabaseMigrationUseCase,
|
|
24
|
+
ValidationError: TriggerValidationError,
|
|
25
|
+
} = require('../../database/use-cases/trigger-database-migration-use-case');
|
|
26
|
+
const {
|
|
27
|
+
GetMigrationStatusUseCase,
|
|
28
|
+
ValidationError: GetValidationError,
|
|
29
|
+
NotFoundError,
|
|
30
|
+
} = require('../../database/use-cases/get-migration-status-use-case');
|
|
31
|
+
const { LambdaInvoker } = require('../../database/adapters/lambda-invoker');
|
|
32
|
+
const {
|
|
33
|
+
GetDatabaseStateViaWorkerUseCase,
|
|
34
|
+
} = require('../../database/use-cases/get-database-state-via-worker-use-case');
|
|
35
|
+
|
|
36
|
+
const router = Router();
|
|
37
|
+
|
|
38
|
+
// Dependency injection
|
|
39
|
+
// Use S3 repository to avoid User table dependency (chicken-and-egg problem)
|
|
40
|
+
const bucketName = process.env.S3_BUCKET_NAME || process.env.MIGRATION_STATUS_BUCKET;
|
|
41
|
+
const migrationStatusRepository = new MigrationStatusRepositoryS3(bucketName);
|
|
42
|
+
|
|
43
|
+
const triggerMigrationUseCase = new TriggerDatabaseMigrationUseCase({
|
|
44
|
+
migrationStatusRepository,
|
|
45
|
+
// Note: QueuerUtil is used directly in the use case (static utility)
|
|
46
|
+
});
|
|
47
|
+
const getStatusUseCase = new GetMigrationStatusUseCase({ migrationStatusRepository });
|
|
48
|
+
|
|
49
|
+
// Lambda invocation for database state check (keeps router lightweight)
|
|
50
|
+
const lambdaInvoker = new LambdaInvoker();
|
|
51
|
+
const workerFunctionName = process.env.WORKER_FUNCTION_NAME ||
|
|
52
|
+
`${process.env.SERVICE || 'unknown'}-${process.env.STAGE || 'production'}-dbMigrationWorker`;
|
|
53
|
+
|
|
54
|
+
const getDatabaseStateUseCase = new GetDatabaseStateViaWorkerUseCase({
|
|
55
|
+
lambdaInvoker,
|
|
56
|
+
workerFunctionName,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Admin API key validation middleware
|
|
61
|
+
* Matches pattern from health.js:72-88
|
|
62
|
+
*/
|
|
63
|
+
const validateApiKey = (req, res, next) => {
|
|
64
|
+
const apiKey = req.headers['x-frigg-admin-api-key'];
|
|
65
|
+
|
|
66
|
+
if (!apiKey || apiKey !== process.env.ADMIN_API_KEY) {
|
|
67
|
+
console.error('Unauthorized access attempt to db-migrate endpoint');
|
|
68
|
+
return res.status(401).json({
|
|
69
|
+
status: 'error',
|
|
70
|
+
message: 'Unauthorized - x-frigg-admin-api-key header required',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
next();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Apply API key validation to all routes
|
|
78
|
+
router.use(validateApiKey);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* POST /db-migrate
|
|
82
|
+
*
|
|
83
|
+
* Trigger database migration (async via SQS queue)
|
|
84
|
+
*
|
|
85
|
+
* Request body:
|
|
86
|
+
* {
|
|
87
|
+
* userId: string (optional, defaults to 'admin'),
|
|
88
|
+
* dbType: 'postgresql' | 'mongodb',
|
|
89
|
+
* stage: string (e.g., 'production', 'dev')
|
|
90
|
+
* }
|
|
91
|
+
*
|
|
92
|
+
* Response (202 Accepted):
|
|
93
|
+
* {
|
|
94
|
+
* success: true,
|
|
95
|
+
* processId: string,
|
|
96
|
+
* state: 'INITIALIZING',
|
|
97
|
+
* statusUrl: string,
|
|
98
|
+
* message: string
|
|
99
|
+
* }
|
|
100
|
+
*/
|
|
101
|
+
router.post(
|
|
102
|
+
'/db-migrate',
|
|
103
|
+
catchAsyncError(async (req, res) => {
|
|
104
|
+
// Migration infrastructure is PostgreSQL-only, so hardcode dbType
|
|
105
|
+
const dbType = 'postgresql';
|
|
106
|
+
const { stage } = req.body;
|
|
107
|
+
// TODO: Extract userId from JWT token when auth is implemented
|
|
108
|
+
const userId = req.body.userId || 'admin';
|
|
109
|
+
|
|
110
|
+
console.log(`Migration trigger request: dbType=${dbType}, stage=${stage || 'auto-detect'}, userId=${userId}`);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await triggerMigrationUseCase.execute({
|
|
114
|
+
userId,
|
|
115
|
+
dbType,
|
|
116
|
+
stage,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// 202 Accepted - request accepted but not completed
|
|
120
|
+
res.status(202).json(result);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// Handle validation errors (400 Bad Request)
|
|
123
|
+
if (error instanceof TriggerValidationError) {
|
|
124
|
+
return res.status(400).json({
|
|
125
|
+
success: false,
|
|
126
|
+
error: error.message,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Re-throw other errors for global error handler
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* GET /db-migrate/status
|
|
138
|
+
*
|
|
139
|
+
* Check if database has pending migrations
|
|
140
|
+
*
|
|
141
|
+
* Query params:
|
|
142
|
+
* - stage: string (optional, defaults to STAGE env var or 'production')
|
|
143
|
+
*
|
|
144
|
+
* Response (200 OK):
|
|
145
|
+
* {
|
|
146
|
+
* upToDate: boolean,
|
|
147
|
+
* pendingMigrations: number,
|
|
148
|
+
* dbType: 'postgresql',
|
|
149
|
+
* stage: string,
|
|
150
|
+
* recommendation?: string (if migrations pending),
|
|
151
|
+
* error?: string (if database check failed)
|
|
152
|
+
* }
|
|
153
|
+
*/
|
|
154
|
+
router.get(
|
|
155
|
+
'/db-migrate/status',
|
|
156
|
+
catchAsyncError(async (req, res) => {
|
|
157
|
+
const stage = req.query.stage || process.env.STAGE || 'production';
|
|
158
|
+
|
|
159
|
+
console.log(`Checking database state: stage=${stage}, worker=${workerFunctionName}`);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// Invoke worker Lambda to check database state
|
|
163
|
+
const status = await getDatabaseStateUseCase.execute(stage);
|
|
164
|
+
|
|
165
|
+
res.status(200).json(status);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// Log full error for debugging
|
|
168
|
+
console.error('Database state check failed:', error);
|
|
169
|
+
|
|
170
|
+
// Return sanitized error to client
|
|
171
|
+
return res.status(500).json({
|
|
172
|
+
success: false,
|
|
173
|
+
error: 'Failed to check database state',
|
|
174
|
+
details: error.message,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* GET /db-migrate/:migrationId
|
|
182
|
+
*
|
|
183
|
+
* Get migration status by migration ID
|
|
184
|
+
*
|
|
185
|
+
* Response (200 OK):
|
|
186
|
+
* {
|
|
187
|
+
* processId: string,
|
|
188
|
+
* type: 'DATABASE_MIGRATION',
|
|
189
|
+
* state: 'INITIALIZING' | 'RUNNING' | 'COMPLETED' | 'FAILED',
|
|
190
|
+
* context: {
|
|
191
|
+
* dbType: string,
|
|
192
|
+
* stage: string,
|
|
193
|
+
* migrationCommand: string (if started)
|
|
194
|
+
* },
|
|
195
|
+
* results: {
|
|
196
|
+
* success: boolean (if completed),
|
|
197
|
+
* duration: string (if completed),
|
|
198
|
+
* error: string (if failed)
|
|
199
|
+
* },
|
|
200
|
+
* createdAt: string,
|
|
201
|
+
* updatedAt: string
|
|
202
|
+
* }
|
|
203
|
+
*/
|
|
204
|
+
router.get(
|
|
205
|
+
'/db-migrate/:migrationId',
|
|
206
|
+
catchAsyncError(async (req, res) => {
|
|
207
|
+
const { migrationId } = req.params;
|
|
208
|
+
const stage = req.query.stage || process.env.STAGE || 'production';
|
|
209
|
+
|
|
210
|
+
console.log(`Migration status request: migrationId=${migrationId}, stage=${stage}`);
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const status = await getStatusUseCase.execute(migrationId, stage);
|
|
214
|
+
|
|
215
|
+
res.status(200).json(status);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
// Handle not found errors (404 Not Found)
|
|
218
|
+
if (error instanceof NotFoundError) {
|
|
219
|
+
return res.status(404).json({
|
|
220
|
+
success: false,
|
|
221
|
+
error: error.message,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Handle validation errors (400 Bad Request)
|
|
226
|
+
if (error instanceof GetValidationError) {
|
|
227
|
+
return res.status(400).json({
|
|
228
|
+
success: false,
|
|
229
|
+
error: error.message,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Re-throw other errors for global error handler
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Minimal Lambda handler (avoids app-handler-helpers which loads core/index.js → user/**)
|
|
240
|
+
const serverlessHttp = require('serverless-http');
|
|
241
|
+
const express = require('express');
|
|
242
|
+
const cors = require('cors');
|
|
243
|
+
|
|
244
|
+
const app = express();
|
|
245
|
+
app.use(cors());
|
|
246
|
+
app.use(express.json());
|
|
247
|
+
app.use(router);
|
|
248
|
+
app.use((err, req, res, next) => {
|
|
249
|
+
console.error('Migration Router Error:', err);
|
|
250
|
+
res.status(500).json({ message: 'Internal Server Error' });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const handler = serverlessHttp(app);
|
|
254
|
+
|
|
255
|
+
module.exports = { handler, router };
|
|
256
|
+
|