feat: AI-генерация обложек + /api/stats + раздача /uploads

- services/covers.js: gpt-image-1, фиксированный стиль emerald-geometric, fallback на ошибки шлюза
- articles.generateAndSaveArticle: запускает обложку в setImmediate (не блокирует ответ)
- routes/articles: POST /backfill-covers для досгенерации
- routes/stats: статистика блога (статьи, слова, токены, просмотры)
- index.js: express.static на /uploads БЕЗ авторизации (публичные картинки)
This commit is contained in:
Alexey Pavlov
2026-05-31 09:17:08 +03:00
parent 500bb0299e
commit c7b83147f1
5 changed files with 180 additions and 0 deletions
+11
View File
@@ -7,6 +7,7 @@ const generateRoutes = require('./src/routes/generate');
const channelsRoutes = require('./src/routes/channels');
const postsRoutes = require('./src/routes/posts');
const articlesRoutes = require('./src/routes/articles');
const statsRoutes = require('./src/routes/stats');
// Start queue worker
require('./src/workers/generation');
@@ -14,6 +15,15 @@ require('./src/workers/generation');
const app = express();
app.use(express.json());
// Раздача загруженных файлов (обложки статей и т.п.)
const path = require('path');
const UPLOADS_DIR = process.env.UPLOADS_DIR || '/var/www/zeropost-uploads';
require('fs').mkdirSync(UPLOADS_DIR, { recursive: true });
// Public uploads — ДО auth-middleware, без секрета
app.use('/uploads', express.static(UPLOADS_DIR, { maxAge: '7d', immutable: true }));
// Simple internal auth middleware
app.use((req, res, next) => {
const secret = req.headers['x-internal-secret'];
@@ -27,6 +37,7 @@ app.use('/api/generate', generateRoutes);
app.use('/api/channels', channelsRoutes);
app.use('/api/posts', postsRoutes);
app.use('/api/articles', articlesRoutes);
app.use('/api/stats', statsRoutes);
app.get('/health', (req, res) => {
res.json({ ok: true, service: 'zeropost-engine', time: new Date() });