@friggframework/devtools 2.0.0-next.4 → 2.0.0-next.41

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 (198) hide show
  1. package/frigg-cli/.eslintrc.js +141 -0
  2. package/frigg-cli/__tests__/jest.config.js +102 -0
  3. package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
  4. package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
  5. package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
  6. package/frigg-cli/__tests__/utils/command-tester.js +170 -0
  7. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  8. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  9. package/frigg-cli/__tests__/utils/test-setup.js +286 -0
  10. package/frigg-cli/build-command/index.js +54 -0
  11. package/frigg-cli/deploy-command/index.js +175 -0
  12. package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
  13. package/frigg-cli/generate-command/azure-generator.js +43 -0
  14. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  15. package/frigg-cli/generate-command/index.js +332 -0
  16. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  17. package/frigg-cli/generate-iam-command.js +115 -0
  18. package/frigg-cli/index.js +47 -1
  19. package/frigg-cli/index.test.js +1 -4
  20. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  21. package/frigg-cli/init-command/index.js +93 -0
  22. package/frigg-cli/init-command/template-handler.js +143 -0
  23. package/frigg-cli/install-command/index.js +1 -4
  24. package/frigg-cli/package.json +51 -0
  25. package/frigg-cli/start-command/index.js +30 -4
  26. package/frigg-cli/start-command/start-command.test.js +155 -0
  27. package/frigg-cli/test/init-command.test.js +180 -0
  28. package/frigg-cli/test/npm-registry.test.js +319 -0
  29. package/frigg-cli/ui-command/index.js +154 -0
  30. package/frigg-cli/utils/app-resolver.js +319 -0
  31. package/frigg-cli/utils/backend-path.js +16 -17
  32. package/frigg-cli/utils/npm-registry.js +167 -0
  33. package/frigg-cli/utils/process-manager.js +199 -0
  34. package/frigg-cli/utils/repo-detection.js +405 -0
  35. package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
  36. package/infrastructure/GENERATE-IAM-DOCS.md +278 -0
  37. package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
  38. package/infrastructure/README.md +443 -0
  39. package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
  40. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  41. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  42. package/infrastructure/aws-discovery.js +1176 -0
  43. package/infrastructure/aws-discovery.test.js +1220 -0
  44. package/infrastructure/build-time-discovery.js +206 -0
  45. package/infrastructure/build-time-discovery.test.js +378 -0
  46. package/infrastructure/create-frigg-infrastructure.js +3 -5
  47. package/infrastructure/env-validator.js +77 -0
  48. package/infrastructure/frigg-deployment-iam-stack.yaml +401 -0
  49. package/infrastructure/iam-generator.js +836 -0
  50. package/infrastructure/iam-generator.test.js +172 -0
  51. package/infrastructure/iam-policy-basic.json +218 -0
  52. package/infrastructure/iam-policy-full.json +288 -0
  53. package/infrastructure/integration.test.js +383 -0
  54. package/infrastructure/run-discovery.js +110 -0
  55. package/infrastructure/serverless-template.js +1493 -138
  56. package/infrastructure/serverless-template.test.js +1804 -0
  57. package/management-ui/.eslintrc.js +22 -0
  58. package/management-ui/README.md +203 -0
  59. package/management-ui/components.json +21 -0
  60. package/management-ui/docs/phase2-integration-guide.md +320 -0
  61. package/management-ui/index.html +13 -0
  62. package/management-ui/package-lock.json +16517 -0
  63. package/management-ui/package.json +76 -0
  64. package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
  65. package/management-ui/postcss.config.js +6 -0
  66. package/management-ui/server/api/backend.js +256 -0
  67. package/management-ui/server/api/cli.js +315 -0
  68. package/management-ui/server/api/codegen.js +663 -0
  69. package/management-ui/server/api/connections.js +857 -0
  70. package/management-ui/server/api/discovery.js +185 -0
  71. package/management-ui/server/api/environment/index.js +1 -0
  72. package/management-ui/server/api/environment/router.js +378 -0
  73. package/management-ui/server/api/environment.js +328 -0
  74. package/management-ui/server/api/integrations.js +876 -0
  75. package/management-ui/server/api/logs.js +248 -0
  76. package/management-ui/server/api/monitoring.js +282 -0
  77. package/management-ui/server/api/open-ide.js +31 -0
  78. package/management-ui/server/api/project.js +1029 -0
  79. package/management-ui/server/api/users/sessions.js +371 -0
  80. package/management-ui/server/api/users/simulation.js +254 -0
  81. package/management-ui/server/api/users.js +362 -0
  82. package/management-ui/server/api-contract.md +275 -0
  83. package/management-ui/server/index.js +873 -0
  84. package/management-ui/server/middleware/errorHandler.js +93 -0
  85. package/management-ui/server/middleware/security.js +32 -0
  86. package/management-ui/server/processManager.js +296 -0
  87. package/management-ui/server/server.js +346 -0
  88. package/management-ui/server/services/aws-monitor.js +413 -0
  89. package/management-ui/server/services/npm-registry.js +347 -0
  90. package/management-ui/server/services/template-engine.js +538 -0
  91. package/management-ui/server/utils/cliIntegration.js +220 -0
  92. package/management-ui/server/utils/environment/auditLogger.js +471 -0
  93. package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
  94. package/management-ui/server/utils/environment/encryption.js +278 -0
  95. package/management-ui/server/utils/environment/envFileManager.js +286 -0
  96. package/management-ui/server/utils/import-commonjs.js +28 -0
  97. package/management-ui/server/utils/response.js +83 -0
  98. package/management-ui/server/websocket/handler.js +325 -0
  99. package/management-ui/src/App.jsx +109 -0
  100. package/management-ui/src/assets/FriggLogo.svg +1 -0
  101. package/management-ui/src/components/AppRouter.jsx +65 -0
  102. package/management-ui/src/components/Button.jsx +70 -0
  103. package/management-ui/src/components/Card.jsx +97 -0
  104. package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
  105. package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
  106. package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
  107. package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
  108. package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
  109. package/management-ui/src/components/ErrorBoundary.jsx +73 -0
  110. package/management-ui/src/components/IntegrationCard.jsx +481 -0
  111. package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
  112. package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
  113. package/management-ui/src/components/IntegrationStatus.jsx +336 -0
  114. package/management-ui/src/components/Layout.jsx +716 -0
  115. package/management-ui/src/components/LoadingSpinner.jsx +113 -0
  116. package/management-ui/src/components/RepositoryPicker.jsx +248 -0
  117. package/management-ui/src/components/SessionMonitor.jsx +350 -0
  118. package/management-ui/src/components/StatusBadge.jsx +208 -0
  119. package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
  120. package/management-ui/src/components/UserSimulation.jsx +327 -0
  121. package/management-ui/src/components/Welcome.jsx +434 -0
  122. package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
  123. package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
  124. package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
  125. package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
  126. package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
  127. package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
  128. package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
  129. package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
  130. package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
  131. package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
  132. package/management-ui/src/components/codegen/index.js +10 -0
  133. package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
  134. package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
  135. package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
  136. package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
  137. package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
  138. package/management-ui/src/components/connections/index.js +5 -0
  139. package/management-ui/src/components/index.js +21 -0
  140. package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
  141. package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
  142. package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
  143. package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
  144. package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
  145. package/management-ui/src/components/monitoring/index.js +6 -0
  146. package/management-ui/src/components/monitoring/monitoring.css +218 -0
  147. package/management-ui/src/components/theme-provider.jsx +52 -0
  148. package/management-ui/src/components/theme-toggle.jsx +39 -0
  149. package/management-ui/src/components/ui/badge.tsx +36 -0
  150. package/management-ui/src/components/ui/button.test.jsx +56 -0
  151. package/management-ui/src/components/ui/button.tsx +57 -0
  152. package/management-ui/src/components/ui/card.tsx +76 -0
  153. package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
  154. package/management-ui/src/components/ui/select.tsx +157 -0
  155. package/management-ui/src/components/ui/skeleton.jsx +15 -0
  156. package/management-ui/src/hooks/useFrigg.jsx +601 -0
  157. package/management-ui/src/hooks/useSocket.jsx +58 -0
  158. package/management-ui/src/index.css +193 -0
  159. package/management-ui/src/lib/utils.ts +6 -0
  160. package/management-ui/src/main.jsx +10 -0
  161. package/management-ui/src/pages/CodeGeneration.jsx +14 -0
  162. package/management-ui/src/pages/Connections.jsx +252 -0
  163. package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
  164. package/management-ui/src/pages/Dashboard.jsx +311 -0
  165. package/management-ui/src/pages/Environment.jsx +314 -0
  166. package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
  167. package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
  168. package/management-ui/src/pages/IntegrationTest.jsx +742 -0
  169. package/management-ui/src/pages/Integrations.jsx +253 -0
  170. package/management-ui/src/pages/Monitoring.jsx +17 -0
  171. package/management-ui/src/pages/Simulation.jsx +155 -0
  172. package/management-ui/src/pages/Users.jsx +492 -0
  173. package/management-ui/src/services/api.js +41 -0
  174. package/management-ui/src/services/apiModuleService.js +193 -0
  175. package/management-ui/src/services/websocket-handlers.js +120 -0
  176. package/management-ui/src/test/api/project.test.js +273 -0
  177. package/management-ui/src/test/components/Welcome.test.jsx +378 -0
  178. package/management-ui/src/test/mocks/server.js +178 -0
  179. package/management-ui/src/test/setup.js +61 -0
  180. package/management-ui/src/test/utils/test-utils.jsx +134 -0
  181. package/management-ui/src/utils/repository.js +98 -0
  182. package/management-ui/src/utils/repository.test.js +118 -0
  183. package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
  184. package/management-ui/tailwind.config.js +63 -0
  185. package/management-ui/tsconfig.json +37 -0
  186. package/management-ui/tsconfig.node.json +10 -0
  187. package/management-ui/vite.config.js +26 -0
  188. package/management-ui/vitest.config.js +38 -0
  189. package/package.json +20 -9
  190. package/infrastructure/app-handler-helpers.js +0 -57
  191. package/infrastructure/backend-utils.js +0 -90
  192. package/infrastructure/routers/auth.js +0 -26
  193. package/infrastructure/routers/integration-defined-routers.js +0 -37
  194. package/infrastructure/routers/middleware/loadUser.js +0 -15
  195. package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
  196. package/infrastructure/routers/user.js +0 -41
  197. package/infrastructure/routers/websocket.js +0 -55
  198. package/infrastructure/workers/integration-defined-workers.js +0 -24
@@ -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