const express = require('express'); const router = express.Router(); const articlesSvc = require('../services/articles'); // GET /api/articles — список router.get('/', async (req, res) => { try { const limit = Math.min(parseInt(req.query.limit) || 20, 100); const offset = parseInt(req.query.offset) || 0; const tag = req.query.tag || null; const list = await articlesSvc.listArticles({ limit, offset, tag }); res.json(list); } catch (err) { res.status(500).json({ error: err.message }); } }); // GET /api/articles/tags — топ тегов router.get('/tags', async (_, res) => { try { const tags = await articlesSvc.getAllTags(); res.json(tags); } catch (err) { res.status(500).json({ error: err.message }); } }); // GET /api/articles/:slug — одна router.get('/:slug', async (req, res) => { try { const a = await articlesSvc.getArticleBySlug(req.params.slug); if (!a) return res.status(404).json({ error: 'Not found' }); res.json(a); } catch (err) { res.status(500).json({ error: err.message }); } }); // POST /api/articles/generate — сгенерировать и сохранить (синхронно, для cron) router.post('/generate', async (req, res) => { try { const { topic, keywords = [], tags = [], autoPublish = true } = req.body; if (!topic) return res.status(400).json({ error: 'topic is required' }); const article = await articlesSvc.generateAndSaveArticle({ topic, keywords, tags, autoPublish }); res.json(article); } catch (err) { console.error('[Articles] generate', err); res.status(500).json({ error: err.message }); } }); // GET /api/articles/admin — все статьи для админки (включая черновики, все поля) router.get('/admin', async (req, res) => { try { const { query } = require('../config/db'); const limit = Math.min(parseInt(req.query.limit) || 100, 200); const offset = parseInt(req.query.offset) || 0; const { rows } = await query( `SELECT id, slug, title, excerpt, cover_url, tags, category, author, reading_time, status, seo_title, seo_descr, views, published_at, created_at, updated_at FROM articles ORDER BY created_at DESC LIMIT $1 OFFSET $2`, [limit, offset] ); res.json(rows); } catch (err) { res.status(500).json({ error: err.message }); } }); // GET /api/articles/id/:id — одна статья по числовому id router.get('/id/:id', async (req, res) => { try { const { query } = require('../config/db'); const { rows } = await query('SELECT * FROM articles WHERE id=$1', [req.params.id]); if (!rows.length) return res.status(404).json({ error: 'Not found' }); res.json(rows[0]); } catch (err) { res.status(500).json({ error: err.message }); } }); // PATCH /api/articles/:id — обновить статью router.patch('/:id', async (req, res) => { try { const { query } = require('../config/db'); const { title, excerpt, content, tags, status, seo_title, seo_descr, cover_url } = req.body; const fields = []; const vals = []; let i = 1; if (title !== undefined) { fields.push(`title=${i++}`); vals.push(title); } if (excerpt !== undefined) { fields.push(`excerpt=${i++}`); vals.push(excerpt); } if (content !== undefined) { fields.push(`content=${i++}`); vals.push(content); } if (tags !== undefined) { fields.push(`tags=${i++}`); vals.push(tags); } if (status !== undefined) { fields.push(`status=${i++}`); vals.push(status); } if (seo_title !== undefined) { fields.push(`seo_title=${i++}`); vals.push(seo_title); } if (seo_descr !== undefined) { fields.push(`seo_descr=${i++}`); vals.push(seo_descr); } if (cover_url !== undefined) { fields.push(`cover_url=${i++}`); vals.push(cover_url); } if (!fields.length) return res.status(400).json({ error: 'Nothing to update' }); fields.push(`updated_at=NOW()`); vals.push(req.params.id); const { rows } = await query( `UPDATE articles SET ${fields.join(', ')} WHERE id=${i} RETURNING *`, vals ); if (!rows.length) return res.status(404).json({ error: 'Not found' }); res.json(rows[0]); } catch (err) { res.status(500).json({ error: err.message }); } }); // DELETE /api/articles/:id — удалить статью router.delete('/:id', async (req, res) => { try { const { query } = require('../config/db'); const { rowCount } = await query('DELETE FROM articles WHERE id=$1', [req.params.id]); if (!rowCount) return res.status(404).json({ error: 'Not found' }); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); // POST /api/articles/backfill-covers — досгенерировать обложки для статей без них router.post('/backfill-covers', async (req, res) => { try { const covers = require('../services/covers'); const limit = parseInt(req.body?.limit) || 3; const result = await covers.backfillCovers({ limit }); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); module.exports = router;