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.
@@ -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 (_req, res) => {
22
+ apiRouter.get('/projects', authMiddleware, async (req, res) => {
18
23
  try {
19
- const projects = await projectService.listProjects();
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
- const project = await projectService.createProject(name, projectPath);
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 project = await projectService.getProject(id);
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 project = await projectService.createProject(name, localPath || repositoryUrl);
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
- const project = await projectService.createProject(projectName || connectedRepo.name, connectedRepo.cloneUrl);
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.projectId = projectId;
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
- res.status(500).json({ error: 'Failed to connect repository' });
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 repo = await githubService.getConnectedRepository(req.params.id);
468
- if (!repo || repo.userId !== req.user.id) {
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.fullName);
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.defaultBranch
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/uploads',
18
- extractDir: process.env.PROJECTS_DIR || '.archicore/projects'
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 tempPath = `.archicore/temp_scan_${Date.now()}.zip`;
121
- await fs.mkdir('.archicore', { recursive: true });
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