@friggframework/core 2.0.0-next.41 → 2.0.0-next.43

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 (197) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +931 -50
  3. package/application/commands/README.md +421 -0
  4. package/application/commands/credential-commands.js +224 -0
  5. package/application/commands/entity-commands.js +315 -0
  6. package/application/commands/integration-commands.js +160 -0
  7. package/application/commands/integration-commands.test.js +123 -0
  8. package/application/commands/user-commands.js +213 -0
  9. package/application/index.js +69 -0
  10. package/core/CLAUDE.md +690 -0
  11. package/core/create-handler.js +0 -6
  12. package/credential/repositories/credential-repository-factory.js +47 -0
  13. package/credential/repositories/credential-repository-interface.js +98 -0
  14. package/credential/repositories/credential-repository-mongo.js +301 -0
  15. package/credential/repositories/credential-repository-postgres.js +307 -0
  16. package/credential/repositories/credential-repository.js +307 -0
  17. package/credential/use-cases/get-credential-for-user.js +21 -0
  18. package/credential/use-cases/update-authentication-status.js +15 -0
  19. package/database/config.js +117 -0
  20. package/database/encryption/README.md +683 -0
  21. package/database/encryption/encryption-integration.test.js +553 -0
  22. package/database/encryption/encryption-schema-registry.js +141 -0
  23. package/database/encryption/encryption-schema-registry.test.js +392 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/field-encryption-service.test.js +525 -0
  26. package/database/encryption/logger.js +79 -0
  27. package/database/encryption/mongo-decryption-fix-verification.test.js +348 -0
  28. package/database/encryption/postgres-decryption-fix-verification.test.js +371 -0
  29. package/database/encryption/postgres-relation-decryption.test.js +245 -0
  30. package/database/encryption/prisma-encryption-extension.js +222 -0
  31. package/database/encryption/prisma-encryption-extension.test.js +439 -0
  32. package/database/index.js +25 -12
  33. package/database/models/readme.md +1 -0
  34. package/database/prisma.js +162 -0
  35. package/database/repositories/health-check-repository-factory.js +38 -0
  36. package/database/repositories/health-check-repository-interface.js +86 -0
  37. package/database/repositories/health-check-repository-mongodb.js +72 -0
  38. package/database/repositories/health-check-repository-postgres.js +75 -0
  39. package/database/repositories/health-check-repository.js +108 -0
  40. package/database/use-cases/check-database-health-use-case.js +34 -0
  41. package/database/use-cases/check-encryption-health-use-case.js +82 -0
  42. package/database/use-cases/test-encryption-use-case.js +252 -0
  43. package/encrypt/Cryptor.js +20 -152
  44. package/encrypt/index.js +1 -2
  45. package/encrypt/test-encrypt.js +0 -2
  46. package/handlers/app-definition-loader.js +38 -0
  47. package/handlers/app-handler-helpers.js +0 -3
  48. package/handlers/auth-flow.integration.test.js +147 -0
  49. package/handlers/backend-utils.js +25 -45
  50. package/handlers/integration-event-dispatcher.js +54 -0
  51. package/handlers/integration-event-dispatcher.test.js +141 -0
  52. package/handlers/routers/HEALTHCHECK.md +103 -1
  53. package/handlers/routers/auth.js +3 -14
  54. package/handlers/routers/health.js +63 -424
  55. package/handlers/routers/health.test.js +7 -0
  56. package/handlers/routers/integration-defined-routers.js +8 -5
  57. package/handlers/routers/user.js +27 -5
  58. package/handlers/routers/websocket.js +5 -3
  59. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  60. package/handlers/use-cases/check-integrations-health-use-case.js +32 -0
  61. package/handlers/workers/integration-defined-workers.js +6 -3
  62. package/index.js +45 -22
  63. package/integrations/index.js +12 -10
  64. package/integrations/integration-base.js +224 -53
  65. package/integrations/integration-router.js +386 -178
  66. package/integrations/options.js +1 -1
  67. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  68. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  69. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  70. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  71. package/integrations/repositories/integration-mapping-repository.js +156 -0
  72. package/integrations/repositories/integration-repository-factory.js +44 -0
  73. package/integrations/repositories/integration-repository-interface.js +115 -0
  74. package/integrations/repositories/integration-repository-mongo.js +271 -0
  75. package/integrations/repositories/integration-repository-postgres.js +319 -0
  76. package/integrations/tests/doubles/dummy-integration-class.js +90 -0
  77. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  78. package/integrations/tests/use-cases/create-integration.test.js +131 -0
  79. package/integrations/tests/use-cases/delete-integration-for-user.test.js +150 -0
  80. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +92 -0
  81. package/integrations/tests/use-cases/get-integration-for-user.test.js +150 -0
  82. package/integrations/tests/use-cases/get-integration-instance.test.js +176 -0
  83. package/integrations/tests/use-cases/get-integrations-for-user.test.js +176 -0
  84. package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
  85. package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
  86. package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
  87. package/integrations/tests/use-cases/update-integration.test.js +141 -0
  88. package/integrations/use-cases/create-integration.js +83 -0
  89. package/integrations/use-cases/delete-integration-for-user.js +73 -0
  90. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  91. package/integrations/use-cases/get-integration-for-user.js +78 -0
  92. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  93. package/integrations/use-cases/get-integration-instance.js +83 -0
  94. package/integrations/use-cases/get-integrations-for-user.js +87 -0
  95. package/integrations/use-cases/get-possible-integrations.js +27 -0
  96. package/integrations/use-cases/index.js +11 -0
  97. package/integrations/use-cases/load-integration-context-full.test.js +329 -0
  98. package/integrations/use-cases/load-integration-context.js +71 -0
  99. package/integrations/use-cases/load-integration-context.test.js +114 -0
  100. package/integrations/use-cases/update-integration-messages.js +44 -0
  101. package/integrations/use-cases/update-integration-status.js +32 -0
  102. package/integrations/use-cases/update-integration.js +93 -0
  103. package/integrations/utils/map-integration-dto.js +36 -0
  104. package/jest-global-setup-noop.js +3 -0
  105. package/jest-global-teardown-noop.js +3 -0
  106. package/{module-plugin → modules}/entity.js +1 -0
  107. package/{module-plugin → modules}/index.js +0 -8
  108. package/modules/module-factory.js +56 -0
  109. package/modules/module-hydration.test.js +205 -0
  110. package/modules/module.js +221 -0
  111. package/modules/repositories/module-repository-factory.js +33 -0
  112. package/modules/repositories/module-repository-interface.js +129 -0
  113. package/modules/repositories/module-repository-mongo.js +386 -0
  114. package/modules/repositories/module-repository-postgres.js +437 -0
  115. package/modules/repositories/module-repository.js +327 -0
  116. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  117. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  118. package/modules/tests/doubles/test-module-factory.js +16 -0
  119. package/modules/tests/doubles/test-module-repository.js +39 -0
  120. package/modules/use-cases/get-entities-for-user.js +32 -0
  121. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  122. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  123. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  124. package/modules/use-cases/get-module.js +56 -0
  125. package/modules/use-cases/process-authorization-callback.js +122 -0
  126. package/modules/use-cases/refresh-entity-options.js +59 -0
  127. package/modules/use-cases/test-module-auth.js +55 -0
  128. package/modules/utils/map-module-dto.js +18 -0
  129. package/package.json +14 -6
  130. package/prisma-mongodb/schema.prisma +318 -0
  131. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  132. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  133. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  134. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  135. package/prisma-postgresql/schema.prisma +300 -0
  136. package/syncs/manager.js +468 -443
  137. package/syncs/repositories/sync-repository-factory.js +38 -0
  138. package/syncs/repositories/sync-repository-interface.js +109 -0
  139. package/syncs/repositories/sync-repository-mongo.js +239 -0
  140. package/syncs/repositories/sync-repository-postgres.js +319 -0
  141. package/syncs/sync.js +0 -1
  142. package/token/repositories/token-repository-factory.js +33 -0
  143. package/token/repositories/token-repository-interface.js +131 -0
  144. package/token/repositories/token-repository-mongo.js +212 -0
  145. package/token/repositories/token-repository-postgres.js +257 -0
  146. package/token/repositories/token-repository.js +219 -0
  147. package/types/integrations/index.d.ts +2 -6
  148. package/types/module-plugin/index.d.ts +5 -57
  149. package/types/syncs/index.d.ts +0 -2
  150. package/user/repositories/user-repository-factory.js +46 -0
  151. package/user/repositories/user-repository-interface.js +198 -0
  152. package/user/repositories/user-repository-mongo.js +250 -0
  153. package/user/repositories/user-repository-postgres.js +311 -0
  154. package/user/tests/doubles/test-user-repository.js +72 -0
  155. package/user/tests/use-cases/create-individual-user.test.js +24 -0
  156. package/user/tests/use-cases/create-organization-user.test.js +28 -0
  157. package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
  158. package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
  159. package/user/tests/use-cases/login-user.test.js +140 -0
  160. package/user/use-cases/create-individual-user.js +61 -0
  161. package/user/use-cases/create-organization-user.js +47 -0
  162. package/user/use-cases/create-token-for-user-id.js +30 -0
  163. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  164. package/user/use-cases/login-user.js +122 -0
  165. package/user/user.js +77 -0
  166. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  167. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  168. package/websocket/repositories/websocket-connection-repository-mongo.js +155 -0
  169. package/websocket/repositories/websocket-connection-repository-postgres.js +195 -0
  170. package/websocket/repositories/websocket-connection-repository.js +160 -0
  171. package/database/models/State.js +0 -9
  172. package/database/models/Token.js +0 -70
  173. package/database/mongo.js +0 -171
  174. package/encrypt/Cryptor.test.js +0 -32
  175. package/encrypt/encrypt.js +0 -104
  176. package/encrypt/encrypt.test.js +0 -1069
  177. package/handlers/routers/middleware/loadUser.js +0 -15
  178. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  179. package/integrations/create-frigg-backend.js +0 -31
  180. package/integrations/integration-factory.js +0 -251
  181. package/integrations/integration-mapping.js +0 -43
  182. package/integrations/integration-model.js +0 -46
  183. package/integrations/integration-user.js +0 -144
  184. package/integrations/test/integration-base.test.js +0 -144
  185. package/module-plugin/auther.js +0 -393
  186. package/module-plugin/credential.js +0 -22
  187. package/module-plugin/entity-manager.js +0 -70
  188. package/module-plugin/manager.js +0 -169
  189. package/module-plugin/module-factory.js +0 -61
  190. package/module-plugin/test/auther.test.js +0 -97
  191. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  192. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  193. /package/{module-plugin → modules}/requester/basic.js +0 -0
  194. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  195. /package/{module-plugin → modules}/requester/requester.js +0 -0
  196. /package/{module-plugin → modules}/requester/requester.test.js +0 -0
  197. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -2,7 +2,6 @@
