@friggframework/devtools 2.0.0--canary.522.cbd3d5a.0 → 2.0.0--canary.517.21b69ac.0

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 (247) hide show
  1. package/.eslintrc.json +3 -0
  2. package/CHANGELOG.md +132 -0
  3. package/frigg-cli/README.md +1 -1
  4. package/frigg-cli/__tests__/unit/commands/doctor.test.js +2 -0
  5. package/frigg-cli/__tests__/unit/commands/install.test.js +17 -21
  6. package/frigg-cli/doctor-command/index.js +16 -17
  7. package/frigg-cli/index.js +6 -21
  8. package/frigg-cli/index.test.js +1 -7
  9. package/frigg-cli/init-command/backend-first-handler.js +42 -124
  10. package/frigg-cli/init-command/index.js +1 -2
  11. package/frigg-cli/init-command/template-handler.js +3 -13
  12. package/frigg-cli/install-command/backend-js.js +3 -3
  13. package/frigg-cli/install-command/environment-variables.js +19 -16
  14. package/frigg-cli/install-command/environment-variables.test.js +13 -12
  15. package/frigg-cli/install-command/index.js +9 -14
  16. package/frigg-cli/install-command/integration-file.js +3 -3
  17. package/frigg-cli/install-command/logger.js +12 -0
  18. package/frigg-cli/install-command/validate-package.js +9 -5
  19. package/frigg-cli/jest.config.js +1 -4
  20. package/frigg-cli/repair-command/index.js +128 -101
  21. package/frigg-cli/start-command/index.js +2 -246
  22. package/frigg-cli/ui-command/index.js +36 -58
  23. package/frigg-cli/utils/repo-detection.js +37 -85
  24. package/infrastructure/docs/iam-policy-templates.md +1 -1
  25. package/infrastructure/domains/networking/vpc-builder.test.js +4 -2
  26. package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
  27. package/infrastructure/domains/shared/cloudformation-discovery.test.js +7 -4
  28. package/infrastructure/domains/shared/resource-discovery.js +5 -5
  29. package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
  30. package/infrastructure/domains/shared/utilities/base-definition-factory.js +2 -25
  31. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
  32. package/infrastructure/infrastructure-composer.test.js +2 -2
  33. package/layers/prisma/.build-complete +3 -0
  34. package/layers/prisma/nodejs/package.json +8 -0
  35. package/management-ui/.eslintrc.js +22 -0
  36. package/management-ui/README.md +109 -245
  37. package/management-ui/components.json +21 -0
  38. package/management-ui/docs/phase2-integration-guide.md +320 -0
  39. package/management-ui/index.html +13 -0
  40. package/management-ui/package.json +76 -0
  41. package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
  42. package/management-ui/postcss.config.js +6 -0
  43. package/management-ui/server/api/backend.js +256 -0
  44. package/management-ui/server/api/cli.js +315 -0
  45. package/management-ui/server/api/codegen.js +663 -0
  46. package/management-ui/server/api/connections.js +857 -0
  47. package/management-ui/server/api/discovery.js +185 -0
  48. package/management-ui/server/api/environment/index.js +1 -0
  49. package/management-ui/server/api/environment/router.js +378 -0
  50. package/management-ui/server/api/environment.js +328 -0
  51. package/management-ui/server/api/integrations.js +876 -0
  52. package/management-ui/server/api/logs.js +248 -0
  53. package/management-ui/server/api/monitoring.js +282 -0
  54. package/management-ui/server/api/open-ide.js +31 -0
  55. package/management-ui/server/api/project.js +1029 -0
  56. package/management-ui/server/api/users/sessions.js +371 -0
  57. package/management-ui/server/api/users/simulation.js +254 -0
  58. package/management-ui/server/api/users.js +362 -0
  59. package/management-ui/server/api-contract.md +275 -0
  60. package/management-ui/server/index.js +873 -0
  61. package/management-ui/server/middleware/errorHandler.js +93 -0
  62. package/management-ui/server/middleware/security.js +32 -0
  63. package/management-ui/server/processManager.js +296 -0
  64. package/management-ui/server/server.js +346 -0
  65. package/management-ui/server/services/aws-monitor.js +413 -0
  66. package/management-ui/server/services/npm-registry.js +347 -0
  67. package/management-ui/server/services/template-engine.js +538 -0
  68. package/management-ui/server/utils/cliIntegration.js +220 -0
  69. package/management-ui/server/utils/environment/auditLogger.js +471 -0
  70. package/management-ui/server/utils/environment/awsParameterStore.js +275 -0
  71. package/management-ui/server/utils/environment/encryption.js +278 -0
  72. package/management-ui/server/utils/environment/envFileManager.js +286 -0
  73. package/management-ui/server/utils/import-commonjs.js +28 -0
  74. package/management-ui/server/utils/response.js +83 -0
  75. package/management-ui/server/websocket/handler.js +325 -0
  76. package/management-ui/src/App.jsx +25 -0
  77. package/management-ui/src/assets/FriggLogo.svg +1 -0
  78. package/management-ui/src/components/AppRouter.jsx +65 -0
  79. package/management-ui/src/components/Button.jsx +70 -0
  80. package/management-ui/src/components/Card.jsx +97 -0
  81. package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
  82. package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
  83. package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
  84. package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
  85. package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
  86. package/management-ui/src/components/ErrorBoundary.jsx +73 -0
  87. package/management-ui/src/components/IntegrationCard.jsx +481 -0
  88. package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
  89. package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
  90. package/management-ui/src/components/IntegrationStatus.jsx +336 -0
  91. package/management-ui/src/components/Layout.jsx +716 -0
  92. package/management-ui/src/components/LoadingSpinner.jsx +113 -0
  93. package/management-ui/src/components/RepositoryPicker.jsx +248 -0
  94. package/management-ui/src/components/SessionMonitor.jsx +350 -0
  95. package/management-ui/src/components/StatusBadge.jsx +208 -0
  96. package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
  97. package/management-ui/src/components/UserSimulation.jsx +327 -0
  98. package/management-ui/src/components/Welcome.jsx +434 -0
  99. package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
  100. package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
  101. package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
  102. package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
  103. package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
  104. package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
  105. package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
  106. package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
  107. package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
  108. package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
  109. package/management-ui/src/components/codegen/index.js +10 -0
  110. package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
  111. package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
  112. package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
  113. package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
  114. package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
  115. package/management-ui/src/components/connections/index.js +5 -0
  116. package/management-ui/src/components/index.js +21 -0
  117. package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
  118. package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
  119. package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
  120. package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
  121. package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
  122. package/management-ui/src/components/monitoring/index.js +6 -0
  123. package/management-ui/src/components/monitoring/monitoring.css +218 -0
  124. package/management-ui/src/components/theme-provider.jsx +52 -0
  125. package/management-ui/src/components/theme-toggle.jsx +39 -0
  126. package/management-ui/src/components/ui/badge.tsx +36 -0
  127. package/management-ui/src/components/ui/button.test.jsx +56 -0
  128. package/management-ui/src/components/ui/button.tsx +57 -0
  129. package/management-ui/src/components/ui/card.tsx +76 -0
  130. package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
  131. package/management-ui/src/components/ui/select.tsx +157 -0
  132. package/management-ui/src/components/ui/skeleton.jsx +15 -0
  133. package/management-ui/src/hooks/useFrigg.jsx +387 -0
  134. package/management-ui/src/hooks/useSocket.jsx +58 -0
  135. package/management-ui/src/index.css +193 -0
  136. package/management-ui/src/lib/utils.ts +6 -0
  137. package/management-ui/src/main.jsx +10 -0
  138. package/management-ui/src/pages/CodeGeneration.jsx +14 -0
  139. package/management-ui/src/pages/Connections.jsx +252 -0
  140. package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
  141. package/management-ui/src/pages/Dashboard.jsx +311 -0
  142. package/management-ui/src/pages/Environment.jsx +314 -0
  143. package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
  144. package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
  145. package/management-ui/src/pages/IntegrationTest.jsx +742 -0
  146. package/management-ui/src/pages/Integrations.jsx +253 -0
  147. package/management-ui/src/pages/Monitoring.jsx +17 -0
  148. package/management-ui/src/pages/Simulation.jsx +155 -0
  149. package/management-ui/src/pages/Users.jsx +492 -0
  150. package/management-ui/src/services/api.js +41 -0
  151. package/management-ui/src/services/apiModuleService.js +193 -0
  152. package/management-ui/src/services/websocket-handlers.js +120 -0
  153. package/management-ui/src/test/api/project.test.js +273 -0
  154. package/management-ui/src/test/components/Welcome.test.jsx +378 -0
  155. package/management-ui/src/test/mocks/server.js +178 -0
  156. package/management-ui/src/test/setup.js +61 -0
  157. package/management-ui/src/test/utils/test-utils.jsx +134 -0
  158. package/management-ui/src/utils/repository.js +98 -0
  159. package/management-ui/src/utils/repository.test.js +118 -0
  160. package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
  161. package/management-ui/tailwind.config.js +63 -0
  162. package/management-ui/tsconfig.json +37 -0
  163. package/management-ui/tsconfig.node.json +10 -0
  164. package/management-ui/vite.config.js +26 -0
  165. package/management-ui/vitest.config.js +38 -0
  166. package/package.json +7 -17
  167. package/frigg-cli/__tests__/application/use-cases/AddApiModuleToIntegrationUseCase.test.js +0 -326
  168. package/frigg-cli/__tests__/application/use-cases/CreateApiModuleUseCase.test.js +0 -337
  169. package/frigg-cli/__tests__/domain/entities/ApiModule.test.js +0 -373
  170. package/frigg-cli/__tests__/domain/entities/AppDefinition.test.js +0 -313
  171. package/frigg-cli/__tests__/domain/services/IntegrationValidator.test.js +0 -269
  172. package/frigg-cli/__tests__/domain/value-objects/IntegrationName.test.js +0 -82
  173. package/frigg-cli/__tests__/infrastructure/adapters/IntegrationJsUpdater.test.js +0 -408
  174. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemApiModuleRepository.test.js +0 -583
  175. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemAppDefinitionRepository.test.js +0 -314
  176. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemIntegrationRepository.test.js +0 -430
  177. package/frigg-cli/__tests__/unit/commands/init.test.js +0 -406
  178. package/frigg-cli/__tests__/unit/commands/repair.test.js +0 -275
  179. package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +0 -411
  180. package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +0 -405
  181. package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +0 -496
  182. package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +0 -474
  183. package/frigg-cli/__tests__/unit/utils/output.test.js +0 -196
  184. package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +0 -93
  185. package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +0 -93
  186. package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +0 -103
  187. package/frigg-cli/container.js +0 -172
  188. package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +0 -286
  189. package/frigg-cli/domain/entities/ApiModule.js +0 -272
  190. package/frigg-cli/domain/entities/AppDefinition.js +0 -227
  191. package/frigg-cli/domain/entities/Integration.js +0 -198
  192. package/frigg-cli/domain/exceptions/DomainException.js +0 -24
  193. package/frigg-cli/domain/ports/IApiModuleRepository.js +0 -53
  194. package/frigg-cli/domain/ports/IAppDefinitionRepository.js +0 -43
  195. package/frigg-cli/domain/ports/IIntegrationRepository.js +0 -61
  196. package/frigg-cli/domain/services/IntegrationValidator.js +0 -185
  197. package/frigg-cli/domain/value-objects/IntegrationId.js +0 -42
  198. package/frigg-cli/domain/value-objects/IntegrationName.js +0 -60
  199. package/frigg-cli/domain/value-objects/SemanticVersion.js +0 -70
  200. package/frigg-cli/infrastructure/UnitOfWork.js +0 -46
  201. package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +0 -197
  202. package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +0 -224
  203. package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +0 -249
  204. package/frigg-cli/infrastructure/adapters/SchemaValidator.js +0 -92
  205. package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +0 -373
  206. package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +0 -116
  207. package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +0 -277
  208. package/frigg-cli/package-lock.json +0 -16226
  209. package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +0 -376
  210. package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +0 -591
  211. package/frigg-cli/start-command/infrastructure/DockerAdapter.js +0 -306
  212. package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +0 -329
  213. package/frigg-cli/templates/backend/.env.example +0 -62
  214. package/frigg-cli/templates/backend/.eslintrc.json +0 -12
  215. package/frigg-cli/templates/backend/.prettierrc +0 -6
  216. package/frigg-cli/templates/backend/docker-compose.yml +0 -22
  217. package/frigg-cli/templates/backend/index.js +0 -96
  218. package/frigg-cli/templates/backend/infrastructure.js +0 -12
  219. package/frigg-cli/templates/backend/jest.config.js +0 -17
  220. package/frigg-cli/templates/backend/package.json +0 -50
  221. package/frigg-cli/templates/backend/src/api-modules/.gitkeep +0 -10
  222. package/frigg-cli/templates/backend/src/base/.gitkeep +0 -7
  223. package/frigg-cli/templates/backend/src/integrations/.gitkeep +0 -10
  224. package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +0 -65
  225. package/frigg-cli/templates/backend/src/utils/.gitkeep +0 -7
  226. package/frigg-cli/templates/backend/test/setup.js +0 -30
  227. package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
  228. package/frigg-cli/templates/backend/ui-extensions/README.md +0 -77
  229. package/frigg-cli/utils/__tests__/repo-detection.test.js +0 -436
  230. package/frigg-cli/utils/output.js +0 -382
  231. package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +0 -205
  232. package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +0 -104
  233. package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +0 -153
  234. package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +0 -162
  235. package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +0 -152
  236. package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +0 -332
  237. package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +0 -191
  238. package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +0 -146
  239. package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +0 -155
  240. package/frigg-cli/validate-command/adapters/cli/validate-command.js +0 -199
  241. package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +0 -35
  242. package/frigg-cli/validate-command/domain/entities/validation-result.js +0 -74
  243. package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +0 -74
  244. package/frigg-cli/validate-command/domain/value-objects/validation-error.js +0 -68
  245. package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +0 -181
  246. package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +0 -128
  247. package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +0 -113
