feat: topic bank + channel limit + onboarding
Topic bank (P6): - DB: channel_topics(channel_id, topic, is_used) - services/topicBank.js: nextTopic, refillManual, addManual, listTopics, checkAndRefill Авто-пополнение когда <5 тем, пачками по 10 через Claude Haiku - routes/generate.js: GET/POST /topics-bank/:channelId, /refill, /add, DELETE /item/:id Channel limit (P7): - routes/channels.js: POST / → проверяет billing.getBalance().channelsMax перед созданием HTTP 402 + CHANNEL_LIMIT_REACHED если лимит исчерпан - channels/new/page.js: при 402 → ошибка + redirect на /plans через 2 сек ENGINE_URL fix: 3040 → 3030 (lib/engine.js)
This commit is contained in:
@@ -250,6 +250,23 @@ router.post('/', async (req, res) => {
|
||||
const userId = getUserId(req);
|
||||
if (!userId) return res.status(401).json({ error: 'x-user-id required' });
|
||||
try {
|
||||
// Проверяем лимит каналов по тарифу
|
||||
const billing = require('../services/billing');
|
||||
const bal = await billing.getBalance(userId);
|
||||
if (bal.channelsMax !== -1) {
|
||||
const { rows: [{ cnt }] } = await require('../config/db').query(
|
||||
'SELECT count(*)::int as cnt FROM channels WHERE user_id=$1 AND is_active=true', [userId]
|
||||
);
|
||||
if (cnt >= bal.channelsMax) {
|
||||
return res.status(402).json({
|
||||
error: `Лимит каналов по тарифу ${bal.planName}: максимум ${bal.channelsMax}. Перейдите на следующий тариф.`,
|
||||
code: 'CHANNEL_LIMIT_REACHED',
|
||||
current: cnt,
|
||||
max: bal.channelsMax,
|
||||
plan: bal.plan,
|
||||
});
|
||||
}
|
||||
}
|
||||
const channel = await channelsSvc.createChannel(userId, req.body);
|
||||
res.json(channel);
|
||||
} catch (err) {
|
||||
|
||||
@@ -168,3 +168,49 @@ router.post('/from-url', async (req, res) => {
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
// ── Topic Bank API ──────────────────────────────────────────
|
||||
|
||||
const topicBank = require('../services/topicBank');
|
||||
|
||||
// GET /api/generate/topics-bank/:channelId — список тем из банка
|
||||
router.get('/topics-bank/:channelId', async (req, res) => {
|
||||
try {
|
||||
const topics = await topicBank.listTopics(req.params.channelId, {
|
||||
limit: 30,
|
||||
includeUsed: req.query.includeUsed === 'true',
|
||||
});
|
||||
const { rows: [{ cnt }] } = await require('../config/db').query(
|
||||
'SELECT count(*)::int as cnt FROM channel_topics WHERE channel_id=$1 AND is_used=false',
|
||||
[req.params.channelId]
|
||||
);
|
||||
res.json({ topics, total_unused: cnt });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// POST /api/generate/topics-bank/:channelId/refill — пополнить банк вручную
|
||||
router.post('/topics-bank/:channelId/refill', async (req, res) => {
|
||||
try {
|
||||
const saved = await topicBank.refillManual(req.params.channelId);
|
||||
res.json({ ok: true, added: saved.length, topics: saved });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// POST /api/generate/topics-bank/:channelId/add — добавить темы вручную
|
||||
router.post('/topics-bank/:channelId/add', async (req, res) => {
|
||||
try {
|
||||
const { topics = [] } = req.body;
|
||||
const saved = await topicBank.addManual(req.params.channelId, topics);
|
||||
res.json({ ok: true, added: saved.length });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// DELETE /api/generate/topics-bank/:id — удалить тему
|
||||
router.delete('/topics-bank/item/:id', async (req, res) => {
|
||||
try {
|
||||
await require('../config/db').query('DELETE FROM channel_topics 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