2
2
  // REMOVING FOR NOW UNTIL WE ADD WEBPACK BACK IN
3
3
  // require('source-map-support').install();
4
4
 
5
- const { connectToDatabase } = require('../database/mongo');
6
5
  const { initDebugLog, flushDebugLog } = require('../logs');
7
6
  const { secretsToEnv } = require('./secrets-to-env');
8
7
 
@@ -11,7 +10,6 @@ const createHandler = (optionByName = {}) => {
11
10
  eventName = 'Event',
12
11
  isUserFacingResponse = true,
13
12
  method,
14
- shouldUseDatabase = true,
15
13
  } = optionByName;
16
14
 
17
15
  if (!method) {
@@ -34,10 +32,6 @@ const createHandler = (optionByName = {}) => {
34
32
  // Helps mongoose reuse the connection. Lowers response times.
35
33
  context.callbackWaitsForEmptyEventLoop = false;
36
34
 
37
- if (shouldUseDatabase) {
38
- await connectToDatabase();
39
- }
40
-
41
35
  // Run the Lambda
42
36
  return await method(event, context);
43
37
  } catch (error) {
@@ -0,0 +1,47 @@
1
+ const { CredentialRepositoryMongo } = require('./credential-repository-mongo');
2
+ const {
3
+ CredentialRepositoryPostgres,
4
+ } = require('./credential-repository-postgres');
5
+ const config = require('../../database/config');
6
+
7
+ /**
8
+ * Credential Repository Factory
9
+ * Creates the appropriate repository adapter based on database type
10
+ *
11
+ * Database-specific implementations:
12
+ * - MongoDB: Uses String IDs (ObjectId), no conversion needed
13
+ * - PostgreSQL: Uses Int IDs, converts String ↔ Int
14
+ *
15
+ * All repository methods return String IDs regardless of database type,
16
+ * ensuring application layer consistency.
17
+ *
18
+ * Usage:
19
+ * ```javascript
20
+ * const repository = createCredentialRepository();
21
+ * ```
22
+ *
23
+ * @returns {CredentialRepositoryInterface} Configured repository adapter
24
+ */
25
+ function createCredentialRepository() {
26
+ const dbType = config.DB_TYPE;
27
+
28
+ switch (dbType) {
29
+ case 'mongodb':
30
+ return new CredentialRepositoryMongo();
31
+
32
+ case 'postgresql':
33
+ return new CredentialRepositoryPostgres();
34
+
35
+ default:
36
+ throw new Error(
37
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
38
+ );
39
+ }
40
+ }
41
+
42
+ module.exports = {
43
+ createCredentialRepository,
44
+ // Export adapters for direct testing
45
+ CredentialRepositoryMongo,
46
+ CredentialRepositoryPostgres,
47
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Credential Repository Interface
3
+ * Abstract base class defining the contract for credential persistence adapters
4
+ *
5
+ * This follows the Port in Hexagonal Architecture:
6
+ * - Domain layer depends on this abstraction
7
+ * - Concrete adapters implement this interface
8
+ * - Use cases receive repositories via dependency injection
9
+ *
10
+ * Note: Currently, Credential model has identical structure across MongoDB and PostgreSQL,
11
+ * so CredentialRepository serves both. This interface exists for consistency and
12
+ * future-proofing if database-specific implementations become needed.
13
+ *
14
+ * @abstract
15
+ */
16
+ class CredentialRepositoryInterface {
17
+ /**
18
+ * Find credential by ID
19
+ *
20
+ * @param {string|number} id - Credential ID
21
+ * @returns {Promise<Object|null>} Credential object or null
22
+ * @abstract
23
+ */
24
+ async findCredentialById(id) {
25
+ throw new Error(
26
+ 'Method findCredentialById must be implemented by subclass'
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Update authentication status
32
+ *
33
+ * @param {string|number} credentialId - Credential ID
34
+ * @param {boolean} authIsValid - Authentication validity status
35
+ * @returns {Promise<Object>} Update result
36
+ * @abstract
37
+ */
38
+ async updateAuthenticationStatus(credentialId, authIsValid) {
39
+ throw new Error(
40
+ 'Method updateAuthenticationStatus must be implemented by subclass'
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Permanently remove a credential document
46
+ *
47
+ * @param {string|number} credentialId - Credential ID
48
+ * @returns {Promise<Object>} Deletion result
49
+ * @abstract
50
+ */
51
+ async deleteCredentialById(credentialId) {
52
+ throw new Error(
53
+ 'Method deleteCredentialById must be implemented by subclass'
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Create or update credential matching identifiers
59
+ *
60
+ * @param {{identifiers: Object, details: Object}} credentialDetails
61
+ * @returns {Promise<Object>} The persisted credential
62
+ * @abstract
63
+ */
64
+ async upsertCredential(credentialDetails) {
65
+ throw new Error(
66
+ 'Method upsertCredential must be implemented by subclass'
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Find a credential by filter criteria
72
+ *
73
+ * @param {Object} filter - Filter criteria
74
+ * @returns {Promise<Object|null>} Credential object or null if not found
75
+ * @abstract
76
+ */
77
+ async findCredential(filter) {
78
+ throw new Error(
79
+ 'Method findCredential must be implemented by subclass'
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Update a credential by ID
85
+ *
86
+ * @param {string|number} credentialId - Credential ID
87
+ * @param {Object} updates - Fields to update
88
+ * @returns {Promise<Object|null>} Updated credential object or null if not found
89
+ * @abstract
90
+ */
91
+ async updateCredential(credentialId, updates) {
92
+ throw new Error(
93
+ 'Method updateCredential must be implemented by subclass'
94
+ );
95
+ }
96
+ }
97
+
98
+ module.exports = { CredentialRepositoryInterface };
@@ -0,0 +1,301 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ CredentialRepositoryInterface,
4
+ } = require('./credential-repository-interface');
5
+
6
+ /**
7
+ * MongoDB Credential Repository Adapter
8
+ * Handles OAuth credentials and API tokens persistence with MongoDB
9
+ *
10
+ * MongoDB-specific characteristics:
11
+ * - Uses String IDs (ObjectId)
12
+ * - No ID conversion needed (IDs are already strings)
13
+ * - Dynamic schema support via JSON field
14
+ */
15
+ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
16
+ constructor() {
17
+ super();
18
+ this.prisma = prisma;
19
+ }
20
+
21
+ /**
22
+ * Find credential by ID
23
+ * Replaces: Credential.findById(id)
24
+ *
25
+ * @param {string} id - Credential ID
26
+ * @returns {Promise<Object|null>} Credential object or null
27
+ */
28
+ async findCredentialById(id) {
29
+ const credential = await this.prisma.credential.findUnique({
30
+ where: { id },
31
+ });
32
+
33
+ if (!credential) {
34
+ return null;
35
+ }
36
+
37
+ // Extract data from JSON field
38
+ const data = credential.data || {};
39
+
40
+ return {
41
+ _id: credential.id,
42
+ id: credential.id,
43
+ user: credential.userId,
44
+ userId: credential.userId,
45
+ externalId: credential.externalId,
46
+ authIsValid: credential.authIsValid,
47
+ subType: credential.subType,
48
+ ...data, // Spread OAuth tokens from JSON field
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Update authentication status
54
+ * Replaces: Credential.updateOne({ _id: credentialId }, { $set: { authIsValid } })
55
+ *
56
+ * @param {string} credentialId - Credential ID
57
+ * @param {boolean} authIsValid - Authentication validity status
58
+ * @returns {Promise<Object>} Update result
59
+ */
60
+ async updateAuthenticationStatus(credentialId, authIsValid) {
61
+ await this.prisma.credential.update({
62
+ where: { id: credentialId },
63
+ data: { authIsValid },
64
+ });
65
+
66
+ return { acknowledged: true, modifiedCount: 1 };
67
+ }
68
+
69
+ /**
70
+ * Permanently remove a credential document
71
+ * Replaces: Credential.deleteOne({ _id: credentialId })
72
+ *
73
+ * @param {string} credentialId - Credential ID
74
+ * @returns {Promise<Object>} Deletion result
75
+ */
76
+ async deleteCredentialById(credentialId) {
77
+ try {
78
+ await this.prisma.credential.delete({
79
+ where: { id: credentialId },
80
+ });
81
+ return { acknowledged: true, deletedCount: 1 };
82
+ } catch (error) {
83
+ if (error.code === 'P2025') {
84
+ // Record not found
85
+ return { acknowledged: true, deletedCount: 0 };
86
+ }
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Create or update credential matching identifiers
93
+ * Replaces: Credential.findOneAndUpdate(query, update, { upsert: true })
94
+ *
95
+ * @param {{identifiers: Object, details: Object}} credentialDetails
96
+ * @returns {Promise<Object>} The persisted credential
97
+ */
98
+ async upsertCredential(credentialDetails) {
99
+ const { identifiers, details } = credentialDetails;
100
+ if (!identifiers)
101
+ throw new Error('identifiers required to upsert credential');
102
+
103
+ // Build where clause from identifiers
104
+ const where = this._convertIdentifiersToWhere(identifiers);
105
+
106
+ // Separate schema fields from dynamic OAuth data
107
+ const {
108
+ user,
109
+ userId,
110
+ externalId,
111
+ authIsValid,
112
+ subType,
113
+ ...oauthData
114
+ } = details;
115
+
116
+ // Find existing credential
117
+ const existing = await this.prisma.credential.findFirst({ where });
118
+
119
+ if (existing) {
120
+ // Update existing - merge OAuth data into existing data JSON
121
+ const mergedData = { ...(existing.data || {}), ...oauthData };
122
+
123
+ const updated = await this.prisma.credential.update({
124
+ where: { id: existing.id },
125
+ data: {
126
+ userId: userId || user || existing.userId,
127
+ externalId:
128
+ externalId !== undefined
129
+ ? externalId
130
+ : existing.externalId,
131
+ authIsValid:
132
+ authIsValid !== undefined
133
+ ? authIsValid
134
+ : existing.authIsValid,
135
+ subType: subType !== undefined ? subType : existing.subType,
136
+ data: mergedData,
137
+ },
138
+ });
139
+
140
+ return {
141
+ id: updated.id,
142
+ externalId: updated.externalId,
143
+ userId: updated.userId,
144
+ authIsValid: updated.authIsValid,
145
+ ...(updated.data || {}),
146
+ };
147
+ }
148
+
149
+ // Create new credential
150
+ const created = await this.prisma.credential.create({
151
+ data: {
152
+ userId: userId || user,
153
+ externalId,
154
+ authIsValid: authIsValid,
155
+ subType,
156
+ data: oauthData,
157
+ },
158
+ });
159
+
160
+ return {
161
+ id: created.id,
162
+ externalId: created.externalId,
163
+ userId: created.userId,
164
+ authIsValid: created.authIsValid,
165
+ ...(created.data || {}),
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Find a credential by filter criteria
171
+ * Replaces: Credential.findOne(query)
172
+ *
173
+ * @param {Object} filter
174
+ * @param {string} [filter.userId] - User ID
175
+ * @param {string} [filter.externalId] - External ID
176
+ * @param {string} [filter.credentialId] - Credential ID
177
+ * @returns {Promise<Object|null>} Credential object or null if not found
178
+ */
179
+ async findCredential(filter) {
180
+ const where = this._convertFilterToWhere(filter);
181
+
182
+ const credential = await this.prisma.credential.findFirst({
183
+ where,
184
+ });
185
+
186
+ if (!credential) {
187
+ return null;
188
+ }
189
+
190
+ const data = credential.data || {};
191
+
192
+ return {
193
+ id: credential.id,
194
+ userId: credential.userId,
195
+ externalId: credential.externalId,
196
+ authIsValid: credential.authIsValid,
197
+ access_token: data.access_token,
198
+ refresh_token: data.refresh_token,
199
+ domain: data.domain,
200
+ ...data,
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Update a credential by ID
206
+ * Replaces: Credential.findByIdAndUpdate(credentialId, { $set: updates })
207
+ *
208
+ * @param {string} credentialId - Credential ID
209
+ * @param {Object} updates - Fields to update
210
+ * @returns {Promise<Object|null>} Updated credential object or null if not found
211
+ */
212
+ async updateCredential(credentialId, updates) {
213
+ // Get existing credential to merge OAuth data
214
+ const existing = await this.prisma.credential.findUnique({
215
+ where: { id: credentialId },
216
+ });
217
+
218
+ if (!existing) {
219
+ return null;
220
+ }
221
+
222
+ // Separate schema fields from OAuth data
223
+ const {
224
+ user,
225
+ userId,
226
+ externalId,
227
+ authIsValid,
228
+ subType,
229
+ ...oauthData
230
+ } = updates;
231
+
232
+ // Merge OAuth data with existing
233
+ const mergedData = { ...(existing.data || {}), ...oauthData };
234
+
235
+ const updated = await this.prisma.credential.update({
236
+ where: { id: credentialId },
237
+ data: {
238
+ userId: userId || user || existing.userId,
239
+ externalId:
240
+ externalId !== undefined ? externalId : existing.externalId,
241
+ authIsValid:
242
+ authIsValid !== undefined ? authIsValid : existing.authIsValid,
243
+ subType: subType !== undefined ? subType : existing.subType,
244
+ data: mergedData,
245
+ },
246
+ });
247
+
248
+ const data = updated.data || {};
249
+
250
+ return {
251
+ id: updated.id,
252
+ userId: updated.userId,
253
+ externalId: updated.externalId,
254
+ authIsValid: updated.authIsValid,
255
+ access_token: data.access_token,
256
+ refresh_token: data.refresh_token,
257
+ domain: data.domain,
258
+ ...data,
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Convert identifiers to Prisma where clause
264
+ * @private
265
+ * @param {Object} identifiers - Identifier fields
266
+ * @returns {Object} Prisma where clause
267
+ */
268
+ _convertIdentifiersToWhere(identifiers) {
269
+ const where = {};
270
+
271
+ if (identifiers._id) where.id = identifiers._id;
272
+ if (identifiers.id) where.id = identifiers.id;
273
+ if (identifiers.user) where.userId = identifiers.user;
274
+ if (identifiers.userId) where.userId = identifiers.userId;
275
+ if (identifiers.externalId) where.externalId = identifiers.externalId;
276
+ if (identifiers.subType) where.subType = identifiers.subType;
277
+
278
+ return where;
279
+ }
280
+
281
+ /**
282
+ * Convert filter to Prisma where clause
283
+ * @private
284
+ * @param {Object} filter - Filter criteria
285
+ * @returns {Object} Prisma where clause
286
+ */
287
+ _convertFilterToWhere(filter) {
288
+ const where = {};
289
+
290
+ if (filter.credentialId) where.id = filter.credentialId;
291
+ if (filter.id) where.id = filter.id;
292
+ if (filter.user) where.userId = filter.user;
293
+ if (filter.userId) where.userId = filter.userId;
294
+ if (filter.externalId) where.externalId = filter.externalId;
295
+ if (filter.subType) where.subType = filter.subType;
296
+
297
+ return where;
298
+ }
299
+ }
300
+
301
+ module.exports = { CredentialRepositoryMongo };