@@ -0,0 +1,857 @@
1
+ import express from 'express'
2
+ import path from 'path'
3
+ import fs from 'fs-extra'
4
+ import crypto from 'crypto'
5
+ import axios from 'axios'
6
+ import { createStandardResponse, createErrorResponse, ERROR_CODES, asyncHandler } from '../utils/response.js'
7
+ import { wsHandler } from '../websocket/handler.js'
8
+
9
+ const router = express.Router();
10
+
11
+ // Helper to get connections data file path
12
+ async function getConnectionsFilePath() {
13
+ const dataDir = path.join(process.cwd(), '../../../backend/data');
14
+ await fs.ensureDir(dataDir);
15
+ return path.join(dataDir, 'connections.json');
16
+ }
17
+
18
+ // Helper to load connections
19
+ async function loadConnections() {
20
+ try {
21
+ const filePath = await getConnectionsFilePath();
22
+ if (await fs.pathExists(filePath)) {
23
+ return await fs.readJson(filePath);
24
+ }
25
+ return { connections: [], entities: [] };
26
+ } catch (error) {
27
+ console.error('Error loading connections:', error);
28
+ return { connections: [], entities: [] };
29
+ }
30
+ }
31
+
32
+ // Helper to save connections
33
+ async function saveConnections(data) {
34
+ const filePath = await getConnectionsFilePath();
35
+ await fs.writeJson(filePath, data, { spaces: 2 });
36
+ }
37
+
38
+ // Get all connections
39
+ router.get('/', async (req, res) => {
40
+ try {
41
+ const { userId, integration, status } = req.query;
42
+ const data = await loadConnections();
43
+ let connections = data.connections || [];
44
+
45
+ // Apply filters
46
+ if (userId) {
47
+ connections = connections.filter(c => c.userId === userId);
48
+ }
49
+
50
+ if (integration) {
51
+ connections = connections.filter(c => c.integration === integration);
52
+ }
53
+
54
+ if (status) {
55
+ connections = connections.filter(c => c.status === status);
56
+ }
57
+
58
+ res.json({
59
+ connections,
60
+ total: connections.length
61
+ });
62
+ } catch (error) {
63
+ res.status(500).json({
64
+ error: error.message,
65
+ details: 'Failed to fetch connections'
66
+ });
67
+ }
68
+ });
69
+
70
+ // Get single connection
71
+ router.get('/:id', async (req, res) => {
72
+ const { id } = req.params;
73
+
74
+ try {
75
+ const data = await loadConnections();
76
+ const connection = data.connections.find(c => c.id === id);
77
+
78
+ if (!connection) {
79
+ return res.status(404).json({
80
+ error: 'Connection not found'
81
+ });
82
+ }
83
+
84
+ res.json(connection);
85
+ } catch (error) {
86
+ res.status(500).json({
87
+ error: error.message,
88
+ details: 'Failed to fetch connection'
89
+ });
90
+ }
91
+ });
92
+
93
+ // Create new connection
94
+ router.post('/', async (req, res) => {
95
+ try {
96
+ const { userId, integration, credentials, metadata } = req.body;
97
+
98
+ if (!userId || !integration) {
99
+ return res.status(400).json({
100
+ error: 'userId and integration are required'
101
+ });
102
+ }
103
+
104
+ const newConnection = {
105
+ id: crypto.randomBytes(16).toString('hex'),
106
+ userId,
107
+ integration,
108
+ status: 'active',
109
+ credentials: credentials || {},
110
+ metadata: metadata || {},
111
+ createdAt: new Date().toISOString(),
112
+ updatedAt: new Date().toISOString(),
113
+ lastUsed: null
114
+ };
115
+
116
+ const data = await loadConnections();
117
+
118
+ // Check if connection already exists
119
+ const existingConnection = data.connections.find(c =>
120
+ c.userId === userId && c.integration === integration
121
+ );
122
+
123
+ if (existingConnection) {
124
+ return res.status(400).json({
125
+ error: 'Connection already exists for this user and integration'
126
+ });
127
+ }
128
+
129
+ data.connections.push(newConnection);
130
+ await saveConnections(data);
131
+
132
+ // Broadcast connection creation
133
+ wsHandler.broadcast('connection-update', {
134
+ action: 'created',
135
+ connection: newConnection,
136
+ timestamp: new Date().toISOString()
137
+ });
138
+
139
+ res.status(201).json(newConnection);
140
+ } catch (error) {
141
+ res.status(500).json({
142
+ error: error.message,
143
+ details: 'Failed to create connection'
144
+ });
145
+ }
146
+ });
147
+
148
+ // Update connection
149
+ router.put('/:id', async (req, res) => {
150
+ const { id } = req.params;
151
+ const updates = req.body;
152
+
153
+ try {
154
+ const data = await loadConnections();
155
+ const connectionIndex = data.connections.findIndex(c => c.id === id);
156
+
157
+ if (connectionIndex === -1) {
158
+ return res.status(404).json({
159
+ error: 'Connection not found'
160
+ });
161
+ }
162
+
163
+ // Update connection
164
+ const updatedConnection = {
165
+ ...data.connections[connectionIndex],
166
+ ...updates,
167
+ id, // Prevent ID from being changed
168
+ updatedAt: new Date().toISOString()
169
+ };
170
+
171
+ data.connections[connectionIndex] = updatedConnection;
172
+ await saveConnections(data);
173
+
174
+ // Broadcast connection update
175
+ wsHandler.broadcast('connection-update', {
176
+ action: 'updated',
177
+ connection: updatedConnection,
178
+ timestamp: new Date().toISOString()
179
+ });
180
+
181
+ res.json(updatedConnection);
182
+ } catch (error) {
183
+ res.status(500).json({
184
+ error: error.message,
185
+ details: 'Failed to update connection'
186
+ });
187
+ }
188
+ });
189
+
190
+ // Delete connection
191
+ router.delete('/:id', async (req, res) => {
192
+ const { id } = req.params;
193
+
194
+ try {
195
+ const data = await loadConnections();
196
+ const connectionIndex = data.connections.findIndex(c => c.id === id);
197
+
198
+ if (connectionIndex === -1) {
199
+ return res.status(404).json({
200
+ error: 'Connection not found'
201
+ });
202
+ }
203
+
204
+ const deletedConnection = data.connections[connectionIndex];
205
+ data.connections.splice(connectionIndex, 1);
206
+
207
+ // Also remove associated entities
208
+ data.entities = data.entities.filter(e => e.connectionId !== id);
209
+
210
+ await saveConnections(data);
211
+
212
+ // Broadcast connection deletion
213
+ wsHandler.broadcast('connection-update', {
214
+ action: 'deleted',
215
+ connectionId: id,
216
+ timestamp: new Date().toISOString()
217
+ });
218
+
219
+ res.json({
220
+ status: 'success',
221
+ message: 'Connection deleted',
222
+ connection: deletedConnection
223
+ });
224
+ } catch (error) {
225
+ res.status(500).json({
226
+ error: error.message,
227
+ details: 'Failed to delete connection'
228
+ });
229
+ }
230
+ });
231
+
232
+ // Test connection with comprehensive checks
233
+ router.post('/:id/test', async (req, res) => {
234
+ const { id } = req.params;
235
+ const { comprehensive = false } = req.body;
236
+
237
+ try {
238
+ const data = await loadConnections();
239
+ const connection = data.connections.find(c => c.id === id);
240
+
241
+ if (!connection) {
242
+ return res.status(404).json({
243
+ error: 'Connection not found'
244
+ });
245
+ }
246
+
247
+ // Perform real connection test
248
+ const results = {};
249
+ const startTime = Date.now();
250
+
251
+ // Test 1: Authentication validation
252
+ try {
253
+ const authStart = Date.now();
254
+ // This would call the actual integration API
255
+ // For now, simulate with a delay
256
+ await new Promise(resolve => setTimeout(resolve, 100));
257
+
258
+ results.auth = {
259
+ success: true,
260
+ message: 'Authentication valid',
261
+ latency: Date.now() - authStart
262
+ };
263
+ } catch (error) {
264
+ results.auth = {
265
+ success: false,
266
+ error: 'Authentication failed',
267
+ message: error.message
268
+ };
269
+ }
270
+
271
+ if (comprehensive && results.auth.success) {
272
+ // Test 2: API connectivity
273
+ try {
274
+ const apiStart = Date.now();
275
+ // Simulate API call
276
+ await new Promise(resolve => setTimeout(resolve, 150));
277
+
278
+ results.api = {
279
+ success: true,
280
+ message: 'API endpoint reachable',
281
+ latency: Date.now() - apiStart
282
+ };
283
+ } catch (error) {
284
+ results.api = {
285
+ success: false,
286
+ error: 'API connectivity failed',
287
+ message: error.message
288
+ };
289
+ }
290
+
291
+ // Test 3: Permissions check
292
+ try {
293
+ const permStart = Date.now();
294
+ // Simulate permissions check
295
+ await new Promise(resolve => setTimeout(resolve, 80));
296
+
297
+ results.permissions = {
298
+ success: true,
299
+ message: 'All required permissions granted',
300
+ latency: Date.now() - permStart
301
+ };
302
+ } catch (error) {
303
+ results.permissions = {
304
+ success: false,
305
+ error: 'Insufficient permissions',
306
+ message: error.message
307
+ };
308
+ }
309
+
310
+ // Test 4: Sample data fetch
311
+ try {
312
+ const dataStart = Date.now();
313
+ // Simulate data fetch
314
+ await new Promise(resolve => setTimeout(resolve, 200));
315
+
316
+ results.data = {
317
+ success: true,
318
+ message: 'Successfully fetched sample data',
319
+ latency: Date.now() - dataStart
320
+ };
321
+ } catch (error) {
322
+ results.data = {
323
+ success: false,
324
+ error: 'Failed to fetch data',
325
+ message: error.message
326
+ };
327
+ }
328
+ }
329
+
330
+ // Calculate summary
331
+ const totalLatency = Date.now() - startTime;
332
+ const successfulTests = Object.values(results).filter(r => r.success).length;
333
+ const totalTests = Object.keys(results).length;
334
+ const avgLatency = Math.round(
335
+ Object.values(results)
336
+ .filter(r => r.latency)
337
+ .reduce((sum, r) => sum + r.latency, 0) /
338
+ Object.values(results).filter(r => r.latency).length
339
+ );
340
+
341
+ const summary = {
342
+ success: successfulTests === totalTests,
343
+ testsRun: totalTests,
344
+ testsPassed: successfulTests,
345
+ totalLatency,
346
+ avgLatency,
347
+ timestamp: new Date().toISOString(),
348
+ canRefreshToken: connection.credentials?.refreshToken ? true : false
349
+ };
350
+
351
+ if (!summary.success) {
352
+ summary.error = 'One or more tests failed';
353
+ summary.suggestion = results.auth.success ?
354
+ 'Check API permissions and connectivity' :
355
+ 'Re-authenticate the connection';
356
+ }
357
+
358
+ // Update connection status and last tested
359
+ connection.lastTested = new Date().toISOString();
360
+ connection.status = summary.success ? 'active' : 'error';
361
+ connection.lastTestResult = summary;
362
+ await saveConnections(data);
363
+
364
+ // Broadcast test result
365
+ wsHandler.broadcast('connection-test', {
366
+ connectionId: id,
367
+ results,
368
+ summary
369
+ });
370
+
371
+ res.json({ results, summary });
372
+ } catch (error) {
373
+ res.status(500).json({
374
+ error: error.message,
375
+ details: 'Failed to test connection'
376
+ });
377
+ }
378
+ });
379
+
380
+ // Get entities for a connection
381
+ router.get('/:id/entities', async (req, res) => {
382
+ const { id } = req.params;
383
+
384
+ try {
385
+ const data = await loadConnections();
386
+ const entities = data.entities.filter(e => e.connectionId === id);
387
+
388
+ res.json({
389
+ entities,
390
+ total: entities.length
391
+ });
392
+ } catch (error) {
393
+ res.status(500).json({
394
+ error: error.message,
395
+ details: 'Failed to fetch entities'
396
+ });
397
+ }
398
+ });
399
+
400
+ // Create entity for a connection
401
+ router.post('/:id/entities', async (req, res) => {
402
+ const { id } = req.params;
403
+ const { type, externalId, data: entityData } = req.body;
404
+
405
+ try {
406
+ const connectionsData = await loadConnections();
407
+ const connection = connectionsData.connections.find(c => c.id === id);
408
+
409
+ if (!connection) {
410
+ return res.status(404).json({
411
+ error: 'Connection not found'
412
+ });
413
+ }
414
+
415
+ const newEntity = {
416
+ id: crypto.randomBytes(16).toString('hex'),
417
+ connectionId: id,
418
+ type: type || 'generic',
419
+ externalId: externalId || crypto.randomBytes(8).toString('hex'),
420
+ data: entityData || {},
421
+ createdAt: new Date().toISOString(),
422
+ updatedAt: new Date().toISOString()
423
+ };
424
+
425
+ connectionsData.entities.push(newEntity);
426
+ await saveConnections(connectionsData);
427
+
428
+ // Broadcast entity creation
429
+ wsHandler.broadcast('entity-update', {
430
+ action: 'created',
431
+ entity: newEntity,
432
+ timestamp: new Date().toISOString()
433
+ });
434
+
435
+ res.status(201).json(newEntity);
436
+ } catch (error) {
437
+ res.status(500).json({
438
+ error: error.message,
439
+ details: 'Failed to create entity'
440
+ });
441
+ }
442
+ });
443
+
444
+ // Sync entities for a connection
445
+ router.post('/:id/sync', async (req, res) => {
446
+ const { id } = req.params;
447
+
448
+ try {
449
+ const data = await loadConnections();
450
+ const connection = data.connections.find(c => c.id === id);
451
+
452
+ if (!connection) {
453
+ return res.status(404).json({
454
+ error: 'Connection not found'
455
+ });
456
+ }
457
+
458
+ // Simulate entity sync
459
+ const syncResult = {
460
+ connectionId: id,
461
+ status: 'success',
462
+ entitiesAdded: Math.floor(Math.random() * 10),
463
+ entitiesUpdated: Math.floor(Math.random() * 5),
464
+ entitiesRemoved: Math.floor(Math.random() * 2),
465
+ duration: Math.floor(Math.random() * 3000) + 1000,
466
+ timestamp: new Date().toISOString()
467
+ };
468
+
469
+ // Update connection last sync
470
+ connection.lastSync = new Date().toISOString();
471
+ await saveConnections(data);
472
+
473
+ // Broadcast sync result
474
+ wsHandler.broadcast('connection-sync', syncResult);
475
+
476
+ res.json(syncResult);
477
+ } catch (error) {
478
+ res.status(500).json({
479
+ error: error.message,
480
+ details: 'Failed to sync entities'
481
+ });
482
+ }
483
+ });
484
+
485
+ // Get connection statistics
486
+ router.get('/stats/summary', async (req, res) => {
487
+ try {
488
+ const data = await loadConnections();
489
+ const connections = data.connections || [];
490
+ const entities = data.entities || [];
491
+
492
+ const stats = {
493
+ totalConnections: connections.length,
494
+ totalEntities: entities.length,
495
+ byIntegration: {},
496
+ byStatus: {},
497
+ activeConnections: connections.filter(c => c.status === 'active').length,
498
+ recentlyUsed: 0
499
+ };
500
+
501
+ const now = new Date();
502
+ const hourAgo = new Date(now - 60 * 60 * 1000);
503
+
504
+ connections.forEach(connection => {
505
+ // Count by integration
506
+ stats.byIntegration[connection.integration] =
507
+ (stats.byIntegration[connection.integration] || 0) + 1;
508
+
509
+ // Count by status
510
+ stats.byStatus[connection.status] =
511
+ (stats.byStatus[connection.status] || 0) + 1;
512
+
513
+ // Count recently used
514
+ if (connection.lastUsed && new Date(connection.lastUsed) > hourAgo) {
515
+ stats.recentlyUsed++;
516
+ }
517
+ });
518
+
519
+ // Count entities by type
520
+ stats.entitiesByType = {};
521
+ entities.forEach(entity => {
522
+ stats.entitiesByType[entity.type] =
523
+ (stats.entitiesByType[entity.type] || 0) + 1;
524
+ });
525
+
526
+ res.json(stats);
527
+ } catch (error) {
528
+ res.status(500).json({
529
+ error: error.message,
530
+ details: 'Failed to get connection statistics'
531
+ });
532
+ }
533
+ });
534
+
535
+ // OAuth initialization
536
+ router.post('/oauth/init', async (req, res) => {
537
+ const { integration, provider } = req.body;
538
+
539
+ try {
540
+ // Generate state for CSRF protection
541
+ const state = crypto.randomBytes(32).toString('hex');
542
+
543
+ // Generate PKCE code verifier and challenge
544
+ const codeVerifier = crypto.randomBytes(32).toString('base64url');
545
+ const codeChallenge = crypto
546
+ .createHash('sha256')
547
+ .update(codeVerifier)
548
+ .digest('base64url');
549
+
550
+ // Store OAuth session
551
+ const oauthSessions = await loadOAuthSessions();
552
+ oauthSessions[state] = {
553
+ integration,
554
+ provider,
555
+ codeVerifier,
556
+ status: 'pending',
557
+ createdAt: new Date().toISOString()
558
+ };
559
+ await saveOAuthSessions(oauthSessions);
560
+
561
+ // Build OAuth URL based on provider
562
+ const redirectUri = `${process.env.APP_URL || 'http://localhost:3001'}/api/connections/oauth/callback`;
563
+ let authUrl;
564
+
565
+ switch (provider) {
566
+ case 'slack':
567
+ authUrl = `https://slack.com/oauth/v2/authorize?` +
568
+ `client_id=${process.env.SLACK_CLIENT_ID}&` +
569
+ `scope=channels:read,chat:write,users:read&` +
570
+ `redirect_uri=${encodeURIComponent(redirectUri)}&` +
571
+ `state=${state}`;
572
+ break;
573
+ case 'google':
574
+ authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
575
+ `client_id=${process.env.GOOGLE_CLIENT_ID}&` +
576
+ `response_type=code&` +
577
+ `scope=${encodeURIComponent('https://www.googleapis.com/auth/userinfo.email')}&` +
578
+ `redirect_uri=${encodeURIComponent(redirectUri)}&` +
579
+ `state=${state}&` +
580
+ `code_challenge=${codeChallenge}&` +
581
+ `code_challenge_method=S256`;
582
+ break;
583
+ // Add more providers as needed
584
+ default:
585
+ throw new Error(`Unsupported OAuth provider: ${provider}`);
586
+ }
587
+
588
+ res.json({
589
+ authUrl,
590
+ state,
591
+ codeVerifier
592
+ });
593
+ } catch (error) {
594
+ res.status(500).json({
595
+ error: error.message,
596
+ details: 'Failed to initialize OAuth flow'
597
+ });
598
+ }
599
+ });
600
+
601
+ // OAuth callback
602
+ router.get('/oauth/callback', async (req, res) => {
603
+ const { code, state, error: oauthError } = req.query;
604
+
605
+ try {
606
+ const oauthSessions = await loadOAuthSessions();
607
+ const session = oauthSessions[state];
608
+
609
+ if (!session) {
610
+ return res.status(400).send('Invalid OAuth state');
611
+ }
612
+
613
+ if (oauthError) {
614
+ session.status = 'error';
615
+ session.error = oauthError;
616
+ await saveOAuthSessions(oauthSessions);
617
+ return res.send('<script>window.close();</script>');
618
+ }
619
+
620
+ // Exchange code for tokens
621
+ // This would be implemented based on the provider
622
+ // For now, simulate success
623
+ session.status = 'completed';
624
+ session.tokens = {
625
+ accessToken: crypto.randomBytes(32).toString('hex'),
626
+ refreshToken: crypto.randomBytes(32).toString('hex'),
627
+ expiresAt: new Date(Date.now() + 3600000).toISOString()
628
+ };
629
+
630
+ // Create the connection
631
+ const newConnection = {
632
+ id: crypto.randomBytes(16).toString('hex'),
633
+ integration: session.integration,
634
+ provider: session.provider,
635
+ status: 'active',
636
+ credentials: session.tokens,
637
+ createdAt: new Date().toISOString(),
638
+ updatedAt: new Date().toISOString()
639
+ };
640
+
641
+ const data = await loadConnections();
642
+ data.connections.push(newConnection);
643
+ await saveConnections(data);
644
+
645
+ session.connectionId = newConnection.id;
646
+ await saveOAuthSessions(oauthSessions);
647
+
648
+ // Close the OAuth window
649
+ res.send('<script>window.close();</script>');
650
+ } catch (error) {
651
+ console.error('OAuth callback error:', error);
652
+ res.status(500).send('OAuth callback failed');
653
+ }
654
+ });
655
+
656
+ // Check OAuth status
657
+ router.get('/oauth/status/:state', async (req, res) => {
658
+ const { state } = req.params;
659
+
660
+ try {
661
+ const oauthSessions = await loadOAuthSessions();
662
+ const session = oauthSessions[state];
663
+
664
+ if (!session) {
665
+ return res.status(404).json({
666
+ error: 'OAuth session not found'
667
+ });
668
+ }
669
+
670
+ if (session.status === 'completed' && session.connectionId) {
671
+ const data = await loadConnections();
672
+ const connection = data.connections.find(c => c.id === session.connectionId);
673
+
674
+ res.json({
675
+ status: 'completed',
676
+ connection
677
+ });
678
+
679
+ // Clean up session
680
+ delete oauthSessions[state];
681
+ await saveOAuthSessions(oauthSessions);
682
+ } else {
683
+ res.json({
684
+ status: session.status,
685
+ error: session.error
686
+ });
687
+ }
688
+ } catch (error) {
689
+ res.status(500).json({
690
+ error: error.message,
691
+ details: 'Failed to check OAuth status'
692
+ });
693
+ }
694
+ });
695
+
696
+ // Get connection health
697
+ router.get('/:id/health', async (req, res) => {
698
+ const { id } = req.params;
699
+
700
+ try {
701
+ const data = await loadConnections();
702
+ const connection = data.connections.find(c => c.id === id);
703
+
704
+ if (!connection) {
705
+ return res.status(404).json({
706
+ error: 'Connection not found'
707
+ });
708
+ }
709
+
710
+ // Calculate health metrics
711
+ const now = Date.now();
712
+ const createdAt = new Date(connection.createdAt).getTime();
713
+ const uptime = Math.floor((now - createdAt) / 1000);
714
+
715
+ // Get API call stats (would be from actual logs)
716
+ const apiCalls = {
717
+ total: Math.floor(Math.random() * 1000) + 100,
718
+ successful: 0,
719
+ failed: 0
720
+ };
721
+ apiCalls.successful = Math.floor(apiCalls.total * 0.95);
722
+ apiCalls.failed = apiCalls.total - apiCalls.successful;
723
+
724
+ const health = {
725
+ status: connection.status === 'active' ? 'healthy' : 'error',
726
+ lastCheck: new Date().toISOString(),
727
+ uptime,
728
+ latency: connection.lastTestResult?.avgLatency || null,
729
+ errorRate: (apiCalls.failed / apiCalls.total) * 100,
730
+ apiCalls,
731
+ recentEvents: [
732
+ {
733
+ type: 'sync_completed',
734
+ timestamp: new Date(now - 300000).toISOString()
735
+ },
736
+ {
737
+ type: 'api_call',
738
+ timestamp: new Date(now - 60000).toISOString()
739
+ }
740
+ ]
741
+ };
742
+
743
+ // Broadcast health update
744
+ wsHandler.broadcast(`connection-health-${id}`, health);
745
+
746
+ res.json(health);
747
+ } catch (error) {
748
+ res.status(500).json({
749
+ error: error.message,
750
+ details: 'Failed to get connection health'
751
+ });
752
+ }
753
+ });
754
+
755
+ // Get entity relationships
756
+ router.get('/:id/relationships', async (req, res) => {
757
+ const { id } = req.params;
758
+
759
+ try {
760
+ const data = await loadConnections();
761
+ const entities = data.entities.filter(e => e.connectionId === id);
762
+
763
+ // Generate relationships based on entity data
764
+ const relationships = [];
765
+
766
+ // Example: Create relationships between entities
767
+ entities.forEach((entity, index) => {
768
+ if (index < entities.length - 1) {
769
+ relationships.push({
770
+ id: crypto.randomBytes(8).toString('hex'),
771
+ fromId: entity.id,
772
+ toId: entities[index + 1].id,
773
+ type: 'related_to',
774
+ createdAt: new Date().toISOString()
775
+ });
776
+ }
777
+ });
778
+
779
+ res.json({
780
+ relationships,
781
+ total: relationships.length
782
+ });
783
+ } catch (error) {
784
+ res.status(500).json({
785
+ error: error.message,
786
+ details: 'Failed to fetch relationships'
787
+ });
788
+ }
789
+ });
790
+
791
+ // Update connection configuration
792
+ router.put('/:id/config', async (req, res) => {
793
+ const { id } = req.params;
794
+ const config = req.body;
795
+
796
+ try {
797
+ const data = await loadConnections();
798
+ const connectionIndex = data.connections.findIndex(c => c.id === id);
799
+
800
+ if (connectionIndex === -1) {
801
+ return res.status(404).json({
802
+ error: 'Connection not found'
803
+ });
804
+ }
805
+
806
+ // Update connection with new config
807
+ data.connections[connectionIndex] = {
808
+ ...data.connections[connectionIndex],
809
+ ...config,
810
+ id, // Prevent ID change
811
+ updatedAt: new Date().toISOString()
812
+ };
813
+
814
+ await saveConnections(data);
815
+
816
+ // Broadcast configuration update
817
+ wsHandler.broadcast('connection-config-update', {
818
+ connectionId: id,
819
+ config,
820
+ timestamp: new Date().toISOString()
821
+ });
822
+
823
+ res.json(data.connections[connectionIndex]);
824
+ } catch (error) {
825
+ res.status(500).json({
826
+ error: error.message,
827
+ details: 'Failed to update connection configuration'
828
+ });
829
+ }
830
+ });
831
+
832
+ // Helper functions for OAuth sessions
833
+ async function getOAuthSessionsPath() {
834
+ const dataDir = path.join(process.cwd(), '../../../backend/data');
835
+ await fs.ensureDir(dataDir);
836
+ return path.join(dataDir, 'oauth-sessions.json');
837
+ }
838
+
839
+ async function loadOAuthSessions() {
840
+ try {
841
+ const filePath = await getOAuthSessionsPath();
842
+ if (await fs.pathExists(filePath)) {
843
+ return await fs.readJson(filePath);
844
+ }
845
+ return {};
846
+ } catch (error) {
847
+ console.error('Error loading OAuth sessions:', error);
848
+ return {};
849
+ }
850
+ }
851
+
852
+ async function saveOAuthSessions(sessions) {
853
+ const filePath = await getOAuthSessionsPath();
854
+ await fs.writeJson(filePath, sessions, { spaces: 2 });
855
+ }
856
+
857
+ export default router