@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.0

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 (237) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +474 -0
  95. package/handlers/routers/auth.js +51 -9
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +84 -283
  100. package/handlers/routers/integration-defined-routers.js +44 -33
  101. package/handlers/routers/integration-webhook-routers.js +73 -52
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/user.js +30 -14
  106. package/handlers/routers/websocket.js +18 -4
  107. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  108. package/handlers/workers/db-migration.js +74 -35
  109. package/handlers/workers/integration-defined-workers.js +31 -20
  110. package/index.js +50 -16
  111. package/infrastructure/scheduler/index.js +15 -11
  112. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  113. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  114. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  115. package/integrations/index.js +4 -0
  116. package/integrations/integration-base.js +9 -6
  117. package/integrations/integration-router.js +1828 -157
  118. package/integrations/options.js +47 -20
  119. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  120. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  121. package/integrations/repositories/integration-repository-factory.js +6 -6
  122. package/integrations/repositories/integration-repository-interface.js +27 -9
  123. package/integrations/repositories/integration-repository-postgres.js +3 -3
  124. package/integrations/repositories/process-repository-factory.js +4 -7
  125. package/integrations/repositories/process-repository-interface.js +9 -5
  126. package/integrations/repositories/process-repository-mongo.js +7 -3
  127. package/integrations/repositories/process-repository-postgres.js +2 -2
  128. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  129. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  130. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  131. package/integrations/use-cases/create-integration.js +36 -32
  132. package/integrations/use-cases/create-process.js +20 -9
  133. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  134. package/integrations/use-cases/execute-proxy-request.js +564 -0
  135. package/integrations/use-cases/get-integration-for-user.js +25 -10
  136. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  137. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  138. package/integrations/use-cases/get-possible-integrations.js +1 -1
  139. package/integrations/use-cases/get-process.js +5 -6
  140. package/integrations/use-cases/index.js +3 -1
  141. package/integrations/use-cases/load-integration-context.js +1 -5
  142. package/integrations/use-cases/update-process-state.js +10 -8
  143. package/integrations/utils/map-integration-dto.js +9 -3
  144. package/jest-setup.js +1 -1
  145. package/jest-teardown.js +1 -1
  146. package/lambda/TimeoutCatcher.js +32 -30
  147. package/lambda/index.js +1 -1
  148. package/logs/index.js +3 -3
  149. package/modules/domain/entities/AuthorizationSession.js +131 -0
  150. package/modules/domain/entities/index.js +10 -0
  151. package/modules/module.js +4 -9
  152. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  153. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  154. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  155. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  156. package/modules/repositories/module-repository-documentdb.js +89 -41
  157. package/modules/repositories/module-repository-factory.js +4 -6
  158. package/modules/repositories/module-repository-interface.js +13 -0
  159. package/modules/repositories/module-repository-mongo.js +40 -5
  160. package/modules/repositories/module-repository-postgres.js +42 -5
  161. package/modules/repositories/module-repository.js +28 -0
  162. package/modules/requester/api-key.js +0 -2
  163. package/modules/requester/basic.js +0 -1
  164. package/modules/requester/oauth-2.js +3 -14
  165. package/modules/test/mock-api/definition.js +15 -10
  166. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  167. package/modules/tests/doubles/test-module-factory.js +10 -4
  168. package/modules/tests/doubles/test-module-repository.js +1 -1
  169. package/modules/use-cases/delete-module-entity.js +23 -0
  170. package/modules/use-cases/get-authorization-requirements.js +110 -0
  171. package/modules/use-cases/get-entities-for-user.js +1 -1
  172. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  173. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  174. package/modules/use-cases/get-module.js +12 -9
  175. package/modules/use-cases/process-authorization-callback.js +12 -6
  176. package/modules/use-cases/process-authorization-step.js +160 -0
  177. package/modules/use-cases/refresh-entity-options.js +8 -6
  178. package/modules/use-cases/start-authorization-session.js +86 -0
  179. package/modules/use-cases/test-module-auth.js +8 -6
  180. package/modules/use-cases/update-module-entity.js +27 -0
  181. package/modules/utils/map-module-dto.js +2 -2
  182. package/openapi/openapi-spec-generator.js +370 -0
  183. package/openapi/openapi-v1.yaml +1360 -0
  184. package/openapi/openapi-v2.yaml +1535 -0
  185. package/package.json +7 -9
  186. package/prisma-mongodb/schema.prisma +87 -0
  187. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  188. package/prisma-postgresql/schema.prisma +85 -0
  189. package/providers/index.js +13 -0
  190. package/providers/resolve-provider.js +93 -0
  191. package/queues/index.js +19 -0
  192. package/queues/providers/index.js +7 -0
  193. package/queues/providers/netlify-background-provider.js +76 -0
  194. package/queues/providers/qstash-queue-provider.js +122 -0
  195. package/queues/queue-client-interface.js +55 -0
  196. package/queues/queue-provider-factory.js +93 -0
  197. package/queues/queue-provider.js +46 -0
  198. package/queues/queuer-util.js +33 -25
  199. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  200. package/syncs/repositories/sync-repository-factory.js +4 -4
  201. package/syncs/repositories/sync-repository-interface.js +6 -2
  202. package/syncs/repositories/sync-repository-postgres.js +19 -13
  203. package/syncs/sync.js +104 -100
  204. package/token/repositories/token-repository-factory.js +4 -6
  205. package/types/assertions/index.d.ts +72 -72
  206. package/types/core/index.d.ts +98 -53
  207. package/types/errors/index.d.ts +61 -54
  208. package/types/eslint-config/index.d.ts +40 -40
  209. package/types/lambda/index.d.ts +26 -26
  210. package/types/logs/index.d.ts +4 -4
  211. package/types/prettier-config/index.d.ts +5 -5
  212. package/types/test-environment/index.d.ts +14 -14
  213. package/types/tsconfig.json +95 -93
  214. package/user/repositories/user-repository-factory.js +4 -4
  215. package/user/tests/doubles/test-user-repository.js +5 -2
  216. package/user/use-cases/authenticate-user.js +2 -4
  217. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  218. package/user/use-cases/create-individual-user.js +8 -7
  219. package/user/use-cases/create-organization-user.js +1 -1
  220. package/user/use-cases/create-token-for-user-id.js +1 -1
  221. package/user/use-cases/delete-user.js +47 -0
  222. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  223. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  224. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  225. package/user/use-cases/login-user.js +8 -8
  226. package/user/user.js +18 -4
  227. package/utils/backend-path.js +2 -2
  228. package/utils/index.js +4 -1
  229. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  230. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  231. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  232. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  233. package/websocket/repositories/websocket-connection-repository.js +26 -20
  234. package/websocket/websocket-message-sender-interface.js +38 -0
  235. package/database/adapters/lambda-invoker.js +0 -97
  236. package/database/repositories/migration-status-repository-s3.js +0 -137
  237. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Admin Scripts Module
