@friggframework/devtools 2.0.0--canary.398.dd443c7.0 → 2.0.0--canary.402.d2f4ae6.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.
- package/frigg-cli/.eslintrc.js +141 -0
- package/frigg-cli/__tests__/jest.config.js +102 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
- package/frigg-cli/__tests__/utils/command-tester.js +170 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +286 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/index.js +19 -1
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/package.json +51 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +25 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/serverless-template.js +177 -292
- package/management-ui/.eslintrc.js +22 -0
- package/management-ui/README.md +203 -0
- package/management-ui/components.json +21 -0
- package/management-ui/docs/phase2-integration-guide.md +320 -0
- package/management-ui/{dist/index.html → index.html} +1 -2
- package/management-ui/package-lock.json +16517 -0
- package/management-ui/package.json +76 -0
- package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
- package/management-ui/postcss.config.js +6 -0
- package/management-ui/server/api/backend.js +256 -0
- package/management-ui/server/api/cli.js +315 -0
- package/management-ui/server/api/codegen.js +663 -0
- package/management-ui/server/api/connections.js +857 -0
- package/management-ui/server/api/discovery.js +185 -0
- package/management-ui/server/api/environment/index.js +1 -0
- package/management-ui/server/api/environment/router.js +378 -0
- package/management-ui/server/api/environment.js +328 -0
- package/management-ui/server/api/integrations.js +876 -0
- package/management-ui/server/api/logs.js +248 -0
- package/management-ui/server/api/monitoring.js +282 -0
- package/management-ui/server/api/open-ide.js +31 -0
- package/management-ui/server/api/project.js +1029 -0
- package/management-ui/server/api/users/sessions.js +371 -0
- package/management-ui/server/api/users/simulation.js +254 -0
- package/management-ui/server/api/users.js +362 -0
- package/management-ui/server/api-contract.md +275 -0
- package/management-ui/server/index.js +873 -0
- package/management-ui/server/middleware/errorHandler.js +93 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +346 -0
- package/management-ui/server/services/aws-monitor.js +413 -0
- package/management-ui/server/services/npm-registry.js +347 -0
- package/management-ui/server/services/template-engine.js +538 -0
- package/management-ui/server/utils/cliIntegration.js +220 -0
- package/management-ui/server/utils/environment/auditLogger.js +471 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
- package/management-ui/server/utils/environment/encryption.js +278 -0
- package/management-ui/server/utils/environment/envFileManager.js +286 -0
- package/management-ui/server/utils/import-commonjs.js +28 -0
- package/management-ui/server/utils/response.js +83 -0
- package/management-ui/server/websocket/handler.js +325 -0
- package/management-ui/src/App.jsx +109 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +70 -0
- package/management-ui/src/components/Card.jsx +97 -0
- package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
- package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
- package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
- package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
- package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
- package/management-ui/src/components/ErrorBoundary.jsx +73 -0
- package/management-ui/src/components/IntegrationCard.jsx +481 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +336 -0
- package/management-ui/src/components/Layout.jsx +716 -0
- package/management-ui/src/components/LoadingSpinner.jsx +113 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +350 -0
- package/management-ui/src/components/StatusBadge.jsx +208 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
- package/management-ui/src/components/UserSimulation.jsx +327 -0
- package/management-ui/src/components/Welcome.jsx +434 -0
- package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
- package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
- package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
- package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
- package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
- package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
- package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
- package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
- package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
- package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
- package/management-ui/src/components/codegen/index.js +10 -0
- package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
- package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
- package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
- package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
- package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
- package/management-ui/src/components/connections/index.js +5 -0
- package/management-ui/src/components/index.js +21 -0
- package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
- package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
- package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
- package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
- package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
- package/management-ui/src/components/monitoring/index.js +6 -0
- package/management-ui/src/components/monitoring/monitoring.css +218 -0
- package/management-ui/src/components/theme-provider.jsx +52 -0
- package/management-ui/src/components/theme-toggle.jsx +39 -0
- package/management-ui/src/components/ui/badge.tsx +36 -0
- package/management-ui/src/components/ui/button.test.jsx +56 -0
- package/management-ui/src/components/ui/button.tsx +57 -0
- package/management-ui/src/components/ui/card.tsx +76 -0
- package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
- package/management-ui/src/components/ui/select.tsx +157 -0
- package/management-ui/src/components/ui/skeleton.jsx +15 -0
- package/management-ui/src/hooks/useFrigg.jsx +601 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +193 -0
- package/management-ui/src/lib/utils.ts +6 -0
- package/management-ui/src/main.jsx +10 -0
- package/management-ui/src/pages/CodeGeneration.jsx +14 -0
- package/management-ui/src/pages/Connections.jsx +252 -0
- package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
- package/management-ui/src/pages/Dashboard.jsx +311 -0
- package/management-ui/src/pages/Environment.jsx +314 -0
- package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
- package/management-ui/src/pages/IntegrationTest.jsx +742 -0
- package/management-ui/src/pages/Integrations.jsx +253 -0
- package/management-ui/src/pages/Monitoring.jsx +17 -0
- package/management-ui/src/pages/Simulation.jsx +155 -0
- package/management-ui/src/pages/Users.jsx +492 -0
- package/management-ui/src/services/api.js +41 -0
- package/management-ui/src/services/apiModuleService.js +193 -0
- package/management-ui/src/services/websocket-handlers.js +120 -0
- package/management-ui/src/test/api/project.test.js +273 -0
- package/management-ui/src/test/components/Welcome.test.jsx +378 -0
- package/management-ui/src/test/mocks/server.js +178 -0
- package/management-ui/src/test/setup.js +61 -0
- package/management-ui/src/test/utils/test-utils.jsx +134 -0
- package/management-ui/src/utils/repository.js +98 -0
- package/management-ui/src/utils/repository.test.js +118 -0
- package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
- package/management-ui/tailwind.config.js +63 -0
- package/management-ui/tsconfig.json +37 -0
- package/management-ui/tsconfig.node.json +10 -0
- package/management-ui/vite.config.js +26 -0
- package/management-ui/vitest.config.js +38 -0
- package/package.json +5 -5
- package/management-ui/dist/assets/index-BA21WgFa.js +0 -1221
- package/management-ui/dist/assets/index-CbM64Oba.js +0 -1221
- package/management-ui/dist/assets/index-CkvseXTC.css +0 -1
- /package/management-ui/{dist/assets/FriggLogo-B7Xx8ZW1.svg → src/assets/FriggLogo.svg} +0 -0
|
@@ -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
|