@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
@@ -1,11 +1,41 @@
1
1
  const { Router } = require('express');
2
- const mongoose = require('mongoose');
3
- const https = require('https');
4
- const http = require('http');
5
2
  const { moduleFactory, integrationFactory } = require('./../backend-utils');
6
3
  const { createAppHandler } = require('./../app-handler-helpers');
4
+ const {
5
+ createHealthCheckRepository,
6
+ } = require('../../database/repositories/health-check-repository-factory');
7
+ const {
8
+ TestEncryptionUseCase,
9
+ } = require('../../database/use-cases/test-encryption-use-case');
10
+ const {
11
+ CheckDatabaseHealthUseCase,
12
+ } = require('../../database/use-cases/check-database-health-use-case');
13
+ const {
14
+ CheckEncryptionHealthUseCase,
15
+ } = require('../../database/use-cases/check-encryption-health-use-case');
16
+ const {
17
+ CheckExternalApisHealthUseCase,
18
+ } = require('../use-cases/check-external-apis-health-use-case');
19
+ const {
20
+ CheckIntegrationsHealthUseCase,
21
+ } = require('../use-cases/check-integrations-health-use-case');
7
22
 
8
23
  const router = Router();
24
+ const healthCheckRepository = createHealthCheckRepository();
25
+ const testEncryptionUseCase = new TestEncryptionUseCase({
26
+ healthCheckRepository,
27
+ });
28
+ const checkDatabaseHealthUseCase = new CheckDatabaseHealthUseCase({
29
+ healthCheckRepository,
30
+ });
31
+ const checkEncryptionHealthUseCase = new CheckEncryptionHealthUseCase({
32
+ testEncryptionUseCase,
33
+ });
34
+ const checkExternalApisHealthUseCase = new CheckExternalApisHealthUseCase();
35
+ const checkIntegrationsHealthUseCase = new CheckIntegrationsHealthUseCase({
36
+ moduleFactory,
37
+ integrationFactory,
38
+ });
9
39
 
