@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
@@ -0,0 +1,122 @@
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
+ const persisted = await this.credentialRepository.upsertCredential(credentialDetails);
97
+ module.credential = persisted;
98
+ }
99
+
100
+ async findOrCreateEntity(entityDetails, moduleName, credentialId) {
101
+ const { identifiers, details } = entityDetails;
102
+
103
+ const existingEntity = await this.moduleRepository.findEntity({
104
+ externalId: identifiers.externalId,
105
+ user: identifiers.user,
106
+ moduleName: moduleName,
107
+ });
108
+
109
+ if (existingEntity) {
110
+ return existingEntity;
111
+ }
112
+
113
+ return await this.moduleRepository.createEntity({
114
+ ...identifiers,
115
+ ...details,
116
+ moduleName: moduleName,
117
+ credential: credentialId,
118
+ });
119
+ }
120
+ }
121
+
122
+ 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.41",
4
+ "version": "2.0.0-next.43",
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.41",
26
- "@friggframework/prettier-config": "2.0.0-next.41",
27
- "@friggframework/test": "2.0.0-next.41",
26
+ "@friggframework/eslint-config": "2.0.0-next.43",
27
+ "@friggframework/prettier-config": "2.0.0-next.43",
28
+ "@friggframework/test": "2.0.0-next.43",
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": "a5c413164c1b492a228689851d037706f7f869af"
67
+ "gitHead": "2619db8a4e885887ad9071fe166cf0e8dee12d91"
60
68
  }
@@ -0,0 +1,318 @@
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
+ // Message arrays (stored as JSON)
163
+ errors Json @default("[]")
164
+ warnings Json @default("[]")
165
+ info Json @default("[]")
166
+ logs Json @default("[]")
167
+
168
+ createdAt DateTime @default(now())
169
+ updatedAt DateTime @updatedAt
170
+
171
+ // Relations
172
+ associations Association[]
173
+ syncs Sync[]
174
+ mappings IntegrationMapping[]
175
+
176
+ @@index([userId])
177
+ @@index([status])
178
+ @@map("Integration")
179
+ }
180
+
181
+ enum IntegrationStatus {
182
+ ENABLED
183
+ NEEDS_CONFIG
184
+ PROCESSING
185
+ DISABLED
186
+ ERROR
187
+ }
188
+
189
+ /// Integration-specific data mappings
190
+ /// All mapping data encrypted with KMS
191
+ model IntegrationMapping {
192
+ id String @id @default(auto()) @map("_id") @db.ObjectId
193
+ integrationId String @db.ObjectId
194
+ integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
195
+ sourceId String?
196
+
197
+ // Encrypted mapping data (handled via Prisma middleware)
198
+ mapping Json?
199
+
200
+ createdAt DateTime @default(now())
201
+ updatedAt DateTime @updatedAt
202
+
203
+ @@unique([integrationId, sourceId])
204
+ @@index([integrationId])
205
+ @@index([sourceId])
206
+ @@map("IntegrationMapping")
207
+ }
208
+
209
+ // ============================================================================
210
+ // SYNC MODELS
211
+ // ============================================================================
212
+
213
+ /// Bidirectional data synchronization tracking
214
+ model Sync {
215
+ id String @id @default(auto()) @map("_id") @db.ObjectId
216
+ integrationId String? @db.ObjectId
217
+ integration Integration? @relation(fields: [integrationId], references: [id], onDelete: Cascade)
218
+
219
+ // Entity references (many-to-many via explicit scalar list)
220
+ entities Entity[] @relation("SyncEntities", fields: [entityIds], references: [id])
221
+ entityIds String[] @db.ObjectId
222
+
223
+ hash String
224
+ name String
225
+
226
+ // Data identifiers (extracted to separate model)
227
+ dataIdentifiers DataIdentifier[]
228
+
229
+ @@index([integrationId])
230
+ @@index([hash])
231
+ @@index([name])
232
+ @@map("Sync")
233
+ }
234
+
235
+ /// Data identifier for sync operations
236
+ /// Replaces nested array structure in Mongoose
237
+ model DataIdentifier {
238
+ id String @id @default(auto()) @map("_id") @db.ObjectId
239
+ syncId String? @db.ObjectId
240
+ sync Sync? @relation(fields: [syncId], references: [id], onDelete: Cascade)
241
+ entityId String @db.ObjectId
242
+ entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
243
+
244
+ // Identifier data (can be any structure)
245
+ idData Json
246
+
247
+ hash String
248
+
249
+ @@index([syncId])
250
+ @@index([entityId])
251
+ @@index([hash])
252
+ @@map("DataIdentifier")
253
+ }
254
+
255
+ // ============================================================================
256
+ // ASSOCIATION MODELS
257
+ // ============================================================================
258
+
259
+ /// Entity associations with cardinality tracking
260
+ model Association {
261
+ id String @id @default(auto()) @map("_id") @db.ObjectId
262
+ integrationId String @db.ObjectId
263
+ integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
264
+ name String
265
+ type AssociationType
266
+ primaryObject String
267
+
268
+ // Associated objects (extracted to separate model)
269
+ objects AssociationObject[]
270
+
271
+ @@index([integrationId])
272
+ @@index([name])
273
+ @@map("Association")
274
+ }
275
+
276
+ /// Association object entry
277
+ /// Replaces nested array structure in Mongoose
278
+ model AssociationObject {
279
+ id String @id @default(auto()) @map("_id") @db.ObjectId
280
+ associationId String @db.ObjectId
281
+ association Association @relation(fields: [associationId], references: [id], onDelete: Cascade)
282
+ entityId String @db.ObjectId
283
+ entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
284
+ objectType String
285
+ objId String
286
+ metadata Json? // Optional metadata
287
+
288
+ @@index([associationId])
289
+ @@index([entityId])
290
+ @@map("AssociationObject")
291
+ }
292
+
293
+ enum AssociationType {
294
+ ONE_TO_MANY
295
+ ONE_TO_ONE
296
+ MANY_TO_ONE
297
+ }
298
+
299
+ // ============================================================================
300
+ // UTILITY MODELS
301
+ // ============================================================================
302
+
303
+ /// Generic state storage
304
+ model State {
305
+ id String @id @default(auto()) @map("_id") @db.ObjectId
306
+ state Json?
307
+
308
+ @@map("State")
309
+ }
310
+
311
+ /// AWS API Gateway WebSocket connection tracking
312
+ model WebsocketConnection {
313
+ id String @id @default(auto()) @map("_id") @db.ObjectId
314
+ connectionId String?
315
+
316
+ @@index([connectionId])
317
+ @@map("WebsocketConnection")
318
+ }