archicore 0.1.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 (118) hide show
  1. package/README.md +530 -0
  2. package/dist/analyzers/dead-code.d.ts +95 -0
  3. package/dist/analyzers/dead-code.js +327 -0
  4. package/dist/analyzers/duplication.d.ts +90 -0
  5. package/dist/analyzers/duplication.js +344 -0
  6. package/dist/analyzers/security.d.ts +79 -0
  7. package/dist/analyzers/security.js +484 -0
  8. package/dist/architecture/index.d.ts +35 -0
  9. package/dist/architecture/index.js +249 -0
  10. package/dist/cli/commands/analyzers.d.ts +6 -0
  11. package/dist/cli/commands/analyzers.js +431 -0
  12. package/dist/cli/commands/export.d.ts +6 -0
  13. package/dist/cli/commands/export.js +78 -0
  14. package/dist/cli/commands/index.d.ts +8 -0
  15. package/dist/cli/commands/index.js +8 -0
  16. package/dist/cli/commands/init.d.ts +26 -0
  17. package/dist/cli/commands/init.js +140 -0
  18. package/dist/cli/commands/interactive.d.ts +7 -0
  19. package/dist/cli/commands/interactive.js +522 -0
  20. package/dist/cli/commands/projects.d.ts +6 -0
  21. package/dist/cli/commands/projects.js +249 -0
  22. package/dist/cli/index.d.ts +7 -0
  23. package/dist/cli/index.js +7 -0
  24. package/dist/cli/ui/box.d.ts +17 -0
  25. package/dist/cli/ui/box.js +62 -0
  26. package/dist/cli/ui/colors.d.ts +49 -0
  27. package/dist/cli/ui/colors.js +86 -0
  28. package/dist/cli/ui/index.d.ts +9 -0
  29. package/dist/cli/ui/index.js +9 -0
  30. package/dist/cli/ui/prompt.d.ts +34 -0
  31. package/dist/cli/ui/prompt.js +122 -0
  32. package/dist/cli/ui/spinner.d.ts +29 -0
  33. package/dist/cli/ui/spinner.js +80 -0
  34. package/dist/cli/ui/table.d.ts +33 -0
  35. package/dist/cli/ui/table.js +84 -0
  36. package/dist/cli/utils/config.d.ts +23 -0
  37. package/dist/cli/utils/config.js +73 -0
  38. package/dist/cli/utils/index.d.ts +6 -0
  39. package/dist/cli/utils/index.js +6 -0
  40. package/dist/cli/utils/session.d.ts +27 -0
  41. package/dist/cli/utils/session.js +117 -0
  42. package/dist/cli.d.ts +8 -0
  43. package/dist/cli.js +295 -0
  44. package/dist/code-index/ast-parser.d.ts +16 -0
  45. package/dist/code-index/ast-parser.js +330 -0
  46. package/dist/code-index/dependency-graph.d.ts +16 -0
  47. package/dist/code-index/dependency-graph.js +161 -0
  48. package/dist/code-index/index.d.ts +44 -0
  49. package/dist/code-index/index.js +124 -0
  50. package/dist/code-index/symbol-extractor.d.ts +13 -0
  51. package/dist/code-index/symbol-extractor.js +150 -0
  52. package/dist/export/index.d.ts +92 -0
  53. package/dist/export/index.js +676 -0
  54. package/dist/github/github-service.d.ts +146 -0
  55. package/dist/github/github-service.js +609 -0
  56. package/dist/impact-engine/index.d.ts +25 -0
  57. package/dist/impact-engine/index.js +284 -0
  58. package/dist/index.d.ts +60 -0
  59. package/dist/index.js +149 -0
  60. package/dist/metrics/index.d.ts +136 -0
  61. package/dist/metrics/index.js +525 -0
  62. package/dist/orchestrator/deepseek-optimizer.d.ts +67 -0
  63. package/dist/orchestrator/deepseek-optimizer.js +320 -0
  64. package/dist/orchestrator/index.d.ts +34 -0
  65. package/dist/orchestrator/index.js +305 -0
  66. package/dist/pr-guardian/index.d.ts +143 -0
  67. package/dist/pr-guardian/index.js +553 -0
  68. package/dist/refactoring/index.d.ts +108 -0
  69. package/dist/refactoring/index.js +580 -0
  70. package/dist/rules-engine/index.d.ts +129 -0
  71. package/dist/rules-engine/index.js +482 -0
  72. package/dist/semantic-memory/embedding-service.d.ts +24 -0
  73. package/dist/semantic-memory/embedding-service.js +120 -0
  74. package/dist/semantic-memory/index.d.ts +45 -0
  75. package/dist/semantic-memory/index.js +206 -0
  76. package/dist/semantic-memory/vector-store.d.ts +27 -0
  77. package/dist/semantic-memory/vector-store.js +166 -0
  78. package/dist/server/index.d.ts +28 -0
  79. package/dist/server/index.js +141 -0
  80. package/dist/server/middleware/api-auth.d.ts +43 -0
  81. package/dist/server/middleware/api-auth.js +256 -0
  82. package/dist/server/routes/admin.d.ts +5 -0
  83. package/dist/server/routes/admin.js +123 -0
  84. package/dist/server/routes/api.d.ts +7 -0
  85. package/dist/server/routes/api.js +362 -0
  86. package/dist/server/routes/auth.d.ts +16 -0
  87. package/dist/server/routes/auth.js +191 -0
  88. package/dist/server/routes/developer.d.ts +8 -0
  89. package/dist/server/routes/developer.js +439 -0
  90. package/dist/server/routes/github.d.ts +7 -0
  91. package/dist/server/routes/github.js +495 -0
  92. package/dist/server/routes/upload.d.ts +7 -0
  93. package/dist/server/routes/upload.js +196 -0
  94. package/dist/server/services/api-key-service.d.ts +81 -0
  95. package/dist/server/services/api-key-service.js +281 -0
  96. package/dist/server/services/auth-service.d.ts +40 -0
  97. package/dist/server/services/auth-service.js +315 -0
  98. package/dist/server/services/project-service.d.ts +123 -0
  99. package/dist/server/services/project-service.js +533 -0
  100. package/dist/server/services/token-service.d.ts +107 -0
  101. package/dist/server/services/token-service.js +416 -0
  102. package/dist/server/services/upload-service.d.ts +93 -0
  103. package/dist/server/services/upload-service.js +464 -0
  104. package/dist/types/api.d.ts +188 -0
  105. package/dist/types/api.js +86 -0
  106. package/dist/types/github.d.ts +335 -0
  107. package/dist/types/github.js +5 -0
  108. package/dist/types/index.d.ts +265 -0
  109. package/dist/types/index.js +32 -0
  110. package/dist/types/user.d.ts +69 -0
  111. package/dist/types/user.js +42 -0
  112. package/dist/utils/file-utils.d.ts +20 -0
  113. package/dist/utils/file-utils.js +163 -0
  114. package/dist/utils/logger.d.ts +17 -0
  115. package/dist/utils/logger.js +41 -0
  116. package/dist/watcher/index.d.ts +125 -0
  117. package/dist/watcher/index.js +397 -0
  118. package/package.json +71 -0