10
40
  const validateApiKey = (req, res, next) => {
11
41
  const apiKey = req.headers['x-api-key'];
@@ -27,385 +57,6 @@ const validateApiKey = (req, res, next) => {
27
57
 
28
58
  router.use(validateApiKey);
29
59
 
30
- const checkExternalAPI = (url, timeout = 5000) => {
31
- return new Promise((resolve) => {
32
- const protocol = url.startsWith('https:') ? https : http;
33
- const startTime = Date.now();
34
-
35
- try {
36
- const request = protocol.get(url, { timeout }, (res) => {
37
- const responseTime = Date.now() - startTime;
38
- resolve({
39
- status: 'healthy',
40
- statusCode: res.statusCode,
41
- responseTime,
42
- reachable: res.statusCode < 500,
43
- });
44
- });
45
-
46
- request.on('error', (error) => {
47
- resolve({
48
- status: 'unhealthy',
49
- error: error.message,
50
- responseTime: Date.now() - startTime,
51
- reachable: false,
52
- });
53
- });
54
-
55
- request.on('timeout', () => {
56
- request.destroy();
57
- resolve({
58
- status: 'timeout',
59
- error: 'Request timeout',
60
- responseTime: timeout,
61
- reachable: false,
62
- });
63
- });
64
- } catch (error) {
65
- resolve({
66
- status: 'error',
67
- error: error.message,
68
- responseTime: Date.now() - startTime,
69
- reachable: false,
70
- });
71
- }
72
- });
73
- };
74
-
75
- const getDatabaseState = () => {
76
- const stateMap = {
77
- 0: 'disconnected',
78
- 1: 'connected',
79
- 2: 'connecting',
80
- 3: 'disconnecting',
81
- };
82
- const readyState = mongoose.connection.readyState;
83
-
84
- return {
85
- readyState,
86
- stateName: stateMap[readyState],
87
- isConnected: readyState === 1,
88
- };
89
- };
90
-
91
- const checkDatabaseHealth = async () => {
92
- const { stateName, isConnected } = getDatabaseState();
93
- const result = {
94
- status: isConnected ? 'healthy' : 'unhealthy',
95
- state: stateName,
96
- };
97
-
98
- if (isConnected) {
99
- const pingStart = Date.now();
100
- await mongoose.connection.db.admin().ping({ maxTimeMS: 2000 });
101
- result.responseTime = Date.now() - pingStart;
102
- }
103
-
104
- return result;
105
- };
106
-
107
- const getEncryptionConfiguration = () => {
108
- const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } =
109
- process.env;
110
-
111
- const defaultBypassStages = ['dev', 'test', 'local'];
112
- const useEnv = BYPASS_ENCRYPTION_STAGE !== undefined;
113
- const bypassStages = useEnv
114
- ? BYPASS_ENCRYPTION_STAGE.split(',').map((s) => s.trim())
115
- : defaultBypassStages;
116
-
117
- const isBypassed = bypassStages.includes(STAGE);
118
- const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
119
- const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '' && !hasAES;
120
- const mode = hasAES ? 'aes' : hasKMS ? 'kms' : 'none';
121
-
122
- return {
123
- stage: STAGE || null,
124
- isBypassed,
125
- hasAES,
126
- hasKMS,
127
- mode,
128
- };
129
- };
130
-
131
- const createTestEncryptionModel = () => {
132
- const { Encrypt } = require('./../../encrypt');
133
-
134
- const testSchema = new mongoose.Schema(
135
- {
136
- testSecret: { type: String, lhEncrypt: true },
137
- normalField: { type: String },
138
- nestedSecret: {
139
- value: { type: String, lhEncrypt: true },
140
- },
141
- },
142
- { timestamps: false }
143
- );
144
-
145
- testSchema.plugin(Encrypt);
146
-
147
- return (
148
- mongoose.models.TestEncryption ||
149
- mongoose.model('TestEncryption', testSchema)
150
- );
151
- };
152
-
153
- const verifyDecryption = (retrievedDoc, originalData) => {
154
- return (
155
- retrievedDoc &&
156
- retrievedDoc.testSecret === originalData.testSecret &&
157
- retrievedDoc.normalField === originalData.normalField &&
158
- retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value
159
- );
160
- };
161
-
162
- const verifyEncryptionInDatabase = async (testDoc, originalData, TestModel) => {
163
- const collectionName = TestModel.collection.name;
164
- const rawDoc = await mongoose.connection.db
165
- .collection(collectionName)
166
- .findOne({ _id: testDoc._id });
167
-
168
- const secretIsEncrypted =
169
- rawDoc &&
170
- typeof rawDoc.testSecret === 'string' &&
171
- rawDoc.testSecret.includes(':') &&
172
- rawDoc.testSecret !== originalData.testSecret;
173
-
174
- const nestedIsEncrypted =
175
- rawDoc?.nestedSecret?.value &&
176
- typeof rawDoc.nestedSecret.value === 'string' &&
177
- rawDoc.nestedSecret.value.includes(':') &&
178
- rawDoc.nestedSecret.value !== originalData.nestedSecret.value;
179
-
180
- const normalNotEncrypted =
181
- rawDoc && rawDoc.normalField === originalData.normalField;
182
-
183
- return {
184
- secretIsEncrypted,
185
- nestedIsEncrypted,
186
- normalNotEncrypted,
187
- };
188
- };
189
-
190
- const evaluateEncryptionTestResults = (decryptionWorks, encryptionResults) => {
191
- const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } =
192
- encryptionResults;
193
-
194
- if (
195
- decryptionWorks &&
196
- secretIsEncrypted &&
197
- nestedIsEncrypted &&
198
- normalNotEncrypted
199
- ) {
200
- return {
201
- status: 'enabled',
202
- testResult: 'Encryption and decryption verified successfully',
203
- };
204
- }
205
-
206
- if (decryptionWorks && (!secretIsEncrypted || !nestedIsEncrypted)) {
207
- return {
208
- status: 'unhealthy',
209
- testResult: 'Fields are not being encrypted in database',
210
- };
211
- }
212
-
213
- if (decryptionWorks && !normalNotEncrypted) {
214
- return {
215
- status: 'unhealthy',
216
- testResult: 'Normal fields are being incorrectly encrypted',
217
- };
218
- }
219
-
220
- return {
221
- status: 'unhealthy',
222
- testResult: 'Decryption failed or data mismatch',
223
- };
224
- };
225
-
226
- const withTimeout = (promise, ms, errorMessage) => {
227
- return Promise.race([
228
- promise,
229
- new Promise((_, reject) =>
230
- setTimeout(() => reject(new Error(errorMessage)), ms)
231
- ),
232
- ]);
233
- };
234
-
235
- const testEncryption = async () => {
236
- // eslint-disable-next-line no-console
237
- console.log('Starting encryption test');
238
- const TestModel = createTestEncryptionModel();
239
- // eslint-disable-next-line no-console
240
- console.log('Test model created');
241
-
242
- const testData = {
243
- testSecret: 'This is a secret value that should be encrypted',
244
- normalField: 'This is a normal field that should not be encrypted',
245
- nestedSecret: {
246
- value: 'This is a nested secret that should be encrypted',
247
- },
248
- };
249
-
250
- const testDoc = new TestModel(testData);
251
- await withTimeout(testDoc.save(), 5000, 'Save operation timed out');
252
- // eslint-disable-next-line no-console
253
- console.log('Test document saved');
254
-
255
- try {
256
- const retrievedDoc = await withTimeout(
257
- TestModel.findById(testDoc._id),
258
- 5000,
259
- 'Find operation timed out'
260
- );
261
- // eslint-disable-next-line no-console
262
- console.log('Test document retrieved');
263
- const decryptionWorks = verifyDecryption(retrievedDoc, testData);
264
- const encryptionResults = await withTimeout(
265
- verifyEncryptionInDatabase(testDoc, testData, TestModel),
266
- 5000,
267
- 'Database verification timed out'
268
- );
269
- // eslint-disable-next-line no-console
270
- console.log('Encryption verification completed');
271
-
272
- const evaluation = evaluateEncryptionTestResults(
273
- decryptionWorks,
274
- encryptionResults
275
- );
276
-
277
- return {
278
- ...evaluation,
279
- encryptionWorks: decryptionWorks,
280
- };
281
- } finally {
282
- await withTimeout(
283
- TestModel.deleteOne({ _id: testDoc._id }),
284
- 5000,
285
- 'Delete operation timed out'
286
- );
287
- // eslint-disable-next-line no-console
288
- console.log('Test document deleted');
289
- }
290
- };
291
-
292
- const checkEncryptionHealth = async () => {
293
- const config = getEncryptionConfiguration();
294
-
295
- if (config.isBypassed || config.mode === 'none') {
296
- // eslint-disable-next-line no-console
297
- console.log('Encryption check bypassed:', {
298
- stage: config.stage,
299
- mode: config.mode,
300
- });
301
-
302
- const testResult = config.isBypassed
303
- ? 'Encryption bypassed for this stage'
304
- : 'No encryption keys configured';
305
-
306
- return {
307
- status: 'disabled',
308
- mode: config.mode,
309
- bypassed: config.isBypassed,
310
- stage: config.stage,
311
- testResult,
312
- encryptionWorks: false,
313
- debug: {
314
- hasKMS: config.hasKMS,
315
- hasAES: config.hasAES,
316
- },
317
- };
318
- }
319
-
320
- try {
321
- const testResults = await testEncryption();
322
-
323
- return {
324
- ...testResults,
325
- mode: config.mode,
326
- bypassed: config.isBypassed,
327
- stage: config.stage,
328
- debug: {
329
- hasKMS: config.hasKMS,
330
- hasAES: config.hasAES,
331
- },
332
- };
333
- } catch (error) {
334
- return {
335
- status: 'unhealthy',
336
- mode: config.mode,
337
- bypassed: config.isBypassed,
338
- stage: config.stage,
339
- testResult: `Encryption test failed: ${error.message}`,
340
- encryptionWorks: false,
341
- debug: {
342
- hasKMS: config.hasKMS,
343
- hasAES: config.hasAES,
344
- },
345
- };
346
- }
347
- };
348
-
349
- const checkExternalAPIs = async () => {
350
- const apis = [
351
- { name: 'github', url: 'https://api.github.com/status' },
352
- { name: 'npm', url: 'https://registry.npmjs.org' },
353
- ];
354
-
355
- const results = await Promise.all(
356
- apis.map((api) =>
357
- checkExternalAPI(api.url).then((result) => ({
358
- name: api.name,
359
- ...result,
360
- }))
361
- )
362
- );
363
-
364
- const apiStatuses = {};
365
- let allReachable = true;
366
-
367
- results.forEach(({ name, ...checkResult }) => {
368
- apiStatuses[name] = checkResult;
369
- if (!checkResult.reachable) {
370
- allReachable = false;
371
- }
372
- });
373
-
374
- return { apiStatuses, allReachable };
375
- };
376
-
377
- const checkIntegrations = () => {
378
- const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
379
- ? moduleFactory.moduleTypes
380
- : [];
381
-
382
- const integrationTypes = Array.isArray(integrationFactory.integrationTypes)
383
- ? integrationFactory.integrationTypes
384
- : [];
385
-
386
- return {
387
- status: 'healthy',
388
- modules: {
389
- count: moduleTypes.length,
390
- available: moduleTypes,
391
- },
392
- integrations: {
393
- count: integrationTypes.length,
394
- available: integrationTypes,
395
- },
396
- };
397
- };
398
-
399
- const buildHealthCheckResponse = (startTime) => {
400
- return {
401
- service: 'frigg-core-api',
402
- status: 'healthy',
403
- timestamp: new Date().toISOString(),
404
- checks: {},
405
- calculateResponseTime: () => Date.now() - startTime,
406
- };
407
- };
408
-
409
60
  // Helper to detect VPC configuration
