@friggframework/core 2.0.0-next.1 → 2.0.0-next.100

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.
Files changed (342) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/admin-scripts/index.js +52 -0
  4. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +166 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +228 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +268 -0
  9. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  10. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  11. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  12. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  13. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  14. package/application/commands/README.md +451 -0
  15. package/application/commands/admin-script-commands.js +358 -0
  16. package/application/commands/credential-commands.js +245 -0
  17. package/application/commands/entity-commands.js +336 -0
  18. package/application/commands/integration-commands.js +343 -0
  19. package/application/commands/process-commands.js +135 -0
  20. package/application/commands/scheduler-commands.js +263 -0
  21. package/application/commands/user-commands.js +312 -0
  22. package/application/index.js +82 -0
  23. package/assertions/index.js +0 -3
  24. package/core/CLAUDE.md +690 -0
  25. package/core/Worker.js +60 -24
  26. package/core/create-handler.js +84 -6
  27. package/credential/repositories/credential-repository-documentdb.js +304 -0
  28. package/credential/repositories/credential-repository-factory.js +54 -0
  29. package/credential/repositories/credential-repository-interface.js +98 -0
  30. package/credential/repositories/credential-repository-mongo.js +269 -0
  31. package/credential/repositories/credential-repository-postgres.js +287 -0
  32. package/credential/repositories/credential-repository.js +300 -0
  33. package/credential/use-cases/get-credential-for-user.js +25 -0
  34. package/credential/use-cases/update-authentication-status.js +15 -0
  35. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  36. package/database/adapters/lambda-invoker.js +97 -0
  37. package/database/config.js +154 -0
  38. package/database/documentdb-encryption-service.js +330 -0
  39. package/database/documentdb-utils.js +136 -0
  40. package/database/encryption/README.md +839 -0
  41. package/database/encryption/documentdb-encryption-service.md +3575 -0
  42. package/database/encryption/encryption-schema-registry.js +401 -0
  43. package/database/encryption/field-encryption-service.js +254 -0
  44. package/database/encryption/logger.js +79 -0
  45. package/database/encryption/prisma-encryption-extension.js +230 -0
  46. package/database/index.js +21 -21
  47. package/database/prisma.js +182 -0
  48. package/database/repositories/health-check-repository-documentdb.js +138 -0
  49. package/database/repositories/health-check-repository-factory.js +48 -0
  50. package/database/repositories/health-check-repository-interface.js +82 -0
  51. package/database/repositories/health-check-repository-mongodb.js +89 -0
  52. package/database/repositories/health-check-repository-postgres.js +82 -0
  53. package/database/repositories/migration-status-repository-s3.js +137 -0
  54. package/database/use-cases/check-database-health-use-case.js +29 -0
  55. package/database/use-cases/check-database-state-use-case.js +81 -0
  56. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  57. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  58. package/database/use-cases/get-migration-status-use-case.js +93 -0
  59. package/database/use-cases/run-database-migration-use-case.js +139 -0
  60. package/database/use-cases/test-encryption-use-case.js +253 -0
  61. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  62. package/database/utils/mongodb-collection-utils.js +94 -0
  63. package/database/utils/mongodb-schema-init.js +108 -0
  64. package/database/utils/prisma-runner.js +477 -0
  65. package/database/utils/prisma-schema-parser.js +182 -0
  66. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  67. package/encrypt/Cryptor.js +34 -168
  68. package/encrypt/index.js +1 -2
  69. package/errors/client-safe-error.js +26 -0
  70. package/errors/fetch-error.js +15 -7
  71. package/errors/index.js +2 -0
  72. package/generated/prisma-mongodb/client.d.ts +1 -0
  73. package/generated/prisma-mongodb/client.js +5 -0
  74. package/generated/prisma-mongodb/default.d.ts +1 -0
  75. package/generated/prisma-mongodb/default.js +5 -0
  76. package/generated/prisma-mongodb/edge.d.ts +1 -0
  77. package/generated/prisma-mongodb/edge.js +374 -0
  78. package/generated/prisma-mongodb/index-browser.js +356 -0
  79. package/generated/prisma-mongodb/index.d.ts +25667 -0
  80. package/generated/prisma-mongodb/index.js +399 -0
  81. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  82. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  83. package/generated/prisma-mongodb/package.json +183 -0
  84. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  85. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  86. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  87. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  88. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  89. package/generated/prisma-mongodb/runtime/library.js +147 -0
  90. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  91. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  92. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  93. package/generated/prisma-mongodb/schema.prisma +432 -0
  94. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  95. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  96. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  97. package/generated/prisma-mongodb/wasm.js +381 -0
  98. package/generated/prisma-postgresql/client.d.ts +1 -0
  99. package/generated/prisma-postgresql/client.js +5 -0
  100. package/generated/prisma-postgresql/default.d.ts +1 -0
  101. package/generated/prisma-postgresql/default.js +5 -0
  102. package/generated/prisma-postgresql/edge.d.ts +1 -0
  103. package/generated/prisma-postgresql/edge.js +395 -0
  104. package/generated/prisma-postgresql/index-browser.js +377 -0
  105. package/generated/prisma-postgresql/index.d.ts +28455 -0
  106. package/generated/prisma-postgresql/index.js +420 -0
  107. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  108. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  109. package/generated/prisma-postgresql/package.json +183 -0
  110. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  111. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  112. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  113. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  114. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  115. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  116. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  117. package/generated/prisma-postgresql/runtime/library.js +147 -0
  118. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  119. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  120. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  121. package/generated/prisma-postgresql/schema.prisma +414 -0
  122. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  123. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  124. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  125. package/generated/prisma-postgresql/wasm.js +402 -0
  126. package/handlers/WEBHOOKS.md +653 -0
  127. package/handlers/app-definition-loader.js +43 -0
  128. package/handlers/app-handler-helpers.js +57 -0
  129. package/handlers/backend-utils.js +316 -0
  130. package/handlers/database-migration-handler.js +227 -0
  131. package/handlers/integration-event-dispatcher.js +54 -0
  132. package/handlers/middleware/admin-auth.js +73 -0
  133. package/handlers/routers/HEALTHCHECK.md +342 -0
  134. package/handlers/routers/auth.js +15 -0
  135. package/handlers/routers/db-migration.handler.js +29 -0
  136. package/handlers/routers/db-migration.js +311 -0
  137. package/handlers/routers/health.js +518 -0
  138. package/handlers/routers/integration-defined-routers.js +117 -0
  139. package/handlers/routers/integration-webhook-routers.js +67 -0
  140. package/handlers/routers/reporting.js +9 -0
  141. package/handlers/routers/user.js +63 -0
  142. package/handlers/routers/websocket.js +57 -0
  143. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  144. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  145. package/handlers/workers/db-migration.js +352 -0
  146. package/handlers/workers/dlq-processor.js +63 -0
  147. package/handlers/workers/integration-defined-workers.js +30 -0
  148. package/index.js +111 -50
  149. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  150. package/infrastructure/scheduler/index.js +33 -0
  151. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  152. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  153. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  154. package/integrations/EXTENSIONS.md +240 -0
  155. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  156. package/integrations/extension.js +254 -0
  157. package/integrations/index.js +20 -10
  158. package/integrations/integration-base.js +619 -72
  159. package/integrations/integration-router.js +460 -218
  160. package/integrations/options.js +3 -4
  161. package/integrations/repositories/config-patch-shared.js +43 -0
  162. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  163. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  164. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  165. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  166. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  167. package/integrations/repositories/integration-mapping-repository.js +156 -0
  168. package/integrations/repositories/integration-repository-documentdb.js +284 -0
  169. package/integrations/repositories/integration-repository-factory.js +51 -0
  170. package/integrations/repositories/integration-repository-interface.js +173 -0
  171. package/integrations/repositories/integration-repository-mongo.js +414 -0
  172. package/integrations/repositories/integration-repository-postgres.js +452 -0
  173. package/integrations/repositories/process-repository-documentdb.js +311 -0
  174. package/integrations/repositories/process-repository-factory.js +53 -0
  175. package/integrations/repositories/process-repository-interface.js +136 -0
  176. package/integrations/repositories/process-repository-mongo.js +262 -0
  177. package/integrations/repositories/process-repository-postgres.js +380 -0
  178. package/integrations/repositories/process-update-ops-shared.js +112 -0
  179. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  180. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  181. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  182. package/integrations/use-cases/create-integration.js +215 -0
  183. package/integrations/use-cases/create-process.js +130 -0
  184. package/integrations/use-cases/delete-integration-for-user.js +119 -0
  185. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  186. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  187. package/integrations/use-cases/get-integration-for-user.js +78 -0
  188. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  189. package/integrations/use-cases/get-integration-instance.js +83 -0
  190. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  191. package/integrations/use-cases/get-possible-integrations.js +27 -0
  192. package/integrations/use-cases/get-process.js +89 -0
  193. package/integrations/use-cases/index.js +19 -0
  194. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  195. package/integrations/use-cases/load-integration-context.js +71 -0
  196. package/integrations/use-cases/patch-integration-config.js +39 -0
  197. package/integrations/use-cases/process-errors.js +28 -0
  198. package/integrations/use-cases/update-integration-config.js +32 -0
  199. package/integrations/use-cases/update-integration-messages.js +44 -0
  200. package/integrations/use-cases/update-integration-status.js +32 -0
  201. package/integrations/use-cases/update-integration.js +92 -0
  202. package/integrations/use-cases/update-process-metrics.js +216 -0
  203. package/integrations/use-cases/update-process-state.js +163 -0
  204. package/integrations/utils/map-integration-dto.js +37 -0
  205. package/jest-global-setup-noop.js +3 -0
  206. package/jest-global-teardown-noop.js +3 -0
  207. package/logs/logger.js +0 -4
  208. package/{module-plugin → modules}/index.js +0 -10
  209. package/modules/module-factory.js +56 -0
  210. package/modules/module.js +274 -0
  211. package/modules/repositories/module-repository-documentdb.js +350 -0
  212. package/modules/repositories/module-repository-factory.js +40 -0
  213. package/modules/repositories/module-repository-interface.js +145 -0
  214. package/modules/repositories/module-repository-mongo.js +436 -0
  215. package/modules/repositories/module-repository-postgres.js +481 -0
  216. package/modules/repositories/module-repository.js +369 -0
  217. package/modules/requester/api-key.js +52 -0
  218. package/modules/requester/oauth-2.js +396 -0
  219. package/modules/requester/requester.js +285 -0
  220. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  221. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  222. package/modules/tests/doubles/test-module-factory.js +16 -0
  223. package/modules/tests/doubles/test-module-repository.js +39 -0
  224. package/modules/use-cases/get-entities-for-user.js +32 -0
  225. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  226. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  227. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  228. package/modules/use-cases/get-module.js +74 -0
  229. package/modules/use-cases/process-authorization-callback.js +243 -0
  230. package/modules/use-cases/refresh-entity-options.js +72 -0
  231. package/modules/use-cases/test-module-auth.js +72 -0
  232. package/modules/utils/map-module-dto.js +18 -0
  233. package/package.json +82 -50
  234. package/prisma-mongodb/schema.prisma +432 -0
  235. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  236. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  237. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  238. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  239. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  240. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  241. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  242. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  243. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  244. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  245. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  246. package/prisma-postgresql/schema.prisma +414 -0
  247. package/queues/queuer-util.js +103 -21
  248. package/reporting/README.md +86 -0
  249. package/reporting/index.js +17 -0
  250. package/reporting/reporting-router.js +77 -0
  251. package/reporting/repositories/reporting-repository-documentdb.js +127 -0
  252. package/reporting/repositories/reporting-repository-factory.js +35 -0
  253. package/reporting/repositories/reporting-repository-interface.js +16 -0
  254. package/reporting/repositories/reporting-repository-mongo.js +54 -0
  255. package/reporting/repositories/reporting-repository-postgres.js +70 -0
  256. package/reporting/use-cases/index.js +6 -0
  257. package/reporting/use-cases/list-integrations-report.js +142 -0
  258. package/syncs/manager.js +468 -443
  259. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  260. package/syncs/repositories/sync-repository-factory.js +43 -0
  261. package/syncs/repositories/sync-repository-interface.js +109 -0
  262. package/syncs/repositories/sync-repository-mongo.js +239 -0
  263. package/syncs/repositories/sync-repository-postgres.js +319 -0
  264. package/syncs/sync.js +0 -1
  265. package/token/repositories/token-repository-documentdb.js +137 -0
  266. package/token/repositories/token-repository-factory.js +40 -0
  267. package/token/repositories/token-repository-interface.js +131 -0
  268. package/token/repositories/token-repository-mongo.js +219 -0
  269. package/token/repositories/token-repository-postgres.js +264 -0
  270. package/token/repositories/token-repository.js +219 -0
  271. package/types/associations/index.d.ts +0 -17
  272. package/types/core/index.d.ts +12 -4
  273. package/types/database/index.d.ts +10 -2
  274. package/types/encrypt/index.d.ts +5 -3
  275. package/types/integrations/index.d.ts +3 -8
  276. package/types/module-plugin/index.d.ts +17 -69
  277. package/types/syncs/index.d.ts +0 -17
  278. package/user/repositories/user-repository-documentdb.js +458 -0
  279. package/user/repositories/user-repository-factory.js +52 -0
  280. package/user/repositories/user-repository-interface.js +214 -0
  281. package/user/repositories/user-repository-mongo.js +323 -0
  282. package/user/repositories/user-repository-postgres.js +377 -0
  283. package/user/tests/doubles/test-user-repository.js +72 -0
  284. package/user/use-cases/authenticate-user.js +127 -0
  285. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  286. package/user/use-cases/create-individual-user.js +61 -0
  287. package/user/use-cases/create-organization-user.js +47 -0
  288. package/user/use-cases/create-token-for-user-id.js +30 -0
  289. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  290. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  291. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  292. package/user/use-cases/login-user.js +122 -0
  293. package/user/user.js +125 -0
  294. package/utils/backend-path.js +38 -0
  295. package/utils/index.js +6 -0
  296. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  297. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  298. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  299. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  300. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  301. package/websocket/repositories/websocket-connection-repository.js +161 -0
  302. package/assertions/is-equal.js +0 -17
  303. package/associations/model.js +0 -54
  304. package/database/models/IndividualUser.js +0 -76
  305. package/database/models/OrganizationUser.js +0 -29
  306. package/database/models/State.js +0 -9
  307. package/database/models/Token.js +0 -70
  308. package/database/models/UserModel.js +0 -7
  309. package/database/models/WebsocketConnection.js +0 -49
  310. package/database/mongo.js +0 -45
  311. package/database/mongoose.js +0 -5
  312. package/encrypt/Cryptor.test.js +0 -32
  313. package/encrypt/encrypt.js +0 -132
  314. package/encrypt/encrypt.test.js +0 -1069
  315. package/encrypt/test-encrypt.js +0 -107
  316. package/errors/base-error.test.js +0 -32
  317. package/errors/fetch-error.test.js +0 -79
  318. package/errors/halt-error.test.js +0 -11
  319. package/errors/validation-errors.test.js +0 -120
  320. package/integrations/create-frigg-backend.js +0 -31
  321. package/integrations/integration-factory.js +0 -251
  322. package/integrations/integration-mapping.js +0 -43
  323. package/integrations/integration-model.js +0 -46
  324. package/integrations/integration-user.js +0 -144
  325. package/integrations/test/integration-base.test.js +0 -144
  326. package/lambda/TimeoutCatcher.test.js +0 -68
  327. package/logs/logger.test.js +0 -76
  328. package/module-plugin/auther.js +0 -350
  329. package/module-plugin/credential.js +0 -22
  330. package/module-plugin/entity-manager.js +0 -70
  331. package/module-plugin/entity.js +0 -46
  332. package/module-plugin/manager.js +0 -169
  333. package/module-plugin/module-factory.js +0 -61
  334. package/module-plugin/requester/api-key.js +0 -36
  335. package/module-plugin/requester/oauth-2.js +0 -219
  336. package/module-plugin/requester/requester.js +0 -165
  337. package/module-plugin/requester/requester.test.js +0 -28
  338. package/module-plugin/test/auther.test.js +0 -97
  339. package/syncs/model.js +0 -62
  340. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  341. /package/{module-plugin → modules}/requester/basic.js +0 -0
  342. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,214 @@
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
+ * Find all individual users linked to an organization user
58
+ *
59
+ * @param {string|number} organizationUserId - Organization user ID
60
+ * @returns {Promise<Object[]>} Array of individual user objects (empty if none)
61
+ * @abstract
62
+ */
63
+ async findIndividualUsersByOrganizationId(organizationUserId) {
64
+ throw new Error(
65
+ 'Method findIndividualUsersByOrganizationId must be implemented by subclass'
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Create token with expiration
71
+ *
72
+ * @param {string|number} userId - User ID
73
+ * @param {string} rawToken - Raw unhashed token
74
+ * @param {number} minutes - Minutes until expiration (default 120)
75
+ * @returns {Promise<string>} Base64 buffer token
76
+ * @abstract
77
+ */
78
+ async createToken(userId, rawToken, minutes = 120) {
79
+ throw new Error('Method createToken must be implemented by subclass');
80
+ }
81
+
82
+ /**
83
+ * Create individual user
84
+ *
85
+ * @param {Object} params - User creation parameters
86
+ * @returns {Promise<Object>} Created user object
87
+ * @abstract
88
+ */
89
+ async createIndividualUser(params) {
90
+ throw new Error(
91
+ 'Method createIndividualUser must be implemented by subclass'
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Create organization user
97
+ *
98
+ * @param {Object} params - Organization creation parameters
99
+ * @returns {Promise<Object>} Created organization object
100
+ * @abstract
101
+ */
102
+ async createOrganizationUser(params) {
103
+ throw new Error(
104
+ 'Method createOrganizationUser must be implemented by subclass'
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Find individual user by username
110
+ *
111
+ * @param {string} username - Username to search for
112
+ * @returns {Promise<Object|null>} User object or null
113
+ * @abstract
114
+ */
115
+ async findIndividualUserByUsername(username) {
116
+ throw new Error(
117
+ 'Method findIndividualUserByUsername must be implemented by subclass'
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Find individual user by app user ID
123
+ *
124
+ * @param {string} appUserId - App user ID to search for
125
+ * @returns {Promise<Object|null>} User object or null
126
+ * @abstract
127
+ */
128
+ async findIndividualUserByAppUserId(appUserId) {
129
+ throw new Error(
130
+ 'Method findIndividualUserByAppUserId must be implemented by subclass'
131
+ );
132
+ }
133
+
134
+ /**
135
+ * Find organization user by app org ID
136
+ *
137
+ * @param {string} appOrgId - App organization ID to search for
138
+ * @returns {Promise<Object|null>} User object or null
139
+ * @abstract
140
+ */
141
+ async findOrganizationUserByAppOrgId(appOrgId) {
142
+ throw new Error(
143
+ 'Method findOrganizationUserByAppOrgId must be implemented by subclass'
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Find individual user by email
149
+ *
150
+ * @param {string} email - Email to search for
151
+ * @returns {Promise<Object|null>} User object or null
152
+ * @abstract
153
+ */
154
+ async findIndividualUserByEmail(email) {
155
+ throw new Error(
156
+ 'Method findIndividualUserByEmail must be implemented by subclass'
157
+ );
158
+ }
159
+
160
+ /**
161
+ * Update individual user
162
+ *
163
+ * @param {string|number} userId - User ID
164
+ * @param {Object} updates - Fields to update
165
+ * @returns {Promise<Object>} Updated user object
166
+ * @abstract
167
+ */
168
+ async updateIndividualUser(userId, updates) {
169
+ throw new Error(
170
+ 'Method updateIndividualUser must be implemented by subclass'
171
+ );
172
+ }
173
+
174
+ /**
175
+ * Update organization user
176
+ *
177
+ * @param {string|number} userId - User ID
178
+ * @param {Object} updates - Fields to update
179
+ * @returns {Promise<Object>} Updated user object
180
+ * @abstract
181
+ */
182
+ async updateOrganizationUser(userId, updates) {
183
+ throw new Error(
184
+ 'Method updateOrganizationUser must be implemented by subclass'
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Delete user by ID
190
+ *
191
+ * @param {string|number} userId - User ID to delete
192
+ * @returns {Promise<boolean>} True if deleted successfully
193
+ * @abstract
194
+ */
195
+ async deleteUser(userId) {
196
+ throw new Error('Method deleteUser must be implemented by subclass');
197
+ }
198
+
199
+ /**
200
+ * Link an individual user to an organization user
201
+ *
202
+ * @param {string|number} individualUserId - Individual user ID
203
+ * @param {string|number} organizationUserId - Organization user ID
204
+ * @returns {Promise<Object>} Updated individual user object
205
+ * @abstract
206
+ */
207
+ async linkIndividualToOrganization(individualUserId, organizationUserId) {
208
+ throw new Error(
209
+ 'Method linkIndividualToOrganization must be implemented by subclass'
210
+ );
211
+ }
212
+ }
213
+
214
+ module.exports = { UserRepositoryInterface };
@@ -0,0 +1,323 @@
1
+ const bcrypt = require('bcryptjs');
2
+ const { prisma } = require('../../database/prisma');
3
+ const {
4
+ createTokenRepository,
5
+ } = require('../../token/repositories/token-repository-factory');
6
+ const { UserRepositoryInterface } = require('./user-repository-interface');
7
+ const { ClientSafeError } = require('../../errors');
8
+
9
+ /**
10
+ * MongoDB User Repository Adapter
11
+ * Handles user operations with discriminator pattern support
12
+ *
13
+ * MongoDB-specific characteristics:
14
+ * - Uses String IDs (ObjectId)
15
+ * - No ID conversion needed (IDs are already strings)
16
+ * - IndividualUser/OrganizationUser discriminators → User model with type field
17
+ */
18
+ class UserRepositoryMongo extends UserRepositoryInterface {
19
+ constructor() {
20
+ super();
21
+ this.prisma = prisma;
22
+ this.tokenRepository = createTokenRepository();
23
+ }
24
+
25
+ /**
26
+ * Get session token from base64 buffer token
27
+ * Delegates to TokenRepository
28
+ *
29
+ * @param {string} token - Base64 buffer token
30
+ * @returns {Promise<Object>} Session token object with string IDs
31
+ */
32
+ async getSessionToken(token) {
33
+ const jsonToken =
34
+ this.tokenRepository.getJSONTokenFromBase64BufferToken(token);
35
+ const sessionToken = await this.tokenRepository.validateAndGetToken(
36
+ jsonToken
37
+ );
38
+ return sessionToken;
39
+ }
40
+
41
+ /**
42
+ * Find organization user by ID
43
+ * Replaces: OrganizationUser.findById(userId)
44
+ *
45
+ * @param {string} userId - User ID
46
+ * @returns {Promise<Object|null>} User object with string IDs or null
47
+ */
48
+ async findOrganizationUserById(userId) {
49
+ return await this.prisma.user.findFirst({
50
+ where: {
51
+ id: userId,
52
+ type: 'ORGANIZATION',
53
+ },
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Find individual user by ID
59
+ * Replaces: IndividualUser.findById(userId)
60
+ *
61
+ * @param {string} userId - User ID
62
+ * @returns {Promise<Object|null>} User object with string IDs or null
63
+ */
64
+ async findIndividualUserById(userId) {
65
+ return await this.prisma.user.findFirst({
66
+ where: {
67
+ id: userId,
68
+ type: 'INDIVIDUAL',
69
+ },
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Find all individual users linked to an organization user
75
+ *
76
+ * @param {string} organizationUserId - Organization user ID
77
+ * @returns {Promise<Object[]>} Array of individual user records (empty if none)
78
+ */
79
+ async findIndividualUsersByOrganizationId(organizationUserId) {
80
+ return await this.prisma.user.findMany({
81
+ where: {
82
+ organizationId: organizationUserId,
83
+ type: 'INDIVIDUAL',
84
+ },
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Create token with expiration
90
+ * Delegates to TokenRepository
91
+ *
92
+ * @param {string} userId - User ID
93
+ * @param {string} rawToken - Raw unhashed token
94
+ * @param {number} minutes - Minutes until expiration (default 120)
95
+ * @returns {Promise<string>} Base64 buffer token
96
+ */
97
+ async createToken(userId, rawToken, minutes = 120) {
98
+ const createdToken = await this.tokenRepository.createTokenWithExpire(
99
+ userId,
100
+ rawToken,
101
+ minutes
102
+ );
103
+ return this.tokenRepository.createBase64BufferToken(
104
+ createdToken,
105
+ rawToken
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Create individual user
111
+ * Replaces: IndividualUser.create(params)
112
+ *
113
+ * @param {Object} params - User creation parameters
114
+ * @param {string} [params.hashword] - Plain text password (will be bcrypt hashed automatically)
115
+ * @returns {Promise<Object>} Created user object with string IDs
116
+ */
117
+ async createIndividualUser(params) {
118
+ const data = {
119
+ type: 'INDIVIDUAL',
120
+ email: params.email,
121
+ username: params.username,
122
+ appUserId: params.appUserId,
123
+ organizationId: params.organization || params.organizationId,
124
+ };
125
+
126
+ if (
127
+ params.hashword !== undefined &&
128
+ params.hashword !== null &&
129
+ params.hashword !== ''
130
+ ) {
131
+ if (typeof params.hashword !== 'string') {
132
+ throw new ClientSafeError('Password must be a string', 400);
133
+ }
134
+
135
+ // Prevent double-hashing: bcrypt hashes start with $2a$ or $2b$
136
+ if (params.hashword.startsWith('$2')) {
137
+ throw new Error(
138
+ 'Password appears to be already hashed. Pass plain text password only.'
139
+ );
140
+ }
141
+
142
+ data.hashword = await bcrypt.hash(params.hashword, 10);
143
+ }
144
+
145
+ return await this.prisma.user.create({ data });
146
+ }
147
+
148
+ /**
149
+ * Create organization user
150
+ * Replaces: OrganizationUser.create(params)
151
+ *
152
+ * @param {Object} params - Organization creation parameters
153
+ * @returns {Promise<Object>} Created organization object with string IDs
154
+ */
155
+ async createOrganizationUser(params) {
156
+ return await this.prisma.user.create({
157
+ data: {
158
+ type: 'ORGANIZATION',
159
+ appOrgId: params.appOrgId,
160
+ name: params.name,
161
+ },
162
+ });
163
+ }
164
+
165
+ /**
166
+ * Find individual user by username
167
+ * Replaces: IndividualUser.findOne({ username })
168
+ *
169
+ * @param {string} username - Username to search for
170
+ * @returns {Promise<Object|null>} User object with string IDs or null
171
+ */
172
+ async findIndividualUserByUsername(username) {
173
+ return await this.prisma.user.findFirst({
174
+ where: {
175
+ type: 'INDIVIDUAL',
176
+ username,
177
+ },
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Find individual user by app user ID
183
+ * Replaces: IndividualUser.getUserByAppUserId(appUserId)
184
+ *
185
+ * @param {string} appUserId - App user ID to search for
186
+ * @returns {Promise<Object|null>} User object with string IDs or null
187
+ */
188
+ async findIndividualUserByAppUserId(appUserId) {
189
+ return await this.prisma.user.findFirst({
190
+ where: {
191
+ type: 'INDIVIDUAL',
192
+ appUserId,
193
+ },
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Find organization user by app org ID
199
+ * Replaces: OrganizationUser.getUserByAppOrgId(appOrgId)
200
+ *
201
+ * @param {string} appOrgId - App organization ID to search for
202
+ * @returns {Promise<Object|null>} User object with string IDs or null
203
+ */
204
+ async findOrganizationUserByAppOrgId(appOrgId) {
205
+ return await this.prisma.user.findFirst({
206
+ where: {
207
+ type: 'ORGANIZATION',
208
+ appOrgId,
209
+ },
210
+ });
211
+ }
212
+
213
+ /**
214
+ * Find individual user by email
215
+ * @param {string} email - Email to search for
216
+ * @returns {Promise<Object|null>} User object with string IDs or null
217
+ */
218
+ async findIndividualUserByEmail(email) {
219
+ return await this.prisma.user.findFirst({
220
+ where: {
221
+ type: 'INDIVIDUAL',
222
+ email,
223
+ },
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Update individual user
229
+ * @param {string} userId - User ID
230
+ * @param {Object} updates - Fields to update
231
+ * @param {string} [updates.hashword] - Plain text password (will be bcrypt hashed automatically)
232
+ * @returns {Promise<Object>} Updated user object with string IDs
233
+ */
234
+ async updateIndividualUser(userId, updates) {
235
+ const data = { ...updates };
236
+
237
+ if (
238
+ data.hashword !== undefined &&
239
+ data.hashword !== null &&
240
+ data.hashword !== ''
241
+ ) {
242
+ if (typeof data.hashword !== 'string') {
243
+ throw new ClientSafeError('Password must be a string', 400);
244
+ }
245
+
246
+ // Prevent double-hashing: bcrypt hashes start with $2a$ or $2b$
247
+ if (data.hashword.startsWith('$2')) {
248
+ throw new Error(
249
+ 'Password appears to be already hashed. Pass plain text password only.'
250
+ );
251
+ }
252
+
253
+ data.hashword = await bcrypt.hash(data.hashword, 10);
254
+ }
255
+
256
+ return await this.prisma.user.update({
257
+ where: { id: userId },
258
+ data,
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Update organization user
264
+ * @param {string} userId - User ID
265
+ * @param {Object} updates - Fields to update
266
+ * @returns {Promise<Object>} Updated user object with string IDs
267
+ */
268
+ async updateOrganizationUser(userId, updates) {
269
+ return await this.prisma.user.update({
270
+ where: { id: userId },
271
+ data: updates,
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Delete user by ID
277
+ *
278
+ * NOTE: This only deletes the user record itself.
279
+ * Prisma's onDelete: Cascade does NOT work reliably with MongoDB (no database-level referential integrity).
280
+ * Integration developers MUST manually cascade delete related records before calling this method:
281
+ * 1. Delete integrations (via deleteIntegrationById)
282
+ * 2. Delete entities (via deleteEntityById)
283
+ * 3. Delete credentials (via deleteCredentialById)
284
+ * 4. Finally delete user (via deleteUserById)
285
+ *
286
+ * @param {string} userId - User ID to delete
287
+ * @returns {Promise<boolean>} True if deleted successfully
288
+ */
289
+ async deleteUser(userId) {
290
+ try {
291
+ await this.prisma.user.delete({
292
+ where: { id: userId },
293
+ });
294
+ return true;
295
+ } catch (error) {
296
+ if (error.code === 'P2025') {
297
+ // Record not found
298
+ return false;
299
+ }
300
+ throw error;
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Link an individual user to an organization user
306
+ * @param {string} individualUserId - Individual user ID (MongoDB ObjectId string)
307
+ * @param {string} organizationUserId - Organization user ID (MongoDB ObjectId string)
308
+ * @returns {Promise<Object>} Updated individual user object
309
+ */
310
+ async linkIndividualToOrganization(individualUserId, organizationUserId) {
311
+ return await this.prisma.user.update({
312
+ where: {
313
+ id: individualUserId,
314
+ type: 'INDIVIDUAL',
315
+ },
316
+ data: {
317
+ organizationId: organizationUserId,
318
+ },
319
+ });
320
+ }
321
+ }
322
+
323
+ module.exports = { UserRepositoryMongo };