feat: post drafts system — batch generation + daily auto-drafts
DB: post_drafts(channel_id, topic, text, image_url, status), channels.auto_draft_*
Engine:
services/draftService.js: generateOneDraft, generateBatch, generateDailyDrafts,
approveDraft(→scheduled_post), rejectDraft, updateDraft, listDrafts
routes/drafts.js: GET/PATCH/DELETE /api/drafts/:id, /approve, /reject
POST /api/channels/:channelId/drafts/generate?count=N (async, returns immediately)
index.js: cron каждые 30 мин → generateDailyDrafts() для каналов с auto_draft_enabled
channels.js: updateChannel сохраняет auto_draft_enabled/count/time
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* drafts.js — API для черновиков постов.
|
||||
*
|
||||
* POST /api/channels/:channelId/drafts/generate?count=3 — batch генерация
|
||||
* GET /api/drafts — все черновики юзера
|
||||
* GET /api/drafts/:channelId/channel — черновики канала
|
||||
* PATCH /api/drafts/:id — редактировать текст
|
||||
* POST /api/drafts/:id/approve — одобрить → scheduled_post
|
||||
* POST /api/drafts/:id/reject — отклонить
|
||||
* DELETE /api/drafts/:id — удалить
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { query } = require('../config/db');
|
||||
const channelsSvc = require('../services/channels');
|
||||
const draftSvc = require('../services/draftService');
|
||||
|
||||
function uid(req) { return req.headers['x-user-id'] ? parseInt(req.headers['x-user-id']) : null; }
|
||||
|
||||
// POST /api/channels/:channelId/drafts/generate
|
||||
router.post('/channels/:channelId/drafts/generate', async (req, res) => {
|
||||
const userId = uid(req);
|
||||
if (!userId) return res.status(401).json({ error: 'x-user-id required' });
|
||||
|
||||
const count = Math.min(parseInt(req.query.count || req.body.count || 3), 10);
|
||||
const withImage = req.body.withImage !== false;
|
||||
|
||||
try {
|
||||
const channel = await channelsSvc.getFullChannel(req.params.channelId);
|
||||
if (!channel) return res.status(404).json({ error: 'Channel not found' });
|
||||
|
||||
// Запускаем асинхронно — отвечаем сразу
|
||||
res.json({ ok: true, message: `Генерирую ${count} черновиков...`, count });
|
||||
|
||||
draftSvc.generateBatch(channel, { count, userId, withImage })
|
||||
.then(r => console.log(`[drafts] batch done ch=${channel.id}: ${r.generated} ok, ${r.errors.length} err`))
|
||||
.catch(e => console.error(`[drafts] batch error: ${e.message}`));
|
||||
} catch (err) {
|
||||
if (!res.headersSent) res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/drafts — все черновики текущего пользователя
|
||||
router.get('/drafts', async (req, res) => {
|
||||
const userId = uid(req);
|
||||
if (!userId) return res.status(401).json({ error: 'x-user-id required' });
|
||||
const { status = 'pending', limit = 30, offset = 0 } = req.query;
|
||||
try {
|
||||
const result = await draftSvc.listDrafts({ userId, status, limit: parseInt(limit), offset: parseInt(offset) });
|
||||
res.json(result);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// GET /api/drafts/:channelId/channel — черновики конкретного канала
|
||||
router.get('/drafts/:channelId/channel', async (req, res) => {
|
||||
const userId = uid(req);
|
||||
if (!userId) return res.status(401).json({ error: 'x-user-id required' });
|
||||
const { status = 'pending', limit = 30, offset = 0 } = req.query;
|
||||
try {
|
||||
const result = await draftSvc.listDrafts({ channelId: req.params.channelId, status, limit: parseInt(limit), offset: parseInt(offset) });
|
||||
res.json(result);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// PATCH /api/drafts/:id — редактировать
|
||||
router.patch('/drafts/:id', async (req, res) => {
|
||||
try {
|
||||
await draftSvc.updateDraft(req.params.id, req.body);
|
||||
res.json({ ok: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// POST /api/drafts/:id/approve
|
||||
router.post('/drafts/:id/approve', async (req, res) => {
|
||||
const userId = uid(req);
|
||||
try {
|
||||
const result = await draftSvc.approveDraft(req.params.id, {
|
||||
scheduledAt: req.body.scheduled_at,
|
||||
userId,
|
||||
});
|
||||
res.json({ ok: true, ...result });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// POST /api/drafts/:id/reject
|
||||
router.post('/drafts/:id/reject', async (req, res) => {
|
||||
try {
|
||||
await draftSvc.rejectDraft(req.params.id);
|
||||
res.json({ ok: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// DELETE /api/drafts/:id
|
||||
router.delete('/drafts/:id', async (req, res) => {
|
||||
try {
|
||||
await query('DELETE FROM post_drafts WHERE id=$1', [req.params.id]);
|
||||
res.json({ ok: true });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user