@friggframework/core 2.0.0-next.6 → 2.0.0-next.60
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/user-commands.js +283 -0
- package/application/index.js +69 -0
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +8 -21
- package/core/create-handler.js +14 -7
- 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 +291 -0
- package/credential/repositories/credential-repository.js +302 -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 +61 -21
- package/database/models/WebsocketConnection.js +16 -10
- package/database/models/readme.md +1 -0
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +134 -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/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 +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 +91 -0
- package/database/utils/mongodb-schema-init.js +106 -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/encrypt/test-encrypt.js +0 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +2 -1
- 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 +334 -0
- package/generated/prisma-mongodb/index-browser.js +316 -0
- package/generated/prisma-mongodb/index.d.ts +22903 -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 +360 -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 +25077 -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 +343 -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 +186 -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/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 +326 -55
- package/integrations/integration-router.js +374 -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 +210 -0
- package/integrations/repositories/integration-repository-factory.js +51 -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-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/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 +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 +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 +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}/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-documentdb.js +307 -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 +377 -0
- package/modules/repositories/module-repository-postgres.js +426 -0
- package/modules/repositories/module-repository.js +316 -0
- package/modules/requester/api-key.js +52 -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 +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 +133 -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 +360 -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 +343 -0
- package/queues/queuer-util.js +27 -22
- 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/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-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/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/api-key.js +0 -36
- 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/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
CredentialRepositoryInterface,
|
|
4
|
+
} = require('./credential-repository-interface');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MongoDB Credential Repository Adapter
|
|
8
|
+
* Handles OAuth credentials and API tokens persistence with MongoDB
|
|
9
|
+
*
|
|
10
|
+
* MongoDB-specific characteristics:
|
|
11
|
+
* - Uses String IDs (ObjectId)
|
|
12
|
+
* - No ID conversion needed (IDs are already strings)
|
|
13
|
+
* - Dynamic schema support via JSON field
|
|
14
|
+
*/
|
|
15
|
+
class CredentialRepositoryMongo extends CredentialRepositoryInterface {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.prisma = prisma;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Find credential by ID
|
|
23
|
+
* Replaces: Credential.findById(id)
|
|
24
|
+
*
|
|
25
|
+
* @param {string} id - Credential ID
|
|
26
|
+
* @returns {Promise<Object|null>} Credential object or null
|
|
27
|
+
*/
|
|
28
|
+
async findCredentialById(id) {
|
|
29
|
+
const credential = await this.prisma.credential.findUnique({
|
|
30
|
+
where: { id },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!credential) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Extract data from JSON field
|
|
38
|
+
const data = credential.data || {};
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
id: credential.id,
|
|
42
|
+
userId: credential.userId,
|
|
43
|
+
externalId: credential.externalId,
|
|
44
|
+
authIsValid: credential.authIsValid,
|
|
45
|
+
...data, // Spread OAuth tokens from JSON field
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update authentication status
|
|
51
|
+
* Replaces: Credential.updateOne({ _id: credentialId }, { $set: { authIsValid } })
|
|
52
|
+
*
|
|
53
|
+
* @param {string} credentialId - Credential ID
|
|
54
|
+
* @param {boolean} authIsValid - Authentication validity status
|
|
55
|
+
* @returns {Promise<Object>} Update result
|
|
56
|
+
*/
|
|
57
|
+
async updateAuthenticationStatus(credentialId, authIsValid) {
|
|
58
|
+
await this.prisma.credential.update({
|
|
59
|
+
where: { id: credentialId },
|
|
60
|
+
data: { authIsValid },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return { acknowledged: true, modifiedCount: 1 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Permanently remove a credential document
|
|
68
|
+
* Replaces: Credential.deleteOne({ _id: credentialId })
|
|
69
|
+
*
|
|
70
|
+
* @param {string} credentialId - Credential ID
|
|
71
|
+
* @returns {Promise<Object>} Deletion result
|
|
72
|
+
*/
|
|
73
|
+
async deleteCredentialById(credentialId) {
|
|
74
|
+
try {
|
|
75
|
+
await this.prisma.credential.delete({
|
|
76
|
+
where: { id: credentialId },
|
|
77
|
+
});
|
|
78
|
+
return { acknowledged: true, deletedCount: 1 };
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error.code === 'P2025') {
|
|
81
|
+
return { acknowledged: true, deletedCount: 0 };
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create or update credential matching identifiers
|
|
89
|
+
* Replaces: Credential.findOneAndUpdate(query, update, { upsert: true })
|
|
90
|
+
*
|
|
91
|
+
* @param {{identifiers: Object, details: Object}} credentialDetails
|
|
92
|
+
* @returns {Promise<Object>} The persisted credential
|
|
93
|
+
*/
|
|
94
|
+
async upsertCredential(credentialDetails) {
|
|
95
|
+
const { identifiers, details } = credentialDetails;
|
|
96
|
+
if (!identifiers)
|
|
97
|
+
throw new Error('identifiers required to upsert credential');
|
|
98
|
+
|
|
99
|
+
if (!identifiers.userId) {
|
|
100
|
+
throw new Error('userId required in identifiers');
|
|
101
|
+
}
|
|
102
|
+
if (!identifiers.externalId) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
'externalId required in identifiers to prevent credential collision. ' +
|
|
105
|
+
'When multiple credentials exist for the same user, both userId and externalId ' +
|
|
106
|
+
'are needed to uniquely identify which credential to update.'
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const where = this._convertIdentifiersToWhere(identifiers);
|
|
111
|
+
|
|
112
|
+
const { authIsValid, ...oauthData } = details;
|
|
113
|
+
|
|
114
|
+
const existing = await this.prisma.credential.findFirst({ where });
|
|
115
|
+
|
|
116
|
+
if (existing) {
|
|
117
|
+
const mergedData = { ...(existing.data || {}), ...oauthData };
|
|
118
|
+
|
|
119
|
+
const updated = await this.prisma.credential.update({
|
|
120
|
+
where: { id: existing.id },
|
|
121
|
+
data: {
|
|
122
|
+
userId: existing.userId,
|
|
123
|
+
externalId: existing.externalId,
|
|
124
|
+
authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
|
|
125
|
+
data: mergedData,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
id: updated.id,
|
|
131
|
+
externalId: updated.externalId,
|
|
132
|
+
userId: updated.userId,
|
|
133
|
+
authIsValid: updated.authIsValid,
|
|
134
|
+
...(updated.data || {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const created = await this.prisma.credential.create({
|
|
139
|
+
data: {
|
|
140
|
+
userId: identifiers.userId,
|
|
141
|
+
externalId: identifiers.externalId,
|
|
142
|
+
authIsValid: authIsValid,
|
|
143
|
+
data: oauthData,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
id: created.id,
|
|
149
|
+
externalId: created.externalId,
|
|
150
|
+
userId: created.userId,
|
|
151
|
+
authIsValid: created.authIsValid,
|
|
152
|
+
...(created.data || {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Find a credential by filter criteria
|
|
158
|
+
* Replaces: Credential.findOne(query)
|
|
159
|
+
*
|
|
160
|
+
* @param {Object} filter
|
|
161
|
+
* @param {string} [filter.userId] - User ID
|
|
162
|
+
* @param {string} [filter.externalId] - External ID
|
|
163
|
+
* @param {string} [filter.credentialId] - Credential ID
|
|
164
|
+
* @returns {Promise<Object|null>} Credential object or null if not found
|
|
165
|
+
*/
|
|
166
|
+
async findCredential(filter) {
|
|
167
|
+
const where = this._convertFilterToWhere(filter);
|
|
168
|
+
|
|
169
|
+
const credential = await this.prisma.credential.findFirst({
|
|
170
|
+
where,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (!credential) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const data = credential.data || {};
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
id: credential.id,
|
|
181
|
+
userId: credential.userId,
|
|
182
|
+
externalId: credential.externalId,
|
|
183
|
+
authIsValid: credential.authIsValid,
|
|
184
|
+
access_token: data.access_token,
|
|
185
|
+
refresh_token: data.refresh_token,
|
|
186
|
+
...data,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Update a credential by ID
|
|
192
|
+
* Replaces: Credential.findByIdAndUpdate(credentialId, { $set: updates })
|
|
193
|
+
*
|
|
194
|
+
* @param {string} credentialId - Credential ID
|
|
195
|
+
* @param {Object} updates - Fields to update
|
|
196
|
+
* @returns {Promise<Object|null>} Updated credential object or null if not found
|
|
197
|
+
*/
|
|
198
|
+
async updateCredential(credentialId, updates) {
|
|
199
|
+
const existing = await this.prisma.credential.findUnique({
|
|
200
|
+
where: { id: credentialId },
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (!existing) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const { authIsValid, ...oauthData } = updates;
|
|
208
|
+
|
|
209
|
+
const mergedData = { ...(existing.data || {}), ...oauthData };
|
|
210
|
+
|
|
211
|
+
const updated = await this.prisma.credential.update({
|
|
212
|
+
where: { id: credentialId },
|
|
213
|
+
data: {
|
|
214
|
+
userId: existing.userId,
|
|
215
|
+
externalId: existing.externalId,
|
|
216
|
+
authIsValid: authIsValid,
|
|
217
|
+
data: mergedData,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const data = updated.data || {};
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
id: updated.id,
|
|
225
|
+
userId: updated.userId,
|
|
226
|
+
externalId: updated.externalId,
|
|
227
|
+
authIsValid: updated.authIsValid,
|
|
228
|
+
access_token: data.access_token,
|
|
229
|
+
refresh_token: data.refresh_token,
|
|
230
|
+
...data,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Convert identifiers to Prisma where clause
|
|
236
|
+
* @private
|
|
237
|
+
* @param {Object} identifiers - Identifier fields
|
|
238
|
+
* @returns {Object} Prisma where clause
|
|
239
|
+
*/
|
|
240
|
+
_convertIdentifiersToWhere(identifiers) {
|
|
241
|
+
const where = {};
|
|
242
|
+
|
|
243
|
+
if (identifiers._id) where.id = identifiers._id;
|
|
244
|
+
if (identifiers.id) where.id = identifiers.id;
|
|
245
|
+
if (identifiers.userId) where.userId = identifiers.userId;
|
|
246
|
+
if (identifiers.externalId) where.externalId = identifiers.externalId;
|
|
247
|
+
|
|
248
|
+
return where;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Convert filter to Prisma where clause
|
|
253
|
+
* @private
|
|
254
|
+
* @param {Object} filter - Filter criteria
|
|
255
|
+
* @returns {Object} Prisma where clause
|
|
256
|
+
*/
|
|
257
|
+
_convertFilterToWhere(filter) {
|
|
258
|
+
const where = {};
|
|
259
|
+
|
|
260
|
+
if (filter.credentialId) where.id = filter.credentialId;
|
|
261
|
+
if (filter.id) where.id = filter.id;
|
|
262
|
+
if (filter.userId) where.userId = filter.userId;
|
|
263
|
+
if (filter.externalId) where.externalId = filter.externalId;
|
|
264
|
+
|
|
265
|
+
return where;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = { CredentialRepositoryMongo };
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
CredentialRepositoryInterface,
|
|
4
|
+
} = require('./credential-repository-interface');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL Credential Repository Adapter
|
|
8
|
+
* Handles OAuth credentials and API tokens persistence with PostgreSQL
|
|
9
|
+
*
|
|
10
|
+
* PostgreSQL-specific characteristics:
|
|
11
|
+
* - Uses Int IDs with autoincrement
|
|
12
|
+
* - Requires ID conversion: String (app layer) ↔ Int (database)
|
|
13
|
+
* - All returned IDs are converted to strings for application layer consistency
|
|
14
|
+
*/
|
|
15
|
+
class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.prisma = prisma;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert string ID to integer for PostgreSQL queries
|
|
23
|
+
* @private
|
|
24
|
+
* @param {string|number|null|undefined} id - ID to convert
|
|
25
|
+
* @returns {number|null|undefined} Integer ID or null/undefined
|
|
26
|
+
* @throws {Error} If ID cannot be converted to integer
|
|
27
|
+
*/
|
|
28
|
+
_convertId(id) {
|
|
29
|
+
if (id === null || id === undefined) return id;
|
|
30
|
+
const parsed = parseInt(id, 10);
|
|
31
|
+
if (isNaN(parsed)) {
|
|
32
|
+
throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find credential by ID
|
|
39
|
+
*
|
|
40
|
+
* @param {string} id - Credential ID (string from application layer)
|
|
41
|
+
* @returns {Promise<Object|null>} Credential object with string IDs or null
|
|
42
|
+
*/
|
|
43
|
+
async findCredentialById(id) {
|
|
44
|
+
const intId = this._convertId(id);
|
|
45
|
+
const credential = await this.prisma.credential.findUnique({
|
|
46
|
+
where: { id: intId },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!credential) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = credential.data || {};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
id: credential.id.toString(),
|
|
57
|
+
userId: credential.userId.toString(),
|
|
58
|
+
externalId: credential.externalId,
|
|
59
|
+
authIsValid: credential.authIsValid,
|
|
60
|
+
...data, // Spread OAuth tokens from JSON field
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Update authentication status
|
|
66
|
+
*
|
|
67
|
+
* @param {string} credentialId - Credential ID (string from application layer)
|
|
68
|
+
* @param {boolean} authIsValid - Authentication validity status
|
|
69
|
+
* @returns {Promise<Object>} Update result
|
|
70
|
+
*/
|
|
71
|
+
async updateAuthenticationStatus(credentialId, authIsValid) {
|
|
72
|
+
const intId = this._convertId(credentialId);
|
|
73
|
+
await this.prisma.credential.update({
|
|
74
|
+
where: { id: intId },
|
|
75
|
+
data: { authIsValid },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { acknowledged: true, modifiedCount: 1 };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Permanently remove a credential document
|
|
83
|
+
*
|
|
84
|
+
* @param {string} credentialId - Credential ID (string from application layer)
|
|
85
|
+
* @returns {Promise<Object>} Deletion result
|
|
86
|
+
*/
|
|
87
|
+
async deleteCredentialById(credentialId) {
|
|
88
|
+
try {
|
|
89
|
+
const intId = this._convertId(credentialId);
|
|
90
|
+
await this.prisma.credential.delete({
|
|
91
|
+
where: { id: intId },
|
|
92
|
+
});
|
|
93
|
+
return { acknowledged: true, deletedCount: 1 };
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error.code === 'P2025') {
|
|
96
|
+
// Record not found
|
|
97
|
+
return { acknowledged: true, deletedCount: 0 };
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create or update credential matching identifiers
|
|
105
|
+
*
|
|
106
|
+
* @param {{identifiers: Object, details: Object}} credentialDetails
|
|
107
|
+
* @returns {Promise<Object>} The persisted credential with string IDs
|
|
108
|
+
*/
|
|
109
|
+
async upsertCredential(credentialDetails) {
|
|
110
|
+
const { identifiers, details } = credentialDetails;
|
|
111
|
+
if (!identifiers)
|
|
112
|
+
throw new Error('identifiers required to upsert credential');
|
|
113
|
+
|
|
114
|
+
// Support both userId (preferred) and user (legacy) for backward compatibility
|
|
115
|
+
if (!identifiers.userId && !identifiers.user) {
|
|
116
|
+
throw new Error('userId required in identifiers');
|
|
117
|
+
}
|
|
118
|
+
if (!identifiers.externalId) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
'externalId required in identifiers to prevent credential collision. ' +
|
|
121
|
+
'When multiple credentials exist for the same user, both userId and externalId ' +
|
|
122
|
+
'are needed to uniquely identify which credential to update.'
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const where = this._convertIdentifiersToWhere(identifiers);
|
|
127
|
+
|
|
128
|
+
const { externalId } = identifiers;
|
|
129
|
+
|
|
130
|
+
const { authIsValid, ...oauthData } = details;
|
|
131
|
+
|
|
132
|
+
const existing = await this.prisma.credential.findFirst({ where });
|
|
133
|
+
|
|
134
|
+
if (existing) {
|
|
135
|
+
const mergedData = { ...(existing.data || {}), ...oauthData };
|
|
136
|
+
|
|
137
|
+
const updated = await this.prisma.credential.update({
|
|
138
|
+
where: { id: existing.id },
|
|
139
|
+
data: {
|
|
140
|
+
userId: this._convertId(existing.userId),
|
|
141
|
+
externalId: existing.externalId,
|
|
142
|
+
authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
|
|
143
|
+
data: mergedData,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
id: updated.id.toString(),
|
|
149
|
+
externalId: updated.externalId,
|
|
150
|
+
userId: updated.userId?.toString(),
|
|
151
|
+
authIsValid: updated.authIsValid,
|
|
152
|
+
...(updated.data || {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const created = await this.prisma.credential.create({
|
|
157
|
+
data: {
|
|
158
|
+
// Use userId from where clause (supports both userId and user fields)
|
|
159
|
+
userId: where.userId,
|
|
160
|
+
externalId,
|
|
161
|
+
authIsValid: authIsValid,
|
|
162
|
+
data: oauthData,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
id: created.id.toString(),
|
|
168
|
+
externalId: created.externalId,
|
|
169
|
+
userId: created.userId?.toString(),
|
|
170
|
+
authIsValid: created.authIsValid,
|
|
171
|
+
...(created.data || {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Find a credential by filter criteria
|
|
177
|
+
*
|
|
178
|
+
* @param {Object} filter
|
|
179
|
+
* @param {string} [filter.userId] - User ID (string from application layer)
|
|
180
|
+
* @param {string} [filter.externalId] - External ID
|
|
181
|
+
* @param {string} [filter.credentialId] - Credential ID (string from application layer)
|
|
182
|
+
* @returns {Promise<Object|null>} Credential object with string IDs or null if not found
|
|
183
|
+
*/
|
|
184
|
+
async findCredential(filter) {
|
|
185
|
+
const where = this._convertFilterToWhere(filter);
|
|
186
|
+
|
|
187
|
+
const credential = await this.prisma.credential.findFirst({
|
|
188
|
+
where,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (!credential) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const data = credential.data || {};
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
id: credential.id.toString(),
|
|
199
|
+
userId: credential.userId?.toString(),
|
|
200
|
+
externalId: credential.externalId,
|
|
201
|
+
authIsValid: credential.authIsValid,
|
|
202
|
+
access_token: data.access_token,
|
|
203
|
+
refresh_token: data.refresh_token,
|
|
204
|
+
...data,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Update a credential by ID
|
|
210
|
+
*
|
|
211
|
+
* @param {string} credentialId - Credential ID (string from application layer)
|
|
212
|
+
* @param {Object} updates - Fields to update
|
|
213
|
+
* @returns {Promise<Object|null>} Updated credential object with string IDs or null if not found
|
|
214
|
+
*/
|
|
215
|
+
async updateCredential(credentialId, updates) {
|
|
216
|
+
const intId = this._convertId(credentialId);
|
|
217
|
+
const existing = await this.prisma.credential.findUnique({
|
|
218
|
+
where: { id: intId },
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (!existing) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { authIsValid, ...oauthData } = updates;
|
|
226
|
+
|
|
227
|
+
const mergedData = { ...(existing.data || {}), ...oauthData };
|
|
228
|
+
|
|
229
|
+
const updated = await this.prisma.credential.update({
|
|
230
|
+
where: { id: intId },
|
|
231
|
+
data: {
|
|
232
|
+
userId: this._convertId(existing.userId),
|
|
233
|
+
externalId: existing.externalId,
|
|
234
|
+
authIsValid: authIsValid,
|
|
235
|
+
data: mergedData,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const data = updated.data || {};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
id: updated.id.toString(),
|
|
243
|
+
userId: updated.userId?.toString(),
|
|
244
|
+
externalId: updated.externalId,
|
|
245
|
+
authIsValid: updated.authIsValid,
|
|
246
|
+
access_token: data.access_token,
|
|
247
|
+
refresh_token: data.refresh_token,
|
|
248
|
+
...data,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Convert identifiers to Prisma where clause (converting IDs to Int)
|
|
254
|
+
* @private
|
|
255
|
+
* @param {Object} identifiers - Identifier fields
|
|
256
|
+
* @returns {Object} Prisma where clause with Int IDs
|
|
257
|
+
*/
|
|
258
|
+
_convertIdentifiersToWhere(identifiers) {
|
|
259
|
+
const where = {};
|
|
260
|
+
|
|
261
|
+
if (identifiers.id) where.id = this._convertId(identifiers.id);
|
|
262
|
+
// Support both userId (preferred) and user (legacy) for backward compatibility
|
|
263
|
+
if (identifiers.userId)
|
|
264
|
+
where.userId = this._convertId(identifiers.userId);
|
|
265
|
+
else if (identifiers.user)
|
|
266
|
+
where.userId = this._convertId(identifiers.user);
|
|
267
|
+
if (identifiers.externalId) where.externalId = identifiers.externalId;
|
|
268
|
+
|
|
269
|
+
return where;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Convert filter to Prisma where clause (converting IDs to Int)
|
|
274
|
+
* @private
|
|
275
|
+
* @param {Object} filter - Filter criteria
|
|
276
|
+
* @returns {Object} Prisma where clause with Int IDs
|
|
277
|
+
*/
|
|
278
|
+
_convertFilterToWhere(filter) {
|
|
279
|
+
const where = {};
|
|
280
|
+
|
|
281
|
+
if (filter.credentialId)
|
|
282
|
+
where.id = this._convertId(filter.credentialId);
|
|
283
|
+
if (filter.id) where.id = this._convertId(filter.id);
|
|
284
|
+
if (filter.userId) where.userId = this._convertId(filter.userId);
|
|
285
|
+
if (filter.externalId) where.externalId = filter.externalId;
|
|
286
|
+
|
|
287
|
+
return where;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = { CredentialRepositoryPostgres };
|