@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
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
const bcrypt = require('bcryptjs');
|
|
2
|
+
const { prisma } = require('../../database/prisma');
|
|
3
|
+
const {
|
|
4
|
+
toObjectId,
|
|
5
|
+
fromObjectId,
|
|
6
|
+
findOne,
|
|
7
|
+
insertOne,
|
|
8
|
+
updateOne,
|
|
9
|
+
deleteOne,
|
|
10
|
+
} = require('../../database/documentdb-utils');
|
|
11
|
+
const {
|
|
12
|
+
createTokenRepository,
|
|
13
|
+
} = require('../../token/repositories/token-repository-factory');
|
|
14
|
+
const { UserRepositoryInterface } = require('./user-repository-interface');
|
|
15
|
+
const { ClientSafeError } = require('../../errors');
|
|
16
|
+
const {
|
|
17
|
+
DocumentDBEncryptionService,
|
|
18
|
+
} = require('../../database/documentdb-encryption-service');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* User repository for DocumentDB.
|
|
22
|
+
* Uses DocumentDBEncryptionService for field-level encryption.
|
|
23
|
+
*
|
|
24
|
+
* Encrypted fields: User.hashword
|
|
25
|
+
*
|
|
26
|
+
* @see DocumentDBEncryptionService
|
|
27
|
+
* @see encryption-schema-registry.js
|
|
28
|
+
*/
|
|
29
|
+
class UserRepositoryDocumentDB extends UserRepositoryInterface {
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
this.prisma = prisma;
|
|
33
|
+
this.tokenRepository = createTokenRepository();
|
|
34
|
+
this.encryptionService = new DocumentDBEncryptionService();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getSessionToken(token) {
|
|
38
|
+
const jsonToken =
|
|
39
|
+
this.tokenRepository.getJSONTokenFromBase64BufferToken(token);
|
|
40
|
+
const sessionToken = await this.tokenRepository.validateAndGetToken(
|
|
41
|
+
jsonToken
|
|
42
|
+
);
|
|
43
|
+
return sessionToken;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async findOrganizationUserById(userId) {
|
|
47
|
+
const doc = await findOne(this.prisma, 'User', {
|
|
48
|
+
_id: toObjectId(userId),
|
|
49
|
+
type: 'ORGANIZATION',
|
|
50
|
+
});
|
|
51
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
52
|
+
'User',
|
|
53
|
+
doc
|
|
54
|
+
);
|
|
55
|
+
return this._mapUser(decrypted);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async findIndividualUserById(userId) {
|
|
59
|
+
const doc = await findOne(this.prisma, 'User', {
|
|
60
|
+
_id: toObjectId(userId),
|
|
61
|
+
type: 'INDIVIDUAL',
|
|
62
|
+
});
|
|
63
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
64
|
+
'User',
|
|
65
|
+
doc
|
|
66
|
+
);
|
|
67
|
+
return this._mapUser(decrypted);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async createToken(userId, rawToken, minutes = 120) {
|
|
71
|
+
const createdToken = await this.tokenRepository.createTokenWithExpire(
|
|
72
|
+
fromObjectId(toObjectId(userId)),
|
|
73
|
+
rawToken,
|
|
74
|
+
minutes
|
|
75
|
+
);
|
|
76
|
+
return this.tokenRepository.createBase64BufferToken(
|
|
77
|
+
createdToken,
|
|
78
|
+
rawToken
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async createIndividualUser(params) {
|
|
83
|
+
const now = new Date();
|
|
84
|
+
const document = {
|
|
85
|
+
type: 'INDIVIDUAL',
|
|
86
|
+
email: params.email ?? null,
|
|
87
|
+
username: params.username ?? null,
|
|
88
|
+
appUserId: params.appUserId ?? null,
|
|
89
|
+
organizationId: params.organization
|
|
90
|
+
? toObjectId(params.organization)
|
|
91
|
+
: params.organizationId
|
|
92
|
+
? toObjectId(params.organizationId)
|
|
93
|
+
: null,
|
|
94
|
+
createdAt: now,
|
|
95
|
+
updatedAt: now,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
params.hashword !== undefined &&
|
|
100
|
+
params.hashword !== null &&
|
|
101
|
+
params.hashword !== ''
|
|
102
|
+
) {
|
|
103
|
+
if (typeof params.hashword !== 'string') {
|
|
104
|
+
throw new ClientSafeError('Password must be a string', 400);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (params.hashword.startsWith('$2')) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
'Password appears to be already hashed. Pass plain text password only.'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Bcrypt hash the password
|
|
114
|
+
document.hashword = await bcrypt.hash(params.hashword, 10);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Encrypt sensitive fields before insert
|
|
118
|
+
const encryptedDocument = await this.encryptionService.encryptFields(
|
|
119
|
+
'User',
|
|
120
|
+
document
|
|
121
|
+
);
|
|
122
|
+
const insertedId = await insertOne(
|
|
123
|
+
this.prisma,
|
|
124
|
+
'User',
|
|
125
|
+
encryptedDocument
|
|
126
|
+
);
|
|
127
|
+
const created = await findOne(this.prisma, 'User', { _id: insertedId });
|
|
128
|
+
|
|
129
|
+
// Defensive check: verify document was found after insert
|
|
130
|
+
if (!created) {
|
|
131
|
+
console.error(
|
|
132
|
+
'[UserRepositoryDocumentDB] User not found after insert',
|
|
133
|
+
{
|
|
134
|
+
insertedId: fromObjectId(insertedId),
|
|
135
|
+
params: {
|
|
136
|
+
username: params.username,
|
|
137
|
+
appUserId: params.appUserId,
|
|
138
|
+
email: params.email,
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
throw new Error(
|
|
143
|
+
'Failed to create individual user: Document not found after insert. ' +
|
|
144
|
+
'This indicates a database consistency issue.'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Decrypt sensitive fields after read
|
|
149
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
150
|
+
'User',
|
|
151
|
+
created
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return this._mapUser(decrypted);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async createOrganizationUser(params) {
|
|
158
|
+
const now = new Date();
|
|
159
|
+
const document = {
|
|
160
|
+
type: 'ORGANIZATION',
|
|
161
|
+
appOrgId: params.appOrgId ?? null,
|
|
162
|
+
name: params.name ?? null,
|
|
163
|
+
createdAt: now,
|
|
164
|
+
updatedAt: now,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Encrypt sensitive fields before insert (consistency with individual user)
|
|
168
|
+
const encryptedDocument = await this.encryptionService.encryptFields(
|
|
169
|
+
'User',
|
|
170
|
+
document
|
|
171
|
+
);
|
|
172
|
+
const insertedId = await insertOne(
|
|
173
|
+
this.prisma,
|
|
174
|
+
'User',
|
|
175
|
+
encryptedDocument
|
|
176
|
+
);
|
|
177
|
+
const created = await findOne(this.prisma, 'User', { _id: insertedId });
|
|
178
|
+
|
|
179
|
+
// Defensive check: verify document was found after insert
|
|
180
|
+
if (!created) {
|
|
181
|
+
console.error(
|
|
182
|
+
'[UserRepositoryDocumentDB] Organization user not found after insert',
|
|
183
|
+
{
|
|
184
|
+
insertedId: fromObjectId(insertedId),
|
|
185
|
+
params: {
|
|
186
|
+
appOrgId: params.appOrgId,
|
|
187
|
+
name: params.name,
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
throw new Error(
|
|
192
|
+
'Failed to create organization user: Document not found after insert. ' +
|
|
193
|
+
'This indicates a database consistency issue.'
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Decrypt sensitive fields after read
|
|
198
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
199
|
+
'User',
|
|
200
|
+
created
|
|
201
|
+
);
|
|
202
|
+
return this._mapUser(decrypted);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async findIndividualUserByUsername(username) {
|
|
206
|
+
const doc = await findOne(this.prisma, 'User', {
|
|
207
|
+
type: 'INDIVIDUAL',
|
|
208
|
+
username,
|
|
209
|
+
});
|
|
210
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
211
|
+
'User',
|
|
212
|
+
doc
|
|
213
|
+
);
|
|
214
|
+
return this._mapUser(decrypted);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async findIndividualUserByAppUserId(appUserId) {
|
|
218
|
+
const doc = await findOne(this.prisma, 'User', {
|
|
219
|
+
type: 'INDIVIDUAL',
|
|
220
|
+
appUserId,
|
|
221
|
+
});
|
|
222
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
223
|
+
'User',
|
|
224
|
+
doc
|
|
225
|
+
);
|
|
226
|
+
return this._mapUser(decrypted);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async findOrganizationUserByAppOrgId(appOrgId) {
|
|
230
|
+
const doc = await findOne(this.prisma, 'User', {
|
|
231
|
+
type: 'ORGANIZATION',
|
|
232
|
+
appOrgId,
|
|
233
|
+
});
|
|
234
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
235
|
+
'User',
|
|
236
|
+
doc
|
|
237
|
+
);
|
|
238
|
+
return this._mapUser(decrypted);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async findIndividualUserByEmail(email) {
|
|
242
|
+
const doc = await findOne(this.prisma, 'User', {
|
|
243
|
+
type: 'INDIVIDUAL',
|
|
244
|
+
email,
|
|
245
|
+
});
|
|
246
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
247
|
+
'User',
|
|
248
|
+
doc
|
|
249
|
+
);
|
|
250
|
+
return this._mapUser(decrypted);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async updateIndividualUser(userId, updates) {
|
|
254
|
+
const objectId = toObjectId(userId);
|
|
255
|
+
if (!objectId) return null;
|
|
256
|
+
|
|
257
|
+
const payload = await this._prepareUpdatePayload(updates);
|
|
258
|
+
payload.updatedAt = new Date();
|
|
259
|
+
|
|
260
|
+
// Encrypt sensitive fields before update
|
|
261
|
+
const encryptedPayload = await this.encryptionService.encryptFields(
|
|
262
|
+
'User',
|
|
263
|
+
payload
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
await updateOne(
|
|
267
|
+
this.prisma,
|
|
268
|
+
'User',
|
|
269
|
+
{ _id: objectId, type: 'INDIVIDUAL' },
|
|
270
|
+
{ $set: encryptedPayload }
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const updated = await findOne(this.prisma, 'User', { _id: objectId });
|
|
274
|
+
|
|
275
|
+
// Defensive check: verify document was found after update
|
|
276
|
+
if (!updated) {
|
|
277
|
+
console.error(
|
|
278
|
+
'[UserRepositoryDocumentDB] Individual user not found after update',
|
|
279
|
+
{
|
|
280
|
+
userId: fromObjectId(objectId),
|
|
281
|
+
updates,
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
throw new Error(
|
|
285
|
+
'Failed to update individual user: Document not found after update. ' +
|
|
286
|
+
'This indicates a database consistency issue.'
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
291
|
+
'User',
|
|
292
|
+
updated
|
|
293
|
+
);
|
|
294
|
+
return this._mapUser(decrypted);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async updateOrganizationUser(userId, updates) {
|
|
298
|
+
const objectId = toObjectId(userId);
|
|
299
|
+
if (!objectId) return null;
|
|
300
|
+
|
|
301
|
+
const payload = { ...updates, updatedAt: new Date() };
|
|
302
|
+
|
|
303
|
+
const encryptedPayload = await this.encryptionService.encryptFields(
|
|
304
|
+
'User',
|
|
305
|
+
payload
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
await updateOne(
|
|
309
|
+
this.prisma,
|
|
310
|
+
'User',
|
|
311
|
+
{ _id: objectId, type: 'ORGANIZATION' },
|
|
312
|
+
{ $set: encryptedPayload }
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const updated = await findOne(this.prisma, 'User', { _id: objectId });
|
|
316
|
+
|
|
317
|
+
if (!updated) {
|
|
318
|
+
console.error(
|
|
319
|
+
'[UserRepositoryDocumentDB] Organization user not found after update',
|
|
320
|
+
{
|
|
321
|
+
userId: fromObjectId(objectId),
|
|
322
|
+
updates,
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
throw new Error(
|
|
326
|
+
'Failed to update organization user: Document not found after update. ' +
|
|
327
|
+
'This indicates a database consistency issue.'
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
332
|
+
'User',
|
|
333
|
+
updated
|
|
334
|
+
);
|
|
335
|
+
return this._mapUser(decrypted);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async deleteUser(userId) {
|
|
339
|
+
const objectId = toObjectId(userId);
|
|
340
|
+
if (!objectId) return false;
|
|
341
|
+
|
|
342
|
+
const result = await deleteOne(this.prisma, 'User', { _id: objectId });
|
|
343
|
+
const deleted = result?.n ?? 0;
|
|
344
|
+
return deleted > 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
_mapUser(doc) {
|
|
348
|
+
if (!doc) {
|
|
349
|
+
console.warn(
|
|
350
|
+
'[UserRepositoryDocumentDB] _mapUser received null/undefined document'
|
|
351
|
+
);
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Use optional chaining for robustness
|
|
356
|
+
return {
|
|
357
|
+
id: fromObjectId(doc?._id),
|
|
358
|
+
type: doc?.type ?? null,
|
|
359
|
+
email: doc?.email ?? null,
|
|
360
|
+
username: doc?.username ?? null,
|
|
361
|
+
hashword: doc?.hashword ?? null,
|
|
362
|
+
appUserId: doc?.appUserId ?? null,
|
|
363
|
+
organizationId: doc?.organizationId
|
|
364
|
+
? fromObjectId(doc.organizationId)
|
|
365
|
+
: null,
|
|
366
|
+
appOrgId: doc?.appOrgId ?? null,
|
|
367
|
+
name: doc?.name ?? null,
|
|
368
|
+
createdAt: this._parseDate(doc?.createdAt),
|
|
369
|
+
updatedAt: this._parseDate(doc?.updatedAt),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async _prepareUpdatePayload(updates = {}) {
|
|
374
|
+
const payload = { ...updates };
|
|
375
|
+
|
|
376
|
+
if (
|
|
377
|
+
payload.hashword !== undefined &&
|
|
378
|
+
payload.hashword !== null &&
|
|
379
|
+
payload.hashword !== ''
|
|
380
|
+
) {
|
|
381
|
+
if (typeof payload.hashword !== 'string') {
|
|
382
|
+
throw new ClientSafeError('Password must be a string', 400);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (payload.hashword.startsWith('$2')) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
'Password appears to be already hashed. Pass plain text password only.'
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
payload.hashword = await bcrypt.hash(payload.hashword, 10);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (payload.organization !== undefined) {
|
|
395
|
+
payload.organizationId = toObjectId(payload.organization);
|
|
396
|
+
delete payload.organization;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (payload.organizationId !== undefined) {
|
|
400
|
+
payload.organizationId = payload.organizationId
|
|
401
|
+
? toObjectId(payload.organizationId)
|
|
402
|
+
: null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return payload;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Parse date value safely, returning undefined for invalid dates
|
|
410
|
+
* @private
|
|
411
|
+
* @param {*} value - Date value from database
|
|
412
|
+
* @returns {Date|undefined} Valid Date object or undefined
|
|
413
|
+
*/
|
|
414
|
+
_parseDate(value) {
|
|
415
|
+
if (!value) return undefined;
|
|
416
|
+
const date = new Date(value);
|
|
417
|
+
return isNaN(date.getTime()) ? undefined : date;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Link an individual user to an organization user
|
|
422
|
+
* @param {string} individualUserId - Individual user ID (MongoDB ObjectId string)
|
|
423
|
+
* @param {string} organizationUserId - Organization user ID (MongoDB ObjectId string)
|
|
424
|
+
* @returns {Promise<Object>} Updated individual user object
|
|
425
|
+
*/
|
|
426
|
+
async linkIndividualToOrganization(individualUserId, organizationUserId) {
|
|
427
|
+
const doc = await updateOne(
|
|
428
|
+
this.prisma,
|
|
429
|
+
'User',
|
|
430
|
+
{ _id: toObjectId(individualUserId), type: 'INDIVIDUAL' },
|
|
431
|
+
{ $set: { organizationId: toObjectId(organizationUserId) } }
|
|
432
|
+
);
|
|
433
|
+
const decrypted = await this.encryptionService.decryptFields(
|
|
434
|
+
'User',
|
|
435
|
+
doc
|
|
436
|
+
);
|
|
437
|
+
return this._mapUser(decrypted);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module.exports = { UserRepositoryDocumentDB };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const { UserRepositoryMongo } = require('./user-repository-mongo');
|
|
2
|
+
const { UserRepositoryPostgres } = require('./user-repository-postgres');
|
|
3
|
+
const { UserRepositoryDocumentDB } = require('./user-repository-documentdb');
|
|
4
|
+
const databaseConfig = require('../../database/config');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* User Repository Factory
|
|
8
|
+
* Creates the appropriate repository adapter based on database type
|
|
9
|
+
*
|
|
10
|
+
* Database-specific implementations:
|
|
11
|
+
* - MongoDB: Uses String IDs (ObjectId), no conversion needed
|
|
12
|
+
* - PostgreSQL: Uses Int IDs, converts String ↔ Int
|
|
13
|
+
*
|
|
14
|
+
* All repository methods return String IDs regardless of database type,
|
|
15
|
+
* ensuring application layer consistency.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* ```javascript
|
|
19
|
+
* const repository = createUserRepository();
|
|
20
|
+
* const user = await repository.findIndividualUserById(id); // ID is string
|
|
21
|
+
* const orgUser = await repository.findOrganizationUserById(id); // ID is string
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @returns {UserRepositoryInterface} Configured repository adapter
|
|
25
|
+
*/
|
|
26
|
+
function createUserRepository() {
|
|
27
|
+
const dbType = databaseConfig.DB_TYPE;
|
|
28
|
+
|
|
29
|
+
switch (dbType) {
|
|
30
|
+
case 'mongodb':
|
|
31
|
+
return new UserRepositoryMongo();
|
|
32
|
+
|
|
33
|
+
case 'postgresql':
|
|
34
|
+
return new UserRepositoryPostgres();
|
|
35
|
+
|
|
36
|
+
case 'documentdb':
|
|
37
|
+
return new UserRepositoryDocumentDB();
|
|
38
|
+
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Unsupported DB_TYPE: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
createUserRepository,
|
|
48
|
+
// Export adapters for direct testing
|
|
49
|
+
UserRepositoryMongo,
|
|
50
|
+
UserRepositoryPostgres,
|
|
51
|
+
UserRepositoryDocumentDB,
|
|
52
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Repository Interface
|
|
3
|
+
* Abstract base class defining the contract for user 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, User model has identical structure across MongoDB and PostgreSQL,
|
|
11
|
+
* so UserRepository serves both. This interface exists for consistency and
|
|
12
|
+
* future-proofing if database-specific implementations become needed.
|
|
13
|
+
*
|
|
14
|
+
* @abstract
|
|
15
|
+
*/
|
|
16
|
+
class UserRepositoryInterface {
|
|
17
|
+
/**
|
|
18
|
+
* Get session token from base64 buffer token
|
|
19
|
+
*
|
|
20
|
+
* @param {string} token - Base64 buffer token
|
|
21
|
+
* @returns {Promise<Object>} Session token object
|
|
22
|
+
* @abstract
|
|
23
|
+
*/
|
|
24
|
+
async getSessionToken(token) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'Method getSessionToken must be implemented by subclass'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Find organization user by ID
|
|
32
|
+
*
|
|
33
|
+
* @param {string|number} userId - User ID
|
|
34
|
+
* @returns {Promise<Object|null>} User object or null
|
|
35
|
+
* @abstract
|
|
36
|
+
*/
|
|
37
|
+
async findOrganizationUserById(userId) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'Method findOrganizationUserById must be implemented by subclass'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Find individual user by ID
|
|
45
|
+
*
|
|
46
|
+
* @param {string|number} userId - User ID
|
|
47
|
+
* @returns {Promise<Object|null>} User object or null
|
|
48
|
+
* @abstract
|
|
49
|
+
*/
|
|
50
|
+
async findIndividualUserById(userId) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
'Method findIndividualUserById must be implemented by subclass'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create token with expiration
|
|
58
|
+
*
|
|
59
|
+
* @param {string|number} userId - User ID
|
|
60
|
+
* @param {string} rawToken - Raw unhashed token
|
|
61
|
+
* @param {number} minutes - Minutes until expiration (default 120)
|
|
62
|
+
* @returns {Promise<string>} Base64 buffer token
|
|
63
|
+
* @abstract
|
|
64
|
+
*/
|
|
65
|
+
async createToken(userId, rawToken, minutes = 120) {
|
|
66
|
+
throw new Error('Method createToken must be implemented by subclass');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create individual user
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} params - User creation parameters
|
|
73
|
+
* @returns {Promise<Object>} Created user object
|
|
74
|
+
* @abstract
|
|
75
|
+
*/
|
|
76
|
+
async createIndividualUser(params) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
'Method createIndividualUser must be implemented by subclass'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create organization user
|
|
84
|
+
*
|
|
85
|
+
* @param {Object} params - Organization creation parameters
|
|
86
|
+
* @returns {Promise<Object>} Created organization object
|
|
87
|
+
* @abstract
|
|
88
|
+
*/
|
|
89
|
+
async createOrganizationUser(params) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
'Method createOrganizationUser must be implemented by subclass'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Find individual user by username
|
|
97
|
+
*
|
|
98
|
+
* @param {string} username - Username to search for
|
|
99
|
+
* @returns {Promise<Object|null>} User object or null
|
|
100
|
+
* @abstract
|
|
101
|
+
*/
|
|
102
|
+
async findIndividualUserByUsername(username) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
'Method findIndividualUserByUsername must be implemented by subclass'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Find individual user by app user ID
|
|
110
|
+
*
|
|
111
|
+
* @param {string} appUserId - App user ID to search for
|
|
112
|
+
* @returns {Promise<Object|null>} User object or null
|
|
113
|
+
* @abstract
|
|
114
|
+
*/
|
|
115
|
+
async findIndividualUserByAppUserId(appUserId) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
'Method findIndividualUserByAppUserId must be implemented by subclass'
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Find organization user by app org ID
|
|
123
|
+
*
|
|
124
|
+
* @param {string} appOrgId - App organization ID to search for
|
|
125
|
+
* @returns {Promise<Object|null>} User object or null
|
|
126
|
+
* @abstract
|
|
127
|
+
*/
|
|
128
|
+
async findOrganizationUserByAppOrgId(appOrgId) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
'Method findOrganizationUserByAppOrgId must be implemented by subclass'
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Find individual user by email
|
|
136
|
+
*
|
|
137
|
+
* @param {string} email - Email to search for
|
|
138
|
+
* @returns {Promise<Object|null>} User object or null
|
|
139
|
+
* @abstract
|
|
140
|
+
*/
|
|
141
|
+
async findIndividualUserByEmail(email) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
'Method findIndividualUserByEmail must be implemented by subclass'
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Update individual user
|
|
149
|
+
*
|
|
150
|
+
* @param {string|number} userId - User ID
|
|
151
|
+
* @param {Object} updates - Fields to update
|
|
152
|
+
* @returns {Promise<Object>} Updated user object
|
|
153
|
+
* @abstract
|
|
154
|
+
*/
|
|
155
|
+
async updateIndividualUser(userId, updates) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
'Method updateIndividualUser must be implemented by subclass'
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Update organization user
|
|
163
|
+
*
|
|
164
|
+
* @param {string|number} userId - User ID
|
|
165
|
+
* @param {Object} updates - Fields to update
|
|
166
|
+
* @returns {Promise<Object>} Updated user object
|
|
167
|
+
* @abstract
|
|
168
|
+
*/
|
|
169
|
+
async updateOrganizationUser(userId, updates) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
'Method updateOrganizationUser must be implemented by subclass'
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Delete user by ID
|
|
177
|
+
*
|
|
178
|
+
* @param {string|number} userId - User ID to delete
|
|
179
|
+
* @returns {Promise<boolean>} True if deleted successfully
|
|
180
|
+
* @abstract
|
|
181
|
+
*/
|
|
182
|
+
async deleteUser(userId) {
|
|
183
|
+
throw new Error('Method deleteUser must be implemented by subclass');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Link an individual user to an organization user
|
|
188
|
+
*
|
|
189
|
+
* @param {string|number} individualUserId - Individual user ID
|
|
190
|
+
* @param {string|number} organizationUserId - Organization user ID
|
|
191
|
+
* @returns {Promise<Object>} Updated individual user object
|
|
192
|
+
* @abstract
|
|
193
|
+
*/
|
|
194
|
+
async linkIndividualToOrganization(individualUserId, organizationUserId) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
'Method linkIndividualToOrganization must be implemented by subclass'
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { UserRepositoryInterface };
|