@@ -0,0 +1,495 @@
1
+ /**
2
+ * GitHub Integration Routes for ArchiCore
3
+ *
4
+ * OAuth flow, repository management, webhooks
5
+ */
6
+ import { Router } from 'express';
7
+ import { randomBytes } from 'crypto';
8
+ import { GitHubService } from '../../github/github-service.js';
9
+ import { ProjectService } from '../services/project-service.js';
10
+ import { authMiddleware } from './auth.js';
11
+ import { Logger } from '../../utils/logger.js';
12
+ export const githubRouter = Router();
13
+ // Services
14
+ const githubService = new GitHubService();
15
+ const projectService = new ProjectService();
16
+ // Store OAuth states (in production, use Redis)
17
+ const oauthStates = new Map();
18
+ // ===== OAUTH FLOW =====
19
+ /**
20
+ * GET /api/github/auth
21
+ * Start OAuth flow - redirect to GitHub
22
+ */
23
+ githubRouter.get('/auth', authMiddleware, async (req, res) => {
24
+ try {
25
+ if (!req.user) {
26
+ res.status(401).json({ error: 'Not authenticated' });
27
+ return;
28
+ }
29
+ if (!githubService.isConfigured()) {
30
+ res.status(503).json({
31
+ error: 'GitHub integration not configured',
32
+ message: 'Set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables'
33
+ });
34
+ return;
35
+ }
36
+ // Generate state token
37
+ const state = randomBytes(32).toString('hex');
38
+ oauthStates.set(state, {
39
+ userId: req.user.id,
40
+ expiresAt: new Date(Date.now() + 10 * 60 * 1000) // 10 minutes
41
+ });
42
+ // Get authorization URL
43
+ const authUrl = githubService.getAuthorizationUrl(state);
44
+ res.json({ url: authUrl });
45
+ }
46
+ catch (error) {
47
+ Logger.error('GitHub auth error:', error);
48
+ res.status(500).json({ error: 'Failed to start GitHub authorization' });
49
+ }
50
+ });
51
+ /**
52
+ * GET /api/github/callback
53
+ * OAuth callback from GitHub
54
+ */
55
+ githubRouter.get('/callback', async (req, res) => {
56
+ try {
57
+ const { code, state, error } = req.query;
58
+ if (error) {
59
+ res.redirect(`/?github_error=${encodeURIComponent(error)}`);
60
+ return;
61
+ }
62
+ if (!code || !state) {
63
+ res.redirect('/?github_error=missing_params');
64
+ return;
65
+ }
66
+ // Validate state
67
+ const stateData = oauthStates.get(state);
68
+ if (!stateData || stateData.expiresAt < new Date()) {
69
+ oauthStates.delete(state);
70
+ res.redirect('/?github_error=invalid_state');
71
+ return;
72
+ }
73
+ oauthStates.delete(state);
74
+ // Exchange code for token
75
+ const tokenResponse = await githubService.exchangeCodeForToken(code);
76
+ // Get GitHub user info
77
+ const githubUser = await githubService.getGitHubUser(tokenResponse.access_token);
78
+ // Save connection
79
+ await githubService.saveConnection(stateData.userId, tokenResponse, githubUser);
80
+ // Redirect to app with success
81
+ res.redirect('/?github_connected=true');
82
+ }
83
+ catch (error) {
84
+ Logger.error('GitHub callback error:', error);
85
+ res.redirect('/?github_error=auth_failed');
86
+ }
87
+ });
88
+ /**
89
+ * GET /api/github/status
90
+ * Check GitHub connection status
91
+ */
92
+ githubRouter.get('/status', authMiddleware, async (req, res) => {
93
+ try {
94
+ if (!req.user) {
95
+ res.status(401).json({ error: 'Not authenticated' });
96
+ return;
97
+ }
98
+ const connection = await githubService.getConnection(req.user.id);
99
+ if (!connection) {
100
+ res.json({
101
+ connected: false,
102
+ configured: githubService.isConfigured()
103
+ });
104
+ return;
105
+ }
106
+ // Get connected repositories count
107
+ const repos = await githubService.getConnectedRepositories(req.user.id);
108
+ res.json({
109
+ connected: true,
110
+ username: connection.githubUsername,
111
+ connectedAt: connection.createdAt,
112
+ repositories: repos.length
113
+ });
114
+ }
115
+ catch (error) {
116
+ Logger.error('GitHub status error:', error);
117
+ res.status(500).json({ error: 'Failed to get GitHub status' });
118
+ }
119
+ });
120
+ /**
121
+ * POST /api/github/disconnect
122
+ * Disconnect GitHub account
123
+ */
124
+ githubRouter.post('/disconnect', authMiddleware, async (req, res) => {
125
+ try {
126
+ if (!req.user) {
127
+ res.status(401).json({ error: 'Not authenticated' });
128
+ return;
129
+ }
130
+ await githubService.disconnect(req.user.id);
131
+ res.json({ success: true });
132
+ }
133
+ catch (error) {
134
+ Logger.error('GitHub disconnect error:', error);
135
+ res.status(500).json({ error: 'Failed to disconnect GitHub' });
136
+ }
137
+ });
138
+ // ===== REPOSITORIES =====
139
+ /**
140
+ * GET /api/github/repositories
141
+ * List available GitHub repositories
142
+ */
143
+ githubRouter.get('/repositories', authMiddleware, async (req, res) => {
144
+ try {
145
+ if (!req.user) {
146
+ res.status(401).json({ error: 'Not authenticated' });
147
+ return;
148
+ }
149
+ const connection = await githubService.getConnection(req.user.id);
150
+ if (!connection) {
151
+ res.status(400).json({ error: 'GitHub not connected' });
152
+ return;
153
+ }
154
+ // Get all user repos from GitHub
155
+ const githubRepos = await githubService.listUserRepositories(req.user.id);
156
+ // Get connected repos
157
+ const connectedRepos = await githubService.getConnectedRepositories(req.user.id);
158
+ const connectedIds = new Set(connectedRepos.map(r => r.githubRepoId));
159
+ // Format response
160
+ const repositories = githubRepos.map(repo => ({
161
+ id: repo.id,
162
+ name: repo.name,
163
+ fullName: repo.full_name,
164
+ description: repo.description,
165
+ private: repo.private,
166
+ language: repo.language,
167
+ stars: repo.stargazers_count,
168
+ forks: repo.forks_count,
169
+ size: repo.size,
170
+ defaultBranch: repo.default_branch,
171
+ updatedAt: repo.updated_at,
172
+ connected: connectedIds.has(repo.id),
173
+ projectId: connectedRepos.find(r => r.githubRepoId === repo.id)?.projectId
174
+ }));
175
+ res.json({ repositories });
176
+ }
177
+ catch (error) {
178
+ Logger.error('List repositories error:', error);
179
+ res.status(500).json({ error: 'Failed to list repositories' });
180
+ }
181
+ });
182
+ /**
183
+ * GET /api/github/repositories/connected
184
+ * List connected repositories
185
+ */
186
+ githubRouter.get('/repositories/connected', authMiddleware, async (req, res) => {
187
+ try {
188
+ if (!req.user) {
189
+ res.status(401).json({ error: 'Not authenticated' });
190
+ return;
191
+ }
192
+ const repositories = await githubService.getConnectedRepositories(req.user.id);
193
+ res.json({
194
+ repositories: repositories.map(repo => ({
195
+ id: repo.id,
196
+ fullName: repo.fullName,
197
+ name: repo.name,
198
+ owner: repo.owner,
199
+ private: repo.private,
200
+ projectId: repo.projectId,
201
+ autoAnalyze: repo.autoAnalyze,
202
+ analyzePRs: repo.analyzePRs,
203
+ status: repo.status,
204
+ lastAnalyzedAt: repo.lastAnalyzedAt,
205
+ createdAt: repo.createdAt
206
+ }))
207
+ });
208
+ }
209
+ catch (error) {
210
+ Logger.error('List connected repositories error:', error);
211
+ res.status(500).json({ error: 'Failed to list connected repositories' });
212
+ }
213
+ });
214
+ /**
215
+ * POST /api/github/repositories/connect
216
+ * Connect a repository to ArchiCore
217
+ */
218
+ githubRouter.post('/repositories/connect', authMiddleware, async (req, res) => {
219
+ try {
220
+ if (!req.user) {
221
+ res.status(401).json({ error: 'Not authenticated' });
222
+ return;
223
+ }
224
+ const { repositoryId, autoAnalyze, analyzePRs, createProject, projectName } = req.body;
225
+ if (!repositoryId) {
226
+ res.status(400).json({ error: 'repositoryId is required' });
227
+ return;
228
+ }
229
+ // Connect repository
230
+ const connectedRepo = await githubService.connectRepository(req.user.id, repositoryId, {
231
+ autoAnalyze: autoAnalyze ?? true,
232
+ analyzePRs: analyzePRs ?? true
233
+ });
234
+ // Optionally create ArchiCore project
235
+ let projectId;
236
+ if (createProject !== false) {
237
+ try {
238
+ const project = await projectService.createProject(projectName || connectedRepo.name, connectedRepo.cloneUrl);
239
+ projectId = project.id;
240
+ // Update connected repo with project ID
241
+ connectedRepo.projectId = projectId;
242
+ }
243
+ catch (e) {
244
+ Logger.warn(`Failed to create project for ${connectedRepo.fullName}: ${e}`);
245
+ }
246
+ }
247
+ res.json({
248
+ success: true,
249
+ repository: {
250
+ id: connectedRepo.id,
251
+ fullName: connectedRepo.fullName,
252
+ projectId,
253
+ webhookActive: !!connectedRepo.webhookId
254
+ }
255
+ });
256
+ }
257
+ catch (error) {
258
+ Logger.error('Connect repository error:', error);
259
+ res.status(500).json({ error: 'Failed to connect repository' });
260
+ }
261
+ });
262
+ /**
263
+ * DELETE /api/github/repositories/:id
264
+ * Disconnect a repository
265
+ */
266
+ githubRouter.delete('/repositories/:id', authMiddleware, async (req, res) => {
267
+ try {
268
+ if (!req.user) {
269
+ res.status(401).json({ error: 'Not authenticated' });
270
+ return;
271
+ }
272
+ const success = await githubService.disconnectRepository(req.user.id, req.params.id);
273
+ if (!success) {
274
+ res.status(404).json({ error: 'Repository not found' });
275
+ return;
276
+ }
277
+ res.json({ success: true });
278
+ }
279
+ catch (error) {
280
+ Logger.error('Disconnect repository error:', error);
281
+ res.status(500).json({ error: 'Failed to disconnect repository' });
282
+ }
283
+ });
284
+ /**
285
+ * POST /api/github/repositories/:id/analyze
286
+ * Trigger analysis for a repository
287
+ */
288
+ githubRouter.post('/repositories/:id/analyze', authMiddleware, async (req, res) => {
289
+ try {
290
+ if (!req.user) {
291
+ res.status(401).json({ error: 'Not authenticated' });
292
+ return;
293
+ }
294
+ const repo = await githubService.getConnectedRepository(req.params.id);
295
+ if (!repo || repo.userId !== req.user.id) {
296
+ res.status(404).json({ error: 'Repository not found' });
297
+ return;
298
+ }
299
+ if (!repo.projectId) {
300
+ res.status(400).json({ error: 'Repository not linked to a project' });
301
+ return;
302
+ }
303
+ // Update status
304
+ await githubService.updateRepositoryStatus(repo.id, 'syncing');
305
+ // Trigger project analysis
306
+ try {
307
+ const result = await projectService.runFullAnalysis(repo.projectId);
308
+ await githubService.updateLastAnalyzed(repo.id);
309
+ await githubService.updateRepositoryStatus(repo.id, 'active');
310
+ res.json({ success: true, result });
311
+ }
312
+ catch (e) {
313
+ await githubService.updateRepositoryStatus(repo.id, 'error', String(e));
314
+ throw e;
315
+ }
316
+ }
317
+ catch (error) {
318
+ Logger.error('Analyze repository error:', error);
319
+ res.status(500).json({ error: 'Failed to analyze repository' });
320
+ }
321
+ });
322
+ // ===== WEBHOOKS =====
323
+ /**
324
+ * POST /api/github/webhook
325
+ * Receive GitHub webhooks
326
+ */
327
+ githubRouter.post('/webhook', async (req, res) => {
328
+ try {
329
+ const event = req.headers['x-github-event'];
330
+ // signature available at req.headers['x-hub-signature-256'] for verification
331
+ const payload = req.body;
332
+ if (!event || !payload) {
333
+ res.status(400).json({ error: 'Invalid webhook' });
334
+ return;
335
+ }
336
+ // Get repository from payload
337
+ const fullName = payload.repository?.full_name;
338
+ if (!fullName) {
339
+ res.status(200).json({ message: 'No repository in payload' });
340
+ return;
341
+ }
342
+ // Find connected repository
343
+ const repo = await githubService.findRepositoryByWebhook(fullName);
344
+ if (!repo) {
345
+ res.status(200).json({ message: 'Repository not connected' });
346
+ return;
347
+ }
348
+ // Verify signature (if webhook secret is set)
349
+ // Note: In production, you'd verify the signature properly using:
350
+ // githubService.verifyWebhookSignature(JSON.stringify(req.body), signature, secret)
351
+ Logger.info(`Webhook received: ${event} for ${fullName}`);
352
+ // Handle different events
353
+ switch (event) {
354
+ case 'push':
355
+ await handlePushEvent(repo, payload);
356
+ break;
357
+ case 'pull_request':
358
+ await handlePullRequestEvent(repo, payload);
359
+ break;
360
+ default:
361
+ Logger.debug(`Unhandled webhook event: ${event}`);
362
+ }
363
+ res.status(200).json({ received: true });
364
+ }
365
+ catch (error) {
366
+ Logger.error('Webhook error:', error);
367
+ res.status(500).json({ error: 'Webhook processing failed' });
368
+ }
369
+ });
370
+ /**
371
+ * Handle push event
372
+ */
373
+ async function handlePushEvent(repo, payload) {
374
+ if (!repo || !repo.autoAnalyze || !repo.projectId)
375
+ return;
376
+ const branch = payload.ref?.replace('refs/heads/', '');
377
+ if (branch !== repo.defaultBranch) {
378
+ Logger.debug(`Push to non-default branch ${branch}, skipping analysis`);
379
+ return;
380
+ }
381
+ Logger.info(`Auto-analyzing ${repo.fullName} after push`);
382
+ try {
383
+ await githubService.updateRepositoryStatus(repo.id, 'syncing');
384
+ await projectService.runFullAnalysis(repo.projectId);
385
+ await githubService.updateLastAnalyzed(repo.id);
386
+ await githubService.updateRepositoryStatus(repo.id, 'active');
387
+ }
388
+ catch (e) {
389
+ Logger.error(`Analysis failed for ${repo.fullName}: ${e}`);
390
+ await githubService.updateRepositoryStatus(repo.id, 'error', String(e));
391
+ }
392
+ }
393
+ /**
394
+ * Handle pull request event
395
+ */
396
+ async function handlePullRequestEvent(repo, payload) {
397
+ if (!repo || !repo.analyzePRs || !repo.projectId)
398
+ return;
399
+ const action = payload.action;
400
+ const pr = payload.pull_request;
401
+ if (!['opened', 'synchronize', 'reopened'].includes(action)) {
402
+ return;
403
+ }
404
+ Logger.info(`Analyzing PR #${pr.number} for ${repo.fullName}`);
405
+ try {
406
+ // Get PR files
407
+ const connection = await githubService.getConnection(repo.userId);
408
+ if (!connection)
409
+ return;
410
+ const files = await githubService.getPullRequestFiles(repo.userId, repo.fullName, pr.number);
411
+ // Analyze impact
412
+ const impact = await projectService.analyzeImpact(repo.projectId, {
413
+ description: pr.title,
414
+ files: files.map(f => f.filename),
415
+ symbols: [],
416
+ type: 'modify'
417
+ });
418
+ // Generate comment
419
+ const riskEmoji = {
420
+ low: '✅',
421
+ medium: '⚠️',
422
+ high: '🔶',
423
+ critical: '🚨'
424
+ };
425
+ const riskLevel = impact.risks.length > 0
426
+ ? impact.risks.reduce((max, r) => ['critical', 'high', 'medium', 'low'].indexOf(r.severity) <
427
+ ['critical', 'high', 'medium', 'low'].indexOf(max) ? r.severity : max, 'low')
428
+ : 'low';
429
+ const comment = `## ArchiCore Analysis ${riskEmoji[riskLevel]}
430
+
431
+ **Risk Level:** ${riskLevel.toUpperCase()}
432
+ **Affected Components:** ${impact.affectedNodes.length}
433
+ **Files Changed:** ${files.length}
434
+
435
+ ### Impact Summary
436
+
437
+ ${impact.affectedNodes.slice(0, 5).map(n => `- **${n.name}** (${n.type}) - ${n.reason}`).join('\n') || 'No significant impact detected.'}
438
+
439
+ ${impact.risks.length > 0 ? `### Risks
440
+
441
+ ${impact.risks.slice(0, 3).map(r => `- ${riskEmoji[r.severity]} **${r.category}**: ${r.description}`).join('\n')}` : ''}
442
+
443
+ ${impact.recommendations.length > 0 ? `### Recommendations
444
+
445
+ ${impact.recommendations.slice(0, 3).map(r => `- ${r.description}`).join('\n')}` : ''}
446
+
447
+ ---
448
+ *Analyzed by [ArchiCore](https://archicore.io)*`;
449
+ // Post comment
450
+ await githubService.postPRComment(repo.userId, repo.fullName, pr.number, comment);
451
+ }
452
+ catch (e) {
453
+ Logger.error(`PR analysis failed for ${repo.fullName}#${pr.number}: ${e}`);
454
+ }
455
+ }
456
+ // ===== BRANCHES =====
457
+ /**
458
+ * GET /api/github/repositories/:id/branches
459
+ * List branches for a repository
460
+ */
461
+ githubRouter.get('/repositories/:id/branches', authMiddleware, async (req, res) => {
462
+ try {
463
+ if (!req.user) {
464
+ res.status(401).json({ error: 'Not authenticated' });
465
+ return;
466
+ }
467
+ const repo = await githubService.getConnectedRepository(req.params.id);
468
+ if (!repo || repo.userId !== req.user.id) {
469
+ res.status(404).json({ error: 'Repository not found' });
470
+ return;
471
+ }
472
+ const branches = await githubService.listBranches(req.user.id, repo.fullName);
473
+ res.json({
474
+ branches: branches.map(b => ({
475
+ name: b.name,
476
+ protected: b.protected,
477
+ isDefault: b.name === repo.defaultBranch
478
+ }))
479
+ });
480
+ }
481
+ catch (error) {
482
+ Logger.error('List branches error:', error);
483
+ res.status(500).json({ error: 'Failed to list branches' });
484
+ }
485
+ });
486
+ // Cleanup expired OAuth states periodically
487
+ setInterval(() => {
488
+ const now = new Date();
489
+ for (const [state, data] of oauthStates.entries()) {
490
+ if (data.expiresAt < now) {
491
+ oauthStates.delete(state);
492
+ }
493
+ }
494
+ }, 60000); // Every minute
495
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Upload Routes
3
+ *
4
+ * API для загрузки проектов через ZIP архивы.
5
+ */
6
+ export declare const uploadRouter: import("express-serve-static-core").Router;
7
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Upload Routes
3
+ *
4
+ * API для загрузки проектов через ZIP архивы.
5
+ */
6
+ import { Router } from 'express';
7
+ import multer from 'multer';
8
+ import path from 'path';
9
+ import { UploadService } from '../services/upload-service.js';
10
+ import { ProjectService } from '../services/project-service.js';
11
+ import { Logger } from '../../utils/logger.js';
12
+ export const uploadRouter = Router();
13
+ // Singleton сервисов
14
+ const uploadService = new UploadService({
15
+ maxFileSize: parseInt(process.env.MAX_UPLOAD_SIZE || '104857600', 10), // 100MB
16
+ maxExtractedSize: parseInt(process.env.MAX_EXTRACTED_SIZE || '524288000', 10), // 500MB
17
+ uploadsDir: process.env.UPLOADS_DIR || '.archicore/uploads',
18
+ extractDir: process.env.PROJECTS_DIR || '.archicore/projects'
19
+ });
20
+ const projectService = new ProjectService();
21
+ // Multer config для загрузки в память
22
+ const upload = multer({
23
+ storage: multer.memoryStorage(),
24
+ limits: {
25
+ fileSize: parseInt(process.env.MAX_UPLOAD_SIZE || '104857600', 10) // 100MB
26
+ },
27
+ fileFilter: (_req, file, cb) => {
28
+ const ext = path.extname(file.originalname).toLowerCase();
29
+ // Разрешаем только ZIP
30
+ if (ext === '.zip') {
31
+ cb(null, true);
32
+ }
33
+ else {
34
+ cb(new Error(`Unsupported file type: ${ext}. Only .zip files are allowed.`));
35
+ }
36
+ }
37
+ });
38
+ /**
39
+ * POST /api/upload
40
+ * Загрузить и распаковать ZIP архив с проектом
41
+ *
42
+ * Body: multipart/form-data
43
+ * - file: ZIP файл
44
+ * - name: (опционально) Имя проекта
45
+ *
46
+ * Response:
47
+ * - uploadId: ID загрузки
48
+ * - projectId: ID созданного проекта
49
+ * - projectPath: Путь к распакованному проекту
50
+ * - stats: Статистика загрузки
51
+ */
52
+ uploadRouter.post('/', upload.single('file'), async (req, res) => {
53
+ try {
54
+ if (!req.file) {
55
+ res.status(400).json({ error: 'No file uploaded' });
56
+ return;
57
+ }
58
+ Logger.progress(`Processing upload: ${req.file.originalname} (${formatBytes(req.file.size)})`);
59
+ // Обрабатываем загрузку
60
+ const uploadResult = await uploadService.processUpload(req.file.buffer, req.file.originalname);
61
+ if (!uploadResult.success) {
62
+ res.status(400).json({
63
+ error: uploadResult.error,
64
+ warnings: uploadResult.warnings,
65
+ stats: uploadResult.stats
66
+ });
67
+ return;
68
+ }
69
+ // Создаём проект в системе
70
+ const projectName = req.body.name ||
71
+ path.basename(req.file.originalname, '.zip');
72
+ const project = await projectService.createProject(projectName, uploadResult.projectPath);
73
+ Logger.success(`Upload complete: ${projectName} (${uploadResult.stats.fileCount} files)`);
74
+ res.json({
75
+ success: true,
76
+ uploadId: uploadResult.uploadId,
77
+ projectId: project.id,
78
+ projectName: project.name,
79
+ projectPath: uploadResult.projectPath,
80
+ warnings: uploadResult.warnings,
81
+ stats: uploadResult.stats
82
+ });
83
+ }
84
+ catch (error) {
85
+ Logger.error('Upload failed:', error);
86
+ if (error instanceof multer.MulterError) {
87
+ if (error.code === 'LIMIT_FILE_SIZE') {
88
+ res.status(413).json({
89
+ error: 'File too large',
90
+ maxSize: process.env.MAX_UPLOAD_SIZE || '100MB'
91
+ });
92
+ return;
93
+ }
94
+ }
95
+ res.status(500).json({
96
+ error: error instanceof Error ? error.message : 'Upload failed'
97
+ });
98
+ }
99
+ });
100
+ /**
101
+ * POST /api/upload/scan
102
+ * Сканировать архив на угрозы без распаковки
103
+ *
104
+ * Body: multipart/form-data
105
+ * - file: ZIP файл
106
+ *
107
+ * Response:
108
+ * - safe: boolean
109
+ * - threats: SecurityThreat[]
110
+ * - warnings: string[]
111
+ */
112
+ uploadRouter.post('/scan', upload.single('file'), async (req, res) => {
113
+ try {
114
+ if (!req.file) {
115
+ res.status(400).json({ error: 'No file uploaded' });
116
+ return;
117
+ }
118
+ // Сохраняем временно для сканирования
119
+ const fs = await import('fs/promises');
120
+ const tempPath = `.archicore/temp_scan_${Date.now()}.zip`;
121
+ await fs.mkdir('.archicore', { recursive: true });
122
+ await fs.writeFile(tempPath, req.file.buffer);
123
+ try {
124
+ const scanResult = await uploadService.scanArchive(tempPath);
125
+ res.json({
126
+ safe: scanResult.safe,
127
+ threats: scanResult.threats,
128
+ warnings: scanResult.warnings,
129
+ fileInfo: {
130
+ name: req.file.originalname,
131
+ size: req.file.size
132
+ }
133
+ });
134
+ }
135
+ finally {
136
+ // Удаляем временный файл
137
+ await fs.rm(tempPath, { force: true });
138
+ }
139
+ }
140
+ catch (error) {
141
+ Logger.error('Scan failed:', error);
142
+ res.status(500).json({
143
+ error: error instanceof Error ? error.message : 'Scan failed'
144
+ });
145
+ }
146
+ });
147
+ /**
148
+ * GET /api/upload/projects
149
+ * Получить список загруженных проектов
150
+ */
151
+ uploadRouter.get('/projects', async (_req, res) => {
152
+ try {
153
+ const projects = await uploadService.listUploadedProjects();
154
+ res.json({ projects });
155
+ }
156
+ catch (error) {
157
+ Logger.error('Failed to list uploaded projects:', error);
158
+ res.status(500).json({ error: 'Failed to list projects' });
159
+ }
160
+ });
161
+ /**
162
+ * DELETE /api/upload/:uploadId
163
+ * Удалить загруженный проект
164
+ */
165
+ uploadRouter.delete('/:uploadId', async (req, res) => {
166
+ try {
167
+ const { uploadId } = req.params;
168
+ await uploadService.deleteProject(uploadId);
169
+ res.json({ success: true });
170
+ }
171
+ catch (error) {
172
+ Logger.error('Failed to delete upload:', error);
173
+ res.status(500).json({ error: 'Failed to delete upload' });
174
+ }
175
+ });
176
+ /**
177
+ * GET /api/upload/limits
178
+ * Получить лимиты загрузки
179
+ */
180
+ uploadRouter.get('/limits', (_req, res) => {
181
+ res.json({
182
+ maxFileSize: parseInt(process.env.MAX_UPLOAD_SIZE || '104857600', 10),
183
+ maxExtractedSize: parseInt(process.env.MAX_EXTRACTED_SIZE || '524288000', 10),
184
+ allowedFormats: ['.zip'],
185
+ maxCompressionRatio: 100
186
+ });
187
+ });
188
+ function formatBytes(bytes) {
189
+ if (bytes === 0)
190
+ return '0 Bytes';
191
+ const k = 1024;
192
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
193
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
194
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
195
+ }
196
+ //# sourceMappingURL=upload.js.map