@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,36 +1,29 @@
1
- const { createFriggBackend, Worker } = require('@friggframework/core');
2
- const { findNearestBackendPackageJson } = require('@friggframework/core/utils');
3
- const path = require('node:path');
4
- const fs = require('fs-extra');
5
-
6
- const backendPath = findNearestBackendPackageJson();
7
- if (!backendPath) {
8
- throw new Error('Could not find backend package.json');
9
- }
10
-
11
- const backendDir = path.dirname(backendPath);
12
- const backendFilePath = path.join(backendDir, 'index.js');
13
- if (!fs.existsSync(backendFilePath)) {
14
- throw new Error('Could not find index.js');
15
- }
16
-
17
- const backendJsFile = require(backendFilePath);
18
1
  const { Router } = require('express');
19
- const appDefinition = backendJsFile.Definition;
2
+ const { Worker } = require('@friggframework/core');
3
+ const {
4
+ IntegrationEventDispatcher,
5
+ } = require('./integration-event-dispatcher');
20
6
 
21
- const backend = createFriggBackend(appDefinition);
22
7
  const loadRouterFromObject = (IntegrationClass, routerObject) => {
23
8
  const router = Router();
24
9
  const { path, method, event } = routerObject;
10
+
25
11
  console.log(
26
12
  `Registering ${method} ${path} for ${IntegrationClass.Definition.name}`
27
13
  );
14
+
28
15
  router[method.toLowerCase()](path, async (req, res, next) => {
29
16
  try {
30
- const integration = new IntegrationClass({});
31
- await integration.loadModules();
32
- await integration.registerEventHandlers();
33
- const result = await integration.send(event, {req, res, next});
17
+ const integrationInstance = new IntegrationClass();
18
+ const dispatcher = new IntegrationEventDispatcher(
19
+ integrationInstance
20
+ );
21
+ const result = await dispatcher.dispatchHttp({
22
+ event,
23
+ req,
24
+ res,
25
+ next,
26
+ });
34
27
  res.json(result);
35
28
  } catch (error) {
36
29
  next(error);
@@ -39,31 +32,19 @@ const loadRouterFromObject = (IntegrationClass, routerObject) => {
39
32
 
40
33
  return router;
41
34
  };
42
- const createQueueWorker = (integrationClass, integrationFactory) => {
35
+
36
+ const createQueueWorker = (integrationClass) => {
43
37
  class QueueWorker extends Worker {
44
38
  async _run(params, context) {
45
39
  try {
46
- let instance;
47
- if (!params.integrationId) {
48
- instance = new integrationClass({});
49
- await instance.loadModules();
50
- // await instance.loadUserActions();
51
- await instance.registerEventHandlers();
52
- console.log(
53
- `${params.event} for ${integrationClass.Definition.name} integration with no integrationId`
54
- );
55
- } else {
56
- instance =
57
- await integrationFactory.getInstanceFromIntegrationId({
58
- integrationId: params.integrationId,
59
- });
60
- console.log(
61
- `${params.event} for ${instance.record.config.type} of integrationId: ${params.integrationId}`
62
- );
63
- }
64
- const res = await instance.send(params.event, {
40
+ const integrationInstance = new integrationClass();
41
+ const dispatcher = new IntegrationEventDispatcher(
42
+ integrationInstance
43
+ );
44
+ const res = await dispatcher.dispatchJob({
45
+ event: params.event,
65
46
  data: params.data,
66
- context,
47
+ context: context,
67
48
  });
68
49
  return res;
69
50
  } catch (error) {
@@ -79,7 +60,6 @@ const createQueueWorker = (integrationClass, integrationFactory) => {
79
60
  };
80
61
 
81
62
  module.exports = {
82
- ...backend,
83
63
  loadRouterFromObject,
84
64
  createQueueWorker,
85
65
  };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Lightweight dispatcher that executes integration event handlers.
3
+ * @param {import('../integrations/integration-base')} integrationInstance Pre-instantiated integration.
4
+ */
5
+ class IntegrationEventDispatcher {
6
+ constructor(integrationInstance) {
7
+ if (!integrationInstance) {
8
+ throw new Error('Integration instance is required');
9
+ }
10
+ this.integrationInstance = integrationInstance;
11
+ }
12
+
13
+ async dispatchHttp({ event, req, res, next }) {
14
+ const instance = this.integrationInstance;
15
+
16
+ const handler = this.findEventHandler(instance, event);
17
+
18
+ if (!handler) {
19
+ const name =
20
+ instance.constructor?.Definition?.name || 'integration';
21
+ throw new Error(`Event ${event} not registered for ${name}`);
22
+ }
23
+
24
+ return await handler.call(instance, { req, res, next });
25
+ }
26
+
27
+ async dispatchJob({ event, data, context }) {
28
+ const instance = this.integrationInstance;
29
+
30
+ const handler = this.findEventHandler(instance, event);
31
+
32
+ if (!handler) {
33
+ const name =
34
+ instance.constructor?.Definition?.name || 'integration';
35
+ throw new Error(`Event ${event} not registered for ${name}`);
36
+ }
37
+
38
+ return await handler.call(instance, { data, context });
39
+ }
40
+
41
+ findEventHandler(integration, event) {
42
+ if (integration.events && integration.events[event]) {
43
+ return integration.events[event].handler;
44
+ }
45
+
46
+ if (integration.defaultEvents && integration.defaultEvents[event]) {
47
+ return integration.defaultEvents[event].handler;
48
+ }
49
+
50
+ return null;
51
+ }
52
+ }
53
+
54
+ module.exports = { IntegrationEventDispatcher };
@@ -0,0 +1,141 @@
1
+ jest.mock('../database/config', () => ({
2
+ DB_TYPE: 'mongodb',
3
+ getDatabaseType: jest.fn(() => 'mongodb'),
4
+ PRISMA_LOG_LEVEL: 'error,warn',
5
+ PRISMA_QUERY_LOGGING: false,
6
+ }));
7
+
8
+ const { IntegrationEventDispatcher } = require('./integration-event-dispatcher');
9
+ const { IntegrationBase } = require('../integrations/integration-base');
10
+
11
+ class TestIntegration extends IntegrationBase {
12
+ static Definition = {
13
+ name: 'test-integration',
14
+ version: '1.0.0',
15
+ modules: {},
16
+ routes: [
17
+ { path: '/auth', method: 'GET', event: 'AUTH_REQUEST' },
18
+ { path: '/data', method: 'GET', event: 'LOAD_DATA' },
19
+ { path: '/job', method: 'POST', event: 'TEST_EVENT' },
20
+ { path: '/dynamic', method: 'GET', event: 'DYNAMIC_EVENT' },
21
+ ],
22
+ };
23
+
24
+ constructor(params) {
25
+ super(params);
26
+ this.events = {
27
+ AUTH_REQUEST: { handler: this.authRequest.bind(this) },
28
+ LOAD_DATA: { handler: this.loadData.bind(this) },
29
+ TEST_EVENT: { handler: this.testHandler.bind(this) },
30
+ };
31
+ }
32
+
33
+ async authRequest() {
34
+ TestIntegration.latestInstance = this;
35
+ return {
36
+ success: true,
37
+ hydrated: this.isHydrated,
38
+ };
39
+ }
40
+
41
+ async loadData() {
42
+ this.assertHydrated('loadData requires hydration');
43
+ return { success: true };
44
+ }
45
+
46
+ async testHandler({ data }) {
47
+ TestIntegration.latestInstance = this;
48
+ return { received: data };
49
+ }
50
+
51
+ async initialize() {
52
+ this.events = {
53
+ ...this.events,
54
+ DYNAMIC_EVENT: { handler: this.dynamicHandler.bind(this) },
55
+ };
56
+ }
57
+
58
+ async dynamicHandler() {
59
+ TestIntegration.latestInstance = this;
60
+ return { dynamic: true };
61
+ }
62
+ }
63
+
64
+ describe('IntegrationEventDispatcher', () => {
65
+ const createDispatcher = () =>
66
+ new IntegrationEventDispatcher(new TestIntegration());
67
+
68
+ beforeEach(() => {
69
+ TestIntegration.latestInstance = null;
70
+ });
71
+
72
+ describe('dispatchHttp', () => {
73
+ it('creates a stateless integration instance for HTTP events', async () => {
74
+ const dispatcher = createDispatcher();
75
+ const result = await dispatcher.dispatchHttp({
76
+ event: 'AUTH_REQUEST',
77
+ req: {},
78
+ res: {},
79
+ next: jest.fn(),
80
+ });
81
+
82
+ expect(result).toEqual({ success: true, hydrated: false });
83
+ expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
84
+ expect(TestIntegration.latestInstance.isHydrated).toBe(false);
85
+ });
86
+
87
+ it('calls initialize to register dynamic events', async () => {
88
+ const dispatcher = createDispatcher();
89
+ await dispatcher.integrationInstance.initialize();
90
+ const result = await dispatcher.dispatchHttp({
91
+ event: 'DYNAMIC_EVENT',
92
+ req: {},
93
+ res: {},
94
+ next: jest.fn(),
95
+ });
96
+
97
+ expect(result).toEqual({ dynamic: true });
98
+ expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
99
+ });
100
+
101
+ it('throws when requesting an unknown event', async () => {
102
+ const dispatcher = createDispatcher();
103
+ await expect(
104
+ dispatcher.dispatchHttp({
105
+ event: 'UNKNOWN',
106
+ req: {},
107
+ res: {},
108
+ next: jest.fn(),
109
+ })
110
+ ).rejects.toThrow('Event UNKNOWN not registered for test-integration');
111
+ });
112
+
113
+ it('does not hydrate automatically for handlers that require data', async () => {
114
+ const dispatcher = createDispatcher();
115
+ await expect(
116
+ dispatcher.dispatchHttp({
117
+ event: 'LOAD_DATA',
118
+ req: {},
119
+ res: {},
120
+ next: jest.fn(),
121
+ })
122
+ ).rejects.toThrow('loadData requires hydration');
123
+ });
124
+ });
125
+
126
+ describe('dispatchJob', () => {
127
+ it('creates a stateless integration instance for job events', async () => {
128
+ const payload = { foo: 'bar' };
129
+ const dispatcher = createDispatcher();
130
+ const result = await dispatcher.dispatchJob({
131
+ event: 'TEST_EVENT',
132
+ data: payload,
133
+ context: {},
134
+ });
135
+
136
+ expect(result).toEqual({ received: payload });
137
+ expect(TestIntegration.latestInstance).toBeInstanceOf(TestIntegration);
138
+ expect(TestIntegration.latestInstance.isHydrated).toBe(false);
139
+ });
140
+ });
141
+ });
@@ -237,4 +237,106 @@ await mongoose.connection.db.admin().ping({ maxTimeMS: 2000 });
237
237
 
238
238
  ## Environment Variables
239
239
 
240
- - `HEALTH_API_KEY`: Required API key for accessing detailed health endpoints
240
+ - `HEALTH_API_KEY`: Required API key for accessing detailed health endpoints
241
+
242
+ ## TODO: DDD/Hexagonal Architecture Refactoring
243
+
244
+ ### Current Architecture Issues
245
+
246
+ The health router (health.js, 677 lines) currently violates DDD/Hexagonal Architecture principles:
247
+
248
+ **✅ What's Good:**
249
+ - Database access properly abstracted through `HealthCheckRepository`
250
+ - `CheckDatabaseHealthUseCase` and `TestEncryptionUseCase` correctly implement use case pattern
251
+ - All tests passing, no breaking changes
252
+
253
+ **❌ Architecture Violations:**
254
+ 1. **Handler contains significant business logic** - Functions like `getEncryptionConfiguration()`, `checkEncryptionHealth()`, `checkKmsDecryptCapability()`, `detectVpcConfiguration()`, `checkExternalAPIs()`, and `checkIntegrations()` contain business logic that should be in use cases
255
+ 2. **Direct infrastructure dependencies** - Handler directly uses `https`, `http`, Node.js `dns`, and factory modules instead of accessing through repositories
256
+ 3. **Mixed concerns** - Single file handles HTTP routing, business logic, infrastructure detection, and response formatting
257
+ 4. **Violates dependency rule** - Handler should only call use cases, never repositories or contain business logic
258
+
259
+ ### Proposed Refactoring Plan
260
+
261
+ #### Priority 1: Extract Core Health Check Use Cases (Immediate)
262
+
263
+ **New Use Cases:**
264
+ 1. `CheckEncryptionHealthUseCase` - Orchestrate encryption testing with configuration checks (from health.js:122-181)
265
+ 2. `CheckKmsConnectivityUseCase` - Test KMS decrypt capability (from health.js:339-490)
266
+ 3. `DetectNetworkConfigurationUseCase` - VPC and network detection (from health.js:244-336)
267
+
268
+ **New Repositories:**
269
+ 1. `EncryptionConfigRepository` - Get encryption mode, bypass rules (from health.js:98-120)
270
+ 2. `KmsRepository` - KMS connectivity testing, decrypt capability checks
271
+ 3. `NetworkRepository` - DNS resolution, VPC detection, TCP connectivity tests
272
+
273
+ #### Priority 2: Extract External Service Checks
274
+
275
+ **New Use Cases:**
276
+ 4. `CheckExternalServicesUseCase` - Check external API availability (from health.js:183-209)
277
+
278
+ **New Repositories:**
279
+ 4. `ExternalServiceRepository` - HTTP-based service health checking with timeout handling
280
+
281
+ #### Priority 3: Extract Integration Checks
282
+
283
+ **New Use Cases:**
284
+ 5. `CheckIntegrationAvailabilityUseCase` - Verify integrations and modules loaded (from health.js:211-231)
285
+
286
+ **Extend Existing:**
287
+ - Add `getAvailableIntegrations()` and `getAvailableModules()` methods to existing `IntegrationRepository`
288
+
289
+ ### Architectural Principles to Follow
290
+
291
+ **The Handler Should Only:**
292
+ - Define routes
293
+ - Call use cases
294
+ - Map use case results to HTTP responses
295
+ - Handle HTTP-specific concerns (status codes, headers)
296
+
297
+ **The Rule:**
298
+ > "Handlers (adapters) should only call use cases, never repositories or business logic directly"
299
+
300
+ **Dependency Direction:**
301
+ ```
302
+ Handler (Adapter Layer)
303
+ ↓ calls
304
+ Use Cases (Application Layer)
305
+ ↓ calls
306
+ Repositories (Infrastructure Layer)
307
+ ↓ calls
308
+ External Systems (Database, APIs, AWS Services)
309
+ ```
310
+
311
+ ### Expected Outcome
312
+
313
+ - Reduce health.js from **677 lines to ~100-150 lines**
314
+ - All business logic moved to use cases
315
+ - All infrastructure access moved to repositories
316
+ - Handler becomes thin HTTP adapter
317
+ - Improved testability (use cases testable without HTTP context)
318
+ - Better reusability (use cases usable in CLI tools, background jobs, etc.)
319
+
320
+ ### Implementation Status
321
+
322
+ - [ ] P1: Extract `CheckEncryptionHealthUseCase`
323
+ - [ ] P1: Create `EncryptionConfigRepository`
324
+ - [ ] P1: Extract `CheckKmsConnectivityUseCase`
325
+ - [ ] P1: Create `KmsRepository`
326
+ - [ ] P1: Extract `DetectNetworkConfigurationUseCase`
327
+ - [ ] P1: Create `NetworkRepository`
328
+ - [ ] P2: Extract `CheckExternalServicesUseCase`
329
+ - [ ] P2: Create `ExternalServiceRepository`
330
+ - [ ] P3: Extract `CheckIntegrationAvailabilityUseCase`
331
+ - [ ] P3: Extend existing `IntegrationRepository`
332
+
333
+ ### Future Considerations (Optional)
334
+
335
+ **Domain Models (Value Objects):**
336
+ - `HealthCheckResult` - Overall health check result with status, checks, timestamp
337
+ - `DatabaseHealth` - Database-specific health information
338
+ - `EncryptionHealth` - Encryption-specific health information
339
+ - `ServiceHealth` - Generic external service health
340
+ - `NetworkConfiguration` - VPC and network detection results
341
+
342
+ These would replace plain objects and provide type safety and business logic encapsulation.
@@ -1,26 +1,15 @@
1
1
  const { createIntegrationRouter } = require('@friggframework/core');
2
2
  const { createAppHandler } = require('./../app-handler-helpers');
3
- const { requireLoggedInUser } = require('./middleware/requireLoggedInUser');
4
- const {
5
- moduleFactory,
6
- integrationFactory,
7
- IntegrationHelper,
8
- } = require('./../backend-utils');
9
3
 
10
- const router = createIntegrationRouter({
11
- factory: { moduleFactory, integrationFactory, IntegrationHelper },
12
- requireLoggedInUser,
13
- getUserId: (req) => req.user.getUserId(),
14
- });
4
+ const router = createIntegrationRouter();
15
5
 
16
6
  router.route('/redirect/:appId').get((req, res) => {
17
7
  res.redirect(
18
- `${process.env.FRONTEND_URI}/redirect/${
19
- req.params.appId
8
+ `${process.env.FRONTEND_URI}/redirect/${req.params.appId
20
9
  }?${new URLSearchParams(req.query)}`
21
10
  );
22
11
  });
23
12
 
24
13
  const handler = createAppHandler('HTTP Event: Auth', router);
25
14
 
26
- module.exports = { handler, router };
15
+ module.exports = { handler };