feat: user_posts service — draft/scheduled/published, Telegram publish with image, cron-driven scheduled publication

This commit is contained in:
Alexey Pavlov
2026-05-31 17:36:01 +03:00
parent 2137a92b28
commit d054023a55
3 changed files with 236 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
const express = require('express');
const router = express.Router();
const svc = require('../services/userPosts');
const getUserId = (req) => parseInt(req.headers['x-user-id']) || null;
// GET /api/user-posts?channel_id=N&status=draft
router.get('/', async (req, res) => {
try {
const userId = getUserId(req);
if (!userId) return res.status(401).json({ error: 'Unauthorized' });
const posts = await svc.listUserPosts({
userId,
channelId: req.query.channel_id ? parseInt(req.query.channel_id) : null,
status: req.query.status || null,
limit: parseInt(req.query.limit) || 50,
});
res.json(posts);
} catch (err) { res.status(500).json({ error: err.message }); }
});
// POST /api/user-posts
router.post('/', async (req, res) => {
try {
const userId = getUserId(req);
if (!userId) return res.status(401).json({ error: 'Unauthorized' });
const { channel_id, content, image_url, topic, status, scheduled_at } = req.body;
if (!channel_id || !content) return res.status(400).json({ error: 'channel_id and content required' });
const post = await svc.savePost({
userId, channelId: channel_id, content,
imageUrl: image_url, topic,
status: status || 'draft',
scheduledAt: scheduled_at,
});
res.json(post);
} catch (err) { res.status(500).json({ error: err.message }); }
});
// GET /api/user-posts/:id
router.get('/:id', async (req, res) => {
try {
const userId = getUserId(req);
if (!userId) return res.status(401).json({ error: 'Unauthorized' });
const post = await svc.getPost(userId, req.params.id);
if (!post) return res.status(404).json({ error: 'Not found' });
res.json(post);
} catch (err) { res.status(500).json({ error: err.message }); }
});
// PATCH /api/user-posts/:id
router.patch('/:id', async (req, res) => {
try {
const userId = getUserId(req);
if (!userId) return res.status(401).json({ error: 'Unauthorized' });
const post = await svc.updatePost(userId, req.params.id, req.body);
if (!post) return res.status(404).json({ error: 'Not found' });
res.json(post);
} catch (err) { res.status(500).json({ error: err.message }); }
});
// DELETE /api/user-posts/:id
router.delete('/:id', async (req, res) => {
try {
const userId = getUserId(req);
if (!userId) return res.status(401).json({ error: 'Unauthorized' });
await svc.deletePost(userId, req.params.id);
res.json({ ok: true });
} catch (err) { res.status(500).json({ error: err.message }); }
});
// POST /api/user-posts/:id/publish — опубликовать прямо сейчас
router.post('/:id/publish', async (req, res) => {
try {
const userId = getUserId(req);
if (!userId) return res.status(401).json({ error: 'Unauthorized' });
const result = await svc.publishPost(userId, req.params.id);
res.json(result);
} catch (err) { res.status(500).json({ error: err.message }); }
});
// POST /api/user-posts/run-scheduled — внутренний endpoint для cron
router.post('/run-scheduled', async (req, res) => {
try {
const result = await svc.runScheduledPublications();
res.json(result);
} catch (err) { res.status(500).json({ error: err.message }); }
});
module.exports = router;