archicore 0.1.4 → 0.1.6
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/dist/cli/commands/interactive.js +136 -18
- package/dist/code-index/ast-parser.js +34 -2
- package/dist/github/github-service.d.ts +8 -0
- package/dist/github/github-service.js +76 -7
- package/dist/server/routes/api.js +68 -25
- package/dist/server/routes/developer.js +2 -1
- package/dist/server/routes/github.js +116 -10
- package/dist/server/routes/upload.js +9 -6
- package/dist/server/services/project-service.d.ts +22 -3
- package/dist/server/services/project-service.js +160 -8
- package/dist/utils/file-utils.js +78 -6
- package/package.json +4 -2
|
@@ -7,16 +7,22 @@ import { Router } from 'express';
|
|
|
7
7
|
import { ProjectService } from '../services/project-service.js';
|
|
8
8
|
import { Logger } from '../../utils/logger.js';
|
|
9
9
|
import { ExportManager } from '../../export/index.js';
|
|
10
|
+
import { authMiddleware } from './auth.js';
|
|
10
11
|
export const apiRouter = Router();
|
|
11
12
|
// Singleton сервиса проектов
|
|
12
13
|
const projectService = new ProjectService();
|
|
14
|
+
// Helper to get user ID from request
|
|
15
|
+
const getUserId = (req) => {
|
|
16
|
+
return req.user?.id;
|
|
17
|
+
};
|
|
13
18
|
/**
|
|
14
19
|
* GET /api/projects
|
|
15
|
-
* Получить список проектов
|
|
20
|
+
* Получить список проектов текущего пользователя
|
|
16
21
|
*/
|
|
17
|
-
apiRouter.get('/projects', async (
|
|
22
|
+
apiRouter.get('/projects', authMiddleware, async (req, res) => {
|
|
18
23
|
try {
|
|
19
|
-
const
|
|
24
|
+
const userId = getUserId(req);
|
|
25
|
+
const projects = await projectService.listProjects(userId);
|
|
20
26
|
res.json({ projects });
|
|
21
27
|
}
|
|
22
28
|
catch (error) {
|
|
@@ -28,14 +34,19 @@ apiRouter.get('/projects', async (_req, res) => {
|
|
|
28
34
|
* POST /api/projects
|
|
29
35
|
* Создать/подключить новый проект
|
|
30
36
|
*/
|
|
31
|
-
apiRouter.post('/projects', async (req, res) => {
|
|
37
|
+
apiRouter.post('/projects', authMiddleware, async (req, res) => {
|
|
32
38
|
try {
|
|
33
39
|
const { name, path: projectPath } = req.body;
|
|
40
|
+
const userId = getUserId(req);
|
|
34
41
|
if (!name || !projectPath) {
|
|
35
42
|
res.status(400).json({ error: 'Name and path are required' });
|
|
36
43
|
return;
|
|
37
44
|
}
|
|
38
|
-
|
|
45
|
+
if (!userId) {
|
|
46
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const project = await projectService.createProject(name, projectPath, userId);
|
|
39
50
|
res.json({ project });
|
|
40
51
|
}
|
|
41
52
|
catch (error) {
|
|
@@ -47,10 +58,11 @@ apiRouter.post('/projects', async (req, res) => {
|
|
|
47
58
|
* GET /api/projects/:id
|
|
48
59
|
* Получить информацию о проекте
|
|
49
60
|
*/
|
|
50
|
-
apiRouter.get('/projects/:id', async (req, res) => {
|
|
61
|
+
apiRouter.get('/projects/:id', authMiddleware, async (req, res) => {
|
|
51
62
|
try {
|
|
52
63
|
const { id } = req.params;
|
|
53
|
-
const
|
|
64
|
+
const userId = getUserId(req);
|
|
65
|
+
const project = await projectService.getProject(id, userId);
|
|
54
66
|
if (!project) {
|
|
55
67
|
res.status(404).json({ error: 'Project not found' });
|
|
56
68
|
return;
|
|
@@ -62,11 +74,26 @@ apiRouter.get('/projects/:id', async (req, res) => {
|
|
|
62
74
|
res.status(500).json({ error: 'Failed to get project' });
|
|
63
75
|
}
|
|
64
76
|
});
|
|
77
|
+
// Middleware to check project access
|
|
78
|
+
async function checkProjectAccess(req, res, next) {
|
|
79
|
+
const { id } = req.params;
|
|
80
|
+
const userId = getUserId(req);
|
|
81
|
+
if (!userId) {
|
|
82
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const isOwner = await projectService.isProjectOwner(id, userId);
|
|
86
|
+
if (!isOwner) {
|
|
87
|
+
res.status(403).json({ error: 'Access denied to this project' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
next();
|
|
91
|
+
}
|
|
65
92
|
/**
|
|
66
93
|
* POST /api/projects/:id/index
|
|
67
94
|
* Запустить индексацию проекта (только для локальных проектов на сервере)
|
|
68
95
|
*/
|
|
69
|
-
apiRouter.post('/projects/:id/index', async (req, res) => {
|
|
96
|
+
apiRouter.post('/projects/:id/index', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
70
97
|
try {
|
|
71
98
|
const { id } = req.params;
|
|
72
99
|
const result = await projectService.indexProject(id);
|
|
@@ -82,7 +109,7 @@ apiRouter.post('/projects/:id/index', async (req, res) => {
|
|
|
82
109
|
* Загрузить индексированные данные с CLI
|
|
83
110
|
* CLI выполняет индексацию локально и отправляет результаты сюда
|
|
84
111
|
*/
|
|
85
|
-
apiRouter.post('/projects/:id/upload-index', async (req, res) => {
|
|
112
|
+
apiRouter.post('/projects/:id/upload-index', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
86
113
|
try {
|
|
87
114
|
const { id } = req.params;
|
|
88
115
|
const { asts, symbols, graph, fileContents, statistics } = req.body;
|
|
@@ -108,7 +135,7 @@ apiRouter.post('/projects/:id/upload-index', async (req, res) => {
|
|
|
108
135
|
* GET /api/projects/:id/architecture
|
|
109
136
|
* Получить архитектурную модель (Digital Twin)
|
|
110
137
|
*/
|
|
111
|
-
apiRouter.get('/projects/:id/architecture', async (req, res) => {
|
|
138
|
+
apiRouter.get('/projects/:id/architecture', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
112
139
|
try {
|
|
113
140
|
const { id } = req.params;
|
|
114
141
|
const architecture = await projectService.getArchitecture(id);
|
|
@@ -123,7 +150,7 @@ apiRouter.get('/projects/:id/architecture', async (req, res) => {
|
|
|
123
150
|
* GET /api/projects/:id/graph
|
|
124
151
|
* Получить граф зависимостей для визуализации
|
|
125
152
|
*/
|
|
126
|
-
apiRouter.get('/projects/:id/graph', async (req, res) => {
|
|
153
|
+
apiRouter.get('/projects/:id/graph', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
127
154
|
try {
|
|
128
155
|
const { id } = req.params;
|
|
129
156
|
const graph = await projectService.getDependencyGraph(id);
|
|
@@ -138,7 +165,7 @@ apiRouter.get('/projects/:id/graph', async (req, res) => {
|
|
|
138
165
|
* POST /api/projects/:id/analyze
|
|
139
166
|
* Анализ влияния изменений
|
|
140
167
|
*/
|
|
141
|
-
apiRouter.post('/projects/:id/analyze', async (req, res) => {
|
|
168
|
+
apiRouter.post('/projects/:id/analyze', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
142
169
|
try {
|
|
143
170
|
const { id } = req.params;
|
|
144
171
|
const { description, files, symbols, type } = req.body;
|
|
@@ -163,7 +190,7 @@ apiRouter.post('/projects/:id/analyze', async (req, res) => {
|
|
|
163
190
|
* POST /api/projects/:id/simulate
|
|
164
191
|
* Симуляция изменения (что будет если...)
|
|
165
192
|
*/
|
|
166
|
-
apiRouter.post('/projects/:id/simulate', async (req, res) => {
|
|
193
|
+
apiRouter.post('/projects/:id/simulate', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
167
194
|
try {
|
|
168
195
|
const { id } = req.params;
|
|
169
196
|
const { change } = req.body;
|
|
@@ -183,7 +210,7 @@ apiRouter.post('/projects/:id/simulate', async (req, res) => {
|
|
|
183
210
|
* POST /api/projects/:id/search
|
|
184
211
|
* Семантический поиск по коду
|
|
185
212
|
*/
|
|
186
|
-
apiRouter.post('/projects/:id/search', async (req, res) => {
|
|
213
|
+
apiRouter.post('/projects/:id/search', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
187
214
|
try {
|
|
188
215
|
const { id } = req.params;
|
|
189
216
|
const { query, limit = 10 } = req.body;
|
|
@@ -203,7 +230,7 @@ apiRouter.post('/projects/:id/search', async (req, res) => {
|
|
|
203
230
|
* POST /api/projects/:id/ask
|
|
204
231
|
* Задать вопрос AI-архитектору
|
|
205
232
|
*/
|
|
206
|
-
apiRouter.post('/projects/:id/ask', async (req, res) => {
|
|
233
|
+
apiRouter.post('/projects/:id/ask', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
207
234
|
try {
|
|
208
235
|
const { id } = req.params;
|
|
209
236
|
const { question, language } = req.body;
|
|
@@ -223,7 +250,7 @@ apiRouter.post('/projects/:id/ask', async (req, res) => {
|
|
|
223
250
|
* GET /api/projects/:id/stats
|
|
224
251
|
* Получить статистику проекта
|
|
225
252
|
*/
|
|
226
|
-
apiRouter.get('/projects/:id/stats', async (req, res) => {
|
|
253
|
+
apiRouter.get('/projects/:id/stats', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
227
254
|
try {
|
|
228
255
|
const { id } = req.params;
|
|
229
256
|
const stats = await projectService.getStatistics(id);
|
|
@@ -238,7 +265,7 @@ apiRouter.get('/projects/:id/stats', async (req, res) => {
|
|
|
238
265
|
* DELETE /api/projects/:id
|
|
239
266
|
* Удалить проект
|
|
240
267
|
*/
|
|
241
|
-
apiRouter.delete('/projects/:id', async (req, res) => {
|
|
268
|
+
apiRouter.delete('/projects/:id', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
242
269
|
try {
|
|
243
270
|
const { id } = req.params;
|
|
244
271
|
await projectService.deleteProject(id);
|
|
@@ -253,7 +280,7 @@ apiRouter.delete('/projects/:id', async (req, res) => {
|
|
|
253
280
|
* GET /api/projects/:id/metrics
|
|
254
281
|
* Получить метрики кода
|
|
255
282
|
*/
|
|
256
|
-
apiRouter.get('/projects/:id/metrics', async (req, res) => {
|
|
283
|
+
apiRouter.get('/projects/:id/metrics', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
257
284
|
try {
|
|
258
285
|
const { id } = req.params;
|
|
259
286
|
const metrics = await projectService.getMetrics(id);
|
|
@@ -268,7 +295,7 @@ apiRouter.get('/projects/:id/metrics', async (req, res) => {
|
|
|
268
295
|
* GET /api/projects/:id/rules
|
|
269
296
|
* Проверить правила архитектуры
|
|
270
297
|
*/
|
|
271
|
-
apiRouter.get('/projects/:id/rules', async (req, res) => {
|
|
298
|
+
apiRouter.get('/projects/:id/rules', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
272
299
|
try {
|
|
273
300
|
const { id } = req.params;
|
|
274
301
|
const result = await projectService.checkRules(id);
|
|
@@ -283,7 +310,7 @@ apiRouter.get('/projects/:id/rules', async (req, res) => {
|
|
|
283
310
|
* GET /api/projects/:id/dead-code
|
|
284
311
|
* Найти мёртвый код
|
|
285
312
|
*/
|
|
286
|
-
apiRouter.get('/projects/:id/dead-code', async (req, res) => {
|
|
313
|
+
apiRouter.get('/projects/:id/dead-code', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
287
314
|
try {
|
|
288
315
|
const { id } = req.params;
|
|
289
316
|
const result = await projectService.findDeadCode(id);
|
|
@@ -298,7 +325,7 @@ apiRouter.get('/projects/:id/dead-code', async (req, res) => {
|
|
|
298
325
|
* GET /api/projects/:id/duplication
|
|
299
326
|
* Найти дублирование кода
|
|
300
327
|
*/
|
|
301
|
-
apiRouter.get('/projects/:id/duplication', async (req, res) => {
|
|
328
|
+
apiRouter.get('/projects/:id/duplication', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
302
329
|
try {
|
|
303
330
|
const { id } = req.params;
|
|
304
331
|
const result = await projectService.findDuplication(id);
|
|
@@ -313,7 +340,7 @@ apiRouter.get('/projects/:id/duplication', async (req, res) => {
|
|
|
313
340
|
* GET /api/projects/:id/security
|
|
314
341
|
* Анализ безопасности
|
|
315
342
|
*/
|
|
316
|
-
apiRouter.get('/projects/:id/security', async (req, res) => {
|
|
343
|
+
apiRouter.get('/projects/:id/security', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
317
344
|
try {
|
|
318
345
|
const { id } = req.params;
|
|
319
346
|
const result = await projectService.analyzeSecurity(id);
|
|
@@ -328,7 +355,7 @@ apiRouter.get('/projects/:id/security', async (req, res) => {
|
|
|
328
355
|
* GET /api/projects/:id/refactoring
|
|
329
356
|
* Получить предложения по рефакторингу
|
|
330
357
|
*/
|
|
331
|
-
apiRouter.get('/projects/:id/refactoring', async (req, res) => {
|
|
358
|
+
apiRouter.get('/projects/:id/refactoring', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
332
359
|
try {
|
|
333
360
|
const { id } = req.params;
|
|
334
361
|
const result = await projectService.getRefactoringSuggestions(id);
|
|
@@ -343,7 +370,7 @@ apiRouter.get('/projects/:id/refactoring', async (req, res) => {
|
|
|
343
370
|
* POST /api/projects/:id/export
|
|
344
371
|
* Экспорт данных проекта
|
|
345
372
|
*/
|
|
346
|
-
apiRouter.post('/projects/:id/export', async (req, res) => {
|
|
373
|
+
apiRouter.post('/projects/:id/export', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
347
374
|
try {
|
|
348
375
|
const { id } = req.params;
|
|
349
376
|
const options = req.body;
|
|
@@ -375,7 +402,7 @@ apiRouter.post('/projects/:id/export', async (req, res) => {
|
|
|
375
402
|
* POST /api/projects/:id/full-analysis
|
|
376
403
|
* Полный анализ проекта (все анализаторы)
|
|
377
404
|
*/
|
|
378
|
-
apiRouter.post('/projects/:id/full-analysis', async (req, res) => {
|
|
405
|
+
apiRouter.post('/projects/:id/full-analysis', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
379
406
|
try {
|
|
380
407
|
const { id } = req.params;
|
|
381
408
|
const result = await projectService.runFullAnalysis(id);
|
|
@@ -386,4 +413,20 @@ apiRouter.post('/projects/:id/full-analysis', async (req, res) => {
|
|
|
386
413
|
res.status(500).json({ error: 'Failed to run full analysis' });
|
|
387
414
|
}
|
|
388
415
|
});
|
|
416
|
+
/**
|
|
417
|
+
* POST /api/projects/:id/documentation
|
|
418
|
+
* Генерация документации по коду проекта
|
|
419
|
+
*/
|
|
420
|
+
apiRouter.post('/projects/:id/documentation', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
421
|
+
try {
|
|
422
|
+
const { id } = req.params;
|
|
423
|
+
const { format = 'markdown', language = 'en' } = req.body;
|
|
424
|
+
const result = await projectService.generateDocumentation(id, { format, language });
|
|
425
|
+
res.json(result);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
Logger.error('Failed to generate documentation:', error);
|
|
429
|
+
res.status(500).json({ error: 'Failed to generate documentation' });
|
|
430
|
+
}
|
|
431
|
+
});
|
|
389
432
|
//# sourceMappingURL=api.js.map
|
|
@@ -218,7 +218,8 @@ developerRouter.post('/v1/projects', apiKeyAuth, requirePermission('write:projec
|
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
220
220
|
// Для API создаём проект по URL или пути
|
|
221
|
-
const
|
|
221
|
+
const userId = req.apiContext?.userId || 'api-user';
|
|
222
|
+
const project = await projectService.createProject(name, localPath || repositoryUrl, userId);
|
|
222
223
|
res.status(201).json({
|
|
223
224
|
success: true,
|
|
224
225
|
data: { project }
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Router } from 'express';
|
|
7
7
|
import { randomBytes } from 'crypto';
|
|
8
|
+
import { mkdir } from 'fs/promises';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import AdmZip from 'adm-zip';
|
|
8
11
|
import { GitHubService } from '../../github/github-service.js';
|
|
9
12
|
import { ProjectService } from '../services/project-service.js';
|
|
10
13
|
import { authMiddleware } from './auth.js';
|
|
@@ -221,11 +224,35 @@ githubRouter.post('/repositories/connect', authMiddleware, async (req, res) => {
|
|
|
221
224
|
res.status(401).json({ error: 'Not authenticated' });
|
|
222
225
|
return;
|
|
223
226
|
}
|
|
224
|
-
const { repositoryId, autoAnalyze, analyzePRs, createProject, projectName } = req.body;
|
|
227
|
+
const { repositoryId, autoAnalyze, analyzePRs, createProject, projectName, forceReconnect, branch } = req.body;
|
|
225
228
|
if (!repositoryId) {
|
|
226
229
|
res.status(400).json({ error: 'repositoryId is required' });
|
|
227
230
|
return;
|
|
228
231
|
}
|
|
232
|
+
// Check if already connected
|
|
233
|
+
const existingRepos = await githubService.getConnectedRepositories(req.user.id);
|
|
234
|
+
const existingRepo = existingRepos.find(r => r.githubRepoId === repositoryId);
|
|
235
|
+
if (existingRepo) {
|
|
236
|
+
if (forceReconnect) {
|
|
237
|
+
// Force reconnect - disconnect first
|
|
238
|
+
Logger.info(`Force reconnecting repository: ${existingRepo.fullName}`);
|
|
239
|
+
await githubService.disconnectRepository(req.user.id, existingRepo.id);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Already connected - return existing connection
|
|
243
|
+
res.json({
|
|
244
|
+
success: true,
|
|
245
|
+
repository: {
|
|
246
|
+
id: existingRepo.id,
|
|
247
|
+
fullName: existingRepo.fullName,
|
|
248
|
+
projectId: existingRepo.projectId,
|
|
249
|
+
webhookActive: !!existingRepo.webhookId
|
|
250
|
+
},
|
|
251
|
+
message: 'Repository already connected'
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
229
256
|
// Connect repository
|
|
230
257
|
const connectedRepo = await githubService.connectRepository(req.user.id, repositoryId, {
|
|
231
258
|
autoAnalyze: autoAnalyze ?? true,
|
|
@@ -233,12 +260,73 @@ githubRouter.post('/repositories/connect', authMiddleware, async (req, res) => {
|
|
|
233
260
|
});
|
|
234
261
|
// Optionally create ArchiCore project
|
|
235
262
|
let projectId;
|
|
263
|
+
Logger.info(`createProject=${createProject}, will create: ${createProject !== false}`);
|
|
236
264
|
if (createProject !== false) {
|
|
237
265
|
try {
|
|
238
|
-
|
|
266
|
+
// Download repository as ZIP (from specified branch or default)
|
|
267
|
+
const targetBranch = branch || connectedRepo.defaultBranch || 'HEAD';
|
|
268
|
+
Logger.progress(`Downloading repository: ${connectedRepo.fullName} (branch: ${targetBranch})`);
|
|
269
|
+
const zipBuffer = await githubService.downloadRepository(req.user.id, connectedRepo.fullName, targetBranch);
|
|
270
|
+
Logger.info(`Downloaded ZIP: ${zipBuffer.length} bytes`);
|
|
271
|
+
// Create projects directory
|
|
272
|
+
const projectsDir = process.env.PROJECTS_DIR || join('.archicore', 'projects');
|
|
273
|
+
await mkdir(projectsDir, { recursive: true });
|
|
274
|
+
Logger.info(`Projects dir: ${projectsDir}`);
|
|
275
|
+
// Extract to project directory using adm-zip
|
|
276
|
+
const projectPath = join(projectsDir, connectedRepo.name);
|
|
277
|
+
await mkdir(projectPath, { recursive: true });
|
|
278
|
+
Logger.info(`Extracting to: ${projectPath}`);
|
|
279
|
+
// Extract ZIP using adm-zip (more reliable than unzipper)
|
|
280
|
+
const zip = new AdmZip(zipBuffer);
|
|
281
|
+
const zipEntries = zip.getEntries();
|
|
282
|
+
Logger.info(`ZIP contains ${zipEntries.length} entries`);
|
|
283
|
+
// Log first few entries for debugging
|
|
284
|
+
zipEntries.slice(0, 10).forEach(entry => {
|
|
285
|
+
Logger.info(` - ${entry.entryName} (${entry.header.size} bytes, dir=${entry.isDirectory})`);
|
|
286
|
+
});
|
|
287
|
+
// Extract all files - use manual extraction for better control
|
|
288
|
+
let extractedCount = 0;
|
|
289
|
+
for (const entry of zipEntries) {
|
|
290
|
+
if (entry.isDirectory) {
|
|
291
|
+
// Create directory
|
|
292
|
+
const dirPath = join(projectPath, entry.entryName);
|
|
293
|
+
await mkdir(dirPath, { recursive: true });
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
// Extract file
|
|
297
|
+
const filePath = join(projectPath, entry.entryName);
|
|
298
|
+
const fileDir = join(projectPath, entry.entryName.split('/').slice(0, -1).join('/'));
|
|
299
|
+
await mkdir(fileDir, { recursive: true });
|
|
300
|
+
const content = entry.getData();
|
|
301
|
+
const { writeFile } = await import('fs/promises');
|
|
302
|
+
await writeFile(filePath, content);
|
|
303
|
+
extractedCount++;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
Logger.info(`Extraction complete: ${extractedCount} files extracted`);
|
|
307
|
+
// GitHub creates a folder like "repo-main" inside, find it
|
|
308
|
+
const { readdir, stat } = await import('fs/promises');
|
|
309
|
+
const contents = await readdir(projectPath);
|
|
310
|
+
Logger.info(`Extracted contents: ${contents.join(', ')}`);
|
|
311
|
+
let actualPath = projectPath;
|
|
312
|
+
// If there's a single folder inside, use that as the project path
|
|
313
|
+
if (contents.length === 1) {
|
|
314
|
+
const innerPath = join(projectPath, contents[0]);
|
|
315
|
+
const stats = await stat(innerPath);
|
|
316
|
+
if (stats.isDirectory()) {
|
|
317
|
+
actualPath = innerPath;
|
|
318
|
+
Logger.info(`Using inner path: ${actualPath}`);
|
|
319
|
+
// List files in the inner directory for debugging
|
|
320
|
+
const innerContents = await readdir(actualPath);
|
|
321
|
+
Logger.info(`Inner folder contents: ${innerContents.join(', ')}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
Logger.success(`Downloaded and extracted to: ${actualPath}`);
|
|
325
|
+
const project = await projectService.createProject(projectName || connectedRepo.name, actualPath, req.user.id);
|
|
239
326
|
projectId = project.id;
|
|
240
|
-
// Update connected repo with project ID
|
|
241
|
-
connectedRepo.
|
|
327
|
+
// Update connected repo with project ID and save to file
|
|
328
|
+
await githubService.updateRepositoryProjectId(connectedRepo.id, projectId);
|
|
329
|
+
Logger.info(`Linked project ${projectId} to repository ${connectedRepo.fullName}`);
|
|
242
330
|
}
|
|
243
331
|
catch (e) {
|
|
244
332
|
Logger.warn(`Failed to create project for ${connectedRepo.fullName}: ${e}`);
|
|
@@ -256,7 +344,8 @@ githubRouter.post('/repositories/connect', authMiddleware, async (req, res) => {
|
|
|
256
344
|
}
|
|
257
345
|
catch (error) {
|
|
258
346
|
Logger.error('Connect repository error:', error);
|
|
259
|
-
|
|
347
|
+
const message = error instanceof Error ? error.message : 'Failed to connect repository';
|
|
348
|
+
res.status(500).json({ error: message });
|
|
260
349
|
}
|
|
261
350
|
});
|
|
262
351
|
/**
|
|
@@ -456,7 +545,7 @@ ${impact.recommendations.slice(0, 3).map(r => `- ${r.description}`).join('\n')}`
|
|
|
456
545
|
// ===== BRANCHES =====
|
|
457
546
|
/**
|
|
458
547
|
* GET /api/github/repositories/:id/branches
|
|
459
|
-
* List branches for a repository
|
|
548
|
+
* List branches for a repository (by GitHub repo ID - not connected yet)
|
|
460
549
|
*/
|
|
461
550
|
githubRouter.get('/repositories/:id/branches', authMiddleware, async (req, res) => {
|
|
462
551
|
try {
|
|
@@ -464,17 +553,34 @@ githubRouter.get('/repositories/:id/branches', authMiddleware, async (req, res)
|
|
|
464
553
|
res.status(401).json({ error: 'Not authenticated' });
|
|
465
554
|
return;
|
|
466
555
|
}
|
|
467
|
-
const
|
|
468
|
-
|
|
556
|
+
const repoId = parseInt(req.params.id, 10);
|
|
557
|
+
// First try to find in connected repos
|
|
558
|
+
const connectedRepo = await githubService.getConnectedRepository(req.params.id);
|
|
559
|
+
if (connectedRepo && connectedRepo.userId === req.user.id) {
|
|
560
|
+
// Use connected repo info
|
|
561
|
+
const branches = await githubService.listBranches(req.user.id, connectedRepo.fullName);
|
|
562
|
+
res.json({
|
|
563
|
+
branches: branches.map(b => ({
|
|
564
|
+
name: b.name,
|
|
565
|
+
protected: b.protected,
|
|
566
|
+
isDefault: b.name === connectedRepo.defaultBranch
|
|
567
|
+
}))
|
|
568
|
+
});
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
// Not connected - find in user's GitHub repos list
|
|
572
|
+
const userRepos = await githubService.listUserRepositories(req.user.id);
|
|
573
|
+
const repo = userRepos.find(r => r.id === repoId);
|
|
574
|
+
if (!repo) {
|
|
469
575
|
res.status(404).json({ error: 'Repository not found' });
|
|
470
576
|
return;
|
|
471
577
|
}
|
|
472
|
-
const branches = await githubService.listBranches(req.user.id, repo.
|
|
578
|
+
const branches = await githubService.listBranches(req.user.id, repo.full_name);
|
|
473
579
|
res.json({
|
|
474
580
|
branches: branches.map(b => ({
|
|
475
581
|
name: b.name,
|
|
476
582
|
protected: b.protected,
|
|
477
|
-
isDefault: b.name === repo.
|
|
583
|
+
isDefault: b.name === repo.default_branch
|
|
478
584
|
}))
|
|
479
585
|
});
|
|
480
586
|
}
|
|
@@ -9,13 +9,14 @@ import path from 'path';
|
|
|
9
9
|
import { UploadService } from '../services/upload-service.js';
|
|
10
10
|
import { ProjectService } from '../services/project-service.js';
|
|
11
11
|
import { Logger } from '../../utils/logger.js';
|
|
12
|
+
import { authMiddleware } from './auth.js';
|
|
12
13
|
export const uploadRouter = Router();
|
|
13
14
|
// Singleton сервисов
|
|
14
15
|
const uploadService = new UploadService({
|
|
15
16
|
maxFileSize: parseInt(process.env.MAX_UPLOAD_SIZE || '104857600', 10), // 100MB
|
|
16
17
|
maxExtractedSize: parseInt(process.env.MAX_EXTRACTED_SIZE || '524288000', 10), // 500MB
|
|
17
|
-
uploadsDir: process.env.UPLOADS_DIR || '.archicore
|
|
18
|
-
extractDir: process.env.PROJECTS_DIR || '.archicore
|
|
18
|
+
uploadsDir: process.env.UPLOADS_DIR || path.join('.archicore', 'uploads'),
|
|
19
|
+
extractDir: process.env.PROJECTS_DIR || path.join('.archicore', 'projects')
|
|
19
20
|
});
|
|
20
21
|
const projectService = new ProjectService();
|
|
21
22
|
// Multer config для загрузки в память
|
|
@@ -49,12 +50,13 @@ const upload = multer({
|
|
|
49
50
|
* - projectPath: Путь к распакованному проекту
|
|
50
51
|
* - stats: Статистика загрузки
|
|
51
52
|
*/
|
|
52
|
-
uploadRouter.post('/', upload.single('file'), async (req, res) => {
|
|
53
|
+
uploadRouter.post('/', authMiddleware, upload.single('file'), async (req, res) => {
|
|
53
54
|
try {
|
|
54
55
|
if (!req.file) {
|
|
55
56
|
res.status(400).json({ error: 'No file uploaded' });
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
59
|
+
const userId = req.user?.id || 'anonymous';
|
|
58
60
|
Logger.progress(`Processing upload: ${req.file.originalname} (${formatBytes(req.file.size)})`);
|
|
59
61
|
// Обрабатываем загрузку
|
|
60
62
|
const uploadResult = await uploadService.processUpload(req.file.buffer, req.file.originalname);
|
|
@@ -69,7 +71,7 @@ uploadRouter.post('/', upload.single('file'), async (req, res) => {
|
|
|
69
71
|
// Создаём проект в системе
|
|
70
72
|
const projectName = req.body.name ||
|
|
71
73
|
path.basename(req.file.originalname, '.zip');
|
|
72
|
-
const project = await projectService.createProject(projectName, uploadResult.projectPath);
|
|
74
|
+
const project = await projectService.createProject(projectName, uploadResult.projectPath, userId);
|
|
73
75
|
Logger.success(`Upload complete: ${projectName} (${uploadResult.stats.fileCount} files)`);
|
|
74
76
|
res.json({
|
|
75
77
|
success: true,
|
|
@@ -117,8 +119,9 @@ uploadRouter.post('/scan', upload.single('file'), async (req, res) => {
|
|
|
117
119
|
}
|
|
118
120
|
// Сохраняем временно для сканирования
|
|
119
121
|
const fs = await import('fs/promises');
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
+
const tempDir = '.archicore';
|
|
123
|
+
const tempPath = path.join(tempDir, `temp_scan_${Date.now()}.zip`);
|
|
124
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
122
125
|
await fs.writeFile(tempPath, req.file.buffer);
|
|
123
126
|
try {
|
|
124
127
|
const scanResult = await uploadService.scanArchive(tempPath);
|
|
@@ -16,6 +16,7 @@ export interface Project {
|
|
|
16
16
|
id: string;
|
|
17
17
|
name: string;
|
|
18
18
|
path: string;
|
|
19
|
+
ownerId: string;
|
|
19
20
|
createdAt: string;
|
|
20
21
|
lastIndexedAt?: string;
|
|
21
22
|
status: 'pending' | 'indexing' | 'ready' | 'error';
|
|
@@ -34,9 +35,13 @@ export declare class ProjectService {
|
|
|
34
35
|
constructor();
|
|
35
36
|
private loadProjects;
|
|
36
37
|
private saveProjects;
|
|
37
|
-
listProjects(): Promise<Project[]>;
|
|
38
|
-
createProject(name: string, projectPath: string): Promise<Project>;
|
|
39
|
-
getProject(id: string): Promise<Project | null>;
|
|
38
|
+
listProjects(userId?: string): Promise<Project[]>;
|
|
39
|
+
createProject(name: string, projectPath: string, ownerId: string): Promise<Project>;
|
|
40
|
+
getProject(id: string, userId?: string): Promise<Project | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Check if user owns project
|
|
43
|
+
*/
|
|
44
|
+
isProjectOwner(projectId: string, userId: string): Promise<boolean>;
|
|
40
45
|
private initializeProjectData;
|
|
41
46
|
private getProjectData;
|
|
42
47
|
indexProject(projectId: string): Promise<{
|
|
@@ -145,5 +150,19 @@ export declare class ProjectService {
|
|
|
145
150
|
* затем пробуем читать из файловой системы (для локальных проектов)
|
|
146
151
|
*/
|
|
147
152
|
private getFileContents;
|
|
153
|
+
/**
|
|
154
|
+
* Генерация документации по коду проекта
|
|
155
|
+
*/
|
|
156
|
+
generateDocumentation(projectId: string, options?: {
|
|
157
|
+
format?: string;
|
|
158
|
+
language?: string;
|
|
159
|
+
}): Promise<{
|
|
160
|
+
documentation: string;
|
|
161
|
+
format: string;
|
|
162
|
+
}>;
|
|
163
|
+
/**
|
|
164
|
+
* Построить текстовое представление структуры файлов
|
|
165
|
+
*/
|
|
166
|
+
private buildFileStructure;
|
|
148
167
|
}
|
|
149
168
|
//# sourceMappingURL=project-service.d.ts.map
|