3
+ *
4
+ * Exports repository interfaces and factories for admin script management.
5
+ * Concrete implementations support MongoDB, PostgreSQL, and DocumentDB.
6
+ *
7
+ * Repository interfaces follow the Port pattern in Hexagonal Architecture:
8
+ * - Define contracts for data access
9
+ * - Enable dependency injection
10
+ * - Allow testing with mocks
11
+ * - Support multiple database implementations
12
+ *
13
+ * Authentication:
14
+ * - Uses ENV-based ADMIN_API_KEY (see handlers/middleware/admin-auth.js)
15
+ * - No database-backed API keys (simplified from original design)
16
+ */
17
+
18
+ // Repository Interfaces
19
+ const { AdminProcessRepositoryInterface } = require('./repositories/admin-process-repository-interface');
20
+ const { ScriptScheduleRepositoryInterface } = require('./repositories/script-schedule-repository-interface');
21
+
22
+ // Repository Factories
23
+ const {
24
+ createAdminProcessRepository,
25
+ AdminProcessRepositoryMongo,
26
+ AdminProcessRepositoryPostgres,
27
+ AdminProcessRepositoryDocumentDB,
28
+ } = require('./repositories/admin-process-repository-factory');
29
+ const {
30
+ createScriptScheduleRepository,
31
+ ScriptScheduleRepositoryMongo,
32
+ ScriptScheduleRepositoryPostgres,
33
+ ScriptScheduleRepositoryDocumentDB,
34
+ } = require('./repositories/script-schedule-repository-factory');
35
+
36
+ module.exports = {
37
+ // Repository Interfaces
38
+ AdminProcessRepositoryInterface,
39
+ ScriptScheduleRepositoryInterface,
40
+
41
+ // Repository Factories (primary exports for use cases)
42
+ createAdminProcessRepository,
43
+ createScriptScheduleRepository,
44
+
45
+ // Concrete Implementations (for testing)
46
+ AdminProcessRepositoryMongo,
47
+ AdminProcessRepositoryPostgres,
48
+ AdminProcessRepositoryDocumentDB,
49
+ ScriptScheduleRepositoryMongo,
50
+ ScriptScheduleRepositoryPostgres,
51
+ ScriptScheduleRepositoryDocumentDB,
52
+ };
@@ -0,0 +1,21 @@
1
+ const {
2
+ AdminApiKeyRepositoryMongo,
3
+ } = require('./admin-api-key-repository-mongo');
4
+
5
+ /**
6
+ * DocumentDB Admin API Key Repository Adapter
7
+ * Extends MongoDB implementation since DocumentDB uses the same Prisma client
8
+ *
9
+ * DocumentDB-specific characteristics:
10
+ * - Uses MongoDB-compatible API
11
+ * - Prisma client handles the connection
12
+ * - IDs are strings with ObjectId format
13
+ * - All operations identical to MongoDB implementation
14
+ */
15
+ class AdminApiKeyRepositoryDocumentDB extends AdminApiKeyRepositoryMongo {
16
+ constructor() {
17
+ super();
18
+ }
19
+ }
20
+
21
+ module.exports = { AdminApiKeyRepositoryDocumentDB };
@@ -0,0 +1,51 @@
1
+ const {
2
+ AdminApiKeyRepositoryPostgres,
3
+ } = require('./admin-api-key-repository-postgres');
4
+ const config = require('../../database/config');
5
+
6
+ /**
7
+ * Admin API Key Repository Factory
8
+ * Creates the appropriate repository adapter based on database type
9
+ *
10
+ * This implements the Factory pattern for Hexagonal Architecture:
11
+ * - Reads database type from app definition (backend/index.js)
12
+ * - Returns correct adapter (MongoDB, DocumentDB, or PostgreSQL)
13
+ * - Provides clear error for unsupported databases
14
+ *
15
+ * Usage:
16
+ * ```javascript
17
+ * const repository = createAdminApiKeyRepository();
18
+ * ```
19
+ *
20
+ * @returns {AdminApiKeyRepositoryInterface} Configured repository adapter
21
+ * @throws {Error} If database type is not supported
22
+ */
23
+ function createAdminApiKeyRepository() {
24
+ const dbType = config.DB_TYPE;
25
+
26
+ switch (dbType) {
27
+ case 'mongodb':
28
+ const { AdminApiKeyRepositoryMongo } = require('./admin-api-key-repository-mongo');
29
+ return new AdminApiKeyRepositoryMongo();
30
+
31
+ case 'postgresql':
32
+ return new AdminApiKeyRepositoryPostgres();
33
+
34
+ case 'documentdb':
35
+ const { AdminApiKeyRepositoryDocumentDB } = require('./admin-api-key-repository-documentdb');
36
+ return new AdminApiKeyRepositoryDocumentDB();
37
+
38
+ default:
39
+ throw new Error(
40
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
41
+ );
42
+ }
43
+ }
44
+
45
+ module.exports = {
46
+ createAdminApiKeyRepository,
47
+ // Export adapters for direct testing
48
+ get AdminApiKeyRepositoryMongo() { return require('./admin-api-key-repository-mongo').AdminApiKeyRepositoryMongo; },
49
+ AdminApiKeyRepositoryPostgres,
50
+ get AdminApiKeyRepositoryDocumentDB() { return require('./admin-api-key-repository-documentdb').AdminApiKeyRepositoryDocumentDB; },
51
+ };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Admin API Key Repository Interface
3
+ * Abstract base class defining the contract for admin API key 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
+ * Admin API keys provide authentication for script execution and management endpoints.
11
+ * Keys are bcrypt-hashed for security and support scoping and expiration.
12
+ *
13
+ * @abstract
14
+ */
15
+ class AdminApiKeyRepositoryInterface {
16
+ /**
17
+ * Create a new admin API key
18
+ *
19
+ * @param {Object} params - API key creation parameters
20
+ * @param {string} params.name - Human-readable name for the key
21
+ * @param {string} params.keyHash - bcrypt hash of the raw key
22
+ * @param {string} params.keyLast4 - Last 4 characters of key (for display)
23
+ * @param {string[]} params.scopes - Array of permission scopes (e.g., ['scripts:execute', 'scripts:read'])
24
+ * @param {Date} [params.expiresAt] - Optional expiration date
25
+ * @param {string} [params.createdBy] - Optional identifier of creator (user/admin)
26
+ * @returns {Promise<Object>} The created API key record
27
+ * @abstract
28
+ */
29
+ async createApiKey({
30
+ name,
31
+ keyHash,
32
+ keyLast4,
33
+ scopes,
34
+ expiresAt,
35
+ createdBy,
36
+ }) {
37
+ throw new Error('Method createApiKey must be implemented by subclass');
38
+ }
39
+
40
+ /**
41
+ * Find an API key by its bcrypt hash
42
+ * Used during authentication to validate incoming keys
43
+ *
44
+ * @param {string} keyHash - The bcrypt hash to search for
45
+ * @returns {Promise<Object|null>} The API key record or null if not found
46
+ * @abstract
47
+ */
48
+ async findApiKeyByHash(keyHash) {
49
+ throw new Error(
50
+ 'Method findApiKeyByHash must be implemented by subclass'
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Find an API key by its ID
56
+ *
57
+ * @param {string|number} id - The API key ID
58
+ * @returns {Promise<Object|null>} The API key record or null if not found
59
+ * @abstract
60
+ */
61
+ async findApiKeyById(id) {
62
+ throw new Error(
63
+ 'Method findApiKeyById must be implemented by subclass'
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Find all active (non-expired, non-deactivated) API keys
69
+ * Used during authentication to check all valid keys
70
+ *
71
+ * @returns {Promise<Array>} Array of active API key records
72
+ * @abstract
73
+ */
74
+ async findActiveApiKeys() {
75
+ throw new Error(
76
+ 'Method findActiveApiKeys must be implemented by subclass'
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Update the lastUsedAt timestamp for an API key
82
+ * Called after successful authentication
83
+ *
84
+ * @param {string|number} id - The API key ID
85
+ * @returns {Promise<Object>} Updated API key record
86
+ * @abstract
87
+ */
88
+ async updateApiKeyLastUsed(id) {
89
+ throw new Error(
90
+ 'Method updateApiKeyLastUsed must be implemented by subclass'
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Deactivate an API key (soft delete)
96
+ * Sets isActive to false, preventing further use
97
+ *
98
+ * @param {string|number} id - The API key ID
99
+ * @returns {Promise<Object>} Updated API key record
100
+ * @abstract
101
+ */
102
+ async deactivateApiKey(id) {
103
+ throw new Error(
104
+ 'Method deactivateApiKey must be implemented by subclass'
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Delete an API key (hard delete)
110
+ * Permanently removes the key from the database
111
+ *
112
+ * @param {string|number} id - The API key ID
113
+ * @returns {Promise<Object>} Deletion result
114
+ * @abstract
115
+ */
116
+ async deleteApiKey(id) {
117
+ throw new Error('Method deleteApiKey must be implemented by subclass');
118
+ }
119
+ }
120
+
121
+ module.exports = { AdminApiKeyRepositoryInterface };
@@ -0,0 +1,155 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ AdminApiKeyRepositoryInterface,
4
+ } = require('./admin-api-key-repository-interface');
5
+
6
+ /**
7
+ * MongoDB Admin API Key Repository Adapter
8
+ * Handles admin API key persistence using Prisma with MongoDB
9
+ *
10
+ * MongoDB-specific characteristics:
11
+ * - IDs are strings with @db.ObjectId
12
+ * - Supports bcrypt hashed keys
13
+ * - Scopes stored as String[] array
14
+ */
15
+ class AdminApiKeyRepositoryMongo extends AdminApiKeyRepositoryInterface {
16
+ constructor() {
17
+ super();
18
+ this.prisma = prisma;
19
+ }
20
+
21
+ /**
22
+ * Create a new admin API key
23
+ *
24
+ * @param {Object} params - API key creation parameters
25
+ * @param {string} params.name - Human-readable name for the key
26
+ * @param {string} params.keyHash - bcrypt hash of the raw key
27
+ * @param {string} params.keyLast4 - Last 4 characters of key (for display)
28
+ * @param {string[]} params.scopes - Array of permission scopes
29
+ * @param {Date} [params.expiresAt] - Optional expiration date
30
+ * @param {string} [params.createdBy] - Optional identifier of creator
31
+ * @returns {Promise<Object>} The created API key record
32
+ */
33
+ async createApiKey({
34
+ name,
35
+ keyHash,
36
+ keyLast4,
37
+ scopes,
38
+ expiresAt,
39
+ createdBy,
40
+ }) {
41
+ const apiKey = await this.prisma.adminApiKey.create({
42
+ data: {
43
+ name,
44
+ keyHash,
45
+ keyLast4,
46
+ scopes,
47
+ expiresAt,
48
+ createdBy,
49
+ },
50
+ });
51
+
52
+ return apiKey;
53
+ }
54
+
55
+ /**
56
+ * Find an API key by its bcrypt hash
57
+ * Used during authentication to validate incoming keys
58
+ *
59
+ * @param {string} keyHash - The bcrypt hash to search for
60
+ * @returns {Promise<Object|null>} The API key record or null if not found
61
+ */
62
+ async findApiKeyByHash(keyHash) {
63
+ const apiKey = await this.prisma.adminApiKey.findUnique({
64
+ where: { keyHash },
65
+ });
66
+
67
+ return apiKey;
68
+ }
69
+
70
+ /**
71
+ * Find an API key by its ID
72
+ *
73
+ * @param {string} id - The API key ID (MongoDB ObjectId as string)
74
+ * @returns {Promise<Object|null>} The API key record or null if not found
75
+ */
76
+ async findApiKeyById(id) {
77
+ const apiKey = await this.prisma.adminApiKey.findUnique({
78
+ where: { id },
79
+ });
80
+
81
+ return apiKey;
82
+ }
83
+
84
+ /**
85
+ * Find all active (non-expired, non-deactivated) API keys
86
+ * Used during authentication to check all valid keys
87
+ *
88
+ * @returns {Promise<Array>} Array of active API key records
89
+ */
90
+ async findActiveApiKeys() {
91
+ const now = new Date();
92
+ const apiKeys = await this.prisma.adminApiKey.findMany({
93
+ where: {
94
+ isActive: true,
95
+ OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
96
+ },
97
+ });
98
+
99
+ return apiKeys;
100
+ }
101
+
102
+ /**
103
+ * Update the lastUsedAt timestamp for an API key
104
+ * Called after successful authentication
105
+ *
106
+ * @param {string} id - The API key ID
107
+ * @returns {Promise<Object>} Updated API key record
108
+ */
109
+ async updateApiKeyLastUsed(id) {
110
+ const apiKey = await this.prisma.adminApiKey.update({
111
+ where: { id },
112
+ data: {
113
+ lastUsedAt: new Date(),
114
+ },
115
+ });
116
+
117
+ return apiKey;
118
+ }
119
+
120
+ /**
121
+ * Deactivate an API key (soft delete)
122
+ * Sets isActive to false, preventing further use
123
+ *
124
+ * @param {string} id - The API key ID
125
+ * @returns {Promise<Object>} Updated API key record
126
+ */
127
+ async deactivateApiKey(id) {
128
+ const apiKey = await this.prisma.adminApiKey.update({
129
+ where: { id },
130
+ data: {
131
+ isActive: false,
132
+ },
133
+ });
134
+
135
+ return apiKey;
136
+ }
137
+
138
+ /**
139
+ * Delete an API key (hard delete)
140
+ * Permanently removes the key from the database
141
+ *
142
+ * @param {string} id - The API key ID
143
+ * @returns {Promise<Object>} Deletion result
144
+ */
145
+ async deleteApiKey(id) {
146
+ await this.prisma.adminApiKey.delete({
147
+ where: { id },
148
+ });
149
+
150
+ // Return Mongoose-compatible result
151
+ return { acknowledged: true, deletedCount: 1 };
152
+ }
153
+ }
154
+
155
+ module.exports = { AdminApiKeyRepositoryMongo };
@@ -0,0 +1,189 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ AdminApiKeyRepositoryInterface,
4
+ } = require('./admin-api-key-repository-interface');
5
+
6
+ /**
7
+ * PostgreSQL Admin API Key Repository Adapter
8
+ * Handles admin API key persistence using Prisma with PostgreSQL
9
+ *
10
+ * PostgreSQL-specific characteristics:
11
+ * - Uses Int IDs with autoincrement
12
+ * - Requires ID conversion: String (app layer) ↔ Int (database)
13
+ * - All returned IDs are converted to strings for application layer consistency
14
+ */
15
+ class AdminApiKeyRepositoryPostgres extends AdminApiKeyRepositoryInterface {
16
+ constructor() {
17
+ super();
18
+ this.prisma = prisma;
19
+ }
20
+
21
+ /**
22
+ * Convert string ID to integer for PostgreSQL queries
23
+ * @private
24
+ * @param {string|number|null|undefined} id - ID to convert
25
+ * @returns {number|null|undefined} Integer ID or null/undefined
26
+ * @throws {Error} If ID cannot be converted to integer
27
+ */
28
+ _convertId(id) {
29
+ if (id === null || id === undefined) return id;
30
+ const parsed = Number.parseInt(id, 10);
31
+ if (Number.isNaN(parsed)) {
32
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
33
+ }
34
+ return parsed;
35
+ }
36
+
37
+ /**
38
+ * Convert API key object IDs to strings
39
+ * @private
40
+ * @param {Object|null} apiKey - API key object from database
41
+ * @returns {Object|null} API key with string IDs
42
+ */
43
+ _convertApiKeyIds(apiKey) {
44
+ if (!apiKey) return apiKey;
45
+ return {
46
+ ...apiKey,
47
+ id: apiKey.id?.toString(),
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Create a new admin API key
53
+ *
54
+ * @param {Object} params - API key creation parameters
55
+ * @param {string} params.name - Human-readable name for the key
56
+ * @param {string} params.keyHash - bcrypt hash of the raw key
57
+ * @param {string} params.keyLast4 - Last 4 characters of key (for display)
58
+ * @param {string[]} params.scopes - Array of permission scopes
59
+ * @param {Date} [params.expiresAt] - Optional expiration date
60
+ * @param {string} [params.createdBy] - Optional identifier of creator
61
+ * @returns {Promise<Object>} The created API key record with string ID
62
+ */
63
+ async createApiKey({
64
+ name,
65
+ keyHash,
66
+ keyLast4,
67
+ scopes,
68
+ expiresAt,
69
+ createdBy,
70
+ }) {
71
+ const apiKey = await this.prisma.adminApiKey.create({
72
+ data: {
73
+ name,
74
+ keyHash,
75
+ keyLast4,
76
+ scopes,
77
+ expiresAt,
78
+ createdBy,
79
+ },
80
+ });
81
+
82
+ return this._convertApiKeyIds(apiKey);
83
+ }
84
+
85
+ /**
86
+ * Find an API key by its bcrypt hash
87
+ * Used during authentication to validate incoming keys
88
+ *
89
+ * @param {string} keyHash - The bcrypt hash to search for
90
+ * @returns {Promise<Object|null>} The API key record with string ID or null if not found
91
+ */
92
+ async findApiKeyByHash(keyHash) {
93
+ const apiKey = await this.prisma.adminApiKey.findUnique({
94
+ where: { keyHash },
95
+ });
96
+
97
+ return this._convertApiKeyIds(apiKey);
98
+ }
99
+
100
+ /**
101
+ * Find an API key by its ID
102
+ *
103
+ * @param {string|number} id - The API key ID
104
+ * @returns {Promise<Object|null>} The API key record with string ID or null if not found
105
+ */
106
+ async findApiKeyById(id) {
107
+ const intId = this._convertId(id);
108
+ const apiKey = await this.prisma.adminApiKey.findUnique({
109
+ where: { id: intId },
110
+ });
111
+
112
+ return this._convertApiKeyIds(apiKey);
113
+ }
114
+
115
+ /**
116
+ * Find all active (non-expired, non-deactivated) API keys
117
+ * Used during authentication to check all valid keys
118
+ *
119
+ * @returns {Promise<Array>} Array of active API key records with string IDs
120
+ */
121
+ async findActiveApiKeys() {
122
+ const now = new Date();
123
+ const apiKeys = await this.prisma.adminApiKey.findMany({
124
+ where: {
125
+ isActive: true,
126
+ OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
127
+ },
128
+ });
129
+
130
+ return apiKeys.map((apiKey) => this._convertApiKeyIds(apiKey));
131
+ }
132
+
133
+ /**
134
+ * Update the lastUsedAt timestamp for an API key
135
+ * Called after successful authentication
136
+ *
137
+ * @param {string|number} id - The API key ID
138
+ * @returns {Promise<Object>} Updated API key record with string ID
139
+ */
140
+ async updateApiKeyLastUsed(id) {
141
+ const intId = this._convertId(id);
142
+ const apiKey = await this.prisma.adminApiKey.update({
143
+ where: { id: intId },
144
+ data: {
145
+ lastUsedAt: new Date(),
146
+ },
147
+ });
148
+
149
+ return this._convertApiKeyIds(apiKey);
150
+ }
151
+
152
+ /**
153
+ * Deactivate an API key (soft delete)
154
+ * Sets isActive to false, preventing further use
155
+ *
156
+ * @param {string|number} id - The API key ID
157
+ * @returns {Promise<Object>} Updated API key record with string ID
158
+ */
159
+ async deactivateApiKey(id) {
160
+ const intId = this._convertId(id);
161
+ const apiKey = await this.prisma.adminApiKey.update({
162
+ where: { id: intId },
163
+ data: {
164
+ isActive: false,
165
+ },
166
+ });
167
+
168
+ return this._convertApiKeyIds(apiKey);
169
+ }
170
+
171
+ /**
172
+ * Delete an API key (hard delete)
173
+ * Permanently removes the key from the database
174
+ *
175
+ * @param {string|number} id - The API key ID
176
+ * @returns {Promise<Object>} Deletion result
177
+ */
178
+ async deleteApiKey(id) {
179
+ const intId = this._convertId(id);
180
+ await this.prisma.adminApiKey.delete({
181
+ where: { id: intId },
182
+ });
183
+
184
+ // Return Mongoose-compatible result
185
+ return { acknowledged: true, deletedCount: 1 };
186
+ }
187
+ }
188
+
189
+ module.exports = { AdminApiKeyRepositoryPostgres };
@@ -0,0 +1,21 @@
1
+ const {
2
+ AdminProcessRepositoryMongo,
3
+ } = require('./admin-process-repository-mongo');
4
+
5
+ /**
6
+ * DocumentDB Admin Process Repository Adapter
7
+ * Extends MongoDB implementation since DocumentDB uses the same Prisma client
8
+ *
9
+ * DocumentDB-specific characteristics:
10
+ * - Uses MongoDB-compatible API
11
+ * - Prisma client handles the connection
12
+ * - IDs are strings with ObjectId format
13
+ * - All operations identical to MongoDB implementation
14
+ */
15
+ class AdminProcessRepositoryDocumentDB extends AdminProcessRepositoryMongo {
16
+ constructor() {
17
+ super();
18
+ }
19
+ }
20
+
21
+ module.exports = { AdminProcessRepositoryDocumentDB };
@@ -0,0 +1,49 @@
1
+ const { AdminProcessRepositoryPostgres } = require('./admin-process-repository-postgres');
2
+ const config = require('../../database/config');
3
+
4
+ /**
5
+ * Admin Process Repository Factory
6
+ * Creates the appropriate repository adapter based on database type
7
+ *
8
+ * This implements the Factory pattern for Hexagonal Architecture:
9
+ * - Reads database type from app definition (backend/index.js)
10
+ * - Returns correct adapter (MongoDB, DocumentDB, or PostgreSQL)
11
+ * - Provides clear error for unsupported databases
12
+ *
13
+ * Usage:
14
+ * ```javascript
15
+ * const repository = createAdminProcessRepository();
16
+ * ```
17
+ *
18
+ * @returns {AdminProcessRepositoryInterface} Configured repository adapter
19
+ * @throws {Error} If database type is not supported
20
+ */
21
+ function createAdminProcessRepository() {
22
+ const dbType = config.DB_TYPE;
23
+
24
+ switch (dbType) {
25
+ case 'mongodb':
26
+ const { AdminProcessRepositoryMongo } = require('./admin-process-repository-mongo');
27
+ return new AdminProcessRepositoryMongo();
28
+
29
+ case 'postgresql':
30
+ return new AdminProcessRepositoryPostgres();
31
+
32
+ case 'documentdb':
33
+ const { AdminProcessRepositoryDocumentDB } = require('./admin-process-repository-documentdb');
34
+ return new AdminProcessRepositoryDocumentDB();
35
+
36
+ default:
37
+ throw new Error(
38
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
39
+ );
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ createAdminProcessRepository,
45
+ // Export adapters for direct testing
46
+ get AdminProcessRepositoryMongo() { return require('./admin-process-repository-mongo').AdminProcessRepositoryMongo; },
47
+ AdminProcessRepositoryPostgres,
48
+ get AdminProcessRepositoryDocumentDB() { return require('./admin-process-repository-documentdb').AdminProcessRepositoryDocumentDB; },
49
+ };