diff --git a/src/routes/admin.js b/src/routes/admin.js index 3eecbac..3ea59f8 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -9,11 +9,19 @@ const { query } = require('../config/db'); function uid(req) { return req.headers['x-user-id'] ? parseInt(req.headers['x-user-id']) : null; } async function requireAdmin(req, res) { + // x-internal-secret уже проверен глобальным middleware (см. index.js). + // Опционально — если есть users.is_admin (multi-user окружение, как на dev2) — проверим; + // если колонки нет (минимальный prod), доверяем секрету. const adminId = uid(req); - if (!adminId) { res.status(401).json({ error: 'x-user-id required' }); return null; } - const { rows: [u] } = await query('SELECT is_admin FROM users WHERE id=$1', [adminId]); - if (!u?.is_admin) { res.status(403).json({ error: 'Forbidden' }); return null; } - return adminId; + if (!adminId) return 'system'; + try { + const { rows: [u] } = await query('SELECT is_admin FROM users WHERE id=$1', [adminId]); + if (u && u.is_admin === false) { res.status(403).json({ error: 'Forbidden' }); return null; } + return adminId; + } catch (err) { + // колонки is_admin нет — это нормально для prod конфига + return adminId; + } } // GET /api/admin/dashboard @@ -559,6 +567,29 @@ router.post('/blog-topics', async (req, res) => { } catch (err) { res.status(500).json({ error: err.message }); } }); + +// PATCH /api/admin/blog-topics/:id — обновить тему (topic, tags, priority, is_used) +router.patch('/blog-topics/:id', async (req, res) => { + if (!await requireAdmin(req, res)) return; + const { topic, tags, priority, is_used } = req.body || {}; + const fields = []; + const vals = []; + if (topic !== undefined) { fields.push(`topic=$${fields.length+1}`); vals.push(String(topic).trim()); } + if (tags !== undefined) { fields.push(`tags=$${fields.length+1}`); vals.push(Array.isArray(tags) ? tags : []); } + if (priority !== undefined) { fields.push(`priority=$${fields.length+1}`); vals.push(parseInt(priority, 10) || 5); } + if (is_used !== undefined) { fields.push(`is_used=$${fields.length+1}`); vals.push(!!is_used); } + if (!fields.length) return res.status(400).json({ error: 'нечего обновлять' }); + vals.push(parseInt(req.params.id, 10)); + try { + const { rows: [row] } = await query( + `UPDATE blog_topics SET ${fields.join(', ')} WHERE id=$${vals.length} RETURNING *`, + vals + ); + if (!row) return res.status(404).json({ error: 'не найдено' }); + res.json({ ok: true, topic: row }); + } catch (err) { res.status(500).json({ error: err.message }); } +}); + // DELETE /api/admin/blog-topics/:id router.delete('/blog-topics/:id', async (req, res) => { if (!await requireAdmin(req, res)) return; @@ -581,14 +612,16 @@ router.post('/blog-topics/generate', async (req, res) => { const ai = require('../services/ai'); const config = require('../config'); - const CATEGORY_NAMES = { - 'ai-tools': 'AI инструменты для работы и бизнеса', - 'ai-dev': 'AI разработка и программирование', - 'automation': 'Автоматизация процессов', - 'cybersec': 'Кибербезопасность', - }; - const system = `Ты редактор tech-блога. Генерируй темы для статей категории "${CATEGORY_NAMES[category] || category}". + // Имя и описание категории берём из БД (а не hardcoded) + const { rows: [catRow] } = await query( + 'SELECT name, description FROM categories WHERE slug=$1', + [category] + ); + const catName = catRow?.name || category; + const catDescr = catRow?.description ? ` (${catRow.description})` : ''; + + const system = `Ты редактор tech-блога. Генерируй темы для статей категории "${catName}"${catDescr}. Темы должны быть: конкретными, практическими, интересными читателям. Формат: точные заголовки статей, не категории. Ответь ТОЛЬКО JSON-массивом строк без markdown.`;