@friggframework/core 2.0.0-next.8 → 2.0.0-next.80
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 +694 -0
- package/README.md +959 -50
- package/application/commands/README.md +451 -0
- package/application/commands/credential-commands.js +245 -0
- package/application/commands/entity-commands.js +336 -0
- package/application/commands/integration-commands.js +210 -0
- package/application/commands/scheduler-commands.js +263 -0
- package/application/commands/user-commands.js +283 -0
- package/application/index.js +73 -0
- package/assertions/index.js +0 -3
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +60 -24
- package/core/create-handler.js +79 -8
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +269 -0
- package/credential/repositories/credential-repository-postgres.js +287 -0
- package/credential/repositories/credential-repository.js +300 -0
- package/credential/use-cases/get-credential-for-user.js +25 -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/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +839 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +268 -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 +21 -21
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +138 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -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 +139 -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 +94 -0
- package/database/utils/mongodb-schema-init.js +108 -0
- package/database/utils/prisma-runner.js +477 -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/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +15 -7
- package/errors/index.js +2 -0
- 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 +335 -0
- package/generated/prisma-mongodb/index-browser.js +317 -0
- package/generated/prisma-mongodb/index.d.ts +22955 -0
- package/generated/prisma-mongodb/index.js +360 -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 +342 -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 +357 -0
- package/generated/prisma-postgresql/index-browser.js +339 -0
- package/generated/prisma-postgresql/index.d.ts +25131 -0
- package/generated/prisma-postgresql/index.js +382 -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 +364 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +57 -0
- package/handlers/backend-utils.js +262 -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 +326 -0
- package/handlers/routers/health.js +516 -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/dlq-processor.js +63 -0
- package/handlers/workers/integration-defined-workers.js +23 -0
- package/index.js +82 -46
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
- package/infrastructure/scheduler/index.js +33 -0
- package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
- package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +364 -55
- package/integrations/integration-router.js +375 -179
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -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-documentdb.js +219 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +144 -0
- package/integrations/repositories/integration-repository-mongo.js +330 -0
- package/integrations/repositories/integration-repository-postgres.js +385 -0
- package/integrations/repositories/process-repository-documentdb.js +243 -0
- package/integrations/repositories/process-repository-factory.js +53 -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/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +105 -0
- package/integrations/tests/doubles/test-integration-repository.js +112 -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 +88 -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 +92 -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 +37 -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}/index.js +0 -10
- package/modules/module-factory.js +56 -0
- package/modules/module.js +256 -0
- package/modules/repositories/module-repository-documentdb.js +335 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +408 -0
- package/modules/repositories/module-repository-postgres.js +453 -0
- package/modules/repositories/module-repository.js +345 -0
- package/modules/requester/api-key.js +52 -0
- package/modules/requester/oauth-2.js +396 -0
- package/{module-plugin → modules}/requester/requester.js +4 -2
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
- 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 +71 -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 +74 -0
- package/modules/use-cases/process-authorization-callback.js +177 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -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/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +345 -0
- package/queues/queuer-util.js +103 -21
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -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-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/associations/index.d.ts +0 -17
- package/types/core/index.d.ts +12 -4
- package/types/database/index.d.ts +10 -2
- package/types/encrypt/index.d.ts +5 -3
- package/types/integrations/index.d.ts +3 -8
- package/types/module-plugin/index.d.ts +17 -69
- package/types/syncs/index.d.ts +0 -17
- package/user/repositories/user-repository-documentdb.js +441 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +201 -0
- package/user/repositories/user-repository-mongo.js +308 -0
- package/user/repositories/user-repository-postgres.js +360 -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 +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -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/assertions/is-equal.js +0 -17
- package/associations/model.js +0 -54
- package/database/models/IndividualUser.js +0 -76
- package/database/models/OrganizationUser.js +0 -29
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/models/UserModel.js +0 -7
- package/database/models/WebsocketConnection.js +0 -49
- package/database/mongo.js +0 -45
- package/database/mongoose.js +0 -5
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- package/encrypt/test-encrypt.js +0 -107
- 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/entity.js +0 -46
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/oauth-2.js +0 -219
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- package/syncs/model.js +0 -62
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
package/core/Worker.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { SQSClient, GetQueueUrlCommand, SendMessageCommand } = require('@aws-sdk/client-sqs');
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const { RequiredPropertyError } = require('../errors');
|
|
4
4
|
const { get } = require('../assertions');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
|
|
6
|
+
const sqs = new SQSClient({ region: process.env.AWS_REGION });
|
|
8
7
|
|
|
9
8
|
class Worker {
|
|
10
9
|
async getQueueURL(params) {
|
|
@@ -12,25 +11,68 @@ class Worker {
|
|
|
12
11
|
// let params = {
|
|
13
12
|
// QueueName: process.env.QueueName
|
|
14
13
|
// };
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
reject(err);
|
|
19
|
-
} else {
|
|
20
|
-
resolve(data.QueueUrl);
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
});
|
|
14
|
+
const command = new GetQueueUrlCommand(params);
|
|
15
|
+
const data = await sqs.send(command);
|
|
16
|
+
return data.QueueUrl;
|
|
24
17
|
}
|
|
25
18
|
|
|
26
19
|
async run(params, context = {}) {
|
|
27
20
|
const records = get(params, 'Records');
|
|
21
|
+
const batchItemFailures = [];
|
|
22
|
+
|
|
23
|
+
console.log(
|
|
24
|
+
`[Worker] run: processing ${records.length} record(s)`
|
|
25
|
+
);
|
|
28
26
|
|
|
29
27
|
for (const record of records) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
// Log record entry with SQS-provided attributes useful for tracing
|
|
29
|
+
// delivery history (ApproximateReceiveCount for retries, etc.).
|
|
30
|
+
let parsedEvent;
|
|
31
|
+
try {
|
|
32
|
+
parsedEvent = JSON.parse(record.body)?.event;
|
|
33
|
+
} catch {
|
|
34
|
+
parsedEvent = undefined;
|
|
35
|
+
}
|
|
36
|
+
console.log(`[Worker] record begin`, {
|
|
37
|
+
messageId: record.messageId,
|
|
38
|
+
event: parsedEvent,
|
|
39
|
+
receiveCount: record.attributes?.ApproximateReceiveCount,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const runParams = JSON.parse(record.body);
|
|
44
|
+
this._validateParams(runParams);
|
|
45
|
+
await this._run(runParams, context);
|
|
46
|
+
console.log(`[Worker] record success`, {
|
|
47
|
+
messageId: record.messageId,
|
|
48
|
+
event: runParams?.event,
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.isHaltError) {
|
|
52
|
+
// HaltError means "discard this message, don't retry".
|
|
53
|
+
// Treat as success so SQS deletes it from the queue.
|
|
54
|
+
// Logged explicitly — silent discards made prod debugging
|
|
55
|
+
// extremely hard; keep this visible.
|
|
56
|
+
console.warn(`[Worker] record halted (discarded, no retry)`, {
|
|
57
|
+
messageId: record.messageId,
|
|
58
|
+
event: parsedEvent,
|
|
59
|
+
reason: error.message,
|
|
60
|
+
statusCode: error.statusCode,
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
console.error(`[Worker] Failed to process record ${record.messageId}:`, error);
|
|
65
|
+
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
66
|
+
}
|
|
33
67
|
}
|
|
68
|
+
|
|
69
|
+
if (batchItemFailures.length > 0) {
|
|
70
|
+
console.warn(
|
|
71
|
+
`[Worker] run: returning ${batchItemFailures.length} batchItemFailure(s) of ${records.length}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { batchItemFailures };
|
|
34
76
|
}
|
|
35
77
|
|
|
36
78
|
async _run(params, context = {}) {
|
|
@@ -54,15 +96,9 @@ class Worker {
|
|
|
54
96
|
}
|
|
55
97
|
|
|
56
98
|
async sendAsyncSQSMessage(params) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
reject(err);
|
|
61
|
-
} else {
|
|
62
|
-
resolve(data.MessageId);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
});
|
|
99
|
+
const command = new SendMessageCommand(params);
|
|
100
|
+
const data = await sqs.send(command);
|
|
101
|
+
return data.MessageId;
|
|
66
102
|
}
|
|
67
103
|
|
|
68
104
|
// Throw an exception if the params do not validate
|
package/core/create-handler.js
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
1
|
// This line should be at the top of the webpacked output, so be sure to require createHandler first in any handlers. "Soon" sourcemaps will be built into Node... after that, this package won't be needed.
|
|
2
|
-
|
|
2
|
+
// REMOVING FOR NOW UNTIL WE ADD WEBPACK BACK IN
|
|
3
|
+
// require('source-map-support').install();
|
|
3
4
|
|
|
4
|
-
const { connectToDatabase } = require('../database/mongo');
|
|
5
5
|
const { initDebugLog, flushDebugLog } = require('../logs');
|
|
6
6
|
const { secretsToEnv } = require('./secrets-to-env');
|
|
7
7
|
|
|
8
|
+
// Best-effort extraction of correlation identifiers from a Lambda event.
|
|
9
|
+
// For SQS: pulls messageIds + parsed event/processId/integrationId from each
|
|
10
|
+
// record body. For HTTP: pulls method+path. Never throws.
|
|
11
|
+
const summarizeLambdaEvent = (event) => {
|
|
12
|
+
if (!event) return {};
|
|
13
|
+
if (Array.isArray(event.Records)) {
|
|
14
|
+
return {
|
|
15
|
+
source: 'sqs',
|
|
16
|
+
records: event.Records.map((r) => {
|
|
17
|
+
let parsed = {};
|
|
18
|
+
try {
|
|
19
|
+
const body = JSON.parse(r.body);
|
|
20
|
+
parsed = {
|
|
21
|
+
event: body?.event,
|
|
22
|
+
processId: body?.data?.processId,
|
|
23
|
+
integrationId: body?.data?.integrationId,
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
// ignore unparseable bodies
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
messageId: r.messageId,
|
|
30
|
+
receiveCount: r.attributes?.ApproximateReceiveCount,
|
|
31
|
+
...parsed,
|
|
32
|
+
};
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (event.httpMethod || event.requestContext?.http) {
|
|
37
|
+
return {
|
|
38
|
+
source: 'http',
|
|
39
|
+
method:
|
|
40
|
+
event.httpMethod || event.requestContext?.http?.method,
|
|
41
|
+
path: event.path || event.rawPath,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { source: 'other' };
|
|
45
|
+
};
|
|
46
|
+
|
|
8
47
|
const createHandler = (optionByName = {}) => {
|
|
9
48
|
const {
|
|
10
49
|
eventName = 'Event',
|
|
11
50
|
isUserFacingResponse = true,
|
|
12
51
|
method,
|
|
13
|
-
shouldUseDatabase = true,
|
|
14
52
|
} = optionByName;
|
|
15
53
|
|
|
16
54
|
if (!method) {
|
|
@@ -18,7 +56,18 @@ const createHandler = (optionByName = {}) => {
|
|
|
18
56
|
}
|
|
19
57
|
|
|
20
58
|
return async (event, context) => {
|
|
59
|
+
const eventSummary = summarizeLambdaEvent(event);
|
|
60
|
+
|
|
21
61
|
try {
|
|
62
|
+
console.info(
|
|
63
|
+
`[createHandler] ${eventName}: handler entry`,
|
|
64
|
+
{
|
|
65
|
+
eventName,
|
|
66
|
+
awsRequestId: context?.awsRequestId,
|
|
67
|
+
...eventSummary,
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
22
71
|
initDebugLog(eventName, event);
|
|
23
72
|
|
|
24
73
|
const requestMethod = event.httpMethod;
|
|
@@ -30,13 +79,9 @@ const createHandler = (optionByName = {}) => {
|
|
|
30
79
|
// If enabled (i.e. if SECRET_ARN is set in process.env) Fetch secrets from AWS Secrets Manager, and set them as environment variables.
|
|
31
80
|
await secretsToEnv();
|
|
32
81
|
|
|
33
|
-
// Helps
|
|
82
|
+
// Helps reuse the database connection. Lowers response times.
|
|
34
83
|
context.callbackWaitsForEmptyEventLoop = false;
|
|
35
84
|
|
|
36
|
-
if (shouldUseDatabase) {
|
|
37
|
-
await connectToDatabase();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
85
|
// Run the Lambda
|
|
41
86
|
return await method(event, context);
|
|
42
87
|
} catch (error) {
|
|
@@ -44,6 +89,18 @@ const createHandler = (optionByName = {}) => {
|
|
|
44
89
|
|
|
45
90
|
// Don't leak implementation details to end users.
|
|
46
91
|
if (isUserFacingResponse) {
|
|
92
|
+
// Allow client-safe errors to pass through with their actual message
|
|
93
|
+
if (error.isClientSafe === true) {
|
|
94
|
+
const statusCode = error.statusCode || 400;
|
|
95
|
+
return {
|
|
96
|
+
statusCode,
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
error: error.message,
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Hide other errors with generic message
|
|
47
104
|
return {
|
|
48
105
|
statusCode: 500,
|
|
49
106
|
body: JSON.stringify({
|
|
@@ -55,7 +112,21 @@ const createHandler = (optionByName = {}) => {
|
|
|
55
112
|
// Handle server-to-server responses.
|
|
56
113
|
|
|
57
114
|
// Halt errors are logged but suceed and won't be retried.
|
|
115
|
+
// Log explicitly — silent suppression here previously made stuck
|
|
116
|
+
// messages invisible to observability tooling. Include
|
|
117
|
+
// eventSummary so operators can correlate across concurrent
|
|
118
|
+
// invocations (processId / messageIds / HTTP path).
|
|
58
119
|
if (error.isHaltError === true) {
|
|
120
|
+
console.warn(
|
|
121
|
+
`[createHandler] ${eventName}: halt error suppressed (no retry)`,
|
|
122
|
+
{
|
|
123
|
+
eventName,
|
|
124
|
+
errorName: error.name,
|
|
125
|
+
errorMessage: error.message,
|
|
126
|
+
statusCode: error.statusCode,
|
|
127
|
+
...eventSummary,
|
|
128
|
+
}
|
|
129
|
+
);
|
|
59
130
|
return;
|
|
60
131
|
}
|
|
61
132
|
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
toObjectId,
|
|
4
|
+
fromObjectId,
|
|
5
|
+
findOne,
|
|
6
|
+
insertOne,
|
|
7
|
+
updateOne,
|
|
8
|
+
deleteOne,
|
|
9
|
+
} = require('../../database/documentdb-utils');
|
|
10
|
+
const {
|
|
11
|
+
CredentialRepositoryInterface,
|
|
12
|
+
} = require('./credential-repository-interface');
|
|
13
|
+
const {
|
|
14
|
+
DocumentDBEncryptionService,
|
|
15
|
+
} = require('../../database/documentdb-encryption-service');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Credential repository for DocumentDB.
|
|
19
|
+
* Uses DocumentDBEncryptionService for field-level encryption.
|
|
20
|
+
*
|
|
21
|
+
* Encrypted fields:
|
|
22
|
+
* - Credential.data.access_token
|
|
23
|
+
* - Credential.data.refresh_token
|
|
24
|
+
* - Credential.data.id_token
|
|
25
|
+
*
|
|
26
|
+
* SECURITY CRITICAL: All OAuth credentials must be encrypted at rest.
|
|
27
|
+
*
|
|
28
|
+
* @see DocumentDBEncryptionService
|
|
29
|
+
* @see encryption-schema-registry.js
|
|
30
|
+
*/
|
|
31
|
+
class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
this.prisma = prisma;
|
|
35
|
+
this.encryptionService = new DocumentDBEncryptionService();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async findCredentialById(id) {
|
|
39
|
+
const objectId = toObjectId(id);
|
|
40
|
+
if (!objectId) return null;
|
|
41
|
+
const doc = await findOne(this.prisma, 'Credential', { _id: objectId });
|
|
42
|
+
if (!doc) return null;
|
|
43
|
+
|
|
44
|
+
const decryptedCredential = await this.encryptionService.decryptFields(
|
|
45
|
+
'Credential',
|
|
46
|
+
doc
|
|
47
|
+
);
|
|
48
|
+
return this._mapCredentialById(decryptedCredential);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async updateAuthenticationStatus(credentialId, authIsValid) {
|
|
52
|
+
const objectId = toObjectId(credentialId);
|
|
53
|
+
if (!objectId) return { acknowledged: false, modifiedCount: 0 };
|
|
54
|
+
const result = await updateOne(
|
|
55
|
+
this.prisma,
|
|
56
|
+
'Credential',
|
|
57
|
+
{ _id: objectId },
|
|
58
|
+
{
|
|
59
|
+
$set: { authIsValid, updatedAt: new Date() },
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
const modified = result?.nModified ?? result?.n ?? 0;
|
|
63
|
+
return { acknowledged: true, modifiedCount: modified };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async deleteCredentialById(credentialId) {
|
|
67
|
+
const objectId = toObjectId(credentialId);
|
|
68
|
+
if (!objectId) return { acknowledged: true, deletedCount: 0 };
|
|
69
|
+
const result = await deleteOne(this.prisma, 'Credential', {
|
|
70
|
+
_id: objectId,
|
|
71
|
+
});
|
|
72
|
+
const deleted = result?.n ?? 0;
|
|
73
|
+
return { acknowledged: true, deletedCount: deleted };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async upsertCredential(credentialDetails) {
|
|
77
|
+
const { identifiers, details } = credentialDetails;
|
|
78
|
+
if (!identifiers)
|
|
79
|
+
throw new Error('identifiers required to upsert credential');
|
|
80
|
+
if (!identifiers.userId) {
|
|
81
|
+
throw new Error('userId required in identifiers');
|
|
82
|
+
}
|
|
83
|
+
if (!identifiers.externalId) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
'externalId required in identifiers to prevent credential collision. When multiple credentials exist for the same user, both userId and externalId are needed to uniquely identify which credential to update.'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const filter = this._buildIdentifierFilter(identifiers);
|
|
90
|
+
const existing = await findOne(this.prisma, 'Credential', filter);
|
|
91
|
+
const now = new Date();
|
|
92
|
+
|
|
93
|
+
const { authIsValid, ...oauthData } = details || {};
|
|
94
|
+
|
|
95
|
+
if (existing) {
|
|
96
|
+
const decryptedExisting =
|
|
97
|
+
await this.encryptionService.decryptFields(
|
|
98
|
+
'Credential',
|
|
99
|
+
existing
|
|
100
|
+
);
|
|
101
|
+
const mergedData = {
|
|
102
|
+
...(decryptedExisting.data || {}),
|
|
103
|
+
...oauthData,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const updateDocument = {
|
|
107
|
+
userId: existing.userId,
|
|
108
|
+
externalId: existing.externalId,
|
|
109
|
+
authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
|
|
110
|
+
data: mergedData,
|
|
111
|
+
updatedAt: now,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const encryptedUpdate = await this.encryptionService.encryptFields(
|
|
115
|
+
'Credential',
|
|
116
|
+
{ data: updateDocument.data }
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
await updateOne(
|
|
120
|
+
this.prisma,
|
|
121
|
+
'Credential',
|
|
122
|
+
{ _id: existing._id },
|
|
123
|
+
{
|
|
124
|
+
$set: {
|
|
125
|
+
userId: updateDocument.userId,
|
|
126
|
+
externalId: updateDocument.externalId,
|
|
127
|
+
authIsValid: updateDocument.authIsValid,
|
|
128
|
+
data: encryptedUpdate.data,
|
|
129
|
+
updatedAt: updateDocument.updatedAt,
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const updated = await findOne(this.prisma, 'Credential', {
|
|
135
|
+
_id: existing._id,
|
|
136
|
+
});
|
|
137
|
+
const decryptedCredential =
|
|
138
|
+
await this.encryptionService.decryptFields(
|
|
139
|
+
'Credential',
|
|
140
|
+
updated
|
|
141
|
+
);
|
|
142
|
+
return this._mapCredential(decryptedCredential);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const plainDocument = {
|
|
146
|
+
userId: toObjectId(identifiers.userId),
|
|
147
|
+
externalId: identifiers.externalId,
|
|
148
|
+
authIsValid: details.authIsValid,
|
|
149
|
+
data: { ...oauthData },
|
|
150
|
+
createdAt: now,
|
|
151
|
+
updatedAt: now,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const encryptedDocument = await this.encryptionService.encryptFields(
|
|
155
|
+
'Credential',
|
|
156
|
+
plainDocument
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const insertedId = await insertOne(
|
|
160
|
+
this.prisma,
|
|
161
|
+
'Credential',
|
|
162
|
+
encryptedDocument
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const created = await findOne(this.prisma, 'Credential', {
|
|
166
|
+
_id: insertedId,
|
|
167
|
+
});
|
|
168
|
+
const decryptedCredential = await this.encryptionService.decryptFields(
|
|
169
|
+
'Credential',
|
|
170
|
+
created
|
|
171
|
+
);
|
|
172
|
+
return this._mapCredential(decryptedCredential);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async findCredential(filter) {
|
|
176
|
+
const query = this._buildFilter(filter);
|
|
177
|
+
const credential = await findOne(this.prisma, 'Credential', query);
|
|
178
|
+
if (!credential) return null;
|
|
179
|
+
|
|
180
|
+
const decryptedCredential = await this.encryptionService.decryptFields(
|
|
181
|
+
'Credential',
|
|
182
|
+
credential
|
|
183
|
+
);
|
|
184
|
+
return this._mapCredential(decryptedCredential);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async updateCredential(credentialId, updates) {
|
|
188
|
+
const objectId = toObjectId(credentialId);
|
|
189
|
+
if (!objectId) return null;
|
|
190
|
+
const existing = await findOne(this.prisma, 'Credential', {
|
|
191
|
+
_id: objectId,
|
|
192
|
+
});
|
|
193
|
+
if (!existing) return null;
|
|
194
|
+
|
|
195
|
+
const { authIsValid, ...oauthData } = updates || {};
|
|
196
|
+
|
|
197
|
+
const decryptedExisting = await this.encryptionService.decryptFields(
|
|
198
|
+
'Credential',
|
|
199
|
+
existing
|
|
200
|
+
);
|
|
201
|
+
const mergedData = { ...(decryptedExisting.data || {}), ...oauthData };
|
|
202
|
+
|
|
203
|
+
const updateDocument = {
|
|
204
|
+
userId: existing.userId,
|
|
205
|
+
externalId: existing.externalId,
|
|
206
|
+
authIsValid: authIsValid,
|
|
207
|
+
data: mergedData,
|
|
208
|
+
updatedAt: new Date(),
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const encryptedUpdate = await this.encryptionService.encryptFields(
|
|
212
|
+
'Credential',
|
|
213
|
+
{ data: updateDocument.data }
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
await updateOne(
|
|
217
|
+
this.prisma,
|
|
218
|
+
'Credential',
|
|
219
|
+
{ _id: objectId },
|
|
220
|
+
{
|
|
221
|
+
$set: {
|
|
222
|
+
userId: updateDocument.userId,
|
|
223
|
+
externalId: updateDocument.externalId,
|
|
224
|
+
authIsValid: updateDocument.authIsValid,
|
|
225
|
+
data: encryptedUpdate.data,
|
|
226
|
+
updatedAt: updateDocument.updatedAt,
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const updated = await findOne(this.prisma, 'Credential', {
|
|
232
|
+
_id: objectId,
|
|
233
|
+
});
|
|
234
|
+
const decryptedCredential = await this.encryptionService.decryptFields(
|
|
235
|
+
'Credential',
|
|
236
|
+
updated
|
|
237
|
+
);
|
|
238
|
+
return this._mapCredential(decryptedCredential);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
_buildIdentifierFilter(identifiers) {
|
|
242
|
+
const filter = {};
|
|
243
|
+
if (identifiers._id || identifiers.id) {
|
|
244
|
+
const idObj = toObjectId(identifiers._id || identifiers.id);
|
|
245
|
+
if (idObj) filter._id = idObj;
|
|
246
|
+
}
|
|
247
|
+
if (identifiers.userId) {
|
|
248
|
+
filter.userId = toObjectId(identifiers.userId);
|
|
249
|
+
}
|
|
250
|
+
if (identifiers.externalId !== undefined) {
|
|
251
|
+
filter.externalId = identifiers.externalId;
|
|
252
|
+
}
|
|
253
|
+
return filter;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
_buildFilter(filter) {
|
|
257
|
+
const query = {};
|
|
258
|
+
if (!filter) return query;
|
|
259
|
+
if (filter.credentialId || filter.id) {
|
|
260
|
+
const idObj = toObjectId(filter.credentialId || filter.id);
|
|
261
|
+
if (idObj) query._id = idObj;
|
|
262
|
+
}
|
|
263
|
+
if (filter.userId !== undefined) {
|
|
264
|
+
query.userId = filter.userId;
|
|
265
|
+
}
|
|
266
|
+
if (filter.externalId !== undefined) {
|
|
267
|
+
query.externalId = filter.externalId;
|
|
268
|
+
}
|
|
269
|
+
return query;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Map credential document to application format
|
|
274
|
+
* Matches MongoDB repository format
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
_mapCredential(doc) {
|
|
278
|
+
const data = doc?.data || {};
|
|
279
|
+
const id = fromObjectId(doc?._id);
|
|
280
|
+
const userId = doc?.userId;
|
|
281
|
+
return {
|
|
282
|
+
id,
|
|
283
|
+
userId,
|
|
284
|
+
externalId: doc?.externalId ?? null,
|
|
285
|
+
authIsValid: doc?.authIsValid ?? null,
|
|
286
|
+
...data,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
_mapCredentialById(doc) {
|
|
291
|
+
const data = doc?.data || {};
|
|
292
|
+
const id = fromObjectId(doc?._id);
|
|
293
|
+
const userId = doc?.userId;
|
|
294
|
+
return {
|
|
295
|
+
id,
|
|
296
|
+
userId,
|
|
297
|
+
externalId: doc?.externalId ?? null,
|
|
298
|
+
authIsValid: doc?.authIsValid ?? null,
|
|
299
|
+
...data,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = { CredentialRepositoryDocumentDB };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { CredentialRepositoryMongo } = require('./credential-repository-mongo');
|
|
2
|
+
const {
|
|
3
|
+
CredentialRepositoryPostgres,
|
|
4
|
+
} = require('./credential-repository-postgres');
|
|
5
|
+
const {
|
|
6
|
+
CredentialRepositoryDocumentDB,
|
|
7
|
+
} = require('./credential-repository-documentdb');
|
|
8
|
+
const config = require('../../database/config');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Credential Repository Factory
|
|
12
|
+
* Creates the appropriate repository adapter based on database type
|
|
13
|
+
*
|
|
14
|
+
* Database-specific implementations:
|
|
15
|
+
* - MongoDB: Uses String IDs (ObjectId), no conversion needed
|
|
16
|
+
* - PostgreSQL: Uses Int IDs, converts String ↔ Int
|
|
17
|
+
*
|
|
18
|
+
* All repository methods return String IDs regardless of database type,
|
|
19
|
+
* ensuring application layer consistency.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```javascript
|
|
23
|
+
* const repository = createCredentialRepository();
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @returns {CredentialRepositoryInterface} Configured repository adapter
|
|
27
|
+
*/
|
|
28
|
+
function createCredentialRepository() {
|
|
29
|
+
const dbType = config.DB_TYPE;
|
|
30
|
+
|
|
31
|
+
switch (dbType) {
|
|
32
|
+
case 'mongodb':
|
|
33
|
+
return new CredentialRepositoryMongo();
|
|
34
|
+
|
|
35
|
+
case 'postgresql':
|
|
36
|
+
return new CredentialRepositoryPostgres();
|
|
37
|
+
|
|
38
|
+
case 'documentdb':
|
|
39
|
+
return new CredentialRepositoryDocumentDB();
|
|
40
|
+
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
createCredentialRepository,
|
|
50
|
+
// Export adapters for direct testing
|
|
51
|
+
CredentialRepositoryMongo,
|
|
52
|
+
CredentialRepositoryPostgres,
|
|
53
|
+
CredentialRepositoryDocumentDB,
|
|
54
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Repository Interface
|
|
3
|
+
* Abstract base class defining the contract for credential 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, Credential model has identical structure across MongoDB and PostgreSQL,
|
|
11
|
+
* so CredentialRepository serves both. This interface exists for consistency and
|
|
12
|
+
* future-proofing if database-specific implementations become needed.
|
|
13
|
+
*
|
|
14
|
+
* @abstract
|
|
15
|
+
*/
|
|
16
|
+
class CredentialRepositoryInterface {
|
|
17
|
+
/**
|
|
18
|
+
* Find credential by ID
|
|
19
|
+
*
|
|
20
|
+
* @param {string|number} id - Credential ID
|
|
21
|
+
* @returns {Promise<Object|null>} Credential object or null
|
|
22
|
+
* @abstract
|
|
23
|
+
*/
|
|
24
|
+
async findCredentialById(id) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'Method findCredentialById must be implemented by subclass'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Update authentication status
|
|
32
|
+
*
|
|
33
|
+
* @param {string|number} credentialId - Credential ID
|
|
34
|
+
* @param {boolean} authIsValid - Authentication validity status
|
|
35
|
+
* @returns {Promise<Object>} Update result
|
|
36
|
+
* @abstract
|
|
37
|
+
*/
|
|
38
|
+
async updateAuthenticationStatus(credentialId, authIsValid) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
'Method updateAuthenticationStatus must be implemented by subclass'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Permanently remove a credential document
|
|
46
|
+
*
|
|
47
|
+
* @param {string|number} credentialId - Credential ID
|
|
48
|
+
* @returns {Promise<Object>} Deletion result
|
|
49
|
+
* @abstract
|
|
50
|
+
*/
|
|
51
|
+
async deleteCredentialById(credentialId) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
'Method deleteCredentialById must be implemented by subclass'
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create or update credential matching identifiers
|
|
59
|
+
*
|
|
60
|
+
* @param {{identifiers: Object, details: Object}} credentialDetails
|
|
61
|
+
* @returns {Promise<Object>} The persisted credential
|
|
62
|
+
* @abstract
|
|
63
|
+
*/
|
|
64
|
+
async upsertCredential(credentialDetails) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'Method upsertCredential must be implemented by subclass'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Find a credential by filter criteria
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} filter - Filter criteria
|
|
74
|
+
* @returns {Promise<Object|null>} Credential object or null if not found
|
|
75
|
+
* @abstract
|
|
76
|
+
*/
|
|
77
|
+
async findCredential(filter) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
'Method findCredential must be implemented by subclass'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Update a credential by ID
|
|
85
|
+
*
|
|
86
|
+
* @param {string|number} credentialId - Credential ID
|
|
87
|
+
* @param {Object} updates - Fields to update
|
|
88
|
+
* @returns {Promise<Object|null>} Updated credential object or null if not found
|
|
89
|
+
* @abstract
|
|
90
|
+
*/
|
|
91
|
+
async updateCredential(credentialId, updates) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'Method updateCredential must be implemented by subclass'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { CredentialRepositoryInterface };
|