@friggframework/core 2.0.0-next.40 → 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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const { Module } = require('../module');
|
|
2
|
+
const { ModuleConstants } = require('../ModuleConstants');
|
|
3
|
+
|
|
4
|
+
class ProcessAuthorizationCallback {
|
|
5
|
+
/**
|
|
6
|
+
* @param {Object} params - Configuration parameters.
|
|
7
|
+
* @param {import('../repositories/module-repository-factory').ModuleRepositoryInterface} params.moduleRepository - Repository for module data operations.
|
|
8
|
+
* @param {import('../../credential/repositories/credential-repository-factory').CredentialRepositoryInterface} params.credentialRepository - Repository for credential data operations.
|
|
9
|
+
* @param {Array<Object>} params.moduleDefinitions - Array of module definitions.
|
|
10
|
+
*/
|
|
11
|
+
constructor({ moduleRepository, credentialRepository, moduleDefinitions }) {
|
|
12
|
+
this.moduleRepository = moduleRepository;
|
|
13
|
+
this.credentialRepository = credentialRepository;
|
|
14
|
+
this.moduleDefinitions = moduleDefinitions;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async execute(userId, entityType, params) {
|
|
18
|
+
const moduleDefinition = this.moduleDefinitions.find((def) => {
|
|
19
|
+
return entityType === def.moduleName;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!moduleDefinition) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Module definition not found for entity type: ${entityType}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// todo: check if we need to pass entity to Module, right now it's null
|
|
29
|
+
let entity = null;
|
|
30
|
+
|
|
31
|
+
const module = new Module({
|
|
32
|
+
userId,
|
|
33
|
+
entity,
|
|
34
|
+
definition: moduleDefinition,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let tokenResponse;
|
|
38
|
+
if (module.apiClass.requesterType === ModuleConstants.authType.oauth2) {
|
|
39
|
+
tokenResponse = await moduleDefinition.requiredAuthMethods.getToken(
|
|
40
|
+
module.api,
|
|
41
|
+
params
|
|
42
|
+
);
|
|
43
|
+
} else {
|
|
44
|
+
tokenResponse =
|
|
45
|
+
await moduleDefinition.requiredAuthMethods.setAuthParams(
|
|
46
|
+
module.api,
|
|
47
|
+
params
|
|
48
|
+
);
|
|
49
|
+
await this.onTokenUpdate(module, moduleDefinition, userId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const authRes = await module.testAuth();
|
|
53
|
+
if (!authRes) {
|
|
54
|
+
throw new Error('Authorization failed');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const entityDetails =
|
|
58
|
+
await moduleDefinition.requiredAuthMethods.getEntityDetails(
|
|
59
|
+
module.api,
|
|
60
|
+
params,
|
|
61
|
+
tokenResponse,
|
|
62
|
+
userId
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
Object.assign(
|
|
66
|
+
entityDetails.details,
|
|
67
|
+
module.apiParamsFromEntity(module.api)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const persistedEntity = await this.findOrCreateEntity(
|
|
71
|
+
entityDetails,
|
|
72
|
+
entityType,
|
|
73
|
+
module.credential.id
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
credential_id: module.credential.id,
|
|
78
|
+
entity_id: persistedEntity.id,
|
|
79
|
+
type: module.getName(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async onTokenUpdate(module, moduleDefinition, userId) {
|
|
84
|
+
const credentialDetails =
|
|
85
|
+
await moduleDefinition.requiredAuthMethods.getCredentialDetails(
|
|
86
|
+
module.api,
|
|
87
|
+
userId
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
Object.assign(
|
|
91
|
+
credentialDetails.details,
|
|
92
|
+
module.apiParamsFromCredential(module.api)
|
|
93
|
+
);
|
|
94
|
+
credentialDetails.details.authIsValid = true;
|
|
95
|
+
|
|
96
|
+
await this.credentialRepository.upsertCredential(credentialDetails);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async findOrCreateEntity(entityDetails, moduleName, credentialId) {
|
|
100
|
+
const { identifiers, details } = entityDetails;
|
|
101
|
+
|
|
102
|
+
const existingEntity = await this.moduleRepository.findEntity({
|
|
103
|
+
externalId: identifiers.externalId,
|
|
104
|
+
user: identifiers.user,
|
|
105
|
+
moduleName: moduleName,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (existingEntity) {
|
|
109
|
+
return existingEntity;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return await this.moduleRepository.createEntity({
|
|
113
|
+
...identifiers,
|
|
114
|
+
...details,
|
|
115
|
+
moduleName: moduleName,
|
|
116
|
+
credential: credentialId,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { ProcessAuthorizationCallback };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { Module } = require('../module');
|
|
2
|
+
|
|
3
|
+
class RefreshEntityOptions {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Object} params
|
|
6
|
+
* @param {import('../repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
|
|
7
|
+
* @param {} params.moduleDefinitions
|
|
8
|
+
*/
|
|
9
|
+
constructor({ moduleRepository, moduleDefinitions }) {
|
|
10
|
+
this.moduleRepository = moduleRepository;
|
|
11
|
+
this.moduleDefinitions = moduleDefinitions;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Retrieve a Module instance for a given user and entity/module type.
|
|
16
|
+
* @param {string} userId
|
|
17
|
+
* @param {string} entityId
|
|
18
|
+
*/
|
|
19
|
+
async execute(entityId, userId, options) {
|
|
20
|
+
const entity = await this.moduleRepository.findEntityById(
|
|
21
|
+
entityId,
|
|
22
|
+
userId
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!entity) {
|
|
26
|
+
throw new Error(`Entity ${entityId} not found`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (entity.userId !== userId) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Entity ${entityId} does not belong to user ${userId}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const entityType = entity.type;
|
|
36
|
+
const moduleDefinition = this.moduleDefinitions.find((def) => {
|
|
37
|
+
const modelName =
|
|
38
|
+
Module.getEntityModelFromDefinition(def).modelName;
|
|
39
|
+
return entityType === modelName;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!moduleDefinition) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Module definition not found for entity type: ${entityType}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const module = new Module({
|
|
49
|
+
userId,
|
|
50
|
+
entity,
|
|
51
|
+
definition: moduleDefinition,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await module.refreshEntityOptions(options);
|
|
55
|
+
return module.getEntityOptions();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { RefreshEntityOptions };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const { Module } = require('../module');
|
|
2
|
+
|
|
3
|
+
class TestModuleAuth {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Object} params - Configuration parameters.
|
|
6
|
+
* @param {import('../repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module data operations.
|
|
7
|
+
* @param {Array<Object>} params.moduleDefinitions - Array of module definitions.
|
|
8
|
+
*/
|
|
9
|
+
constructor({ moduleRepository, moduleDefinitions }) {
|
|
10
|
+
this.moduleRepository = moduleRepository;
|
|
11
|
+
this.moduleDefinitions = moduleDefinitions;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async execute(entityId, userId) {
|
|
15
|
+
const entity = await this.moduleRepository.findEntityById(
|
|
16
|
+
entityId,
|
|
17
|
+
userId
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (!entity) {
|
|
21
|
+
throw new Error(`Entity ${entityId} not found`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (entity.userId !== userId) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Entity ${entityId} does not belong to user ${userId}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const entityType = entity.type;
|
|
31
|
+
const moduleDefinition = this.moduleDefinitions.find((def) => {
|
|
32
|
+
const modelName =
|
|
33
|
+
Module.getEntityModelFromDefinition(def).modelName;
|
|
34
|
+
return entityType === modelName;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!moduleDefinition) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Module definition not found for entity type: ${entityType}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const module = new Module({
|
|
44
|
+
userId,
|
|
45
|
+
entity,
|
|
46
|
+
definition: moduleDefinition,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const testAuthResponse = await module.testAuth();
|
|
50
|
+
|
|
51
|
+
return testAuthResponse;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { TestModuleAuth };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import('../module').Module} moduleInstance
|
|
3
|
+
* Convert a Module domain instance to a plain DTO suitable for JSON responses.
|
|
4
|
+
*/
|
|
5
|
+
function mapModuleClassToModuleDTO(moduleInstance) {
|
|
6
|
+
if (!moduleInstance) return null;
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
id: moduleInstance.entity.id,
|
|
10
|
+
name: moduleInstance.name,
|
|
11
|
+
userId: moduleInstance.userId,
|
|
12
|
+
entity: moduleInstance.entity,
|
|
13
|
+
credentialId: moduleInstance.credential?._id?.toString(),
|
|
14
|
+
type: moduleInstance.getName()
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { mapModuleClassToModuleDTO };
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.42",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@hapi/boom": "^10.0.1",
|
|
7
|
+
"@prisma/client": "^6.16.3",
|
|
7
8
|
"aws-sdk": "^2.1200.0",
|
|
8
9
|
"bcryptjs": "^2.4.3",
|
|
9
10
|
"body-parser": "^1.20.2",
|
|
@@ -22,9 +23,9 @@
|
|
|
22
23
|
"uuid": "^9.0.1"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
26
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
27
|
-
"@friggframework/test": "2.0.0-next.
|
|
26
|
+
"@friggframework/eslint-config": "2.0.0-next.42",
|
|
27
|
+
"@friggframework/prettier-config": "2.0.0-next.42",
|
|
28
|
+
"@friggframework/test": "2.0.0-next.42",
|
|
28
29
|
"@types/lodash": "4.17.15",
|
|
29
30
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
30
31
|
"chai": "^4.3.6",
|
|
@@ -34,12 +35,19 @@
|
|
|
34
35
|
"eslint-plugin-promise": "^7.0.0",
|
|
35
36
|
"jest": "^29.7.0",
|
|
36
37
|
"prettier": "^2.7.1",
|
|
38
|
+
"prisma": "^6.16.3",
|
|
37
39
|
"sinon": "^16.1.1",
|
|
38
40
|
"typescript": "^5.0.2"
|
|
39
41
|
},
|
|
40
42
|
"scripts": {
|
|
41
43
|
"lint:fix": "prettier --write --loglevel error . && eslint . --fix",
|
|
42
|
-
"test": "jest --passWithNoTests # TODO"
|
|
44
|
+
"test": "jest --passWithNoTests # TODO",
|
|
45
|
+
"prisma:generate:mongo": "npx prisma generate --schema ./prisma-mongodb/schema.prisma",
|
|
46
|
+
"prisma:generate:postgres": "npx prisma generate --schema ./prisma-postgresql/schema.prisma",
|
|
47
|
+
"prisma:generate": "npm run prisma:generate:mongo && npm run prisma:generate:postgres",
|
|
48
|
+
"prisma:push:mongo": "npx prisma db push --schema ./prisma-mongodb/schema.prisma",
|
|
49
|
+
"prisma:migrate:postgres": "npx prisma migrate dev --schema ./prisma-postgresql/schema.prisma",
|
|
50
|
+
"postinstall": "npm run prisma:generate"
|
|
43
51
|
},
|
|
44
52
|
"author": "",
|
|
45
53
|
"license": "MIT",
|
|
@@ -56,5 +64,5 @@
|
|
|
56
64
|
"publishConfig": {
|
|
57
65
|
"access": "public"
|
|
58
66
|
},
|
|
59
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "9b956c72ec74f70bb5efd336782cf269c79c3d9b"
|
|
60
68
|
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// Frigg Framework - Prisma Schema
|
|
2
|
+
// MongoDB database schema for enterprise integration platform
|
|
3
|
+
// Migration from Mongoose ODM to Prisma ORM
|
|
4
|
+
|
|
5
|
+
generator client {
|
|
6
|
+
provider = "prisma-client-js"
|
|
7
|
+
output = "../node_modules/@prisma-mongodb/client"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
datasource db {
|
|
11
|
+
provider = "mongodb"
|
|
12
|
+
url = env("DATABASE_URL")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// USER MODELS
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/// User model with discriminator pattern support
|
|
20
|
+
/// Replaces Mongoose discriminators (IndividualUser, OrganizationUser)
|
|
21
|
+
model User {
|
|
22
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
23
|
+
type UserType
|
|
24
|
+
|
|
25
|
+
// Timestamps
|
|
26
|
+
createdAt DateTime @default(now())
|
|
27
|
+
updatedAt DateTime @updatedAt
|
|
28
|
+
|
|
29
|
+
// IndividualUser fields (nullable for organizations)
|
|
30
|
+
email String?
|
|
31
|
+
username String?
|
|
32
|
+
hashword String? // Bcrypt hashed password (handled in application layer)
|
|
33
|
+
appUserId String?
|
|
34
|
+
organizationId String? @db.ObjectId
|
|
35
|
+
|
|
36
|
+
// Self-referential relation for organization membership
|
|
37
|
+
organization User? @relation("OrgMembers", fields: [organizationId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
|
38
|
+
members User[] @relation("OrgMembers")
|
|
39
|
+
|
|
40
|
+
// OrganizationUser fields (nullable for individuals)
|
|
41
|
+
appOrgId String?
|
|
42
|
+
name String?
|
|
43
|
+
|
|
44
|
+
// Relations
|
|
45
|
+
tokens Token[]
|
|
46
|
+
credentials Credential[]
|
|
47
|
+
entities Entity[]
|
|
48
|
+
integrations Integration[]
|
|
49
|
+
|
|
50
|
+
@@unique([email])
|
|
51
|
+
@@unique([username])
|
|
52
|
+
@@unique([appOrgId])
|
|
53
|
+
@@index([type])
|
|
54
|
+
@@index([appUserId])
|
|
55
|
+
@@map("User")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
enum UserType {
|
|
59
|
+
INDIVIDUAL
|
|
60
|
+
ORGANIZATION
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// AUTHENTICATION MODELS
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
/// Authentication tokens with expiration
|
|
68
|
+
/// Bcrypt hashed tokens stored (handled in application layer)
|
|
69
|
+
model Token {
|
|
70
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
71
|
+
token String // Bcrypt hashed
|
|
72
|
+
created DateTime @default(now())
|
|
73
|
+
expires DateTime?
|
|
74
|
+
userId String @db.ObjectId
|
|
75
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
76
|
+
|
|
77
|
+
@@index([userId])
|
|
78
|
+
@@index([expires])
|
|
79
|
+
@@map("Token")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// CREDENTIAL & ENTITY MODELS
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
/// OAuth credentials and API tokens
|
|
87
|
+
/// All sensitive data encrypted with KMS at rest
|
|
88
|
+
model Credential {
|
|
89
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
90
|
+
userId String? @db.ObjectId
|
|
91
|
+
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
92
|
+
subType String?
|
|
93
|
+
authIsValid Boolean?
|
|
94
|
+
externalId String?
|
|
95
|
+
|
|
96
|
+
// Dynamic OAuth fields stored as JSON (encrypted via Prisma middleware)
|
|
97
|
+
// Contains: access_token, refresh_token, domain, expires_in, token_type, etc.
|
|
98
|
+
data Json @default("{}")
|
|
99
|
+
|
|
100
|
+
createdAt DateTime @default(now())
|
|
101
|
+
updatedAt DateTime @updatedAt
|
|
102
|
+
|
|
103
|
+
// Relations
|
|
104
|
+
entities Entity[]
|
|
105
|
+
|
|
106
|
+
@@index([userId])
|
|
107
|
+
@@index([externalId])
|
|
108
|
+
@@map("Credential")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// External service entities (API connections)
|
|
112
|
+
model Entity {
|
|
113
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
114
|
+
credentialId String? @db.ObjectId
|
|
115
|
+
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull)
|
|
116
|
+
subType String?
|
|
117
|
+
userId String? @db.ObjectId
|
|
118
|
+
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
119
|
+
name String?
|
|
120
|
+
moduleName String?
|
|
121
|
+
externalId String?
|
|
122
|
+
|
|
123
|
+
createdAt DateTime @default(now())
|
|
124
|
+
updatedAt DateTime @updatedAt
|
|
125
|
+
|
|
126
|
+
// Relations - many-to-many with scalar lists
|
|
127
|
+
integrations Integration[] @relation("IntegrationEntities", fields: [integrationIds], references: [id])
|
|
128
|
+
integrationIds String[] @db.ObjectId
|
|
129
|
+
|
|
130
|
+
syncs Sync[] @relation("SyncEntities", fields: [syncIds], references: [id])
|
|
131
|
+
syncIds String[] @db.ObjectId
|
|
132
|
+
|
|
133
|
+
dataIdentifiers DataIdentifier[]
|
|
134
|
+
associationObjects AssociationObject[]
|
|
135
|
+
|
|
136
|
+
@@index([userId])
|
|
137
|
+
@@index([externalId])
|
|
138
|
+
@@index([moduleName])
|
|
139
|
+
@@index([credentialId])
|
|
140
|
+
@@map("Entity")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// INTEGRATION MODELS
|
|
145
|
+
// ============================================================================
|
|
146
|
+
|
|
147
|
+
/// Main integration configuration and state
|
|
148
|
+
model Integration {
|
|
149
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
150
|
+
userId String? @db.ObjectId
|
|
151
|
+
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
152
|
+
status IntegrationStatus @default(ENABLED)
|
|
153
|
+
|
|
154
|
+
// Configuration and version
|
|
155
|
+
config Json? // Integration configuration object
|
|
156
|
+
version String?
|
|
157
|
+
|
|
158
|
+
// Entity references (many-to-many via explicit scalar list)
|
|
159
|
+
entities Entity[] @relation("IntegrationEntities", fields: [entityIds], references: [id])
|
|
160
|
+
entityIds String[] @db.ObjectId
|
|
161
|
+
|
|
162
|
+
// Entity reference map (Map<String, String> stored as JSON)
|
|
163
|
+
entityReferenceMap Json? @default("{}")
|
|
164
|
+
|
|
165
|
+
// Message arrays (stored as JSON)
|
|
166
|
+
errors Json @default("[]")
|
|
167
|
+
warnings Json @default("[]")
|
|
168
|
+
info Json @default("[]")
|
|
169
|
+
logs Json @default("[]")
|
|
170
|
+
|
|
171
|
+
createdAt DateTime @default(now())
|
|
172
|
+
updatedAt DateTime @updatedAt
|
|
173
|
+
|
|
174
|
+
// Relations
|
|
175
|
+
associations Association[]
|
|
176
|
+
syncs Sync[]
|
|
177
|
+
mappings IntegrationMapping[]
|
|
178
|
+
|
|
179
|
+
@@index([userId])
|
|
180
|
+
@@index([status])
|
|
181
|
+
@@map("Integration")
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
enum IntegrationStatus {
|
|
185
|
+
ENABLED
|
|
186
|
+
NEEDS_CONFIG
|
|
187
|
+
PROCESSING
|
|
188
|
+
DISABLED
|
|
189
|
+
ERROR
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Integration-specific data mappings
|
|
193
|
+
/// All mapping data encrypted with KMS
|
|
194
|
+
model IntegrationMapping {
|
|
195
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
196
|
+
integrationId String @db.ObjectId
|
|
197
|
+
integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
|
|
198
|
+
sourceId String?
|
|
199
|
+
|
|
200
|
+
// Encrypted mapping data (handled via Prisma middleware)
|
|
201
|
+
mapping Json?
|
|
202
|
+
|
|
203
|
+
createdAt DateTime @default(now())
|
|
204
|
+
updatedAt DateTime @updatedAt
|
|
205
|
+
|
|
206
|
+
@@unique([integrationId, sourceId])
|
|
207
|
+
@@index([integrationId])
|
|
208
|
+
@@index([sourceId])
|
|
209
|
+
@@map("IntegrationMapping")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// SYNC MODELS
|
|
214
|
+
// ============================================================================
|
|
215
|
+
|
|
216
|
+
/// Bidirectional data synchronization tracking
|
|
217
|
+
model Sync {
|
|
218
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
219
|
+
integrationId String? @db.ObjectId
|
|
220
|
+
integration Integration? @relation(fields: [integrationId], references: [id], onDelete: Cascade)
|
|
221
|
+
|
|
222
|
+
// Entity references (many-to-many via explicit scalar list)
|
|
223
|
+
entities Entity[] @relation("SyncEntities", fields: [entityIds], references: [id])
|
|
224
|
+
entityIds String[] @db.ObjectId
|
|
225
|
+
|
|
226
|
+
hash String
|
|
227
|
+
name String
|
|
228
|
+
|
|
229
|
+
// Data identifiers (extracted to separate model)
|
|
230
|
+
dataIdentifiers DataIdentifier[]
|
|
231
|
+
|
|
232
|
+
@@index([integrationId])
|
|
233
|
+
@@index([hash])
|
|
234
|
+
@@index([name])
|
|
235
|
+
@@map("Sync")
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// Data identifier for sync operations
|
|
239
|
+
/// Replaces nested array structure in Mongoose
|
|
240
|
+
model DataIdentifier {
|
|
241
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
242
|
+
syncId String? @db.ObjectId
|
|
243
|
+
sync Sync? @relation(fields: [syncId], references: [id], onDelete: Cascade)
|
|
244
|
+
entityId String @db.ObjectId
|
|
245
|
+
entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
|
|
246
|
+
|
|
247
|
+
// Identifier data (can be any structure)
|
|
248
|
+
idData Json
|
|
249
|
+
|
|
250
|
+
hash String
|
|
251
|
+
|
|
252
|
+
@@index([syncId])
|
|
253
|
+
@@index([entityId])
|
|
254
|
+
@@index([hash])
|
|
255
|
+
@@map("DataIdentifier")
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ============================================================================
|
|
259
|
+
// ASSOCIATION MODELS
|
|
260
|
+
// ============================================================================
|
|
261
|
+
|
|
262
|
+
/// Entity associations with cardinality tracking
|
|
263
|
+
model Association {
|
|
264
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
265
|
+
integrationId String @db.ObjectId
|
|
266
|
+
integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
|
|
267
|
+
name String
|
|
268
|
+
type AssociationType
|
|
269
|
+
primaryObject String
|
|
270
|
+
|
|
271
|
+
// Associated objects (extracted to separate model)
|
|
272
|
+
objects AssociationObject[]
|
|
273
|
+
|
|
274
|
+
@@index([integrationId])
|
|
275
|
+
@@index([name])
|
|
276
|
+
@@map("Association")
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// Association object entry
|
|
280
|
+
/// Replaces nested array structure in Mongoose
|
|
281
|
+
model AssociationObject {
|
|
282
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
283
|
+
associationId String @db.ObjectId
|
|
284
|
+
association Association @relation(fields: [associationId], references: [id], onDelete: Cascade)
|
|
285
|
+
entityId String @db.ObjectId
|
|
286
|
+
entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
|
|
287
|
+
objectType String
|
|
288
|
+
objId String
|
|
289
|
+
metadata Json? // Optional metadata
|
|
290
|
+
|
|
291
|
+
@@index([associationId])
|
|
292
|
+
@@index([entityId])
|
|
293
|
+
@@map("AssociationObject")
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
enum AssociationType {
|
|
297
|
+
ONE_TO_MANY
|
|
298
|
+
ONE_TO_ONE
|
|
299
|
+
MANY_TO_ONE
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// UTILITY MODELS
|
|
304
|
+
// ============================================================================
|
|
305
|
+
|
|
306
|
+
/// Generic state storage
|
|
307
|
+
model State {
|
|
308
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
309
|
+
state Json?
|
|
310
|
+
|
|
311
|
+
@@map("State")
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/// AWS API Gateway WebSocket connection tracking
|
|
315
|
+
model WebsocketConnection {
|
|
316
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
317
|
+
connectionId String?
|
|
318
|
+
|
|
319
|
+
@@index([connectionId])
|
|
320
|
+
@@map("WebsocketConnection")
|
|
321
|
+
}
|