410
61
  const detectVpcConfiguration = async () => {
411
62
  const results = {
@@ -666,12 +317,16 @@ router.get('/health', async (_req, res) => {
666
317
  });
667
318
 
668
319
  router.get('/health/detailed', async (_req, res) => {
669
- // eslint-disable-next-line no-console
670
320
  console.log('Starting detailed health check');
671
321
  const startTime = Date.now();
672
- const response = buildHealthCheckResponse(startTime);
673
322
 
674
- // Log environment before any async operations
323
+ const response = {
324
+ service: 'frigg-core-api',
325
+ status: 'healthy',
326
+ timestamp: new Date().toISOString(),
327
+ checks: {},
328
+ };
329
+
675
330
  console.log('Health Check Environment:', {
676
331
  hasKmsKeyArn: !!process.env.KMS_KEY_ARN,
677
332
  awsRegion: process.env.AWS_REGION,
@@ -680,7 +335,6 @@ router.get('/health/detailed', async (_req, res) => {
680
335
  stage: process.env.STAGE,
681
336
  });
682
337
 
683
- // 1. Network diagnostics (run first to understand connectivity)
684
338
  try {
685
339
  console.log('Running network diagnostics...');
686
340
  const networkStart = Date.now();
@@ -703,10 +357,8 @@ router.get('/health/detailed', async (_req, res) => {
703
357
  console.log('Network diagnostics error:', error.message);
704
358
  }
705
359
 
706
- // 2. KMS decrypt capability (must succeed before DB assumed healthy if encryption depends on KMS)
707
360
  try {
708
361
  console.log('About to check KMS capability...');
709
- // Wrap the entire KMS check in a timeout (allow up to 25 seconds for slow VPC)
710
362
  const kmsCheckPromise = checkKmsDecryptCapability();
711
363
  const kmsTimeoutPromise = new Promise((_, reject) =>
712
364
  setTimeout(
@@ -722,22 +374,18 @@ router.get('/health/detailed', async (_req, res) => {
722
374
  if (response.checks.kms.status === 'unhealthy') {
723
375
  response.status = 'unhealthy';
724
376
  }
725
- // eslint-disable-next-line no-console
726
377
  console.log('KMS check completed:', response.checks.kms);
727
378
  } catch (error) {
728
379
  response.checks.kms = { status: 'unhealthy', error: error.message };
729
380
  response.status = 'unhealthy';
730
- // eslint-disable-next-line no-console
731
381
  console.log('KMS check error:', error.message);
732
382
  }
733
383
 
734
384
  try {
735
- response.checks.database = await checkDatabaseHealth();
736
- const dbState = getDatabaseState();
737
- if (!dbState.isConnected) {
385
+ response.checks.database = await checkDatabaseHealthUseCase.execute();
386
+ if (response.checks.database.status === 'unhealthy') {
738
387
  response.status = 'unhealthy';
739
388
  }
740
- // eslint-disable-next-line no-console
741
389
  console.log('Database check completed:', response.checks.database);
742
390
  } catch (error) {
743
391
  response.checks.database = {
@@ -745,16 +393,14 @@ router.get('/health/detailed', async (_req, res) => {
745
393
  error: error.message,
746
394
  };
747
395
  response.status = 'unhealthy';
748
- // eslint-disable-next-line no-console
749
396
  console.log('Database check error:', error.message);
750
397
  }
751
398
 
752
399
  try {
753
- response.checks.encryption = await checkEncryptionHealth();
400
+ response.checks.encryption = await checkEncryptionHealthUseCase.execute();
754
401
  if (response.checks.encryption.status === 'unhealthy') {
755
402
  response.status = 'unhealthy';
756
403
  }
757
- // eslint-disable-next-line no-console
758
404
  console.log('Encryption check completed:', response.checks.encryption);
759
405
  } catch (error) {
760
406
  response.checks.encryption = {
@@ -762,42 +408,42 @@ router.get('/health/detailed', async (_req, res) => {
762
408
  error: error.message,
763
409
  };
764
410
  response.status = 'unhealthy';
765
- // eslint-disable-next-line no-console
766
411
  console.log('Encryption check error:', error.message);
767
412
  }
768
413
 
769
- const { apiStatuses, allReachable } = await checkExternalAPIs();
770
- response.checks.externalApis = apiStatuses;
771
- if (!allReachable) {
414
+ try {
415
+ const { apiStatuses, allReachable } = await checkExternalApisHealthUseCase.execute();
416
+ response.checks.externalApis = apiStatuses;
417
+ if (!allReachable) {
418
+ response.status = 'unhealthy';
419
+ }
420
+ console.log('External APIs check completed:', response.checks.externalApis);
421
+ } catch (error) {
422
+ response.checks.externalApis = {
423
+ status: 'unhealthy',
424
+ error: error.message,
425
+ };
772
426
  response.status = 'unhealthy';
427
+ console.log('External APIs check error:', error.message);
773
428
  }
774
- // eslint-disable-next-line no-console
775
- console.log('External APIs check completed:', response.checks.externalApis);
776
429
 
777
430
  try {
778
- response.checks.integrations = checkIntegrations();
779
- // eslint-disable-next-line no-console
780
- console.log(
781
- 'Integrations check completed:',
782
- response.checks.integrations
783
- );
431
+ response.checks.integrations = checkIntegrationsHealthUseCase.execute();
432
+ console.log('Integrations check completed:', response.checks.integrations);
784
433
  } catch (error) {
785
434
  response.checks.integrations = {
786
435
  status: 'unhealthy',
787
436
  error: error.message,
788
437
  };
789
438
  response.status = 'unhealthy';
790
- // eslint-disable-next-line no-console
791
439
  console.log('Integrations check error:', error.message);
792
440
  }
793
441
 
794
- response.responseTime = response.calculateResponseTime();
795
- delete response.calculateResponseTime;
442
+ response.responseTime = Date.now() - startTime;
796
443
 
797
444
  const statusCode = response.status === 'healthy' ? 200 : 503;
798
445
  res.status(statusCode).json(response);
799
446
 
800
- // eslint-disable-next-line no-console
801
447
  console.log(
802
448
  'Final health status:',
803
449
  response.status,
@@ -814,18 +460,11 @@ router.get('/health/live', (_req, res) => {
814
460
  });
815
461
 
816
462
  router.get('/health/ready', async (_req, res) => {
817
- const dbState = getDatabaseState();
818
- const isDbReady = dbState.isConnected;
463
+ const dbHealth = await checkDatabaseHealthUseCase.execute();
464
+ const isDbReady = dbHealth.status === 'healthy';
819
465
 
820
- let areModulesReady = false;
821
- try {
822
- const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
823
- ? moduleFactory.moduleTypes
824
- : [];
825
- areModulesReady = moduleTypes.length > 0;
826
- } catch (error) {
827
- areModulesReady = false;
828
- }
466
+ const integrationsHealth = checkIntegrationsHealthUseCase.execute();
467
+ const areModulesReady = integrationsHealth.modules.count > 0;
829
468
 
830
469
  const isReady = isDbReady && areModulesReady;
831
470
 
@@ -1,5 +1,12 @@
1
1
  process.env.HEALTH_API_KEY = 'test-api-key';
2
2
 
3
+ jest.mock('../../database/config', () => ({
4
+ DB_TYPE: 'mongodb',
5
+ getDatabaseType: jest.fn(() => 'mongodb'),
6
+ PRISMA_LOG_LEVEL: 'error,warn',
7
+ PRISMA_QUERY_LOGGING: false,
8
+ }));
9
+
3
10
  jest.mock('mongoose', () => ({
4
11
  set: jest.fn(),
5
12
  connection: {
@@ -1,15 +1,18 @@
1
1
  const { createAppHandler } = require('./../app-handler-helpers');
2
2
  const {
3
- integrationFactory,
4
- loadRouterFromObject,
5
- } = require('./../backend-utils');
3
+ loadAppDefinition,
4
+ } = require('../app-definition-loader');
6
5
  const { Router } = require('express');
6
+ const { loadRouterFromObject } = require('../backend-utils');
7
7
 
8
8
  const handlers = {};
9
- for (const IntegrationClass of integrationFactory.integrationClasses) {
9
+ const { integrations: integrationClasses } = loadAppDefinition();
10
+
11
+ //todo: this should be in a use case class
12
+ for (const IntegrationClass of integrationClasses) {
10
13
  const router = Router();
11
14
  const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
12
-
15
+
13
16
  console.log(`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`);
14
17
 
15
18
  for (const routeDef of IntegrationClass.Definition.routes) {
@@ -1,10 +1,31 @@
1
1
  const express = require('express');
2
2
  const { createAppHandler } = require('../app-handler-helpers');
3
3
  const { checkRequiredParams } = require('@friggframework/core');
4
- const { User } = require('../backend-utils');
4
+ const {
5
+ createUserRepository,
6
+ } = require('../../user/repositories/user-repository-factory');
7
+ const {
8
+ CreateIndividualUser,
9
+ } = require('../../user/use-cases/create-individual-user');
10
+ const { LoginUser } = require('../../user/use-cases/login-user');
11
+ const {
12
+ CreateTokenForUserId,
13
+ } = require('../../user/use-cases/create-token-for-user-id');
5
14
  const catchAsyncError = require('express-async-handler');
15
+ const { loadAppDefinition } = require('../app-definition-loader');
6
16
 
7
17
  const router = express();
18
+ const { userConfig } = loadAppDefinition();
19
+ const userRepository = createUserRepository();
20
+ const createIndividualUser = new CreateIndividualUser({
21
+ userRepository,
22
+ userConfig,
23
+ });
24
+ const loginUser = new LoginUser({
25
+ userRepository,
26
+ userConfig,
27
+ });
28
+ const createTokenForUserId = new CreateTokenForUserId({ userRepository });
8
29
 
9
30
  // define the login endpoint
10
31
  router.route('/user/login').post(
@@ -13,8 +34,8 @@ router.route('/user/login').post(
13
34
  'username',
14
35
  'password',
15
36
  ]);
16
- const user = await User.loginUser({ username, password });
17
- const token = await user.createUserToken(120);
37
+ const user = await loginUser.execute({ username, password });
38
+ const token = await createTokenForUserId.execute(user.getId(), 120);
18
39
  res.status(201);
19
40
  res.json({ token });
20
41
  })
@@ -26,11 +47,12 @@ router.route('/user/create').post(
26
47
  'username',
27
48
  'password',
28
49
  ]);
29
- const user = await User.createIndividualUser({
50
+
51
+ const user = await createIndividualUser.execute({
30
52
  username,
31
53
  password,
32
54
  });
33
- const token = await user.createUserToken(120);
55
+ const token = await createTokenForUserId.execute(user.getId(), 120);
34
56
  res.status(201);
35
57
  res.json({ token });
36
58
  })