@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.dba001a.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 (235) 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 +450 -0
  95. package/handlers/routers/auth.js +23 -2
  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 +39 -249
  100. package/handlers/routers/integration-defined-routers.js +4 -4
  101. package/handlers/routers/integration-webhook-routers.js +17 -7
  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/websocket.js +9 -3
  106. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  107. package/handlers/workers/db-migration.js +74 -35
  108. package/index.js +50 -16
  109. package/infrastructure/scheduler/index.js +15 -11
  110. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  111. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  112. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  113. package/integrations/index.js +4 -0
  114. package/integrations/integration-base.js +9 -6
  115. package/integrations/integration-router.js +1828 -157
  116. package/integrations/options.js +47 -20
  117. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  118. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  119. package/integrations/repositories/integration-repository-factory.js +6 -6
  120. package/integrations/repositories/integration-repository-interface.js +27 -9
  121. package/integrations/repositories/integration-repository-postgres.js +3 -3
  122. package/integrations/repositories/process-repository-factory.js +4 -7
  123. package/integrations/repositories/process-repository-interface.js +9 -5
  124. package/integrations/repositories/process-repository-mongo.js +7 -3
  125. package/integrations/repositories/process-repository-postgres.js +2 -2
  126. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  127. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  128. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  129. package/integrations/use-cases/create-integration.js +36 -32
  130. package/integrations/use-cases/create-process.js +20 -9
  131. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  132. package/integrations/use-cases/execute-proxy-request.js +564 -0
  133. package/integrations/use-cases/get-integration-for-user.js +25 -10
  134. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  135. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  136. package/integrations/use-cases/get-possible-integrations.js +1 -1
  137. package/integrations/use-cases/get-process.js +5 -6
  138. package/integrations/use-cases/index.js +3 -1
  139. package/integrations/use-cases/load-integration-context.js +1 -5
  140. package/integrations/use-cases/update-process-state.js +10 -8
  141. package/integrations/utils/map-integration-dto.js +9 -3
  142. package/jest-setup.js +1 -1
  143. package/jest-teardown.js +1 -1
  144. package/lambda/TimeoutCatcher.js +32 -30
  145. package/lambda/index.js +1 -1
  146. package/logs/index.js +3 -3
  147. package/modules/domain/entities/AuthorizationSession.js +131 -0
  148. package/modules/domain/entities/index.js +10 -0
  149. package/modules/module.js +4 -9
  150. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  151. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  152. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  153. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  154. package/modules/repositories/module-repository-documentdb.js +89 -41
  155. package/modules/repositories/module-repository-factory.js +4 -6
  156. package/modules/repositories/module-repository-interface.js +13 -0
  157. package/modules/repositories/module-repository-mongo.js +40 -5
  158. package/modules/repositories/module-repository-postgres.js +42 -5
  159. package/modules/repositories/module-repository.js +28 -0
  160. package/modules/requester/api-key.js +0 -2
  161. package/modules/requester/basic.js +0 -1
  162. package/modules/requester/oauth-2.js +3 -14
  163. package/modules/test/mock-api/definition.js +15 -10
  164. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  165. package/modules/tests/doubles/test-module-factory.js +10 -4
  166. package/modules/tests/doubles/test-module-repository.js +1 -1
  167. package/modules/use-cases/delete-module-entity.js +23 -0
  168. package/modules/use-cases/get-authorization-requirements.js +110 -0
  169. package/modules/use-cases/get-entities-for-user.js +1 -1
  170. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  171. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  172. package/modules/use-cases/get-module.js +12 -9
  173. package/modules/use-cases/process-authorization-callback.js +12 -6
  174. package/modules/use-cases/process-authorization-step.js +160 -0
  175. package/modules/use-cases/refresh-entity-options.js +8 -6
  176. package/modules/use-cases/start-authorization-session.js +86 -0
  177. package/modules/use-cases/test-module-auth.js +8 -6
  178. package/modules/use-cases/update-module-entity.js +27 -0
  179. package/modules/utils/map-module-dto.js +2 -2
  180. package/openapi/openapi-spec-generator.js +370 -0
  181. package/openapi/openapi-v1.yaml +1360 -0
  182. package/openapi/openapi-v2.yaml +1535 -0
  183. package/package.json +7 -9
  184. package/prisma-mongodb/schema.prisma +87 -0
  185. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  186. package/prisma-postgresql/schema.prisma +85 -0
  187. package/providers/index.js +13 -0
  188. package/providers/resolve-provider.js +93 -0
  189. package/queues/index.js +19 -0
  190. package/queues/providers/index.js +7 -0
  191. package/queues/providers/netlify-background-provider.js +76 -0
  192. package/queues/providers/qstash-queue-provider.js +122 -0
  193. package/queues/queue-client-interface.js +55 -0
  194. package/queues/queue-provider-factory.js +93 -0
  195. package/queues/queue-provider.js +46 -0
  196. package/queues/queuer-util.js +33 -25
  197. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  198. package/syncs/repositories/sync-repository-factory.js +4 -4
  199. package/syncs/repositories/sync-repository-interface.js +6 -2
  200. package/syncs/repositories/sync-repository-postgres.js +19 -13
  201. package/syncs/sync.js +104 -100
  202. package/token/repositories/token-repository-factory.js +4 -6
  203. package/types/assertions/index.d.ts +72 -72
  204. package/types/core/index.d.ts +98 -53
  205. package/types/errors/index.d.ts +61 -54
  206. package/types/eslint-config/index.d.ts +40 -40
  207. package/types/lambda/index.d.ts +26 -26
  208. package/types/logs/index.d.ts +4 -4
  209. package/types/prettier-config/index.d.ts +5 -5
  210. package/types/test-environment/index.d.ts +14 -14
  211. package/types/tsconfig.json +95 -93
  212. package/user/repositories/user-repository-factory.js +4 -4
  213. package/user/tests/doubles/test-user-repository.js +5 -2
  214. package/user/use-cases/authenticate-user.js +2 -4
  215. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  216. package/user/use-cases/create-individual-user.js +8 -7
  217. package/user/use-cases/create-organization-user.js +1 -1
  218. package/user/use-cases/create-token-for-user-id.js +1 -1
  219. package/user/use-cases/delete-user.js +47 -0
  220. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  221. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  222. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  223. package/user/use-cases/login-user.js +8 -8
  224. package/user/user.js +18 -4
  225. package/utils/backend-path.js +2 -2
  226. package/utils/index.js +4 -1
  227. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  228. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  229. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  230. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  231. package/websocket/repositories/websocket-connection-repository.js +26 -20
  232. package/websocket/websocket-message-sender-interface.js +38 -0
  233. package/database/adapters/lambda-invoker.js +0 -97
  234. package/database/repositories/migration-status-repository-s3.js +0 -137
  235. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -4,12 +4,14 @@
