archicore 0.1.5 → 0.1.7
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 +162 -16
- 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 +73 -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,31 @@ 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
|
+
Logger.info(`checkProjectAccess: projectId=${id}, userId=${userId}`);
|
|
82
|
+
if (!userId) {
|
|
83
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const isOwner = await projectService.isProjectOwner(id, userId);
|
|
87
|
+
Logger.info(`checkProjectAccess: isOwner=${isOwner}`);
|
|
88
|
+
if (!isOwner) {
|
|
89
|
+
// Log project details for debugging
|
|
90
|
+
const project = await projectService.getProject(id);
|
|
91
|
+
Logger.warn(`Access denied: userId=${userId}, project.ownerId=${project?.ownerId}`);
|
|
92
|
+
res.status(403).json({ error: 'Access denied to this project' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
next();
|
|
96
|
+
}
|
|
65
97
|
/**
|
|
66
98
|
* POST /api/projects/:id/index
|
|
67
99
|
* Запустить индексацию проекта (только для локальных проектов на сервере)
|
|
68
100
|
*/
|
|
69
|
-
apiRouter.post('/projects/:id/index', async (req, res) => {
|
|
101
|
+
apiRouter.post('/projects/:id/index', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
70
102
|
try {
|
|
71
103
|
const { id } = req.params;
|
|
72
104
|
const result = await projectService.indexProject(id);
|
|
@@ -82,7 +114,7 @@ apiRouter.post('/projects/:id/index', async (req, res) => {
|
|
|
82
114
|
* Загрузить индексированные данные с CLI
|
|
83
115
|
* CLI выполняет индексацию локально и отправляет результаты сюда
|
|
84
116
|
*/
|
|
85
|
-
apiRouter.post('/projects/:id/upload-index', async (req, res) => {
|
|
117
|
+
apiRouter.post('/projects/:id/upload-index', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
86
118
|
try {
|
|
87
119
|
const { id } = req.params;
|
|
88
120
|
const { asts, symbols, graph, fileContents, statistics } = req.body;
|
|
@@ -108,7 +140,7 @@ apiRouter.post('/projects/:id/upload-index', async (req, res) => {
|
|
|
108
140
|
* GET /api/projects/:id/architecture
|
|
109
141
|
* Получить архитектурную модель (Digital Twin)
|
|
110
142
|
*/
|
|
111
|
-
apiRouter.get('/projects/:id/architecture', async (req, res) => {
|
|
143
|
+
apiRouter.get('/projects/:id/architecture', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
112
144
|
try {
|
|
113
145
|
const { id } = req.params;
|
|
114
146
|
const architecture = await projectService.getArchitecture(id);
|
|
@@ -123,7 +155,7 @@ apiRouter.get('/projects/:id/architecture', async (req, res) => {
|
|
|
123
155
|
* GET /api/projects/:id/graph
|
|
124
156
|
* Получить граф зависимостей для визуализации
|
|
125
157
|
*/
|
|
126
|
-
apiRouter.get('/projects/:id/graph', async (req, res) => {
|
|
158
|
+
apiRouter.get('/projects/:id/graph', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
127
159
|
try {
|
|
128
160
|
const { id } = req.params;
|
|
129
161
|
const graph = await projectService.getDependencyGraph(id);
|
|
@@ -138,7 +170,7 @@ apiRouter.get('/projects/:id/graph', async (req, res) => {
|
|
|
138
170
|
* POST /api/projects/:id/analyze
|
|
139
171
|
* Анализ влияния изменений
|
|
140
172
|
*/
|
|
141
|
-
apiRouter.post('/projects/:id/analyze', async (req, res) => {
|
|
173
|
+
apiRouter.post('/projects/:id/analyze', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
142
174
|
try {
|
|
143
175
|
const { id } = req.params;
|
|
144
176
|
const { description, files, symbols, type } = req.body;
|
|
@@ -163,7 +195,7 @@ apiRouter.post('/projects/:id/analyze', async (req, res) => {
|
|
|
163
195
|
* POST /api/projects/:id/simulate
|
|
164
196
|
* Симуляция изменения (что будет если...)
|
|
165
197
|
*/
|
|
166
|
-
apiRouter.post('/projects/:id/simulate', async (req, res) => {
|
|
198
|
+
apiRouter.post('/projects/:id/simulate', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
167
199
|
try {
|
|
168
200
|
const { id } = req.params;
|
|
169
201
|
const { change } = req.body;
|
|
@@ -183,7 +215,7 @@ apiRouter.post('/projects/:id/simulate', async (req, res) => {
|
|
|
183
215
|
* POST /api/projects/:id/search
|
|
184
216
|
* Семантический поиск по коду
|
|
185
217
|
*/
|
|
186
|
-
apiRouter.post('/projects/:id/search', async (req, res) => {
|
|
218
|
+
apiRouter.post('/projects/:id/search', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
187
219
|
try {
|
|
188
220
|
const { id } = req.params;
|
|
189
221
|
const { query, limit = 10 } = req.body;
|
|
@@ -203,7 +235,7 @@ apiRouter.post('/projects/:id/search', async (req, res) => {
|
|
|
203
235
|
* POST /api/projects/:id/ask
|
|
204
236
|
* Задать вопрос AI-архитектору
|
|
205
237
|
*/
|
|
206
|
-
apiRouter.post('/projects/:id/ask', async (req, res) => {
|
|
238
|
+
apiRouter.post('/projects/:id/ask', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
207
239
|
try {
|
|
208
240
|
const { id } = req.params;
|
|
209
241
|
const { question, language } = req.body;
|
|
@@ -223,7 +255,7 @@ apiRouter.post('/projects/:id/ask', async (req, res) => {
|
|
|
223
255
|
* GET /api/projects/:id/stats
|
|
224
256
|
* Получить статистику проекта
|
|
225
257
|
*/
|
|
226
|
-
apiRouter.get('/projects/:id/stats', async (req, res) => {
|
|
258
|
+
apiRouter.get('/projects/:id/stats', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
227
259
|
try {
|
|
228
260
|
const { id } = req.params;
|
|
229
261
|
const stats = await projectService.getStatistics(id);
|
|
@@ -238,7 +270,7 @@ apiRouter.get('/projects/:id/stats', async (req, res) => {
|
|
|
238
270
|
* DELETE /api/projects/:id
|
|
239
271
|
* Удалить проект
|
|
240
272
|
*/
|
|
241
|
-
apiRouter.delete('/projects/:id', async (req, res) => {
|
|
273
|
+
apiRouter.delete('/projects/:id', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
242
274
|
try {
|
|
243
275
|
const { id } = req.params;
|
|
244
276
|
await projectService.deleteProject(id);
|
|
@@ -253,7 +285,7 @@ apiRouter.delete('/projects/:id', async (req, res) => {
|
|
|
253
285
|
* GET /api/projects/:id/metrics
|
|
254
286
|
* Получить метрики кода
|
|
255
287
|
*/
|
|
256
|
-
apiRouter.get('/projects/:id/metrics', async (req, res) => {
|
|
288
|
+
apiRouter.get('/projects/:id/metrics', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
257
289
|
try {
|
|
258
290
|
const { id } = req.params;
|
|
259
291
|
const metrics = await projectService.getMetrics(id);
|
|
@@ -268,7 +300,7 @@ apiRouter.get('/projects/:id/metrics', async (req, res) => {
|
|
|
268
300
|
* GET /api/projects/:id/rules
|
|
269
301
|
* Проверить правила архитектуры
|
|
270
302
|
*/
|
|
271
|
-
apiRouter.get('/projects/:id/rules', async (req, res) => {
|
|
303
|
+
apiRouter.get('/projects/:id/rules', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
272
304
|
try {
|
|
273
305
|
const { id } = req.params;
|
|
274
306
|
const result = await projectService.checkRules(id);
|
|
@@ -283,7 +315,7 @@ apiRouter.get('/projects/:id/rules', async (req, res) => {
|
|
|
283
315
|
* GET /api/projects/:id/dead-code
|
|
284
316
|
* Найти мёртвый код
|
|
285
317
|
*/
|
|
286
|
-
apiRouter.get('/projects/:id/dead-code', async (req, res) => {
|
|
318
|
+
apiRouter.get('/projects/:id/dead-code', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
287
319
|
try {
|
|
288
320
|
const { id } = req.params;
|
|
289
321
|
const result = await projectService.findDeadCode(id);
|
|
@@ -298,7 +330,7 @@ apiRouter.get('/projects/:id/dead-code', async (req, res) => {
|
|
|
298
330
|
* GET /api/projects/:id/duplication
|
|
299
331
|
* Найти дублирование кода
|
|
300
332
|
*/
|
|
301
|
-
apiRouter.get('/projects/:id/duplication', async (req, res) => {
|
|
333
|
+
apiRouter.get('/projects/:id/duplication', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
302
334
|
try {
|
|
303
335
|
const { id } = req.params;
|
|
304
336
|
const result = await projectService.findDuplication(id);
|
|
@@ -313,7 +345,7 @@ apiRouter.get('/projects/:id/duplication', async (req, res) => {
|
|
|
313
345
|
* GET /api/projects/:id/security
|
|
314
346
|
* Анализ безопасности
|
|
315
347
|
*/
|
|
316
|
-
apiRouter.get('/projects/:id/security', async (req, res) => {
|
|
348
|
+
apiRouter.get('/projects/:id/security', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
317
349
|
try {
|
|
318
350
|
const { id } = req.params;
|
|
319
351
|
const result = await projectService.analyzeSecurity(id);
|
|
@@ -328,7 +360,7 @@ apiRouter.get('/projects/:id/security', async (req, res) => {
|
|
|
328
360
|
* GET /api/projects/:id/refactoring
|
|
329
361
|
* Получить предложения по рефакторингу
|
|
330
362
|
*/
|
|
331
|
-
apiRouter.get('/projects/:id/refactoring', async (req, res) => {
|
|
363
|
+
apiRouter.get('/projects/:id/refactoring', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
332
364
|
try {
|
|
333
365
|
const { id } = req.params;
|
|
334
366
|
const result = await projectService.getRefactoringSuggestions(id);
|
|
@@ -343,7 +375,7 @@ apiRouter.get('/projects/:id/refactoring', async (req, res) => {
|
|
|
343
375
|
* POST /api/projects/:id/export
|
|
344
376
|
* Экспорт данных проекта
|
|
345
377
|
*/
|
|
346
|
-
apiRouter.post('/projects/:id/export', async (req, res) => {
|
|
378
|
+
apiRouter.post('/projects/:id/export', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
347
379
|
try {
|
|
348
380
|
const { id } = req.params;
|
|
349
381
|
const options = req.body;
|
|
@@ -375,7 +407,7 @@ apiRouter.post('/projects/:id/export', async (req, res) => {
|
|
|
375
407
|
* POST /api/projects/:id/full-analysis
|
|
376
408
|
* Полный анализ проекта (все анализаторы)
|
|
377
409
|
*/
|
|
378
|
-
apiRouter.post('/projects/:id/full-analysis', async (req, res) => {
|
|
410
|
+
apiRouter.post('/projects/:id/full-analysis', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
379
411
|
try {
|
|
380
412
|
const { id } = req.params;
|
|
381
413
|
const result = await projectService.runFullAnalysis(id);
|
|
@@ -386,4 +418,20 @@ apiRouter.post('/projects/:id/full-analysis', async (req, res) => {
|
|
|
386
418
|
res.status(500).json({ error: 'Failed to run full analysis' });
|
|
387
419
|
}
|
|
388
420
|
});
|
|
421
|
+
/**
|
|
422
|
+
* POST /api/projects/:id/documentation
|
|
423
|
+
* Генерация документации по коду проекта
|
|
424
|
+
*/
|
|
425
|
+
apiRouter.post('/projects/:id/documentation', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
426
|
+
try {
|
|
427
|
+
const { id } = req.params;
|
|
428
|
+
const { format = 'markdown', language = 'en' } = req.body;
|
|
429
|
+
const result = await projectService.generateDocumentation(id, { format, language });
|
|
430
|
+
res.json(result);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
Logger.error('Failed to generate documentation:', error);
|
|
434
|
+
res.status(500).json({ error: 'Failed to generate documentation' });
|
|
435
|
+
}
|
|
436
|
+
});
|
|
389
437
|
//# 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
|