@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,169 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ AuthorizationSession,
4
+ } = require('../domain/entities/AuthorizationSession');
5
+ const {
6
+ AuthorizationSessionRepositoryInterface,
7
+ } = require('./authorization-session-repository-interface');
8
+
9
+ /**
10
+ * MongoDB Authorization Session Repository Adapter
11
+ * Handles AuthorizationSession persistence operations for MongoDB via Prisma
12
+ *
13
+ * MongoDB-specific characteristics:
14
+ * - Uses String IDs (Prisma's default for MongoDB)
15
+ * - TTL index on expiresAt for automatic cleanup
16
+ * - No ID conversion needed (IDs are already strings)
17
+ *
18
+ * Schema Requirements (Prisma):
19
+ * ```prisma
20
+ * model AuthorizationSession {
21
+ * id String @id @default(auto()) @map("_id") @db.ObjectId
22
+ * sessionId String @unique
23
+ * userId String
24
+ * entityType String
25
+ * currentStep Int @default(1)
26
+ * maxSteps Int
27
+ * stepData Json @default("{}")
28
+ * expiresAt DateTime
29
+ * completed Boolean @default(false)
30
+ * createdAt DateTime @default(now())
31
+ * updatedAt DateTime @updatedAt
32
+ *
33
+ * @@index([sessionId])
34
+ * @@index([userId, entityType])
35
+ * @@index([expiresAt])
36
+ * @@index([completed])
37
+ * }
38
+ * ```
39
+ */
40
+ class AuthorizationSessionRepositoryMongo extends AuthorizationSessionRepositoryInterface {
41
+ constructor(prismaClient = prisma) {
42
+ super();
43
+ this.prisma = prismaClient; // Allow injection for testing
44
+ }
45
+
46
+ /**
47
+ * Create a new authorization session
48
+ *
49
+ * @param {AuthorizationSession} session - Session entity to create
50
+ * @returns {Promise<AuthorizationSession>} Created session entity
51
+ */
52
+ async create(session) {
53
+ const doc = await this.prisma.authorizationSession.create({
54
+ data: {
55
+ sessionId: session.sessionId,
56
+ userId: session.userId,
57
+ entityType: session.entityType,
58
+ currentStep: session.currentStep,
59
+ maxSteps: session.maxSteps,
60
+ stepData: session.stepData,
61
+ expiresAt: session.expiresAt,
62
+ completed: session.completed,
63
+ },
64
+ });
65
+
66
+ return this._toEntity(doc);
67
+ }
68
+
69
+ /**
70
+ * Find session by session ID
71
+ * Excludes expired sessions
72
+ *
73
+ * @param {string} sessionId - Unique session identifier
74
+ * @returns {Promise<AuthorizationSession|null>} Session entity or null
75
+ */
76
+ async findBySessionId(sessionId) {
77
+ const doc = await this.prisma.authorizationSession.findFirst({
78
+ where: {
79
+ sessionId,
80
+ expiresAt: { gt: new Date() },
81
+ },
82
+ });
83
+
84
+ return doc ? this._toEntity(doc) : null;
85
+ }
86
+
87
+ /**
88
+ * Find active session for user and entity type
89
+ * Returns most recent active session
90
+ *
91
+ * @param {string} userId - User ID
92
+ * @param {string} entityType - Entity type (module name)
93
+ * @returns {Promise<AuthorizationSession|null>} Session entity or null
94
+ */
95
+ async findActiveSession(userId, entityType) {
96
+ const doc = await this.prisma.authorizationSession.findFirst({
97
+ where: {
98
+ userId,
99
+ entityType,
100
+ completed: false,
101
+ expiresAt: { gt: new Date() },
102
+ },
103
+ orderBy: { createdAt: 'desc' },
104
+ });
105
+
106
+ return doc ? this._toEntity(doc) : null;
107
+ }
108
+
109
+ /**
110
+ * Update existing session
111
+ *
112
+ * @param {AuthorizationSession} session - Session entity with updated data
113
+ * @returns {Promise<AuthorizationSession>} Updated session entity
114
+ */
115
+ async update(session) {
116
+ const updated = await this.prisma.authorizationSession.update({
117
+ where: { sessionId: session.sessionId },
118
+ data: {
119
+ currentStep: session.currentStep,
120
+ stepData: session.stepData,
121
+ completed: session.completed,
122
+ updatedAt: new Date(),
123
+ },
124
+ });
125
+
126
+ return this._toEntity(updated);
127
+ }
128
+
129
+ /**
130
+ * Delete expired sessions (cleanup operation)
131
+ * Note: MongoDB TTL index handles automatic deletion, but this provides
132
+ * manual cleanup capability
133
+ *
134
+ * @returns {Promise<number>} Number of deleted sessions
135
+ */
136
+ async deleteExpired() {
137
+ const result = await this.prisma.authorizationSession.deleteMany({
138
+ where: {
139
+ expiresAt: { lt: new Date() },
140
+ },
141
+ });
142
+
143
+ return result.count;
144
+ }
145
+
146
+ /**
147
+ * Convert Prisma document to domain entity
148
+ *
149
+ * @private
150
+ * @param {Object} doc - Prisma document
151
+ * @returns {AuthorizationSession} Domain entity
152
+ */
153
+ _toEntity(doc) {
154
+ return new AuthorizationSession({
155
+ sessionId: doc.sessionId,
156
+ userId: doc.userId,
157
+ entityType: doc.entityType,
158
+ currentStep: doc.currentStep,
159
+ maxSteps: doc.maxSteps,
160
+ stepData: doc.stepData,
161
+ expiresAt: doc.expiresAt,
162
+ completed: doc.completed,
163
+ createdAt: doc.createdAt,
164
+ updatedAt: doc.updatedAt,
165
+ });
166
+ }
167
+ }
168
+
169
+ module.exports = { AuthorizationSessionRepositoryMongo };
@@ -0,0 +1,170 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ AuthorizationSession,
4
+ } = require('../domain/entities/AuthorizationSession');
5
+ const {
6
+ AuthorizationSessionRepositoryInterface,
7
+ } = require('./authorization-session-repository-interface');
8
+
9
+ /**
10
+ * PostgreSQL Authorization Session Repository Adapter
11
+ * Handles AuthorizationSession persistence operations for PostgreSQL via Prisma
12
+ *
13
+ * PostgreSQL-specific characteristics:
14
+ * - Uses Int IDs (auto-incrementing)
15
+ * - Indexes on sessionId, userId+entityType, expiresAt, completed
16
+ * - JSON/JSONB support for stepData
17
+ * - No automatic TTL (manual cleanup via deleteExpired)
18
+ *
19
+ * Schema Requirements (Prisma):
20
+ * ```prisma
21
+ * model AuthorizationSession {
22
+ * id Int @id @default(autoincrement())
23
+ * sessionId String @unique
24
+ * userId String
25
+ * entityType String
26
+ * currentStep Int @default(1)
27
+ * maxSteps Int
28
+ * stepData Json @default("{}")
29
+ * expiresAt DateTime
30
+ * completed Boolean @default(false)
31
+ * createdAt DateTime @default(now())
32
+ * updatedAt DateTime @updatedAt
33
+ *
34
+ * @@index([sessionId])
35
+ * @@index([userId, entityType])
36
+ * @@index([expiresAt])
37
+ * @@index([completed])
38
+ * }
39
+ * ```
40
+ */
41
+ class AuthorizationSessionRepositoryPostgres extends AuthorizationSessionRepositoryInterface {
42
+ constructor(prismaClient = prisma) {
43
+ super();
44
+ this.prisma = prismaClient; // Allow injection for testing
45
+ }
46
+
47
+ /**
48
+ * Create a new authorization session
49
+ *
50
+ * @param {AuthorizationSession} session - Session entity to create
51
+ * @returns {Promise<AuthorizationSession>} Created session entity
52
+ */
53
+ async create(session) {
54
+ const created = await this.prisma.authorizationSession.create({
55
+ data: {
56
+ sessionId: session.sessionId,
57
+ userId: session.userId,
58
+ entityType: session.entityType,
59
+ currentStep: session.currentStep,
60
+ maxSteps: session.maxSteps,
61
+ stepData: session.stepData,
62
+ expiresAt: session.expiresAt,
63
+ completed: session.completed,
64
+ },
65
+ });
66
+
67
+ return this._toEntity(created);
68
+ }
69
+
70
+ /**
71
+ * Find session by session ID
72
+ * Excludes expired sessions
73
+ *
74
+ * @param {string} sessionId - Unique session identifier
75
+ * @returns {Promise<AuthorizationSession|null>} Session entity or null
76
+ */
77
+ async findBySessionId(sessionId) {
78
+ const record = await this.prisma.authorizationSession.findFirst({
79
+ where: {
80
+ sessionId,
81
+ expiresAt: { gt: new Date() },
82
+ },
83
+ });
84
+
85
+ return record ? this._toEntity(record) : null;
86
+ }
87
+
88
+ /**
89
+ * Find active session for user and entity type
90
+ * Returns most recent active session
91
+ *
92
+ * @param {string} userId - User ID
93
+ * @param {string} entityType - Entity type (module name)
94
+ * @returns {Promise<AuthorizationSession|null>} Session entity or null
95
+ */
96
+ async findActiveSession(userId, entityType) {
97
+ const record = await this.prisma.authorizationSession.findFirst({
98
+ where: {
99
+ userId,
100
+ entityType,
101
+ completed: false,
102
+ expiresAt: { gt: new Date() },
103
+ },
104
+ orderBy: { createdAt: 'desc' },
105
+ });
106
+
107
+ return record ? this._toEntity(record) : null;
108
+ }
109
+
110
+ /**
111
+ * Update existing session
112
+ *
113
+ * @param {AuthorizationSession} session - Session entity with updated data
114
+ * @returns {Promise<AuthorizationSession>} Updated session entity
115
+ */
116
+ async update(session) {
117
+ const updated = await this.prisma.authorizationSession.update({
118
+ where: { sessionId: session.sessionId },
119
+ data: {
120
+ currentStep: session.currentStep,
121
+ stepData: session.stepData,
122
+ completed: session.completed,
123
+ updatedAt: new Date(),
124
+ },
125
+ });
126
+
127
+ return this._toEntity(updated);
128
+ }
129
+
130
+ /**
131
+ * Delete expired sessions (cleanup operation)
132
+ * PostgreSQL doesn't have TTL indexes, so this must be called periodically
133
+ * Recommend running as cron job or scheduled task
134
+ *
135
+ * @returns {Promise<number>} Number of deleted sessions
136
+ */
137
+ async deleteExpired() {
138
+ const result = await this.prisma.authorizationSession.deleteMany({
139
+ where: {
140
+ expiresAt: { lt: new Date() },
141
+ },
142
+ });
143
+
144
+ return result.count;
145
+ }
146
+
147
+ /**
148
+ * Convert Prisma record to domain entity
149
+ *
150
+ * @private
151
+ * @param {Object} record - Prisma record
152
+ * @returns {AuthorizationSession} Domain entity
153
+ */
154
+ _toEntity(record) {
155
+ return new AuthorizationSession({
156
+ sessionId: record.sessionId,
157
+ userId: record.userId,
158
+ entityType: record.entityType,
159
+ currentStep: record.currentStep,
160
+ maxSteps: record.maxSteps,
161
+ stepData: record.stepData,
162
+ expiresAt: record.expiresAt,
163
+ completed: record.completed,
164
+ createdAt: record.createdAt,
165
+ updatedAt: record.updatedAt,
166
+ });
167
+ }
168
+ }
169
+
170
+ module.exports = { AuthorizationSessionRepositoryPostgres };
@@ -9,7 +9,9 @@ const {
9
9
  deleteOne,
10
10
  } = require('../../database/documentdb-utils');
