@friggframework/core 2.0.0-next.41 → 2.0.0-next.42
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.
- package/CLAUDE.md +693 -0
- package/README.md +931 -50
- package/application/commands/README.md +421 -0
- package/application/commands/credential-commands.js +224 -0
- package/application/commands/entity-commands.js +315 -0
- package/application/commands/integration-commands.js +160 -0
- package/application/commands/integration-commands.test.js +123 -0
- package/application/commands/user-commands.js +213 -0
- package/application/index.js +69 -0
- package/core/CLAUDE.md +690 -0
- package/core/create-handler.js +0 -6
- package/credential/repositories/credential-repository-factory.js +47 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +301 -0
- package/credential/repositories/credential-repository-postgres.js +307 -0
- package/credential/repositories/credential-repository.js +307 -0
- package/credential/use-cases/get-credential-for-user.js +21 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/config.js +117 -0
- package/database/encryption/README.md +683 -0
- package/database/encryption/encryption-integration.test.js +553 -0
- package/database/encryption/encryption-schema-registry.js +141 -0
- package/database/encryption/encryption-schema-registry.test.js +392 -0
- package/database/encryption/field-encryption-service.js +226 -0
- package/database/encryption/field-encryption-service.test.js +525 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/mongo-decryption-fix-verification.test.js +348 -0
- package/database/encryption/postgres-decryption-fix-verification.test.js +371 -0
- package/database/encryption/postgres-relation-decryption.test.js +245 -0
- package/database/encryption/prisma-encryption-extension.js +222 -0
- package/database/encryption/prisma-encryption-extension.test.js +439 -0
- package/database/index.js +25 -12
- package/database/models/readme.md +1 -0
- package/database/prisma.js +162 -0
- package/database/repositories/health-check-repository-factory.js +38 -0
- package/database/repositories/health-check-repository-interface.js +86 -0
- package/database/repositories/health-check-repository-mongodb.js +72 -0
- package/database/repositories/health-check-repository-postgres.js +75 -0
- package/database/repositories/health-check-repository.js +108 -0
- package/database/use-cases/check-database-health-use-case.js +34 -0
- package/database/use-cases/check-encryption-health-use-case.js +82 -0
- package/database/use-cases/test-encryption-use-case.js +252 -0
- package/encrypt/Cryptor.js +20 -152
- package/encrypt/index.js +1 -2
- package/encrypt/test-encrypt.js +0 -2
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +0 -3
- package/handlers/auth-flow.integration.test.js +147 -0
- package/handlers/backend-utils.js +25 -45
- package/handlers/integration-event-dispatcher.js +54 -0
- package/handlers/integration-event-dispatcher.test.js +141 -0
- package/handlers/routers/HEALTHCHECK.md +103 -1
- package/handlers/routers/auth.js +3 -14
- package/handlers/routers/health.js +63 -424
- package/handlers/routers/health.test.js +7 -0
- package/handlers/routers/integration-defined-routers.js +8 -5
- package/handlers/routers/user.js +25 -5
- package/handlers/routers/websocket.js +5 -3
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +32 -0
- package/handlers/workers/integration-defined-workers.js +6 -3
- package/index.js +45 -22
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +224 -53
- package/integrations/integration-router.js +386 -178
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-factory.js +44 -0
- package/integrations/repositories/integration-repository-interface.js +115 -0
- package/integrations/repositories/integration-repository-mongo.js +271 -0
- package/integrations/repositories/integration-repository-postgres.js +319 -0
- package/integrations/tests/doubles/dummy-integration-class.js +90 -0
- package/integrations/tests/doubles/test-integration-repository.js +99 -0
- package/integrations/tests/use-cases/create-integration.test.js +131 -0
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +150 -0
- package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +92 -0
- package/integrations/tests/use-cases/get-integration-for-user.test.js +150 -0
- package/integrations/tests/use-cases/get-integration-instance.test.js +176 -0
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +176 -0
- package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
- package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
- package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
- package/integrations/tests/use-cases/update-integration.test.js +141 -0
- package/integrations/use-cases/create-integration.js +83 -0
- package/integrations/use-cases/delete-integration-for-user.js +73 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +87 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/index.js +11 -0
- package/integrations/use-cases/load-integration-context-full.test.js +329 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/load-integration-context.test.js +114 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +93 -0
- package/integrations/utils/map-integration-dto.js +36 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/{module-plugin → modules}/entity.js +1 -0
- package/{module-plugin → modules}/index.js +0 -8
- package/modules/module-factory.js +56 -0
- package/modules/module-hydration.test.js +205 -0
- package/modules/module.js +221 -0
- package/modules/repositories/module-repository-factory.js +33 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +386 -0
- package/modules/repositories/module-repository-postgres.js +437 -0
- package/modules/repositories/module-repository.js +327 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +59 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +31 -0
- package/modules/use-cases/get-module.js +56 -0
- package/modules/use-cases/process-authorization-callback.js +121 -0
- package/modules/use-cases/refresh-entity-options.js +59 -0
- package/modules/use-cases/test-module-auth.js +55 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +14 -6
- package/prisma-mongodb/schema.prisma +321 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +303 -0
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-factory.js +38 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/token/repositories/token-repository-factory.js +33 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +212 -0
- package/token/repositories/token-repository-postgres.js +257 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/integrations/index.d.ts +2 -6
- package/types/module-plugin/index.d.ts +5 -57
- package/types/syncs/index.d.ts +0 -2
- package/user/repositories/user-repository-factory.js +46 -0
- package/user/repositories/user-repository-interface.js +198 -0
- package/user/repositories/user-repository-mongo.js +250 -0
- package/user/repositories/user-repository-postgres.js +311 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/tests/use-cases/create-individual-user.test.js +24 -0
- package/user/tests/use-cases/create-organization-user.test.js +28 -0
- package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
- package/user/tests/use-cases/login-user.test.js +140 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +77 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +155 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +195 -0
- package/websocket/repositories/websocket-connection-repository.js +160 -0
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/mongo.js +0 -171
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -104
- package/encrypt/encrypt.test.js +0 -1069
- package/handlers/routers/middleware/loadUser.js +0 -15
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/test/auther.test.js +0 -97
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/api-key.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
- /package/{module-plugin → modules}/requester/requester.js +0 -0
- /package/{module-plugin → modules}/requester/requester.test.js +0 -0
- /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
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
35
|
+
|
|
36
|
+
const createQueueWorker = (integrationClass) => {
|
|
43
37
|
class QueueWorker extends Worker {
|
|
44
38
|
async _run(params, context) {
|
|
45
39
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
package/handlers/routers/auth.js
CHANGED
|
@@ -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
|
|
15
|
+
module.exports = { handler };
|