@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.
Files changed (303) hide show
  1. package/CLAUDE.md +694 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +210 -0
  7. package/application/commands/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/assertions/index.js +0 -3
  11. package/core/CLAUDE.md +690 -0
  12. package/core/Worker.js +60 -24
  13. package/core/create-handler.js +79 -8
  14. package/credential/repositories/credential-repository-documentdb.js +304 -0
  15. package/credential/repositories/credential-repository-factory.js +54 -0
  16. package/credential/repositories/credential-repository-interface.js +98 -0
  17. package/credential/repositories/credential-repository-mongo.js +269 -0
  18. package/credential/repositories/credential-repository-postgres.js +287 -0
  19. package/credential/repositories/credential-repository.js +300 -0
  20. package/credential/use-cases/get-credential-for-user.js +25 -0
  21. package/credential/use-cases/update-authentication-status.js +15 -0
  22. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  23. package/database/adapters/lambda-invoker.js +97 -0
  24. package/database/config.js +154 -0
  25. package/database/documentdb-encryption-service.js +330 -0
  26. package/database/documentdb-utils.js +136 -0
  27. package/database/encryption/README.md +839 -0
  28. package/database/encryption/documentdb-encryption-service.md +3575 -0
  29. package/database/encryption/encryption-schema-registry.js +268 -0
  30. package/database/encryption/field-encryption-service.js +226 -0
  31. package/database/encryption/logger.js +79 -0
  32. package/database/encryption/prisma-encryption-extension.js +222 -0
  33. package/database/index.js +21 -21
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +138 -0
  36. package/database/repositories/health-check-repository-factory.js +48 -0
  37. package/database/repositories/health-check-repository-interface.js +82 -0
  38. package/database/repositories/health-check-repository-mongodb.js +89 -0
  39. package/database/repositories/health-check-repository-postgres.js +82 -0
  40. package/database/repositories/migration-status-repository-s3.js +137 -0
  41. package/database/use-cases/check-database-health-use-case.js +29 -0
  42. package/database/use-cases/check-database-state-use-case.js +81 -0
  43. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  44. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  45. package/database/use-cases/get-migration-status-use-case.js +93 -0
  46. package/database/use-cases/run-database-migration-use-case.js +139 -0
  47. package/database/use-cases/test-encryption-use-case.js +253 -0
  48. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  49. package/database/utils/mongodb-collection-utils.js +94 -0
  50. package/database/utils/mongodb-schema-init.js +108 -0
  51. package/database/utils/prisma-runner.js +477 -0
  52. package/database/utils/prisma-schema-parser.js +182 -0
  53. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  54. package/encrypt/Cryptor.js +34 -168
  55. package/encrypt/index.js +1 -2
  56. package/errors/client-safe-error.js +26 -0
  57. package/errors/fetch-error.js +15 -7
  58. package/errors/index.js +2 -0
  59. package/generated/prisma-mongodb/client.d.ts +1 -0
  60. package/generated/prisma-mongodb/client.js +4 -0
  61. package/generated/prisma-mongodb/default.d.ts +1 -0
  62. package/generated/prisma-mongodb/default.js +4 -0
  63. package/generated/prisma-mongodb/edge.d.ts +1 -0
  64. package/generated/prisma-mongodb/edge.js +335 -0
  65. package/generated/prisma-mongodb/index-browser.js +317 -0
  66. package/generated/prisma-mongodb/index.d.ts +22955 -0
  67. package/generated/prisma-mongodb/index.js +360 -0
  68. package/generated/prisma-mongodb/package.json +183 -0
  69. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  70. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  71. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  72. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  73. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  74. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  75. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  76. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  77. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  78. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  79. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  80. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  81. package/generated/prisma-mongodb/schema.prisma +362 -0
  82. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  83. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  84. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  85. package/generated/prisma-mongodb/wasm.js +342 -0
  86. package/generated/prisma-postgresql/client.d.ts +1 -0
  87. package/generated/prisma-postgresql/client.js +4 -0
  88. package/generated/prisma-postgresql/default.d.ts +1 -0
  89. package/generated/prisma-postgresql/default.js +4 -0
  90. package/generated/prisma-postgresql/edge.d.ts +1 -0
  91. package/generated/prisma-postgresql/edge.js +357 -0
  92. package/generated/prisma-postgresql/index-browser.js +339 -0
  93. package/generated/prisma-postgresql/index.d.ts +25131 -0
  94. package/generated/prisma-postgresql/index.js +382 -0
  95. package/generated/prisma-postgresql/package.json +183 -0
  96. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  97. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  98. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  99. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  100. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  101. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  102. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  103. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  104. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  105. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  106. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  107. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  108. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  109. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  110. package/generated/prisma-postgresql/schema.prisma +345 -0
  111. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  112. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  113. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  114. package/generated/prisma-postgresql/wasm.js +364 -0
  115. package/handlers/WEBHOOKS.md +653 -0
  116. package/handlers/app-definition-loader.js +38 -0
  117. package/handlers/app-handler-helpers.js +57 -0
  118. package/handlers/backend-utils.js +262 -0
  119. package/handlers/database-migration-handler.js +227 -0
  120. package/handlers/integration-event-dispatcher.js +54 -0
  121. package/handlers/routers/HEALTHCHECK.md +342 -0
  122. package/handlers/routers/auth.js +15 -0
  123. package/handlers/routers/db-migration.handler.js +29 -0
  124. package/handlers/routers/db-migration.js +326 -0
  125. package/handlers/routers/health.js +516 -0
  126. package/handlers/routers/integration-defined-routers.js +45 -0
  127. package/handlers/routers/integration-webhook-routers.js +67 -0
  128. package/handlers/routers/user.js +63 -0
  129. package/handlers/routers/websocket.js +57 -0
  130. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  131. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  132. package/handlers/workers/db-migration.js +352 -0
  133. package/handlers/workers/dlq-processor.js +63 -0
  134. package/handlers/workers/integration-defined-workers.js +23 -0
  135. package/index.js +82 -46
  136. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  137. package/infrastructure/scheduler/index.js +33 -0
  138. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  139. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  140. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  141. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  142. package/integrations/index.js +12 -10
  143. package/integrations/integration-base.js +364 -55
  144. package/integrations/integration-router.js +375 -179
  145. package/integrations/options.js +1 -1
  146. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  147. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  148. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  149. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  150. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  151. package/integrations/repositories/integration-mapping-repository.js +156 -0
  152. package/integrations/repositories/integration-repository-documentdb.js +219 -0
  153. package/integrations/repositories/integration-repository-factory.js +51 -0
  154. package/integrations/repositories/integration-repository-interface.js +144 -0
  155. package/integrations/repositories/integration-repository-mongo.js +330 -0
  156. package/integrations/repositories/integration-repository-postgres.js +385 -0
  157. package/integrations/repositories/process-repository-documentdb.js +243 -0
  158. package/integrations/repositories/process-repository-factory.js +53 -0
  159. package/integrations/repositories/process-repository-interface.js +90 -0
  160. package/integrations/repositories/process-repository-mongo.js +190 -0
  161. package/integrations/repositories/process-repository-postgres.js +217 -0
  162. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  163. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  164. package/integrations/tests/doubles/test-integration-repository.js +112 -0
  165. package/integrations/use-cases/create-integration.js +83 -0
  166. package/integrations/use-cases/create-process.js +128 -0
  167. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  168. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  169. package/integrations/use-cases/get-integration-for-user.js +78 -0
  170. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  171. package/integrations/use-cases/get-integration-instance.js +83 -0
  172. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  173. package/integrations/use-cases/get-possible-integrations.js +27 -0
  174. package/integrations/use-cases/get-process.js +87 -0
  175. package/integrations/use-cases/index.js +19 -0
  176. package/integrations/use-cases/load-integration-context.js +71 -0
  177. package/integrations/use-cases/update-integration-messages.js +44 -0
  178. package/integrations/use-cases/update-integration-status.js +32 -0
  179. package/integrations/use-cases/update-integration.js +92 -0
  180. package/integrations/use-cases/update-process-metrics.js +201 -0
  181. package/integrations/use-cases/update-process-state.js +119 -0
  182. package/integrations/utils/map-integration-dto.js +37 -0
  183. package/jest-global-setup-noop.js +3 -0
  184. package/jest-global-teardown-noop.js +3 -0
  185. package/logs/logger.js +0 -4
  186. package/{module-plugin → modules}/index.js +0 -10
  187. package/modules/module-factory.js +56 -0
  188. package/modules/module.js +256 -0
  189. package/modules/repositories/module-repository-documentdb.js +335 -0
  190. package/modules/repositories/module-repository-factory.js +40 -0
  191. package/modules/repositories/module-repository-interface.js +129 -0
  192. package/modules/repositories/module-repository-mongo.js +408 -0
  193. package/modules/repositories/module-repository-postgres.js +453 -0
  194. package/modules/repositories/module-repository.js +345 -0
  195. package/modules/requester/api-key.js +52 -0
  196. package/modules/requester/oauth-2.js +396 -0
  197. package/{module-plugin → modules}/requester/requester.js +4 -2
  198. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  199. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  200. package/modules/tests/doubles/test-module-factory.js +16 -0
  201. package/modules/tests/doubles/test-module-repository.js +39 -0
  202. package/modules/use-cases/get-entities-for-user.js +32 -0
  203. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  204. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  205. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  206. package/modules/use-cases/get-module.js +74 -0
  207. package/modules/use-cases/process-authorization-callback.js +177 -0
  208. package/modules/use-cases/refresh-entity-options.js +72 -0
  209. package/modules/use-cases/test-module-auth.js +72 -0
  210. package/modules/utils/map-module-dto.js +18 -0
  211. package/package.json +82 -50
  212. package/prisma-mongodb/schema.prisma +362 -0
  213. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  214. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  215. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  216. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  217. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  218. package/prisma-postgresql/schema.prisma +345 -0
  219. package/queues/queuer-util.js +103 -21
  220. package/syncs/manager.js +468 -443
  221. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  222. package/syncs/repositories/sync-repository-factory.js +43 -0
  223. package/syncs/repositories/sync-repository-interface.js +109 -0
  224. package/syncs/repositories/sync-repository-mongo.js +239 -0
  225. package/syncs/repositories/sync-repository-postgres.js +319 -0
  226. package/syncs/sync.js +0 -1
  227. package/token/repositories/token-repository-documentdb.js +137 -0
  228. package/token/repositories/token-repository-factory.js +40 -0
  229. package/token/repositories/token-repository-interface.js +131 -0
  230. package/token/repositories/token-repository-mongo.js +219 -0
  231. package/token/repositories/token-repository-postgres.js +264 -0
  232. package/token/repositories/token-repository.js +219 -0
  233. package/types/associations/index.d.ts +0 -17
  234. package/types/core/index.d.ts +12 -4
  235. package/types/database/index.d.ts +10 -2
  236. package/types/encrypt/index.d.ts +5 -3
  237. package/types/integrations/index.d.ts +3 -8
  238. package/types/module-plugin/index.d.ts +17 -69
  239. package/types/syncs/index.d.ts +0 -17
  240. package/user/repositories/user-repository-documentdb.js +441 -0
  241. package/user/repositories/user-repository-factory.js +52 -0
  242. package/user/repositories/user-repository-interface.js +201 -0
  243. package/user/repositories/user-repository-mongo.js +308 -0
  244. package/user/repositories/user-repository-postgres.js +360 -0
  245. package/user/tests/doubles/test-user-repository.js +72 -0
  246. package/user/use-cases/authenticate-user.js +127 -0
  247. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  248. package/user/use-cases/create-individual-user.js +61 -0
  249. package/user/use-cases/create-organization-user.js +47 -0
  250. package/user/use-cases/create-token-for-user-id.js +30 -0
  251. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  252. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  253. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  254. package/user/use-cases/login-user.js +122 -0
  255. package/user/user.js +125 -0
  256. package/utils/backend-path.js +38 -0
  257. package/utils/index.js +6 -0
  258. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  259. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  260. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  261. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  262. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  263. package/websocket/repositories/websocket-connection-repository.js +161 -0
  264. package/assertions/is-equal.js +0 -17
  265. package/associations/model.js +0 -54
  266. package/database/models/IndividualUser.js +0 -76
  267. package/database/models/OrganizationUser.js +0 -29
  268. package/database/models/State.js +0 -9
  269. package/database/models/Token.js +0 -70
  270. package/database/models/UserModel.js +0 -7
  271. package/database/models/WebsocketConnection.js +0 -49
  272. package/database/mongo.js +0 -45
  273. package/database/mongoose.js +0 -5
  274. package/encrypt/Cryptor.test.js +0 -32
  275. package/encrypt/encrypt.js +0 -132
  276. package/encrypt/encrypt.test.js +0 -1069
  277. package/encrypt/test-encrypt.js +0 -107
  278. package/errors/base-error.test.js +0 -32
  279. package/errors/fetch-error.test.js +0 -79
  280. package/errors/halt-error.test.js +0 -11
  281. package/errors/validation-errors.test.js +0 -120
  282. package/integrations/create-frigg-backend.js +0 -31
  283. package/integrations/integration-factory.js +0 -251
  284. package/integrations/integration-mapping.js +0 -43
  285. package/integrations/integration-model.js +0 -46
  286. package/integrations/integration-user.js +0 -144
  287. package/integrations/test/integration-base.test.js +0 -144
  288. package/lambda/TimeoutCatcher.test.js +0 -68
  289. package/logs/logger.test.js +0 -76
  290. package/module-plugin/auther.js +0 -393
  291. package/module-plugin/credential.js +0 -22
  292. package/module-plugin/entity-manager.js +0 -70
  293. package/module-plugin/entity.js +0 -46
  294. package/module-plugin/manager.js +0 -169
  295. package/module-plugin/module-factory.js +0 -61
  296. package/module-plugin/requester/api-key.js +0 -36
  297. package/module-plugin/requester/oauth-2.js +0 -219
  298. package/module-plugin/requester/requester.test.js +0 -28
  299. package/module-plugin/test/auther.test.js +0 -97
  300. package/syncs/model.js +0 -62
  301. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  302. /package/{module-plugin → modules}/requester/basic.js +0 -0
  303. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Get Migration Status Use Case