11
11
  const { ModuleRepositoryInterface } = require('./module-repository-interface');
12
- const { DocumentDBEncryptionService } = require('../../database/documentdb-encryption-service');
12
+ const {
13
+ DocumentDBEncryptionService,
14
+ } = require('../../database/documentdb-encryption-service');
13
15
 
14
16
  /**
15
17
  * Module/Entity repository for DocumentDB.
@@ -50,16 +52,34 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
50
52
  }
51
53
  const filter = { userId: objectId };
52
54
  const docs = await findMany(this.prisma, 'Entity', filter);
53
- const credentialMap = await this._fetchCredentialsBulk(docs.map((doc) => doc.credentialId));
54
- return docs.map((doc) => this._mapEntity(doc, credentialMap.get(fromObjectId(doc.credentialId)) || null));
55
+ const credentialMap = await this._fetchCredentialsBulk(
56
+ docs.map((doc) => doc.credentialId)
57
+ );
58
+ return docs.map((doc) =>
59
+ this._mapEntity(
60
+ doc,
61
+ credentialMap.get(fromObjectId(doc.credentialId)) || null
62
+ )
63
+ );
55
64
  }
56
65
 
57
66
  async findEntitiesByIds(entitiesIds) {
58
- const ids = (entitiesIds || []).map((id) => toObjectId(id)).filter(Boolean);
67
+ const ids = (entitiesIds || [])
68
+ .map((id) => toObjectId(id))
69
+ .filter(Boolean);
59
70
  if (ids.length === 0) return [];
60
- const docs = await findMany(this.prisma, 'Entity', { _id: { $in: ids } });
61
- const credentialMap = await this._fetchCredentialsBulk(docs.map((doc) => doc.credentialId));
62
- return docs.map((doc) => this._mapEntity(doc, credentialMap.get(fromObjectId(doc.credentialId)) || null));
71
+ const docs = await findMany(this.prisma, 'Entity', {
72
+ _id: { $in: ids },
73
+ });
74
+ const credentialMap = await this._fetchCredentialsBulk(
75
+ docs.map((doc) => doc.credentialId)
76
+ );
77
+ return docs.map((doc) =>
78
+ this._mapEntity(
79
+ doc,
80
+ credentialMap.get(fromObjectId(doc.credentialId)) || null
81
+ )
82
+ );
63
83
  }
64
84
 
65
85
  async findEntitiesByUserIdAndModuleName(userId, moduleName) {
@@ -72,8 +92,15 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
72
92
  moduleName,
73
93
  };
74
94
  const docs = await findMany(this.prisma, 'Entity', filter);
75
- const credentialMap = await this._fetchCredentialsBulk(docs.map((doc) => doc.credentialId));
76
- return docs.map((doc) => this._mapEntity(doc, credentialMap.get(fromObjectId(doc.credentialId)) || null));
95
+ const credentialMap = await this._fetchCredentialsBulk(
96
+ docs.map((doc) => doc.credentialId)
97
+ );
98
+ return docs.map((doc) =>
99
+ this._mapEntity(
100
+ doc,
101
+ credentialMap.get(fromObjectId(doc.credentialId)) || null
102
+ )
103
+ );
77
104
  }
78
105
 
79
106
  async unsetCredential(entityId) {
@@ -174,7 +201,9 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
174
201
  async deleteEntity(entityId) {
175
202
  const objectId = toObjectId(entityId);
176
203
  if (!objectId) return false;
177
- const result = await deleteOne(this.prisma, 'Entity', { _id: objectId });
204
+ const result = await deleteOne(this.prisma, 'Entity', {
205
+ _id: objectId,
206
+ });
178
207
  const deleted = result?.n ?? 0;
179
208
  return deleted > 0;
180
209
  }
@@ -190,13 +219,17 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
190
219
 
191
220
  // Use raw findOne to bypass Prisma encryption extension
192
221
  const rawCredential = await findOne(this.prisma, 'Credential', {
193
- _id: objectId
222
+ _id: objectId,
194
223
  });
195
224
 
196
225
  if (!rawCredential) return null;
197
226
 
198
227
  // Decrypt sensitive fields using service
199
- const decryptedCredential = await this.encryptionService.decryptFields('Credential', rawCredential);
228
+ const decryptedCredential =
229
+ await this.encryptionService.decryptFields(
230
+ 'Credential',
231
+ rawCredential
232
+ );
200
233
 
201
234
  // Return in same format
202
235
  const credential = {
@@ -206,12 +239,15 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
206
239
  authIsValid: decryptedCredential.authIsValid ?? null,
207
240
  createdAt: decryptedCredential.createdAt,
208
241
  updatedAt: decryptedCredential.updatedAt,
209
- data: decryptedCredential.data
242
+ data: decryptedCredential.data,
210
243
  };
211
244
 
212
245
  return this._convertCredentialIds(credential);
213
246
  } catch (error) {
214
- console.error(`Failed to fetch/decrypt credential ${id}:`, error.message);
247
+ console.error(
248
+ `Failed to fetch/decrypt credential ${id}:`,
249
+ error.message
250
+ );
215
251
  // Return null instead of throwing to allow graceful degradation
216
252
  // This repository is read-only (doesn't create/update credentials)
217
253
  // Entities can still be loaded even if their credential is corrupted/unreadable
@@ -229,45 +265,55 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
229
265
 
230
266
  try {
231
267
  // Convert string IDs to ObjectIds for bulk query
232
- const objectIds = ids.map(id => toObjectId(id)).filter(Boolean);
268
+ const objectIds = ids.map((id) => toObjectId(id)).filter(Boolean);
233
269
  if (objectIds.length === 0) return new Map();
234
270
 
235
271
  // Use raw findMany to bypass Prisma encryption extension
236
272
  const rawCredentials = await findMany(this.prisma, 'Credential', {
237
- _id: { $in: objectIds }
273
+ _id: { $in: objectIds },
238
274
  });
239
275
 
240
276
  // Decrypt all credentials in parallel
241
- const decryptionPromises = rawCredentials.map(async (rawCredential) => {
242
- try {
243
- // Decrypt sensitive fields using service
244
- const decryptedCredential = await this.encryptionService.decryptFields('Credential', rawCredential);
245
-
246
- // Build credential object in same format as Prisma would return
247
- const credential = {
248
- id: fromObjectId(decryptedCredential._id),
249
- userId: fromObjectId(decryptedCredential.userId),
250
- externalId: decryptedCredential.externalId ?? null,
251
- authIsValid: decryptedCredential.authIsValid ?? null,
252
- createdAt: decryptedCredential.createdAt,
253
- updatedAt: decryptedCredential.updatedAt,
254
- data: decryptedCredential.data
255
- };
256
-
257
- return this._convertCredentialIds(credential);
258
- } catch (error) {
259
- const credId = fromObjectId(rawCredential._id);
260
- console.error(`Failed to decrypt credential ${credId}:`, error.message);
261
- return null;
277
+ const decryptionPromises = rawCredentials.map(
278
+ async (rawCredential) => {
279
+ try {
280
+ // Decrypt sensitive fields using service
281
+ const decryptedCredential =
282
+ await this.encryptionService.decryptFields(
283
+ 'Credential',
284
+ rawCredential
285
+ );
286
+
287
+ // Build credential object in same format as Prisma would return
288
+ const credential = {
289
+ id: fromObjectId(decryptedCredential._id),
290
+ userId: fromObjectId(decryptedCredential.userId),
291
+ externalId: decryptedCredential.externalId ?? null,
292
+ authIsValid:
293
+ decryptedCredential.authIsValid ?? null,
294
+ createdAt: decryptedCredential.createdAt,
295
+ updatedAt: decryptedCredential.updatedAt,
296
+ data: decryptedCredential.data,
297
+ };
298
+
299
+ return this._convertCredentialIds(credential);
300
+ } catch (error) {
301
+ const credId = fromObjectId(rawCredential._id);
302
+ console.error(
303
+ `Failed to decrypt credential ${credId}:`,
304
+ error.message
305
+ );
306
+ return null;
307
+ }
262
308
  }
263
- });
309
+ );
264
310
 
265
311
  // Wait for all decryptions to complete
266
312
  const decryptedCredentials = await Promise.all(decryptionPromises);
267
313
 
268
314
  // Build Map from results, filtering out nulls
269
315
  const map = new Map();
270
- decryptedCredentials.forEach(credential => {
316
+ decryptedCredentials.forEach((credential) => {
271
317
  if (credential) {
272
318
  map.set(credential.id, credential);
273
319
  }
@@ -308,12 +354,15 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
308
354
  if (userObj) query.userId = userObj;
309
355
  }
310
356
  if (filter.credential || filter.credentialId) {
311
- const credObj = toObjectId(filter.credential || filter.credentialId);
357
+ const credObj = toObjectId(
358
+ filter.credential || filter.credentialId
359
+ );
312
360
  if (credObj) query.credentialId = credObj;
313
361
  }
314
362
  if (filter.name) query.name = filter.name;
315
363
  if (filter.moduleName) query.moduleName = filter.moduleName;
316
364
  if (filter.externalId) query.externalId = filter.externalId;
365
+ if (filter.isGlobal !== undefined) query.isGlobal = filter.isGlobal;
317
366
  return query;
318
367
  }
319
368
 
@@ -332,4 +381,3 @@ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
332
381
  }
333
382
 
334
383
  module.exports = { ModuleRepositoryDocumentDB };
335
-
@@ -1,8 +1,4 @@
1
- const { ModuleRepositoryMongo } = require('./module-repository-mongo');
2
1
  const { ModuleRepositoryPostgres } = require('./module-repository-postgres');
3
- const {
4
- ModuleRepositoryDocumentDB,
5
- } = require('./module-repository-documentdb');
6
2
  const config = require('../../database/config');
7
3
 
8
4
  /**
@@ -16,12 +12,14 @@ function createModuleRepository() {
16
12
 
17
13
  switch (dbType) {
18
14
  case 'mongodb':
15
+ const { ModuleRepositoryMongo } = require('./module-repository-mongo');
19
16
  return new ModuleRepositoryMongo();
20
17
 
21
18
  case 'postgresql':
22
19
  return new ModuleRepositoryPostgres();
23
20
 
24
21
  case 'documentdb':
22
+ const { ModuleRepositoryDocumentDB } = require('./module-repository-documentdb');
25
23
  return new ModuleRepositoryDocumentDB();
26
24
 
27
25
  default:
@@ -34,7 +32,7 @@ function createModuleRepository() {
34
32
  module.exports = {
35
33
  createModuleRepository,
36
34
  // Export adapters for direct testing
37
- ModuleRepositoryMongo,
35
+ get ModuleRepositoryMongo() { return require('./module-repository-mongo').ModuleRepositoryMongo; },
38
36
  ModuleRepositoryPostgres,
39
- ModuleRepositoryDocumentDB,
37
+ get ModuleRepositoryDocumentDB() { return require('./module-repository-documentdb').ModuleRepositoryDocumentDB; },
40
38
  };
@@ -91,6 +91,19 @@ class ModuleRepositoryInterface {
91
91
  throw new Error('Method findEntity must be implemented by subclass');
92
92
  }
93
93
 
94
+ /**
95
+ * Find entities matching filter criteria
96
+ *
97
+ * @param {Object} filter - Filter criteria
98
+ * @returns {Promise<Array>} Array of entity objects
99
+ * @abstract
100
+ */
101
+ async findEntitiesBy(filter) {
102
+ throw new Error(
103
+ 'Method findEntitiesBy must be implemented by subclass'
104
+ );
105
+ }
106
+
94
107
  /**
95
108
  * Create a new entity
96
109
  *