From e5e7e9ef989cc5e17e1ff760ecb5e3f1d974f7e7 Mon Sep 17 00:00:00 2001 From: Alexey Pavlov Date: Sun, 31 May 2026 14:43:27 +0300 Subject: [PATCH] feat: categories table, API, category field in articles --- index.js | 2 ++ src/routes/articles.js | 2 +- src/routes/categories.js | 28 ++++++++++++++++++++++++++++ src/services/articles.js | 10 ++++------ 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 src/routes/categories.js diff --git a/index.js b/index.js index 9c8b9f5..a035d8c 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ const articlesRoutes = require('./src/routes/articles'); const statsRoutes = require('./src/routes/stats'); const notesRoutes = require('./src/routes/notes'); const seriesRoutes = require('./src/routes/series'); +const categoriesRoutes = require('./src/routes/categories'); // Start queue worker require('./src/workers/generation'); @@ -42,6 +43,7 @@ app.use('/api/articles', articlesRoutes); app.use('/api/stats', statsRoutes); app.use('/api/notes', notesRoutes); app.use('/api/series', seriesRoutes); +app.use('/api/categories', categoriesRoutes); app.get('/health', (req, res) => { res.json({ ok: true, service: 'zeropost-engine', time: new Date() }); diff --git a/src/routes/articles.js b/src/routes/articles.js index 83ebd80..a76fe7b 100644 --- a/src/routes/articles.js +++ b/src/routes/articles.js @@ -57,7 +57,7 @@ router.get('/admin', async (req, res) => { 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, author, reading_time, + `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] diff --git a/src/routes/categories.js b/src/routes/categories.js new file mode 100644 index 0000000..c494cc6 --- /dev/null +++ b/src/routes/categories.js @@ -0,0 +1,28 @@ +const express = require('express'); +const router = express.Router(); +const { query } = require('../config/db'); + +// GET /api/categories +router.get('/', async (_, res) => { + try { + const { rows } = await query('SELECT * FROM categories ORDER BY sort_order'); + res.json(rows); + } catch (err) { res.status(500).json({ error: err.message }); } +}); + +// GET /api/categories/:slug/articles +router.get('/:slug/articles', async (req, res) => { + try { + const limit = Math.min(parseInt(req.query.limit) || 20, 100); + const offset = parseInt(req.query.offset) || 0; + const { rows } = await query( + `SELECT id,slug,title,excerpt,cover_url,tags,category,author,reading_time,published_at + FROM articles WHERE status='published' AND category=$1 + ORDER BY published_at DESC LIMIT $2 OFFSET $3`, + [req.params.slug, limit, offset] + ); + res.json(rows); + } catch (err) { res.status(500).json({ error: err.message }); } +}); + +module.exports = router; diff --git a/src/services/articles.js b/src/services/articles.js index 2b84e24..8fd5d08 100644 --- a/src/services/articles.js +++ b/src/services/articles.js @@ -31,14 +31,12 @@ function estimateReadingTime(text) { /** * Список опубликованных статей. */ -async function listArticles({ limit = 20, offset = 0, tag = null } = {}) { - let sql = `SELECT id, slug, title, excerpt, cover_url, tags, author, reading_time, published_at +async function listArticles({ limit = 20, offset = 0, tag = null, category = null } = {}) { + let sql = `SELECT id, slug, title, excerpt, cover_url, tags, category, author, reading_time, published_at FROM articles WHERE status='published'`; const params = []; - if (tag) { - sql += ` AND tags ?? $${params.length + 1}`; - params.push(tag); - } + if (tag) { sql += ` AND tags ?? ${params.length + 1}`; params.push(tag); } + if (category) { sql += ` AND category=${params.length + 1}`; params.push(category); } sql += ` ORDER BY published_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`; params.push(limit, offset); const { rows } = await query(sql, params);