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.
@@ -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,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 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