From b5fa77ea01da5860b1b655e8cee3d78b8d112cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=20=28Claude=29?= Date: Sat, 13 Jun 2026 10:35:50 +0300 Subject: [PATCH] feat: autogen blog admin API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit routes/admin.js: GET /autogen — настройки+статистика+очередь+размеры банков тем PATCH /autogen/:category — enabled/per_day/run_hour/run_minute POST /autogen/:category/run — ручной запуск генерации POST /autogen/queue — добавить тему с приоритетом DELETE /autogen/queue/:id — удалить тему --- src/routes/admin.js | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/routes/admin.js b/src/routes/admin.js index 7ad2223..2ede442 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -405,3 +405,88 @@ router.get('/logs', async (req, res) => { res.json({ errors: all, total: all.length, topErrors }); } catch (err) { res.status(500).json({ error: err.message }); } }); + +// ── AUTOGEN BLOG ───────────────────────────────────────────── + +// GET /api/admin/autogen — статус автогенерации блога +router.get('/autogen', async (req, res) => { + if (!await requireAdmin(req, res)) return; + try { + const { getAutogenStatus, TOPIC_BANK } = require('../services/autogen'); + const status = await getAutogenStatus(); + + // Статистика статей по категориям за последние 7 дней + const { rows: recentStats } = await query(` + SELECT category, count(*)::int as cnt_7d, + max(created_at) as last_article_at + FROM articles + WHERE status='published' AND created_at > NOW() - INTERVAL '7 days' + GROUP BY category + `); + const byCategory = Object.fromEntries(recentStats.map(r => [r.category, r])); + + // Очередь тем + const { rows: queueItems } = await query( + `SELECT * FROM content_queue ORDER BY priority DESC, created_at ASC LIMIT 20` + ); + + const topicBankSizes = Object.fromEntries( + Object.entries(TOPIC_BANK).map(([k, v]) => [k, v.length]) + ); + + res.json({ settings: status, byCategory, queue: queueItems, topicBankSizes }); + } catch (err) { res.status(500).json({ error: err.message }); } +}); + +// PATCH /api/admin/autogen/:category — обновить настройки категории +router.patch('/autogen/:category', async (req, res) => { + if (!await requireAdmin(req, res)) return; + try { + const { enabled, per_day, run_hour, run_minute } = req.body; + const fields = []; const vals = []; let i = 1; + if (enabled !== undefined) { fields.push(`enabled=$${i++}`); vals.push(enabled); } + if (per_day !== undefined) { fields.push(`per_day=$${i++}`); vals.push(per_day); } + if (run_hour !== undefined) { fields.push(`run_hour=$${i++}`); vals.push(run_hour); } + if (run_minute !== undefined) { fields.push(`run_minute=$${i++}`); vals.push(run_minute); } + if (!fields.length) return res.status(400).json({ error: 'Nothing to update' }); + vals.push(req.params.category); + await query(`UPDATE autogen_settings SET ${fields.join(',')} WHERE category=$${i}`, vals); + res.json({ ok: true }); + } catch (err) { res.status(500).json({ error: err.message }); } +}); + +// POST /api/admin/autogen/:category/run — запустить генерацию вручную +router.post('/autogen/:category/run', async (req, res) => { + if (!await requireAdmin(req, res)) return; + try { + res.json({ ok: true, message: `Генерация категории ${req.params.category} запущена` }); + const { runAutogenForCategory } = require('../services/autogen'); + runAutogenForCategory(req.params.category).catch(e => + console.error(`[Autogen manual] ${req.params.category}: ${e.message}`) + ); + } catch (err) { res.status(500).json({ error: err.message }); } +}); + +// POST /api/admin/autogen/queue — добавить тему в очередь +router.post('/autogen/queue', async (req, res) => { + if (!await requireAdmin(req, res)) return; + const { category, topic, tags = [], keywords = [], priority = 5 } = req.body; + if (!category || !topic) return res.status(400).json({ error: 'category и topic обязательны' }); + try { + const { rows: [item] } = await query( + `INSERT INTO content_queue (category, topic, tags, keywords, priority) + VALUES ($1,$2,$3,$4,$5) RETURNING *`, + [category, topic, JSON.stringify(tags), JSON.stringify(keywords), priority] + ); + res.json(item); + } catch (err) { res.status(500).json({ error: err.message }); } +}); + +// DELETE /api/admin/autogen/queue/:id +router.delete('/autogen/queue/:id', async (req, res) => { + if (!await requireAdmin(req, res)) return; + try { + await query('DELETE FROM content_queue WHERE id=$1', [req.params.id]); + res.json({ ok: true }); + } catch (err) { res.status(500).json({ error: err.message }); } +});