feat: articles — публичный блог zeropost.ru

- БД: таблица articles (slug, title, excerpt, content, cover, tags, SEO)
- services/articles.js: slugify (ru→en транслит), reading_time, генерация со встроенным blog-channel
- routes/articles.js: GET list/tags/:slug, POST /generate
- Универсальный blogChannel со стилем для лонгридов: tone:friendly, structure:headers, без эмодзи и хэштегов
- generateAndSaveArticle: вытаскивает title из H1, генерит excerpt, считает время чтения
This commit is contained in:
Alexey Pavlov
2026-05-31 08:45:34 +03:00
parent 5599de59ce
commit 500bb0299e
4 changed files with 230 additions and 0 deletions
+25
View File
@@ -119,6 +119,29 @@ const migrate = async () => {
);
`);
// articles — статьи для публичного блога zeropost.ru
await query(`
CREATE TABLE IF NOT EXISTS articles (
id SERIAL PRIMARY KEY,
slug VARCHAR(255) UNIQUE NOT NULL,
title VARCHAR(500) NOT NULL,
excerpt TEXT,
content TEXT NOT NULL,
cover_url TEXT,
tags JSONB DEFAULT '[]'::jsonb,
author VARCHAR(100) DEFAULT 'ZeroPost AI',
reading_time INTEGER,
status VARCHAR(20) DEFAULT 'published',
views INTEGER DEFAULT 0,
seo_title VARCHAR(500),
seo_descr TEXT,
job_id INTEGER REFERENCES generation_jobs(id) ON DELETE SET NULL,
published_at TIMESTAMPTZ DEFAULT NOW(),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
`);
// индексы
await query(`
CREATE INDEX IF NOT EXISTS idx_channels_user ON channels(user_id);
@@ -126,6 +149,8 @@ const migrate = async () => {
CREATE INDEX IF NOT EXISTS idx_posts_scheduled ON posts(scheduled_at) WHERE status='scheduled';
CREATE INDEX IF NOT EXISTS idx_jobs_status ON generation_jobs(status);
CREATE INDEX IF NOT EXISTS idx_jobs_user ON generation_jobs(user_id);
CREATE INDEX IF NOT EXISTS idx_articles_slug ON articles(slug);
CREATE INDEX IF NOT EXISTS idx_articles_status_pub ON articles(status, published_at DESC);
`);
console.log('[DB] Migrations applied');