@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
package/database/index.js
CHANGED
|
@@ -1,25 +1,38 @@
|
|
|
1
|
+
//todo: probably most of this file content can be removed
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Database Module Index
|
|
5
|
+
* Exports Mongoose models and connection utilities
|
|
6
|
+
*
|
|
7
|
+
* Note: Frigg uses the Repository pattern for data access.
|
|
8
|
+
* Models are not meant to be used directly - use repositories instead:
|
|
9
|
+
* - SyncRepository (syncs/sync-repository.js)
|
|
10
|
+
* - IntegrationRepository (integrations/integration-repository.js)
|
|
11
|
+
* - CredentialRepository (credential/credential-repository.js)
|
|
12
|
+
* etc.
|
|
13
|
+
*/
|
|
14
|
+
|
|
1
15
|
const { mongoose } = require('./mongoose');
|
|
2
|
-
const {
|
|
3
|
-
connectToDatabase,
|
|
4
|
-
disconnectFromDatabase,
|
|
5
|
-
createObjectId,
|
|
6
|
-
} = require('./mongo');
|
|
7
16
|
const { IndividualUser } = require('./models/IndividualUser');
|
|
8
17
|
const { OrganizationUser } = require('./models/OrganizationUser');
|
|
9
|
-
const { State } = require('./models/State');
|
|
10
|
-
const { Token } = require('./models/Token');
|
|
11
18
|
const { UserModel } = require('./models/UserModel');
|
|
12
19
|
const { WebsocketConnection } = require('./models/WebsocketConnection');
|
|
13
20
|
|
|
21
|
+
// Prisma exports
|
|
22
|
+
const { prisma } = require('./prisma');
|
|
23
|
+
const { TokenRepository } = require('../token/repositories/token-repository');
|
|
24
|
+
const {
|
|
25
|
+
WebsocketConnectionRepository,
|
|
26
|
+
} = require('../websocket/repositories/websocket-connection-repository');
|
|
27
|
+
|
|
14
28
|
module.exports = {
|
|
15
29
|
mongoose,
|
|
16
|
-
connectToDatabase,
|
|
17
|
-
disconnectFromDatabase,
|
|
18
|
-
createObjectId,
|
|
19
30
|
IndividualUser,
|
|
20
31
|
OrganizationUser,
|
|
21
|
-
State,
|
|
22
|
-
Token,
|
|
23
32
|
UserModel,
|
|
24
33
|
WebsocketConnection,
|
|
34
|
+
// Prisma
|
|
35
|
+
prisma,
|
|
36
|
+
TokenRepository,
|
|
37
|
+
WebsocketConnectionRepository,
|
|
25
38
|
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const { mongoose } = require('../mongoose');
|
|
2
|
-
const
|
|
2
|
+
const {
|
|
3
|
+
ApiGatewayManagementApiClient,
|
|
4
|
+
PostToConnectionCommand,
|
|
5
|
+
} = require('@aws-sdk/client-apigatewaymanagementapi');
|
|
3
6
|
|
|
4
7
|
const schema = new mongoose.Schema({
|
|
5
8
|
connectionId: { type: mongoose.Schema.Types.String },
|
|
@@ -8,24 +11,27 @@ const schema = new mongoose.Schema({
|
|
|
8
11
|
// Add a static method to get active connections
|
|
9
12
|
schema.statics.getActiveConnections = async function () {
|
|
10
13
|
try {
|
|
14
|
+
// Return empty array if websockets are not configured
|
|
15
|
+
if (!process.env.WEBSOCKET_API_ENDPOINT) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
const connections = await this.find({}, 'connectionId');
|
|
12
20
|
return connections.map((conn) => ({
|
|
13
21
|
connectionId: conn.connectionId,
|
|
14
22
|
send: async (data) => {
|
|
15
|
-
const apigwManagementApi = new
|
|
16
|
-
apiVersion: '2018-11-29',
|
|
23
|
+
const apigwManagementApi = new ApiGatewayManagementApiClient({
|
|
17
24
|
endpoint: process.env.WEBSOCKET_API_ENDPOINT,
|
|
18
25
|
});
|
|
19
26
|
|
|
20
27
|
try {
|
|
21
|
-
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.promise();
|
|
28
|
+
const command = new PostToConnectionCommand({
|
|
29
|
+
ConnectionId: conn.connectionId,
|
|
30
|
+
Data: JSON.stringify(data),
|
|
31
|
+
});
|
|
32
|
+
await apigwManagementApi.send(command);
|
|
27
33
|
} catch (error) {
|
|
28
|
-
if (error.statusCode === 410) {
|
|
34
|
+
if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
|
|
29
35
|
console.log(`Stale connection ${conn.connectionId}`);
|
|
30
36
|
await this.deleteOne({
|
|
31
37
|
connectionId: conn.connectionId,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// todo: we need to get rid of this entire models folder
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const {
|
|
2
|
+
createEncryptionExtension,
|
|
3
|
+
} = require('./encryption/prisma-encryption-extension');
|
|
4
|
+
const { registerCustomSchema } = require('./encryption/encryption-schema-registry');
|
|
5
|
+
const { logger } = require('./encryption/logger');
|
|
6
|
+
const { Cryptor } = require('../encrypt/Cryptor');
|
|
7
|
+
const config = require('./config');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ensures DATABASE_URL is set for MongoDB connections
|
|
11
|
+
* Falls back to MONGO_URI if DATABASE_URL is not set
|
|
12
|
+
* Infrastructure layer concern - maps legacy MONGO_URI to Prisma's expected DATABASE_URL
|
|
13
|
+
*
|
|
14
|
+
* Note: This should only be called when DB_TYPE is 'mongodb'
|
|
15
|
+
*/
|
|
16
|
+
function ensureMongoDbUrl() {
|
|
17
|
+
// If DATABASE_URL is already set, use it
|
|
18
|
+
if (process.env.DATABASE_URL && process.env.DATABASE_URL.trim()) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Fallback to MONGO_URI for backwards compatibility with DocumentDB deployments
|
|
23
|
+
if (process.env.MONGO_URI && process.env.MONGO_URI.trim()) {
|
|
24
|
+
process.env.DATABASE_URL = process.env.MONGO_URI;
|
|
25
|
+
logger.debug('Using MONGO_URI as DATABASE_URL for MongoDB connection');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Neither is set - error
|
|
30
|
+
throw new Error(
|
|
31
|
+
'DATABASE_URL or MONGO_URI environment variable must be set for MongoDB'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getEncryptionConfig() {
|
|
36
|
+
const STAGE = process.env.STAGE || process.env.NODE_ENV || 'development';
|
|
37
|
+
const shouldBypassEncryption = ['dev', 'test', 'local'].includes(STAGE);
|
|
38
|
+
|
|
39
|
+
if (shouldBypassEncryption) {
|
|
40
|
+
return { enabled: false };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const hasKMS =
|
|
44
|
+
process.env.KMS_KEY_ARN && process.env.KMS_KEY_ARN.trim() !== '';
|
|
45
|
+
const hasAES =
|
|
46
|
+
process.env.AES_KEY_ID && process.env.AES_KEY_ID.trim() !== '';
|
|
47
|
+
|
|
48
|
+
if (!hasKMS && !hasAES) {
|
|
49
|
+
logger.warn(
|
|
50
|
+
'No encryption keys configured (KMS_KEY_ARN or AES_KEY_ID). ' +
|
|
51
|
+
'Field-level encryption disabled. Set STAGE=production and configure keys to enable.'
|
|
52
|
+
);
|
|
53
|
+
return { enabled: false };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
enabled: true,
|
|
58
|
+
method: hasKMS ? 'kms' : 'aes',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Loads and registers custom encryption schema from appDefinition
|
|
64
|
+
* Gracefully handles cases where appDefinition is not available
|
|
65
|
+
*/
|
|
66
|
+
function loadCustomEncryptionSchema() {
|
|
67
|
+
try {
|
|
68
|
+
// Lazy require to avoid circular dependency issues
|
|
69
|
+
const path = require('node:path');
|
|
70
|
+
const { findNearestBackendPackageJson } = require('../utils');
|
|
71
|
+
|
|
72
|
+
const backendPackagePath = findNearestBackendPackageJson();
|
|
73
|
+
if (!backendPackagePath) {
|
|
74
|
+
return; // No backend found, skip custom schema
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const backendDir = path.dirname(backendPackagePath);
|
|
78
|
+
const backendIndexPath = path.join(backendDir, 'index.js');
|
|
79
|
+
|
|
80
|
+
const backendModule = require(backendIndexPath);
|
|
81
|
+
const appDefinition = backendModule?.Definition;
|
|
82
|
+
|
|
83
|
+
if (!appDefinition) {
|
|
84
|
+
return; // No app definition found
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const customSchema = appDefinition.encryption?.schema;
|
|
88
|
+
|
|
89
|
+
if (customSchema && Object.keys(customSchema).length > 0) {
|
|
90
|
+
registerCustomSchema(customSchema);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// Silently ignore errors - custom schema is optional
|
|
94
|
+
// This handles cases like:
|
|
95
|
+
// - Backend package.json not found (tests, standalone usage)
|
|
96
|
+
// - No appDefinition defined
|
|
97
|
+
// - No custom encryption schema specified
|
|
98
|
+
logger.debug('Could not load custom encryption schema:', error.message);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const prismaClientSingleton = () => {
|
|
103
|
+
let PrismaClient;
|
|
104
|
+
|
|
105
|
+
// Helper to try loading Prisma client from multiple locations
|
|
106
|
+
const loadPrismaClient = (dbType) => {
|
|
107
|
+
const paths = [
|
|
108
|
+
// Lambda layer location (when using Prisma Lambda layer)
|
|
109
|
+
`/opt/nodejs/node_modules/generated/prisma-${dbType}`,
|
|
110
|
+
// Local development location (relative to core package)
|
|
111
|
+
`../generated/prisma-${dbType}`,
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const path of paths) {
|
|
115
|
+
try {
|
|
116
|
+
return require(path).PrismaClient;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
// Continue to next path
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Cannot find Prisma client for ${dbType}. Tried paths: ${paths.join(', ')}`
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (config.DB_TYPE === 'mongodb') {
|
|
128
|
+
// Ensure DATABASE_URL is set (fallback to MONGO_URI if needed)
|
|
129
|
+
ensureMongoDbUrl();
|
|
130
|
+
PrismaClient = loadPrismaClient('mongodb');
|
|
131
|
+
} else if (config.DB_TYPE === 'postgresql') {
|
|
132
|
+
PrismaClient = loadPrismaClient('postgresql');
|
|
133
|
+
} else {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Unsupported database type: ${config.DB_TYPE}. Supported values: 'mongodb', 'postgresql'`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let client = new PrismaClient({
|
|
140
|
+
log: process.env.PRISMA_LOG_LEVEL
|
|
141
|
+
? process.env.PRISMA_LOG_LEVEL.split(',')
|
|
142
|
+
: ['error', 'warn'],
|
|
143
|
+
errorFormat: 'pretty',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const encryptionConfig = getEncryptionConfig();
|
|
147
|
+
|
|
148
|
+
if (encryptionConfig.enabled) {
|
|
149
|
+
try {
|
|
150
|
+
// Load custom encryption schema from appDefinition before creating extension
|
|
151
|
+
loadCustomEncryptionSchema();
|
|
152
|
+
|
|
153
|
+
const cryptor = new Cryptor({
|
|
154
|
+
shouldUseAws: encryptionConfig.method === 'kms',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
client = client.$extends(
|
|
158
|
+
createEncryptionExtension({
|
|
159
|
+
cryptor,
|
|
160
|
+
enabled: true,
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
logger.info(
|
|
165
|
+
`Field-level encryption enabled using ${encryptionConfig.method.toUpperCase()}`
|
|
166
|
+
);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.error(
|
|
169
|
+
'Failed to initialize encryption extension:',
|
|
170
|
+
error
|
|
171
|
+
);
|
|
172
|
+
logger.warn('Continuing without encryption...');
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
logger.info('Field-level encryption disabled');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return client;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const globalForPrisma = global;
|
|
182
|
+
|
|
183
|
+
// Lazy initialization - only create singleton when first accessed
|
|
184
|
+
function getPrismaClient() {
|
|
185
|
+
if (!globalForPrisma._prismaInstance) {
|
|
186
|
+
globalForPrisma._prismaInstance = prismaClientSingleton();
|
|
187
|
+
}
|
|
188
|
+
return globalForPrisma._prismaInstance;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Export a getter for lazy initialization
|
|
192
|
+
const prisma = new Proxy({}, {
|
|
193
|
+
get(target, prop) {
|
|
194
|
+
return getPrismaClient()[prop];
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
async function disconnectPrisma() {
|
|
199
|
+
await getPrismaClient().$disconnect();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function connectPrisma() {
|
|
203
|
+
await getPrismaClient().$connect();
|
|
204
|
+
|
|
205
|
+
// Initialize MongoDB schema - ensure all collections exist
|
|
206
|
+
// Only run for MongoDB/DocumentDB (not PostgreSQL)
|
|
207
|
+
// This prevents "Cannot create namespace in multi-document transaction" errors
|
|
208
|
+
if (config.DB_TYPE === 'mongodb') {
|
|
209
|
+
const { initializeMongoDBSchema } = require('./utils/mongodb-schema-init');
|
|
210
|
+
await initializeMongoDBSchema();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return getPrismaClient();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = {
|
|
217
|
+
prisma,
|
|
218
|
+
connectPrisma,
|
|
219
|
+
disconnectPrisma,
|
|
220
|
+
getEncryptionConfig,
|
|
221
|
+
ensureMongoDbUrl, // Exported for testing
|
|
222
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { HealthCheckRepositoryMongoDB } = require('./health-check-repository-mongodb');
|
|
2
|
+
const { HealthCheckRepositoryPostgreSQL } = require('./health-check-repository-postgres');
|
|
3
|
+
const config = require('../config');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Factory function to create a health check repository for the configured database type.
|
|
7
|
+
* Requires explicit prismaClient injection to support IoC container patterns.
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} options
|
|
10
|
+
* @param {Object} options.prismaClient - Prisma client instance (required for dependency injection)
|
|
11
|
+
* @returns {HealthCheckRepositoryInterface} Database-specific health check repository
|
|
12
|
+
* @throws {Error} If prismaClient is not provided
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const { prisma } = require('../prisma');
|
|
16
|
+
* const repository = createHealthCheckRepository({ prismaClient: prisma });
|
|
17
|
+
*/
|
|
18
|
+
function createHealthCheckRepository({ prismaClient } = {}) {
|
|
19
|
+
if (!prismaClient) {
|
|
20
|
+
throw new Error('prismaClient is required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const dbType = config.DB_TYPE;
|
|
24
|
+
|
|
25
|
+
switch (dbType) {
|
|
26
|
+
case 'mongodb':
|
|
27
|
+
return new HealthCheckRepositoryMongoDB({ prismaClient });
|
|
28
|
+
|
|
29
|
+
case 'postgresql':
|
|
30
|
+
return new HealthCheckRepositoryPostgreSQL({ prismaClient });
|
|
31
|
+
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
createHealthCheckRepository,
|
|
41
|
+
HealthCheckRepositoryMongoDB,
|
|
42
|
+
HealthCheckRepositoryPostgreSQL,
|
|
43
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Repository Interface
|
|
3
|
+
* Abstract base class defining the contract for health check persistence adapters
|
|
4
|
+
*
|
|
5
|
+
* This follows the Port in Hexagonal Architecture:
|
|
6
|
+
* - Domain layer depends on this abstraction
|
|
7
|
+
* - Concrete adapters implement this interface
|
|
8
|
+
* - Use cases receive repositories via dependency injection
|
|
9
|
+
*
|
|
10
|
+
* Note: Currently, HealthCheckRepository has identical structure across MongoDB and PostgreSQL,
|
|
11
|
+
* so HealthCheckRepository serves both. This interface exists for consistency and
|
|
12
|
+
* future-proofing if database-specific implementations become needed.
|
|
13
|
+
*
|
|
14
|
+
* @abstract
|
|
15
|
+
*/
|
|
16
|
+
class HealthCheckRepositoryInterface {
|
|
17
|
+
/**
|
|
18
|
+
* Ping database to verify connectivity
|
|
19
|
+
*
|
|
20
|
+
* @param {number} maxTimeMS - Maximum time in milliseconds
|
|
21
|
+
* @returns {Promise<number>} Response time in milliseconds
|
|
22
|
+
* @abstract
|
|
23
|
+
*/
|
|
24
|
+
async pingDatabase(maxTimeMS) {
|
|
25
|
+
throw new Error('Method pingDatabase must be implemented by subclass');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Save a test document
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} TestModel - Prisma model
|
|
32
|
+
* @param {Object} data - Data to save
|
|
33
|
+
* @returns {Promise<Object>} Saved document
|
|
34
|
+
* @abstract
|
|
35
|
+
*/
|
|
36
|
+
async saveTestDocument(TestModel, data) {
|
|
37
|
+
throw new Error('Method saveTestDocument must be implemented by subclass');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Find test document by ID
|
|
42
|
+
*
|
|
43
|
+
* @param {Object} TestModel - Prisma model
|
|
44
|
+
* @param {string|number} id - Document ID
|
|
45
|
+
* @returns {Promise<Object|null>} Document or null
|
|
46
|
+
* @abstract
|
|
47
|
+
*/
|
|
48
|
+
async findTestDocumentById(TestModel, id) {
|
|
49
|
+
throw new Error('Method findTestDocumentById must be implemented by subclass');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get raw document from collection
|
|
54
|
+
*
|
|
55
|
+
* @param {string} collectionName - Collection name
|
|
56
|
+
* @param {Object} filter - Filter criteria
|
|
57
|
+
* @returns {Promise<Object|null>} Raw document or null
|
|
58
|
+
* @abstract
|
|
59
|
+
*/
|
|
60
|
+
async getRawDocumentFromCollection(collectionName, filter) {
|
|
61
|
+
throw new Error('Method getRawDocumentFromCollection must be implemented by subclass');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Delete test document
|
|
66
|
+
*
|
|
67
|
+
* @param {Object} TestModel - Prisma model
|
|
68
|
+
* @param {string|number} id - Document ID
|
|
69
|
+
* @returns {Promise<Object>} Deletion result
|
|
70
|
+
* @abstract
|
|
71
|
+
*/
|
|
72
|
+
async deleteTestDocument(TestModel, id) {
|
|
73
|
+
throw new Error('Method deleteTestDocument must be implemented by subclass');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get database connection state
|
|
78
|
+
*
|
|
79
|
+
* @returns {Promise<Object>} Connection state info
|
|
80
|
+
* @abstract
|
|
81
|
+
*/
|
|
82
|
+
async getDatabaseConnectionState() {
|
|
83
|
+
throw new Error('Method getDatabaseConnectionState must be implemented by subclass');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { HealthCheckRepositoryInterface };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const { mongoose } = require('../mongoose');
|
|
2
|
+
const {
|
|
3
|
+
HealthCheckRepositoryInterface,
|
|
4
|
+
} = require('./health-check-repository-interface');
|
|
5
|
+
|
|
6
|
+
class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} params
|
|
9
|
+
* @param {Object} params.prismaClient - Prisma client instance
|
|
10
|
+
*/
|
|
11
|
+
constructor({ prismaClient }) {
|
|
12
|
+
super();
|
|
13
|
+
this.prisma = prismaClient;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @returns {Promise<{readyState: number, stateName: string, isConnected: boolean}>}
|
|
18
|
+
*/
|
|
19
|
+
async getDatabaseConnectionState() {
|
|
20
|
+
let isConnected = false;
|
|
21
|
+
let stateName = 'unknown';
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await this.prisma.$runCommandRaw({ ping: 1 });
|
|
25
|
+
isConnected = true;
|
|
26
|
+
stateName = 'connected';
|
|
27
|
+
} catch (error) {
|
|
28
|
+
stateName = 'disconnected';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
readyState: isConnected ? 1 : 0,
|
|
33
|
+
readyState: isConnected ? 1 : 0,
|
|
34
|
+
stateName,
|
|
35
|
+
isConnected,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async pingDatabase(maxTimeMS = 2000) {
|
|
40
|
+
const pingStart = Date.now();
|
|
41
|
+
|
|
42
|
+
// Create a timeout promise that rejects after maxTimeMS
|
|
43
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
44
|
+
setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Race between the database ping and the timeout
|
|
48
|
+
await Promise.race([
|
|
49
|
+
prisma.$queryRaw`SELECT 1`.catch(() => {
|
|
50
|
+
// For MongoDB, use runCommandRaw instead
|
|
51
|
+
return prisma.$runCommandRaw({ ping: 1 });
|
|
52
|
+
}),
|
|
53
|
+
timeoutPromise
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
return Date.now() - pingStart;
|
|
57
|
+
}
|
|
58
|
+
return Date.now() - pingStart;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async createCredential(credentialData) {
|
|
62
|
+
return await this.prisma.credential.create({
|
|
63
|
+
data: credentialData,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async findCredentialById(id) {
|
|
68
|
+
return await this.prisma.credential.findUnique({
|
|
69
|
+
where: { id },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {string} id
|
|
75
|
+
* @returns {Promise<Object|null>}
|
|
76
|
+
*/
|
|
77
|
+
async getRawCredentialById(id) {
|
|
78
|
+
const { ObjectId } = require('mongodb');
|
|
79
|
+
return await mongoose.connection.db
|
|
80
|
+
.collection('Credential')
|
|
81
|
+
.findOne({ _id: new ObjectId(id) });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async deleteCredential(id) {
|
|
85
|
+
await this.prisma.credential.delete({
|
|
86
|
+
where: { id },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { HealthCheckRepositoryMongoDB };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const {
|
|
2
|
+
HealthCheckRepositoryInterface,
|
|
3
|
+
} = require('./health-check-repository-interface');
|
|
4
|
+
|
|
5
|
+
class HealthCheckRepositoryPostgreSQL extends HealthCheckRepositoryInterface {
|
|
6
|
+
/**
|
|
7
|
+
* @param {Object} params
|
|
8
|
+
* @param {Object} params.prismaClient - Prisma client instance
|
|
9
|
+
*/
|
|
10
|
+
constructor({ prismaClient }) {
|
|
11
|
+
super();
|
|
12
|
+
this.prisma = prismaClient;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @returns {Promise<{readyState: number, stateName: string, isConnected: boolean}>}
|
|
17
|
+
*/
|
|
18
|
+
async getDatabaseConnectionState() {
|
|
19
|
+
let isConnected = false;
|
|
20
|
+
let stateName = 'unknown';
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
await this.prisma.$queryRaw`SELECT 1`;
|
|
24
|
+
isConnected = true;
|
|
25
|
+
stateName = 'connected';
|
|
26
|
+
} catch (error) {
|
|
27
|
+
stateName = 'disconnected';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
readyState: isConnected ? 1 : 0,
|
|
32
|
+
stateName,
|
|
33
|
+
isConnected,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {number} maxTimeMS
|
|
39
|
+
* @returns {Promise<number>} Response time in milliseconds
|
|
40
|
+
*/
|
|
41
|
+
async pingDatabase(maxTimeMS = 2000) {
|
|
42
|
+
const pingStart = Date.now();
|
|
43
|
+
await this.prisma.$queryRaw`SELECT 1`;
|
|
44
|
+
return Date.now() - pingStart;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async createCredential(credentialData) {
|
|
48
|
+
return await this.prisma.credential.create({
|
|
49
|
+
data: credentialData,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async findCredentialById(id) {
|
|
54
|
+
return await this.prisma.credential.findUnique({
|
|
55
|
+
where: { id },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {string} id
|
|
61
|
+
* @returns {Promise<Object|null>}
|
|
62
|
+
*/
|
|
63
|
+
async getRawCredentialById(id) {
|
|
64
|
+
const results = await this.prisma.$queryRaw`
|
|
65
|
+
SELECT * FROM "Credential" WHERE id = ${id}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
if (!results || results.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return results[0];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async deleteCredential(id) {
|
|
76
|
+
await this.prisma.credential.delete({
|
|
77
|
+
where: { id },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { HealthCheckRepositoryPostgreSQL };
|