4
4
  * HTTP API for triggering and monitoring database migrations.
5
5
  *
6
6
  * Endpoints:
7
- * - GET /db-migrate/status - Check if migrations are pending
8
- * - POST /db-migrate - Trigger async migration (queues job)
9
- * - GET /db-migrate/:processId - Check migration status
7
+ * - GET /admin/db-migrate/status - Check if migrations are pending
8
+ * - POST /admin/db-migrate - Trigger async migration (queues job)
9
+ * - GET /admin/db-migrate/:processId - Check migration status
10
+ * - POST /admin/db-migrate/resolve - Resolve failed migration
10
11
  *
11
12
  * Security:
12
- * - Requires ADMIN_API_KEY header for all requests
13
+ * - Requires x-frigg-admin-api-key header for all requests
14
+ * - Uses shared validateAdminApiKey middleware
13
15
  *
14
16
  * Architecture:
15
17
  * - Router (Adapter Layer) → Use Cases (Domain) → Repositories (Infrastructure)
@@ -18,7 +20,8 @@
18
20
 
19
21
  const { Router } = require('express');
20
22
  const catchAsyncError = require('express-async-handler');
21
- const { MigrationStatusRepositoryS3 } = require('../../database/repositories/migration-status-repository-s3');
23
+ const { validateAdminApiKey } = require('../middleware/admin-auth');
24
+ const { resolveProvider } = require('../../providers/resolve-provider');
22
25
  const {
23
26
  TriggerDatabaseMigrationUseCase,
24
27
  ValidationError: TriggerValidationError,
@@ -28,57 +31,77 @@ const {
28
31
  ValidationError: GetValidationError,
29
32
  NotFoundError,
30
33
  } = require('../../database/use-cases/get-migration-status-use-case');
31
- const { LambdaInvoker } = require('../../database/adapters/lambda-invoker');
32
34
  const {
33
35
  GetDatabaseStateViaWorkerUseCase,
34
36
  } = require('../../database/use-cases/get-database-state-via-worker-use-case');
35
37
 
36
38
  const router = Router();
37
39
 
38
- // Dependency injection
39
- // Use S3 repository to avoid User table dependency (chicken-and-egg problem)
40
- const bucketName = process.env.S3_BUCKET_NAME || process.env.MIGRATION_STATUS_BUCKET;
41
- const migrationStatusRepository = new MigrationStatusRepositoryS3(bucketName);
42
-
43
- const triggerMigrationUseCase = new TriggerDatabaseMigrationUseCase({
44
- migrationStatusRepository,
45
- // Note: QueuerUtil is used directly in the use case (static utility)
46
- });
47
- const getStatusUseCase = new GetMigrationStatusUseCase({ migrationStatusRepository });
48
-
49
- // Lambda invocation for database state check (keeps router lightweight)
50
- const lambdaInvoker = new LambdaInvoker();
51
- const workerFunctionName = process.env.WORKER_FUNCTION_NAME ||
52
- `${process.env.SERVICE || 'unknown'}-${process.env.STAGE || 'production'}-dbMigrationWorker`;
53
-
54
- const getDatabaseStateUseCase = new GetDatabaseStateViaWorkerUseCase({
55
- lambdaInvoker,
56
- workerFunctionName,
57
- });
40
+ // Dependency injection — lazy-initialized on first request.
41
+ // Uses resolveProvider() to get the correct adapters for the active platform.
42
+ let _migrationStatusRepository = null;
43
+ let _triggerMigrationUseCase = null;
44
+ let _getStatusUseCase = null;
45
+ let _functionInvoker = null;
46
+
47
+ function getMigrationDeps() {
48
+ if (!_migrationStatusRepository) {
49
+ const provider = resolveProvider();
50
+
51
+ // Migration status storage (AWS: S3, others: provider-specific)
52
+ const MigrationStatusRepository = provider.MigrationStatusRepositoryS3;
53
+ if (!MigrationStatusRepository) {
54
+ throw new Error(
55
+ `Provider '${provider.name}' does not export a MigrationStatusRepository`
56
+ );
57
+ }
58
+ const bucketName =
59
+ process.env.S3_BUCKET_NAME || process.env.MIGRATION_STATUS_BUCKET;
60
+ _migrationStatusRepository = new MigrationStatusRepository(bucketName);
61
+ _triggerMigrationUseCase = new TriggerDatabaseMigrationUseCase({
62
+ migrationStatusRepository: _migrationStatusRepository,
63
+ });
64
+ _getStatusUseCase = new GetMigrationStatusUseCase({
65
+ migrationStatusRepository: _migrationStatusRepository,
66
+ });
58
67
 
59
- /**
60
- * Admin API key validation middleware
61
- * Matches pattern from health.js:72-88
62
- */
63
- const validateApiKey = (req, res, next) => {
64
- const apiKey = req.headers['x-frigg-admin-api-key'];
65
-
66
- if (!apiKey || apiKey !== process.env.ADMIN_API_KEY) {
67
- console.error('Unauthorized access attempt to db-migrate endpoint');
68
- return res.status(401).json({
69
- status: 'error',
70
- message: 'Unauthorized - x-frigg-admin-api-key header required',
68
+ // Function invoker (AWS: LambdaInvoker, Netlify: HTTP-based)
69
+ _functionInvoker = provider.invokeFunctionAdapter;
70
+ if (!_functionInvoker) {
71
+ throw new Error(
72
+ `Provider '${provider.name}' does not export an invokeFunctionAdapter`
73
+ );
74
+ }
75
+ }
76
+ return {
77
+ migrationStatusRepository: _migrationStatusRepository,
78
+ triggerMigrationUseCase: _triggerMigrationUseCase,
79
+ getStatusUseCase: _getStatusUseCase,
80
+ lambdaInvoker: _functionInvoker,
81
+ };
82
+ }
83
+ let _getDatabaseStateUseCase = null;
84
+ function getDatabaseStateUseCaseInstance() {
85
+ if (!_getDatabaseStateUseCase) {
86
+ const { lambdaInvoker } = getMigrationDeps();
87
+ const workerFunctionName =
88
+ process.env.WORKER_FUNCTION_NAME ||
89
+ `${process.env.SERVICE || 'unknown'}-${
90
+ process.env.STAGE || 'production'
91
+ }-dbMigrationWorker`;
92
+ _getDatabaseStateUseCase = new GetDatabaseStateViaWorkerUseCase({
93
+ lambdaInvoker,
94
+ workerFunctionName,
71
95
  });
72
96
  }
97
+ return _getDatabaseStateUseCase;
98
+ }
73
99
 
74
- next();
75
- };
76
-
77
- // Apply API key validation to all routes
78
- router.use(validateApiKey);
100
+ // Apply admin API key validation to all routes (shared middleware)
101
+ router.use(validateAdminApiKey);
79
102
 
80
103
  /**
81
- * POST /db-migrate
104
+ * POST /admin/db-migrate
82
105
  *
83
106
  * Trigger database migration (async via SQS queue)
84
107
  *
@@ -99,17 +122,21 @@ router.use(validateApiKey);
99
122
  * }
100
123
  */
101
124
  router.post(
102
- '/db-migrate',
125
+ '/admin/db-migrate',
103
126
  catchAsyncError(async (req, res) => {
104
127
  const dbType = req.body.dbType || process.env.DB_TYPE || 'postgresql';
105
128
  const { stage } = req.body;
106
129
  // TODO: Extract userId from JWT token when auth is implemented
107
130
  const userId = req.body.userId || 'admin';
108
131
 
109
- console.log(`Migration trigger request: dbType=${dbType}, stage=${stage || 'auto-detect'}, userId=${userId}`);
132
+ console.log(
133
+ `Migration trigger request: dbType=${dbType}, stage=${
134
+ stage || 'auto-detect'
135
+ }, userId=${userId}`
136
+ );
110
137
 
111
138
  try {
112
- const result = await triggerMigrationUseCase.execute({
139
+ const result = await getMigrationDeps().triggerMigrationUseCase.execute({
113
140
  userId,
114
141
  dbType,
115
142
  stage,
@@ -133,10 +160,10 @@ router.post(
133
160
  );
134
161
 
135
162
  /**
136
- * GET /db-migrate/status
163
+ * GET /admin/db-migrate/status
137
164
  *
138
165
  * Check if database has pending migrations
139
- *
166
+ *
140
167
  * Query params:
141
168
  * - stage: string (optional, defaults to STAGE env var or 'production')
142
169
  *
@@ -151,15 +178,17 @@ router.post(
151
178
  * }
152
179
  */
153
180
  router.get(
154
- '/db-migrate/status',
181
+ '/admin/db-migrate/status',
155
182
  catchAsyncError(async (req, res) => {
156
183
  const stage = req.query.stage || process.env.STAGE || 'production';
157
184
 
158
- console.log(`Checking database state: stage=${stage}, worker=${workerFunctionName}`);
185
+ console.log(
186
+ `Checking database state: stage=${stage}`
187
+ );
159
188
 
160
189
  try {
161
190
  // Invoke worker Lambda to check database state
162
- const status = await getDatabaseStateUseCase.execute(stage);
191
+ const status = await getDatabaseStateUseCaseInstance().execute(stage);
163
192
 
164
193
  res.status(200).json(status);
165
194
  } catch (error) {
@@ -177,7 +206,7 @@ router.get(
177
206
  );
178
207
 
179
208
  /**
180
- * GET /db-migrate/:migrationId
209
+ * GET /admin/db-migrate/:migrationId
181
210
  *
182
211
  * Get migration status by migration ID
183
212
  *
@@ -201,15 +230,17 @@ router.get(
201
230
  * }
202
231
  */
203
232
  router.get(
204
- '/db-migrate/:migrationId',
233
+ '/admin/db-migrate/:migrationId',
205
234
  catchAsyncError(async (req, res) => {
206
235
  const { migrationId } = req.params;
207
236
  const stage = req.query.stage || process.env.STAGE || 'production';
208
237
 
209
- console.log(`Migration status request: migrationId=${migrationId}, stage=${stage}`);
238
+ console.log(
239
+ `Migration status request: migrationId=${migrationId}, stage=${stage}`
240
+ );
210
241
 
211
242
  try {
212
- const status = await getStatusUseCase.execute(migrationId, stage);
243
+ const status = await getMigrationDeps().getStatusUseCase.execute(migrationId, stage);
213
244
 
214
245
  res.status(200).json(status);
215
246
  } catch (error) {
@@ -236,7 +267,7 @@ router.get(
236
267
  );
237
268
 
238
269
  /**
239
- * POST /db-migrate/resolve
270
+ * POST /admin/db-migrate/resolve
240
271
  *
241
272
  * Resolve a failed migration by marking it as applied or rolled back
242
273
  *
@@ -256,24 +287,26 @@ router.get(
256
287
  * }
257
288
  */
258
289
  router.post(
259
- '/db-migrate/resolve',
290
+ '/admin/db-migrate/resolve',
260
291
  catchAsyncError(async (req, res) => {
261
292
  const { migrationName, action = 'applied' } = req.body;
262
293
 
263
- console.log(`Migration resolve request: migration=${migrationName}, action=${action}`);
294
+ console.log(
295
+ `Migration resolve request: migration=${migrationName}, action=${action}`
296
+ );
264
297
 
265
298
  // Validation
266
299
  if (!migrationName) {
267
300
  return res.status(400).json({
268
301
  success: false,
269
- error: 'migrationName is required'
302
+ error: 'migrationName is required',
270
303
  });
271
304
  }
272
305
 
273
306
  if (!['applied', 'rolled-back'].includes(action)) {
274
307
  return res.status(400).json({
275
308
  success: false,
276
- error: 'action must be either "applied" or "rolled-back"'
309
+ error: 'action must be either "applied" or "rolled-back"',
277
310
  });
278
311
  }
279
312
 
@@ -281,12 +314,16 @@ router.post(
281
314
  // Import prismaRunner here to avoid circular dependencies
282
315
  const prismaRunner = require('../../database/utils/prisma-runner');
283
316
 
284
- const result = await prismaRunner.runPrismaMigrateResolve(migrationName, action, true);
317
+ const result = await prismaRunner.runPrismaMigrateResolve(
318
+ migrationName,
319
+ action,
320
+ true
321
+ );
285
322
 
286
323
  if (!result.success) {
287
324
  return res.status(500).json({
288
325
  success: false,
289
- error: `Failed to resolve migration: ${result.error}`
326
+ error: `Failed to resolve migration: ${result.error}`,
290
327
  });
291
328
  }
292
329
 
@@ -294,13 +331,13 @@ router.post(
294
331
  success: true,
295
332
  message: `Migration ${migrationName} marked as ${action}`,
296
333
  migrationName,
297
- action
334
+ action,
298
335
  });
299
336
  } catch (error) {
300
337
  console.error('Migration resolve failed:', error);
301
338
  return res.status(500).json({
302
339
  success: false,
303
- error: error.message
340
+ error: error.message,
304
341
  });
305
342
  }
306
343
  })
@@ -323,4 +360,3 @@ app.use((err, _req, res, _next) => {
323
360
  const handler = serverlessHttp(app);
324
361
 
325
362
  module.exports = { handler, router };
326
-
@@ -0,0 +1,180 @@
1
+ /**
2
+ * API Documentation Router
3
+ *
4
+ * Serves dynamic OpenAPI specs and Scalar UI documentation for both v1 and v2 APIs.
5
+ * Specs are generated dynamically based on appDefinition and installed modules.
6
+ *
7
+ * Endpoints:
8
+ * - GET /api/docs - Main documentation UI with version selector
9
+ * - GET /api/v1/docs - v1-specific documentation
10
+ * - GET /api/v2/docs - v2-specific documentation
11
+ * - GET /api/openapi.json - v2 spec (default/current)
12
+ * - GET /api/openapi-v1.json - v1 spec
13
+ * - GET /api/openapi-v2.json - v2 spec
14
+ */
15
+
16
+ const { Router } = require('express');
17
+ const { createAppHandler } = require('./../app-handler-helpers');
18
+ const {
19
+ generateOpenApiSpec,
20
+ generateOpenApiSpecV1,
21
+ generateOpenApiSpecV2,
22
+ } = require('../../openapi/openapi-spec-generator');
23
+
24
+ const router = Router();
25
+
26
+ let cachedAppDefinition = null;
27
+
28
+ /**
29
+ * Load the appDefinition for spec generation
30
+ * Lazy-loads and caches to avoid performance overhead
31
+ */
32
+ function loadAppDefinitionForDocs() {
33
+ if (cachedAppDefinition) return cachedAppDefinition;
34
+
35
+ try {
36
+ const { loadAppDefinition } = require('../app-definition-loader');
37
+ const { integrations } = loadAppDefinition();
38
+ cachedAppDefinition = { integrations };
39
+ return cachedAppDefinition;
40
+ } catch (error) {
41
+ // App definition not available (e.g., in test environment)
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Generate Scalar HTML with version selector
48
+ * @param {Object} options - Configuration options
49
+ * @param {string} options.specUrl - Primary spec URL
50
+ * @param {Array} options.sources - Array of spec sources for version selector
51
+ * @param {string} options.title - Page title
52
+ */
53
+ function generateScalarHtml({
54
+ specUrl,
55
+ sources,
56
+ title = 'Frigg API Documentation',
57
+ }) {
58
+ // If sources provided, use multi-spec configuration
59
+ const config = sources ? { sources } : { url: specUrl };
60
+
61
+ return `<!DOCTYPE html>
62
+ <html>
63
+ <head>
64
+ <title>${title}</title>
65
+ <meta charset="utf-8" />
66
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
67
+ <style>
68
+ body { margin: 0; padding: 0; }
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <script id="api-reference" data-configuration='${JSON.stringify(
73
+ config
74
+ )}'></script>
75
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
76
+ </body>
77
+ </html>`;
78
+ }
79
+
80
+ // ============================================================================
81
+ // OpenAPI Spec Endpoints
82
+ // ============================================================================
83
+
84
+ /**
85
+ * GET /api/openapi.json - Default (v2) OpenAPI spec
86
+ */
87
+ router.get('/api/openapi.json', (req, res) => {
88
+ try {
89
+ const serverUrl = `${req.protocol}://${req.get('host')}`;
90
+ const appDefinition = loadAppDefinitionForDocs();
91
+ const spec = generateOpenApiSpecV2(appDefinition, { serverUrl });
92
+ res.json(spec);
93
+ } catch (error) {
94
+ console.error('Failed to generate OpenAPI spec:', error.message);
95
+ res.status(500).json({ error: 'Failed to load API specification' });
96
+ }
97
+ });
98
+
99
+ /**
100
+ * GET /api/openapi-v1.json - v1 API OpenAPI spec
101
+ */
102
+ router.get('/api/openapi-v1.json', (req, res) => {
103
+ try {
104
+ const serverUrl = `${req.protocol}://${req.get('host')}`;
105
+ const appDefinition = loadAppDefinitionForDocs();
106
+ const spec = generateOpenApiSpecV1(appDefinition, { serverUrl });
107
+ res.json(spec);
108
+ } catch (error) {
109
+ console.error('Failed to generate v1 OpenAPI spec:', error.message);
110
+ res.status(500).json({ error: 'Failed to load API specification' });
111
+ }
112
+ });
113
+
114
+ /**
115
+ * GET /api/openapi-v2.json - v2 API OpenAPI spec
116
+ */
117
+ router.get('/api/openapi-v2.json', (req, res) => {
118
+ try {
119
+ const serverUrl = `${req.protocol}://${req.get('host')}`;
120
+ const appDefinition = loadAppDefinitionForDocs();
121
+ const spec = generateOpenApiSpecV2(appDefinition, { serverUrl });
122
+ res.json(spec);
123
+ } catch (error) {
124
+ console.error('Failed to generate v2 OpenAPI spec:', error.message);
125
+ res.status(500).json({ error: 'Failed to load API specification' });
126
+ }
127
+ });
128
+
129
+ // ============================================================================
130
+ // Documentation UI Endpoints
131
+ // ============================================================================
132
+
133
+ /**
134
+ * GET /api/docs - Main documentation with version selector
135
+ * Shows both v1 and v2 APIs with a dropdown to switch between them
136
+ */
137
+ router.get('/api/docs', (_req, res) => {
138
+ const html = generateScalarHtml({
139
+ sources: [
140
+ {
141
+ title: 'API v2 (Current)',
142
+ slug: 'v2',
143
+ url: '/api/openapi-v2.json',
144
+ },
145
+ {
146
+ title: 'API v1 (Legacy)',
147
+ slug: 'v1',
148
+ url: '/api/openapi-v1.json',
149
+ },
150
+ ],
151
+ title: 'Frigg API Documentation',
152
+ });
153
+ res.type('html').send(html);
154
+ });
155
+
156
+ /**
157
+ * GET /api/v1/docs - v1-specific documentation
158
+ */
159
+ router.get('/api/v1/docs', (_req, res) => {
160
+ const html = generateScalarHtml({
161
+ specUrl: '/api/openapi-v1.json',
162
+ title: 'Frigg API v1 Documentation',
163
+ });
164
+ res.type('html').send(html);
165
+ });
166
+
167
+ /**
168
+ * GET /api/v2/docs - v2-specific documentation
169
+ */
170
+ router.get('/api/v2/docs', (_req, res) => {
171
+ const html = generateScalarHtml({
172
+ specUrl: '/api/openapi-v2.json',
173
+ title: 'Frigg API v2 Documentation',
174
+ });
175
+ res.type('html').send(html);
176
+ });
177
+
178
+ const handler = createAppHandler('HTTP Event: Docs', router, false);
179
+
180
+ module.exports = { handler, router };