@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,319 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const { SyncRepositoryInterface } = require('./sync-repository-interface');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PostgreSQL Sync Repository Adapter
|
|
6
|
+
* Handles sync persistence using Prisma with PostgreSQL
|
|
7
|
+
*
|
|
8
|
+
* PostgreSQL-specific characteristics:
|
|
9
|
+
* - Uses implicit join tables for entity relations (_EntityToSync)
|
|
10
|
+
* - Uses Int IDs with autoincrement
|
|
11
|
+
* - Requires ID conversion: String (app layer) ↔ Int (database)
|
|
12
|
+
* - All returned IDs are converted to strings for application layer consistency
|
|
13
|
+
* - Uses connect/disconnect syntax for relations
|
|
14
|
+
*/
|
|
15
|
+
class SyncRepositoryPostgres extends SyncRepositoryInterface {
|
|
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
|
+
* Convert sync object IDs to strings
|
|
39
|
+
* @private
|
|
40
|
+
* @param {Object|null} sync - Sync object from database
|
|
41
|
+
* @returns {Object|null} Sync with string IDs
|
|
42
|
+
*/
|
|
43
|
+
_convertSyncIds(sync) {
|
|
44
|
+
if (!sync) return sync;
|
|
45
|
+
return {
|
|
46
|
+
...sync,
|
|
47
|
+
id: sync.id?.toString(),
|
|
48
|
+
integrationId: sync.integrationId?.toString(),
|
|
49
|
+
entities: sync.entities?.map(e => ({
|
|
50
|
+
...e,
|
|
51
|
+
id: e.id?.toString(),
|
|
52
|
+
userId: e.userId?.toString(),
|
|
53
|
+
credentialId: e.credentialId?.toString()
|
|
54
|
+
})),
|
|
55
|
+
dataIdentifiers: sync.dataIdentifiers?.map(di => ({
|
|
56
|
+
...di,
|
|
57
|
+
id: di.id?.toString(),
|
|
58
|
+
syncId: di.syncId?.toString(),
|
|
59
|
+
entityId: di.entityId?.toString(),
|
|
60
|
+
entity: di.entity ? {
|
|
61
|
+
...di.entity,
|
|
62
|
+
id: di.entity.id?.toString(),
|
|
63
|
+
userId: di.entity.userId?.toString(),
|
|
64
|
+
credentialId: di.entity.credentialId?.toString()
|
|
65
|
+
} : di.entity
|
|
66
|
+
}))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get a sync object by name, data identifier, and entity
|
|
72
|
+
*
|
|
73
|
+
* @param {string} name - The sync object name
|
|
74
|
+
* @param {Object} dataIdentifier - The data identifier object
|
|
75
|
+
* @param {string} entity - The entity ID (string from application layer)
|
|
76
|
+
* @returns {Promise<Object|null>} The sync object with string IDs or null
|
|
77
|
+
*/
|
|
78
|
+
async getSyncObject(name, dataIdentifier, entity) {
|
|
79
|
+
const intEntityId = this._convertId(entity);
|
|
80
|
+
const syncList = await this.prisma.sync.findMany({
|
|
81
|
+
where: {
|
|
82
|
+
name,
|
|
83
|
+
dataIdentifiers: {
|
|
84
|
+
some: {
|
|
85
|
+
idData: dataIdentifier,
|
|
86
|
+
entityId: intEntityId,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
include: {
|
|
91
|
+
entities: true,
|
|
92
|
+
dataIdentifiers: {
|
|
93
|
+
include: {
|
|
94
|
+
entity: true,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (syncList.length === 1) {
|
|
101
|
+
return this._convertSyncIds(syncList[0]);
|
|
102
|
+
} else if (syncList.length === 0) {
|
|
103
|
+
return null;
|
|
104
|
+
} else {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`There are multiple sync objects with the name ${name}, for entities [${syncList[0].entities}] [${syncList[1].entities}]`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create or update a sync object
|
|
113
|
+
*
|
|
114
|
+
* @param {Object} filter - Filter criteria for finding existing sync
|
|
115
|
+
* @param {Object} syncData - Sync data to create/update (with string IDs from application layer)
|
|
116
|
+
* @returns {Promise<Object>} The created or updated sync object with string IDs
|
|
117
|
+
*/
|
|
118
|
+
async upsertSync(filter, syncData) {
|
|
119
|
+
// Find existing sync
|
|
120
|
+
const where = this._convertFilterToWhere(filter);
|
|
121
|
+
const existing = await this.prisma.sync.findFirst({ where });
|
|
122
|
+
|
|
123
|
+
// Convert IDs in syncData if present
|
|
124
|
+
const convertedData = { ...syncData };
|
|
125
|
+
if (convertedData.integrationId) {
|
|
126
|
+
convertedData.integrationId = this._convertId(convertedData.integrationId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (existing) {
|
|
130
|
+
// Update existing
|
|
131
|
+
const updated = await this.prisma.sync.update({
|
|
132
|
+
where: { id: existing.id },
|
|
133
|
+
data: convertedData,
|
|
134
|
+
});
|
|
135
|
+
return this._convertSyncIds(updated);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create new
|
|
139
|
+
const created = await this.prisma.sync.create({
|
|
140
|
+
data: convertedData,
|
|
141
|
+
});
|
|
142
|
+
return this._convertSyncIds(created);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Update a sync object by ID
|
|
147
|
+
*
|
|
148
|
+
* @param {string} id - The sync object ID (string from application layer)
|
|
149
|
+
* @param {Object} updates - Updates to apply (with string IDs from application layer)
|
|
150
|
+
* @returns {Promise<Object>} The updated sync object with string IDs
|
|
151
|
+
*/
|
|
152
|
+
async updateSync(id, updates) {
|
|
153
|
+
const intId = this._convertId(id);
|
|
154
|
+
|
|
155
|
+
// Convert IDs in updates if present
|
|
156
|
+
const convertedUpdates = { ...updates };
|
|
157
|
+
if (convertedUpdates.integrationId) {
|
|
158
|
+
convertedUpdates.integrationId = this._convertId(convertedUpdates.integrationId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const updated = await this.prisma.sync.update({
|
|
162
|
+
where: { id: intId },
|
|
163
|
+
data: convertedUpdates,
|
|
164
|
+
});
|
|
165
|
+
return this._convertSyncIds(updated);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Add a data identifier to a sync object
|
|
170
|
+
*
|
|
171
|
+
* @param {string} syncId - The sync object ID (string from application layer)
|
|
172
|
+
* @param {Object} dataIdentifier - The data identifier to add (with string entity ID)
|
|
173
|
+
* @returns {Promise<Object>} The updated sync object with string IDs
|
|
174
|
+
*/
|
|
175
|
+
async addDataIdentifier(syncId, dataIdentifier) {
|
|
176
|
+
const intSyncId = this._convertId(syncId);
|
|
177
|
+
const intEntityId = this._convertId(dataIdentifier.entity);
|
|
178
|
+
|
|
179
|
+
// In Prisma, we create a new DataIdentifier record linked to the Sync
|
|
180
|
+
await this.prisma.dataIdentifier.create({
|
|
181
|
+
data: {
|
|
182
|
+
syncId: intSyncId,
|
|
183
|
+
entityId: intEntityId,
|
|
184
|
+
idData: dataIdentifier.id,
|
|
185
|
+
hash: dataIdentifier.hash,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Return updated sync object
|
|
190
|
+
const sync = await this.prisma.sync.findUnique({
|
|
191
|
+
where: { id: intSyncId },
|
|
192
|
+
include: {
|
|
193
|
+
dataIdentifiers: true,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
return this._convertSyncIds(sync);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get entity object ID for entity ID from sync object
|
|
201
|
+
*
|
|
202
|
+
* This is a pure helper method (no database access)
|
|
203
|
+
*
|
|
204
|
+
* @param {Object} syncObj - The sync object (with string IDs from application layer)
|
|
205
|
+
* @param {string} entityId - The entity ID (string from application layer)
|
|
206
|
+
* @returns {Object} The entity object ID
|
|
207
|
+
*/
|
|
208
|
+
getEntityObjIdForEntityIdFromObject(syncObj, entityId) {
|
|
209
|
+
if (!syncObj.dataIdentifiers) {
|
|
210
|
+
throw new Error('Sync object must include dataIdentifiers');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (let dataIdentifier of syncObj.dataIdentifiers) {
|
|
214
|
+
// Compare string IDs (both should be strings at this point)
|
|
215
|
+
if (dataIdentifier.entityId === entityId) {
|
|
216
|
+
return dataIdentifier.idData;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Sync object ${syncObj.id} does not contain a data identifier for entity ${entityId}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Find sync objects by filter
|
|
227
|
+
*
|
|
228
|
+
* @param {Object} filter - Filter criteria (with string IDs from application layer)
|
|
229
|
+
* @returns {Promise<Array>} Array of sync objects with string IDs
|
|
230
|
+
*/
|
|
231
|
+
async findSyncs(filter) {
|
|
232
|
+
const where = this._convertFilterToWhere(filter);
|
|
233
|
+
const syncs = await this.prisma.sync.findMany({
|
|
234
|
+
where,
|
|
235
|
+
include: {
|
|
236
|
+
entities: true,
|
|
237
|
+
dataIdentifiers: {
|
|
238
|
+
include: {
|
|
239
|
+
entity: true,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
return syncs.map(sync => this._convertSyncIds(sync));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Find one sync object by filter
|
|
249
|
+
*
|
|
250
|
+
* @param {Object} filter - Filter criteria (with string IDs from application layer)
|
|
251
|
+
* @returns {Promise<Object|null>} The sync object with string IDs or null
|
|
252
|
+
*/
|
|
253
|
+
async findOneSync(filter) {
|
|
254
|
+
const where = this._convertFilterToWhere(filter);
|
|
255
|
+
const sync = await this.prisma.sync.findFirst({
|
|
256
|
+
where,
|
|
257
|
+
include: {
|
|
258
|
+
entities: true,
|
|
259
|
+
dataIdentifiers: {
|
|
260
|
+
include: {
|
|
261
|
+
entity: true,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
return this._convertSyncIds(sync);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Delete a sync object by ID
|
|
271
|
+
*
|
|
272
|
+
* @param {string} id - The sync object ID (string from application layer)
|
|
273
|
+
* @returns {Promise<Object>} The deletion result with string IDs
|
|
274
|
+
*/
|
|
275
|
+
async deleteSync(id) {
|
|
276
|
+
const intId = this._convertId(id);
|
|
277
|
+
// Prisma will cascade delete dataIdentifiers automatically
|
|
278
|
+
const deleted = await this.prisma.sync.delete({
|
|
279
|
+
where: { id: intId },
|
|
280
|
+
});
|
|
281
|
+
return this._convertSyncIds(deleted);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Convert Mongoose-style filter to Prisma where clause (converting IDs to Int)
|
|
286
|
+
* @private
|
|
287
|
+
* @param {Object} filter - Mongoose filter (with string IDs from application layer)
|
|
288
|
+
* @returns {Object} Prisma where clause (with Int IDs for PostgreSQL)
|
|
289
|
+
*/
|
|
290
|
+
_convertFilterToWhere(filter) {
|
|
291
|
+
const where = {};
|
|
292
|
+
|
|
293
|
+
// Handle _id field (Mongoose uses _id, Prisma uses id)
|
|
294
|
+
if (filter._id) {
|
|
295
|
+
where.id = this._convertId(filter._id);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Handle id field
|
|
299
|
+
if (filter.id) {
|
|
300
|
+
where.id = this._convertId(filter.id);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Handle integrationId field
|
|
304
|
+
if (filter.integrationId) {
|
|
305
|
+
where.integrationId = this._convertId(filter.integrationId);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Handle integration field (Mongoose uses integration, Prisma uses integrationId)
|
|
309
|
+
if (filter.integration) {
|
|
310
|
+
where.integrationId = this._convertId(filter.integration);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Copy non-ID fields
|
|
314
|
+
const { _id, id, integrationId, integration, ...rest } = filter;
|
|
315
|
+
return { ...where, ...rest };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module.exports = { SyncRepositoryPostgres };
|
package/syncs/sync.js
CHANGED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const bcrypt = require('bcryptjs');
|
|
3
|
+
const {
|
|
4
|
+
toObjectId,
|
|
5
|
+
fromObjectId,
|
|
6
|
+
findMany,
|
|
7
|
+
findOne,
|
|
8
|
+
insertOne,
|
|
9
|
+
deleteOne,
|
|
10
|
+
deleteMany,
|
|
11
|
+
} = require('../../database/documentdb-utils');
|
|
12
|
+
const { TokenRepositoryInterface } = require('./token-repository-interface');
|
|
13
|
+
const { ClientSafeError } = require('../../errors');
|
|
14
|
+
|
|
15
|
+
const BCRYPT_ROUNDS = 10;
|
|
16
|
+
|
|
17
|
+
class TokenRepositoryDocumentDB extends TokenRepositoryInterface {
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.prisma = prisma;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async createTokenWithExpire(userId, rawToken, minutes) {
|
|
24
|
+
const tokenHash = await bcrypt.hash(rawToken, BCRYPT_ROUNDS);
|
|
25
|
+
const expires = new Date(Date.now() + minutes * 60000);
|
|
26
|
+
const now = new Date();
|
|
27
|
+
const document = {
|
|
28
|
+
token: tokenHash,
|
|
29
|
+
expires,
|
|
30
|
+
userId: toObjectId(userId),
|
|
31
|
+
created: now,
|
|
32
|
+
};
|
|
33
|
+
const insertedId = await insertOne(this.prisma, 'Token', document);
|
|
34
|
+
const created = await findOne(this.prisma, 'Token', {
|
|
35
|
+
_id: insertedId,
|
|
36
|
+
});
|
|
37
|
+
return this._mapToken(created);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async validateAndGetToken(tokenObj) {
|
|
41
|
+
const objectId = toObjectId(tokenObj.id);
|
|
42
|
+
if (!objectId) {
|
|
43
|
+
throw new ClientSafeError(
|
|
44
|
+
'Invalid Token: Token does not exist',
|
|
45
|
+
401
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const record = await findOne(this.prisma, 'Token', { _id: objectId });
|
|
49
|
+
if (!record) {
|
|
50
|
+
throw new ClientSafeError(
|
|
51
|
+
'Invalid Token: Token does not exist',
|
|
52
|
+
401
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const isValid = await bcrypt.compare(tokenObj.token, record.token);
|
|
56
|
+
if (!isValid) {
|
|
57
|
+
throw new ClientSafeError(
|
|
58
|
+
'Invalid Token: Token does not match',
|
|
59
|
+
401
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (record.expires && new Date(record.expires) < new Date()) {
|
|
63
|
+
throw new ClientSafeError('Invalid Token: Token is expired', 401);
|
|
64
|
+
}
|
|
65
|
+
return this._mapToken(record);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async findTokenById(tokenId) {
|
|
69
|
+
const objectId = toObjectId(tokenId);
|
|
70
|
+
if (!objectId) return null;
|
|
71
|
+
const record = await findOne(this.prisma, 'Token', { _id: objectId });
|
|
72
|
+
return record ? this._mapToken(record) : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async findTokensByUserId(userId) {
|
|
76
|
+
const objectId = toObjectId(userId);
|
|
77
|
+
const filter = objectId ? { userId: objectId } : {};
|
|
78
|
+
const records = await findMany(this.prisma, 'Token', filter);
|
|
79
|
+
return records.map((record) => this._mapToken(record));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async deleteToken(tokenId) {
|
|
83
|
+
const objectId = toObjectId(tokenId);
|
|
84
|
+
if (!objectId) return { acknowledged: true, deletedCount: 0 };
|
|
85
|
+
const result = await deleteOne(this.prisma, 'Token', { _id: objectId });
|
|
86
|
+
const deleted = result?.n ?? 0;
|
|
87
|
+
return { acknowledged: true, deletedCount: deleted };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async deleteExpiredTokens() {
|
|
91
|
+
const result = await deleteMany(this.prisma, 'Token', {
|
|
92
|
+
expires: { $lt: new Date() },
|
|
93
|
+
});
|
|
94
|
+
const deleted = result?.n ?? 0;
|
|
95
|
+
return { acknowledged: true, deletedCount: deleted };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async deleteTokensByUserId(userId) {
|
|
99
|
+
const objectId = toObjectId(userId);
|
|
100
|
+
if (!objectId) return { acknowledged: true, deletedCount: 0 };
|
|
101
|
+
const result = await deleteMany(this.prisma, 'Token', {
|
|
102
|
+
userId: objectId,
|
|
103
|
+
});
|
|
104
|
+
const deleted = result?.n ?? 0;
|
|
105
|
+
return { acknowledged: true, deletedCount: deleted };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
createJSONToken(token, rawToken) {
|
|
109
|
+
return JSON.stringify({
|
|
110
|
+
id: token.id,
|
|
111
|
+
token: rawToken,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
createBase64BufferToken(token, rawToken) {
|
|
116
|
+
const jsonVal = this.createJSONToken(token, rawToken);
|
|
117
|
+
return Buffer.from(jsonVal).toString('base64');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getJSONTokenFromBase64BufferToken(buffer) {
|
|
121
|
+
const tokenStr = Buffer.from(buffer.trim(), 'base64').toString('ascii');
|
|
122
|
+
return JSON.parse(tokenStr);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_mapToken(record) {
|
|
126
|
+
if (!record) return null;
|
|
127
|
+
return {
|
|
128
|
+
id: fromObjectId(record._id),
|
|
129
|
+
token: record.token,
|
|
130
|
+
expires: record.expires ? new Date(record.expires) : null,
|
|
131
|
+
userId: fromObjectId(record.userId),
|
|
132
|
+
created: record.created ? new Date(record.created) : null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { TokenRepositoryDocumentDB };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { TokenRepositoryMongo } = require('./token-repository-mongo');
|
|
2
|
+
const { TokenRepositoryPostgres } = require('./token-repository-postgres');
|
|
3
|
+
const {
|
|
4
|
+
TokenRepositoryDocumentDB,
|
|
5
|
+
} = require('./token-repository-documentdb');
|
|
6
|
+
const config = require('../../database/config');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Token Repository Factory
|
|
10
|
+
* Creates the appropriate repository adapter based on database type
|
|
11
|
+
*
|
|
12
|
+
* @returns {TokenRepositoryInterface} Configured repository adapter
|
|
13
|
+
*/
|
|
14
|
+
function createTokenRepository() {
|
|
15
|
+
const dbType = config.DB_TYPE;
|
|
16
|
+
|
|
17
|
+
switch (dbType) {
|
|
18
|
+
case 'mongodb':
|
|
19
|
+
return new TokenRepositoryMongo();
|
|
20
|
+
|
|
21
|
+
case 'postgresql':
|
|
22
|
+
return new TokenRepositoryPostgres();
|
|
23
|
+
|
|
24
|
+
case 'documentdb':
|
|
25
|
+
return new TokenRepositoryDocumentDB();
|
|
26
|
+
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
createTokenRepository,
|
|
36
|
+
// Export adapters for direct testing
|
|
37
|
+
TokenRepositoryMongo,
|
|
38
|
+
TokenRepositoryPostgres,
|
|
39
|
+
TokenRepositoryDocumentDB,
|
|
40
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Repository Interface
|
|
3
|
+
* Abstract base class defining the contract for token 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, Token model has identical structure across MongoDB and PostgreSQL,
|
|
11
|
+
* so TokenRepository serves both. This interface exists for consistency and
|
|
12
|
+
* future-proofing if database-specific implementations become needed.
|
|
13
|
+
*
|
|
14
|
+
* @abstract
|
|
15
|
+
*/
|
|
16
|
+
class TokenRepositoryInterface {
|
|
17
|
+
/**
|
|
18
|
+
* Create token with expiration
|
|
19
|
+
*
|
|
20
|
+
* @param {string|number} userId - User ID
|
|
21
|
+
* @param {string} rawToken - Raw unhashed token
|
|
22
|
+
* @param {number} minutes - Minutes until expiration
|
|
23
|
+
* @returns {Promise<Object>} Created token object
|
|
24
|
+
* @abstract
|
|
25
|
+
*/
|
|
26
|
+
async createTokenWithExpire(userId, rawToken, minutes) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'Method createTokenWithExpire must be implemented by subclass'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate and get token
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} tokenObj - Token object with id and token
|
|
36
|
+
* @returns {Promise<Object>} Validated token object
|
|
37
|
+
* @abstract
|
|
38
|
+
*/
|
|
39
|
+
async validateAndGetToken(tokenObj) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'Method validateAndGetToken must be implemented by subclass'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find token by ID
|
|
47
|
+
*
|
|
48
|
+
* @param {string|number} tokenId - Token ID
|
|
49
|
+
* @returns {Promise<Object|null>} Token object or null
|
|
50
|
+
* @abstract
|
|
51
|
+
*/
|
|
52
|
+
async findTokenById(tokenId) {
|
|
53
|
+
throw new Error('Method findTokenById must be implemented by subclass');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Find all tokens for a user
|
|
58
|
+
*
|
|
59
|
+
* @param {string|number} userId - User ID
|
|
60
|
+
* @returns {Promise<Array>} Array of token objects
|
|
61
|
+
* @abstract
|
|
62
|
+
*/
|
|
63
|
+
async findTokensByUserId(userId) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'Method findTokensByUserId must be implemented by subclass'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Delete token by ID
|
|
71
|
+
*
|
|
72
|
+
* @param {string|number} tokenId - Token ID
|
|
73
|
+
* @returns {Promise<boolean>} True if deleted
|
|
74
|
+
* @abstract
|
|
75
|
+
*/
|
|
76
|
+
async deleteToken(tokenId) {
|
|
77
|
+
throw new Error('Method deleteToken must be implemented by subclass');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Delete expired tokens
|
|
82
|
+
*
|
|
83
|
+
* @returns {Promise<number>} Number of deleted tokens
|
|
84
|
+
* @abstract
|
|
85
|
+
*/
|
|
86
|
+
async deleteExpiredTokens() {
|
|
87
|
+
throw new Error(
|
|
88
|
+
'Method deleteExpiredTokens must be implemented by subclass'
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Delete all tokens for a user
|
|
94
|
+
*
|
|
95
|
+
* @param {string|number} userId - User ID
|
|
96
|
+
* @returns {Promise<number>} Number of deleted tokens
|
|
97
|
+
* @abstract
|
|
98
|
+
*/
|
|
99
|
+
async deleteTokensByUserId(userId) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
'Method deleteTokensByUserId must be implemented by subclass'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create base64 buffer token
|
|
107
|
+
*
|
|
108
|
+
* @param {Object} token - Token object
|
|
109
|
+
* @param {string} rawToken - Raw token
|
|
110
|
+
* @returns {string} Base64 encoded token
|
|
111
|
+
*/
|
|
112
|
+
createBase64BufferToken(token, rawToken) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
'Method createBase64BufferToken must be implemented by subclass'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get JSON token from base64 buffer token
|
|
120
|
+
*
|
|
121
|
+
* @param {string} base64Token - Base64 encoded token
|
|
122
|
+
* @returns {Object} Decoded token object
|
|
123
|
+
*/
|
|
124
|
+
getJSONTokenFromBase64BufferToken(base64Token) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
'Method getJSONTokenFromBase64BufferToken must be implemented by subclass'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = { TokenRepositoryInterface };
|