/
sweetebin
/
hackaton_26
Обзор
Документация
Войти
/
sweetebin
/
hackaton_26
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
web-form-project/mock-server/server.js
174 строки
6 KB
AKorostelev
web form project added
28 июл 2026, 10:57
28 июл 2026, 10:57
4339e77
Код
Авторство
О чём код?
// Мок-сервер анализа отзывов: реализует контракт из docs/api.md. // Хранит проекты в памяти (при старте — два с готовым анализом), // «переанализирует» пул данных с задержкой ~2 секунды. import express from 'express'; import cors from 'cors'; import multer from 'multer'; import path from 'node:path'; import { buildResult, seedProjects } from './fixtures.js'; const PORT = process.env.PORT || 3001; const PROCESSING_MS = 2000; const ALLOWED_EXT = new Set(['.docx', '.md', '.txt', '.csv']); const JIRA_KEY_RE = /^[A-Z][A-Z0-9]{1,9}$/; const app = express(); app.use(cors()); app.use(express.json({ limit: '10mb' })); const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } }); const projects = new Map(seedProjects().map((p) => [p.id, p])); let projectSeq = projects.size; let sourceSeq = 100; // Возвращает описание нового источника данных из запроса или пишет ошибку в res function extractSource(req, res) { if (req.file) { const ext = path.extname(req.file.originalname).toLowerCase(); if (!ALLOWED_EXT.has(ext)) { res.status(400).json({ error: `Неподдерживаемый формат файла: ${ext}. Допустимы: docx, md, txt, csv.` }); return null; } sourceSeq += 1; return { id: `src${sourceSeq}`, kind: 'file', name: Buffer.from(req.file.originalname, 'latin1').toString('utf8'), addedAt: new Date().toISOString(), }; } const text = req.body?.text; if (typeof text !== 'string' || !text.trim()) { res.status(400).json({ error: 'Передайте файл в поле "file" или непустой текст в поле "text".' }); return null; } sourceSeq += 1; return { id: `src${sourceSeq}`, kind: 'text', name: 'Вставка текста', addedAt: new Date().toISOString() }; } function startAnalysis(project) { project.status = 'processing'; setTimeout(() => { project.result = buildResult(project.jiraProjectKey); project.status = 'done'; project.updatedAt = new Date().toISOString(); }, PROCESSING_MS); } function summary(p) { return { id: p.id, name: p.name, jiraProjectKey: p.jiraProjectKey, status: p.status, sourcesCount: p.sources.length, updatedAt: p.updatedAt, stats: p.status === 'done' && p.result ? { clusters: p.result.clusters.length, signals: p.result.signals.length, pendingTasks: p.result.suggestedTasks.filter((t) => t.status === 'pending').length, } : null, }; } app.get('/api/projects', (_req, res) => { res.json([...projects.values()].map(summary)); }); app.post('/api/projects', upload.single('file'), (req, res) => { const jiraProjectKey = (req.body?.jiraProjectKey || '').trim().toUpperCase(); if (!JIRA_KEY_RE.test(jiraProjectKey)) { return res.status(400).json({ error: 'Укажите корректный ключ Jira-проекта: 2–10 символов, латинские буквы и цифры, начинается с буквы (например, PROJ).', }); } const source = extractSource(req, res); if (!source) return; projectSeq += 1; const project = { id: `p${projectSeq}`, name: (req.body?.name || '').trim() || `Проект ${jiraProjectKey}`, jiraProjectKey, status: 'processing', sources: [source], updatedAt: new Date().toISOString(), result: null, jiraCounter: 0, }; projects.set(project.id, project); startAnalysis(project); res.status(201).json({ projectId: project.id, status: 'processing' }); }); app.get('/api/projects/:id', (req, res) => { const p = projects.get(req.params.id); if (!p) return res.status(404).json({ error: 'Проект не найден.' }); const { id, name, jiraProjectKey, status, sources, updatedAt, result } = p; res.json({ id, name, jiraProjectKey, status, sources, updatedAt, result: status === 'done' ? result : null, }); }); app.post('/api/projects/:id/data', upload.single('file'), (req, res) => { const p = projects.get(req.params.id); if (!p) return res.status(404).json({ error: 'Проект не найден.' }); if (p.status === 'processing') { return res.status(409).json({ error: 'Проект уже анализируется, дождитесь завершения.' }); } const source = extractSource(req, res); if (!source) return; p.sources.push(source); startAnalysis(p); res.status(202).json({ projectId: p.id, status: 'processing' }); }); function findTask(req, res) { const p = projects.get(req.params.id); if (!p || p.status !== 'done') { res.status(404).json({ error: 'Проект не найден или анализ ещё не завершён.' }); return null; } const task = p.result.suggestedTasks.find((t) => t.id === req.params.taskId); if (!task) { res.status(404).json({ error: 'Задача не найдена.' }); return null; } if (task.status !== 'pending') { res.status(409).json({ error: `Задача уже обработана (${task.status}).` }); return null; } return { project: p, task }; } app.post('/api/projects/:id/tasks/:taskId/approve', (req, res) => { const found = findTask(req, res); if (!found) return; const { project, task } = found; project.jiraCounter += 1; task.status = 'approved'; task.jiraId = `${project.jiraProjectKey}-${project.jiraCounter}`; task.jiraUrl = `https://jira.example.com/browse/${task.jiraId}`; res.status(201).json({ jiraId: task.jiraId, jiraUrl: task.jiraUrl }); }); app.post('/api/projects/:id/tasks/:taskId/reject', (req, res) => { const found = findTask(req, res); if (!found) return; found.task.status = 'rejected'; res.json({ status: 'rejected' }); }); app.listen(PORT, () => { console.log(`Мок-сервер анализа отзывов: http://localhost:${PORT}`); });