3
+ *
4
+ * Retrieves the status of a database migration by process ID.
5
+ * Formats the Process record for migration-specific response.
6
+ *
7
+ * This use case follows the Frigg hexagonal architecture pattern where:
8
+ * - Routers (adapters) call use cases
9
+ * - Use cases contain business logic and formatting
10
+ * - Use cases call repositories for data access
11
+ */
12
+
13
+ class GetMigrationStatusUseCase {
14
+ /**
15
+ * @param {Object} dependencies
16
+ * @param {Object} dependencies.migrationStatusRepository - Repository for migration status (S3)
17
+ */
18
+ constructor({ migrationStatusRepository }) {
19
+ if (!migrationStatusRepository) {
20
+ throw new Error('migrationStatusRepository dependency is required');
21
+ }
22
+ this.migrationStatusRepository = migrationStatusRepository;
23
+ }
24
+
25
+ /**
26
+ * Execute get migration status
27
+ *
28
+ * @param {string} migrationId - Migration ID to retrieve
29
+ * @param {string} [stage] - Deployment stage (defaults to env.STAGE)
30
+ * @returns {Promise<Object>} Migration status from S3
31
+ * @throws {NotFoundError} If migration not found
32
+ * @throws {ValidationError} If migrationId is invalid
33
+ */
34
+ async execute(migrationId, stage = null) {
35
+ // Validation
36
+ this._validateParams(migrationId);
37
+
38
+ const effectiveStage = stage || process.env.STAGE || 'production';
39
+
40
+ // Get migration status from S3
41
+ try {
42
+ const migrationStatus = await this.migrationStatusRepository.get(migrationId, effectiveStage);
43
+ return migrationStatus;
44
+ } catch (error) {
45
+ if (error.message.includes('not found')) {
46
+ throw new NotFoundError(`Migration not found: ${migrationId}`);
47
+ }
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Validate parameters
54
+ * @private
55
+ */
56
+ _validateParams(migrationId) {
57
+ if (!migrationId) {
58
+ throw new ValidationError('migrationId is required');
59
+ }
60
+
61
+ if (typeof migrationId !== 'string') {
62
+ throw new ValidationError('migrationId must be a string');
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Custom error for validation failures
69
+ */
70
+ class ValidationError extends Error {
71
+ constructor(message) {
72
+ super(message);
73
+ this.name = 'ValidationError';
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Custom error for not found resources
79
+ */
80
+ class NotFoundError extends Error {
81
+ constructor(message) {
82
+ super(message);
83
+ this.name = 'NotFoundError';
84
+ this.statusCode = 404;
85
+ }
86
+ }
87
+
88
+ module.exports = {
89
+ GetMigrationStatusUseCase,
90
+ ValidationError,
91
+ NotFoundError,
92
+ };
93
+
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Run Database Migration Use Case
3
+ *
4
+ * Business logic for running Prisma database migrations.
5
+ * Orchestrates Prisma client generation and migration execution.
6
+ *
7
+ * This use case follows the Frigg hexagonal architecture pattern where:
8
+ * - Handlers (adapters) call use cases
9
+ * - Use cases contain business logic and orchestration
10
+ * - Use cases call repositories/utilities for data access
11
+ */
12
+
13
+ class RunDatabaseMigrationUseCase {
14
+ /**
15
+ * @param {Object} dependencies
16
+ * @param {Object} dependencies.prismaRunner - Prisma runner utilities
17
+ */
18
+ constructor({ prismaRunner }) {
19
+ if (!prismaRunner) {
20
+ throw new Error('prismaRunner dependency is required');
21
+ }
22
+ this.prismaRunner = prismaRunner;
23
+ }
24
+
25
+ /**
26
+ * Execute database migration
27
+ *
28
+ * @param {Object} params
29
+ * @param {string} params.dbType - Database type ('postgresql', 'mongodb', or 'documentdb')
30
+ * @param {string} params.stage - Deployment stage (determines migration command)
31
+ * @param {boolean} [params.verbose=false] - Enable verbose output
32
+ * @returns {Promise<Object>} Migration result { success, dbType, stage, command, message }
33
+ * @throws {MigrationError} If migration fails
34
+ * @throws {ValidationError} If parameters are invalid
35
+ */
36
+ async execute({ dbType, stage, verbose = false }) {
37
+ // Validation
38
+ this._validateParams({ dbType, stage });
39
+
40
+ // Step 1: Generate Prisma client
41
+ const generateResult = await this.prismaRunner.runPrismaGenerate(dbType, verbose);
42
+
43
+ if (!generateResult.success) {
44
+ throw new MigrationError(
45
+ `Failed to generate Prisma client: ${generateResult.error || 'Unknown error'}`,
46
+ { dbType, stage, step: 'generate', output: generateResult.output }
47
+ );
48
+ }
49
+
50
+ // Step 2: Run migrations based on database type
51
+ let migrationResult;
52
+ let migrationCommand;
53
+
54
+ if (dbType === 'postgresql') {
55
+ migrationCommand = this.prismaRunner.getMigrationCommand(stage);
56
+ migrationResult = await this.prismaRunner.runPrismaMigrate(migrationCommand, verbose);
57
+
58
+ if (!migrationResult.success) {
59
+ throw new MigrationError(
60
+ `PostgreSQL migration failed: ${migrationResult.error || 'Unknown error'}`,
61
+ { dbType, stage, command: migrationCommand, step: 'migrate', output: migrationResult.output }
62
+ );
63
+ }
64
+ } else if (dbType === 'mongodb' || dbType === 'documentdb') {
65
+ migrationCommand = 'db push';
66
+ // Use non-interactive mode for automated/Lambda environments
67
+ migrationResult = await this.prismaRunner.runPrismaDbPush(verbose, true);
68
+
69
+ if (!migrationResult.success) {
70
+ throw new MigrationError(
71
+ `Mongo-compatible push failed: ${migrationResult.error || 'Unknown error'}`,
72
+ { dbType, stage, command: migrationCommand, step: 'push', output: migrationResult.output }
73
+ );
74
+ }
75
+ } else {
76
+ throw new ValidationError(
77
+ `Unsupported database type: ${dbType}. Must be 'postgresql', 'mongodb', or 'documentdb'.`
78
+ );
79
+ }
80
+
81
+ // Return success result
82
+ return {
83
+ success: true,
84
+ dbType,
85
+ stage,
86
+ command: migrationCommand,
87
+ message: 'Database migration completed successfully',
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Validate execution parameters
93
+ * @private
94
+ */
95
+ _validateParams({ dbType, stage }) {
96
+ if (!dbType) {
97
+ throw new ValidationError('dbType is required');
98
+ }
99
+
100
+ if (typeof dbType !== 'string') {
101
+ throw new ValidationError('dbType must be a string');
102
+ }
103
+
104
+ if (!stage) {
105
+ throw new ValidationError('stage is required');
106
+ }
107
+
108
+ if (typeof stage !== 'string') {
109
+ throw new ValidationError('stage must be a string');
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Custom error for migration failures
116
+ */
117
+ class MigrationError extends Error {
118
+ constructor(message, context = {}) {
119
+ super(message);
120
+ this.name = 'MigrationError';
121
+ this.context = context;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Custom error for validation failures
127
+ */
128
+ class ValidationError extends Error {
129
+ constructor(message) {
130
+ super(message);
131
+ this.name = 'ValidationError';
132
+ }
133
+ }
134
+
135
+ module.exports = {
136
+ RunDatabaseMigrationUseCase,
137
+ MigrationError,
138
+ ValidationError,
139
+ };
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Use Case for testing encryption functionality.
3
+ * Contains business logic for verifying that encryption and decryption work correctly.
4
+ *
5
+ * Follows DDD/Hexagonal Architecture:
6
+ * - Application Layer (this use case)
7
+ * - Depends on Infrastructure Layer (HealthCheckRepository)
8
+ */
9
+ class TestEncryptionUseCase {
10
+ /**
11
+ * @param {Object} params
12
+ * @param {import('../health-check-repository-interface').HealthCheckRepositoryInterface} params.healthCheckRepository
13
+ */
14
+ constructor({ healthCheckRepository }) {
15
+ this.repository = healthCheckRepository;
16
+ }
17
+
18
+ /**
19
+ * Execute encryption test
20
+ * Orchestrates the full encryption test workflow using Prisma
21
+ * @returns {Promise<Object>} Test results with status and details
22
+ */
23
+ async execute() {
24
+ const testData = {
25
+ testSecret: 'This is a secret value that should be encrypted',
26
+ normalField: 'This is a normal field that should not be encrypted',
27
+ nestedSecret: {
28
+ value: 'This is a nested secret that should be encrypted',
29
+ },
30
+ };
31
+
32
+ const credentialData = this._mapTestDataToCredential(testData);
33
+
34
+ const credential = await this._withTimeout(
35
+ this.repository.createCredential(credentialData),
36
+ 5000,
37
+ 'Save operation timed out'
38
+ );
39
+
40
+ try {
41
+ const retrievedCredential = await this._withTimeout(
42
+ this.repository.findCredentialById(credential.id),
43
+ 5000,
44
+ 'Find operation timed out'
45
+ );
46
+
47
+ const retrievedTestData =
48
+ this._mapCredentialToTestData(retrievedCredential);
49
+ const decryptionWorks = this._verifyDecryption(
50
+ retrievedTestData,
51
+ testData
52
+ );
53
+
54
+ const rawCredential = await this._withTimeout(
55
+ this.repository.getRawCredentialById(credential.id),
56
+ 5000,
57
+ 'Database verification timed out'
58
+ );
59
+
60
+ const rawTestData = this._mapRawCredentialToTestData(rawCredential);
61
+ const encryptionResults = this._verifyEncryptionInDatabase(
62
+ rawTestData,
63
+ testData
64
+ );
65
+
66
+ return this._evaluateEncryptionResults(
67
+ decryptionWorks,
68
+ encryptionResults
69
+ );
70
+ } finally {
71
+ await this._withTimeout(
72
+ this.repository.deleteCredential(credential.id),
73
+ 5000,
74
+ 'Delete operation timed out'
75
+ );
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Map test data format to Credential model format
81
+ * @param {Object} testData - Test data with testSecret, normalField, nestedSecret
82
+ * @returns {Object} Credential data structure
83
+ * @private
84
+ */
85
+ _mapTestDataToCredential(testData) {
86
+ // Note: Using camelCase for Prisma compatibility (both MongoDB and PostgreSQL)
87
+ // Changed from snake_case (user_id, entity_id) to camelCase (userId, externalId)
88
+ return {
89
+ externalId: 'test-encryption-entity',
90
+ data: {
91
+ access_token: testData.testSecret, // Encrypted field
92
+ refresh_token: testData.nestedSecret?.value, // Encrypted field
93
+ domain: testData.normalField, // Not encrypted
94
+ },
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Map Credential model format to test data format
100
+ * @param {Object} credential - Credential from database
101
+ * @returns {Object} Test data format
102
+ * @private
103
+ */
104
+ _mapCredentialToTestData(credential) {
105
+ if (!credential) {
106
+ return null;
107
+ }
108
+
109
+ return {
110
+ id: credential.id,
111
+ testSecret: credential.data.access_token,
112
+ normalField: credential.data.domain,
113
+ nestedSecret: {
114
+ value: credential.data.refresh_token,
115
+ },
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Map raw Credential data to test data format
121
+ * @param {Object} rawCredential - Raw credential from database
122
+ * @returns {Object} Test data format with raw encrypted values
123
+ * @private
124
+ */
125
+ _mapRawCredentialToTestData(rawCredential) {
126
+ if (!rawCredential) {
127
+ return null;
128
+ }
129
+
130
+ return {
131
+ testSecret: rawCredential.data?.access_token,
132
+ normalField: rawCredential.data?.domain,
133
+ nestedSecret: {
134
+ value: rawCredential.data?.refresh_token,
135
+ },
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Verify that a document was decrypted correctly
141
+ * @param {Object} retrievedDoc - Document retrieved from database
142
+ * @param {Object} originalData - Original unencrypted data
143
+ * @returns {boolean} True if decryption worked correctly
144
+ * @private
145
+ */
146
+ _verifyDecryption(retrievedDoc, originalData) {
147
+ return (
148
+ retrievedDoc &&
149
+ retrievedDoc.testSecret === originalData.testSecret &&
150
+ retrievedDoc.normalField === originalData.normalField &&
151
+ retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Verify that data was encrypted in the database
157
+ * Business rule: Encrypted fields should contain ':' and differ from original
158
+ * @param {Object} rawDoc - Raw document from database
159
+ * @param {Object} originalData - Original unencrypted data
160
+ * @returns {Object} Encryption verification results
161
+ * @private
162
+ */
163
+ _verifyEncryptionInDatabase(rawDoc, originalData) {
164
+ const secretIsEncrypted =
165
+ rawDoc &&
166
+ typeof rawDoc.testSecret === 'string' &&
167
+ rawDoc.testSecret.includes(':') &&
168
+ rawDoc.testSecret !== originalData.testSecret;
169
+
170
+ const nestedIsEncrypted =
171
+ rawDoc?.nestedSecret?.value &&
172
+ typeof rawDoc.nestedSecret.value === 'string' &&
173
+ rawDoc.nestedSecret.value.includes(':') &&
174
+ rawDoc.nestedSecret.value !== originalData.nestedSecret.value;
175
+
176
+ const normalNotEncrypted =
177
+ rawDoc && rawDoc.normalField === originalData.normalField;
178
+
179
+ return {
180
+ secretIsEncrypted,
181
+ nestedIsEncrypted,
182
+ normalNotEncrypted,
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Evaluate encryption test results
188
+ * Business logic for determining if encryption is healthy
189
+ * @param {boolean} decryptionWorks - Whether decryption succeeded
190
+ * @param {Object} encryptionResults - Encryption verification results
191
+ * @returns {Object} Test status and result message
192
+ * @private
193
+ */
194
+ _evaluateEncryptionResults(decryptionWorks, encryptionResults) {
195
+ const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } =
196
+ encryptionResults;
197
+
198
+ if (
199
+ decryptionWorks &&
200
+ secretIsEncrypted &&
201
+ nestedIsEncrypted &&
202
+ normalNotEncrypted
203
+ ) {
204
+ return {
205
+ status: 'enabled',
206
+ testResult:
207
+ 'Encryption and decryption verified successfully',
208
+ encryptionWorks: true,
209
+ };
210
+ }
211
+
212
+ if (decryptionWorks && (!secretIsEncrypted || !nestedIsEncrypted)) {
213
+ return {
214
+ status: 'unhealthy',
215
+ testResult: 'Fields are not being encrypted in database',
216
+ encryptionWorks: false,
217
+ };
218
+ }
219
+
220
+ if (decryptionWorks && !normalNotEncrypted) {
221
+ return {
222
+ status: 'unhealthy',
223
+ testResult: 'Normal fields are being incorrectly encrypted',
224
+ encryptionWorks: false,
225
+ };
226
+ }
227
+
228
+ return {
229
+ status: 'unhealthy',
230
+ testResult: 'Decryption failed or data mismatch',
231
+ encryptionWorks: false,
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Execute promise with timeout
237
+ * @param {Promise} promise - Promise to execute
238
+ * @param {number} ms - Timeout in milliseconds
239
+ * @param {string} errorMessage - Error message for timeout
240
+ * @returns {Promise} Promise that rejects on timeout
241
+ * @private
242
+ */
243
+ _withTimeout(promise, ms, errorMessage) {
244
+ return Promise.race([
245
+ promise,
246
+ new Promise((_, reject) =>
247
+ setTimeout(() => reject(new Error(errorMessage)), ms)
248
+ ),
249
+ ]);
250
+ }
251
+ }
252
+
253
+ module.exports = { TestEncryptionUseCase };
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Trigger Database Migration Use Case
3
+ *
4
+ * Business logic for triggering async database migrations via SQS queue.
5
+ * Creates a Process record for tracking and sends migration job to queue.
6
+ *
7
+ * This use case follows the Frigg hexagonal architecture pattern where:
8
+ * - Routers (adapters) call use cases
9
+ * - Use cases contain business logic and orchestration
10
+ * - Use cases call repositories for data access
11
+ * - Use cases delegate infrastructure concerns (SQS) to utilities
12
+ *
13
+ * Flow:
14
+ * 1. Validate migration parameters
15
+ * 2. Create Process record (state: INITIALIZING)
16
+ * 3. Send message to SQS queue (fire-and-forget)
17
+ * 4. Return process info immediately (async pattern)
18
+ */
19
+
20
+ const { QueuerUtil } = require('../../queues/queuer-util');
21
+
22
+ class TriggerDatabaseMigrationUseCase {
23
+ /**
24
+ * @param {Object} dependencies
25
+ * @param {Object} dependencies.migrationStatusRepository - Repository for migration status (S3)
26
+ * @param {Object} [dependencies.queuerUtil] - SQS utility (injectable for testing)
27
+ */
28
+ constructor({ migrationStatusRepository, queuerUtil = QueuerUtil }) {
29
+ if (!migrationStatusRepository) {
30
+ throw new Error('migrationStatusRepository dependency is required');
31
+ }
32
+ this.migrationStatusRepository = migrationStatusRepository;
33
+ this.queuerUtil = queuerUtil;
34
+ }
35
+
36
+ /**
37
+ * Execute database migration trigger
38
+ *
39
+ * @param {Object} params
40
+ * @param {string} params.userId - User ID triggering the migration
41
+ * @param {string} params.dbType - Database type ('postgresql', 'mongodb', or 'documentdb')
42
+ * @param {string} params.stage - Deployment stage (determines migration command)
43
+ * @returns {Promise<Object>} Process info { success, processId, state, statusUrl, message }
44
+ * @throws {ValidationError} If parameters are invalid
45
+ * @throws {Error} If process creation or queue send fails
46
+ */
47
+ async execute({ userId, dbType, stage }) {
48
+ // Validation
49
+ this._validateParams({ userId, dbType, stage });
50
+
51
+ // Create migration status in S3 (no User table dependency)
52
+ const migrationStatus = await this.migrationStatusRepository.create({
53
+ stage: stage || process.env.STAGE || 'production',
54
+ triggeredBy: userId || 'system',
55
+ triggeredAt: new Date().toISOString(),
56
+ });
57
+
58
+ console.log(`Created migration status: ${migrationStatus.migrationId}`);
59
+
60
+ // Get queue URL from environment
61
+ const queueUrl = process.env.DB_MIGRATION_QUEUE_URL;
62
+ if (!queueUrl) {
63
+ throw new Error(
64
+ 'DB_MIGRATION_QUEUE_URL environment variable is not set. ' +
65
+ 'Cannot send migration to queue.'
66
+ );
67
+ }
68
+
69
+ // Send message to SQS queue (async fire-and-forget)
70
+ try {
71
+ await this.queuerUtil.send(
72
+ {
73
+ migrationId: migrationStatus.migrationId,
74
+ dbType,
75
+ stage,
76
+ },
77
+ queueUrl
78
+ );
79
+
80
+ console.log(`Sent migration job to queue: ${migrationStatus.migrationId}`);
81
+ } catch (error) {
82
+ console.error(`Failed to send migration to queue:`, error);
83
+
84
+ // Update migration status to FAILED
85
+ await this.migrationStatusRepository.update({
86
+ migrationId: migrationStatus.migrationId,
87
+ stage: migrationStatus.stage,
88
+ state: 'FAILED',
89
+ error: `Failed to queue migration: ${error.message}`,
90
+ });
91
+
92
+ throw new Error(
93
+ `Failed to queue migration: ${error.message}`
94
+ );
95
+ }
96
+
97
+ // Return migration info immediately (don't wait for migration completion)
98
+ return {
99
+ success: true,
100
+ migrationId: migrationStatus.migrationId,
101
+ state: migrationStatus.state,
102
+ statusUrl: `/db-migrate/${migrationStatus.migrationId}`,
103
+ s3Key: `migrations/${migrationStatus.stage}/${migrationStatus.migrationId}.json`,
104
+ message: 'Database migration queued successfully',
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Validate execution parameters
110
+ * @private
111
+ */
112
+ _validateParams({ userId, dbType, stage }) {
113
+ // userId is optional for system migrations
114
+ if (userId && typeof userId !== 'string') {
115
+ throw new ValidationError('userId must be a string');
116
+ }
117
+
118
+ if (!dbType) {
119
+ throw new ValidationError('dbType is required');
120
+ }
121
+
122
+ if (typeof dbType !== 'string') {
123
+ throw new ValidationError('dbType must be a string');
124
+ }
125
+
126
+ const validDbTypes = ['postgresql', 'mongodb', 'documentdb'];
127
+ if (!validDbTypes.includes(dbType)) {
128
+ throw new ValidationError(
129
+ `Invalid dbType: "${dbType}". Must be one of: ${validDbTypes.join(', ')}`
130
+ );
131
+ }
132
+
133
+ if (!stage) {
134
+ throw new ValidationError('stage is required');
135
+ }
136
+
137
+ if (typeof stage !== 'string') {
138
+ throw new ValidationError('stage must be a string');
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Custom error for validation failures
145
+ */
146
+ class ValidationError extends Error {
147
+ constructor(message) {
148
+ super(message);
149
+ this.name = 'ValidationError';
150
+ }
151
+ }
152
+
153
+ module.exports = {
154
+ TriggerDatabaseMigrationUseCase,
155
+ ValidationError,
156
+ };
157
+