feat: autogen run_hour/run_minute, publish_slots, scheduled_posts tables and routes
This commit is contained in:
@@ -26,13 +26,15 @@ router.post('/run', async (req, res) => {
|
|||||||
// PATCH /api/autogen/settings/:category — обновить настройки
|
// PATCH /api/autogen/settings/:category — обновить настройки
|
||||||
router.patch('/settings/:category', async (req, res) => {
|
router.patch('/settings/:category', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { enabled, per_day } = req.body;
|
const { enabled, per_day, run_hour, run_minute } = req.body;
|
||||||
const fields = []; const vals = []; let i = 1;
|
const fields = []; const vals = []; let i = 1;
|
||||||
if (enabled !== undefined) { fields.push(`enabled=$${i++}`); vals.push(enabled); }
|
if (enabled !== undefined) { fields.push(`enabled=${i++}`); vals.push(enabled); }
|
||||||
if (per_day !== undefined) { fields.push(`per_day=$${i++}`); vals.push(per_day); }
|
if (per_day !== undefined) { fields.push(`per_day=${i++}`); vals.push(per_day); }
|
||||||
|
if (run_hour !== undefined) { fields.push(`run_hour=${i++}`); vals.push(run_hour); }
|
||||||
|
if (run_minute !== undefined) { fields.push(`run_minute=${i++}`); vals.push(run_minute); }
|
||||||
if (!fields.length) return res.status(400).json({ error: 'Nothing to update' });
|
if (!fields.length) return res.status(400).json({ error: 'Nothing to update' });
|
||||||
vals.push(req.params.category);
|
vals.push(req.params.category);
|
||||||
await query(`UPDATE autogen_settings SET ${fields.join(',')} WHERE category=$${i}`, vals);
|
await query(`UPDATE autogen_settings SET ${fields.join(',')} WHERE category=${i}`, vals);
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -209,3 +209,73 @@ router.delete('/:id', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
|
// ── Publish slots ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// GET /api/channels/admin/:id/slots
|
||||||
|
router.get('/admin/:id/slots', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { rows } = await query(
|
||||||
|
`SELECT * FROM publish_slots WHERE channel_id=$1 ORDER BY sort_order, slot_hour, slot_minute`,
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
|
res.json(rows);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /api/channels/admin/:id/slots
|
||||||
|
router.post('/admin/:id/slots', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { slot_hour, slot_minute, label, enabled = true } = req.body;
|
||||||
|
const { rows: existing } = await query(
|
||||||
|
`SELECT COUNT(*) as cnt FROM publish_slots WHERE channel_id=$1`, [req.params.id]
|
||||||
|
);
|
||||||
|
const sort_order = parseInt(existing[0].cnt);
|
||||||
|
const { rows } = await query(
|
||||||
|
`INSERT INTO publish_slots (channel_id, slot_hour, slot_minute, label, enabled, sort_order)
|
||||||
|
VALUES ($1,$2,$3,$4,$5,$6)
|
||||||
|
ON CONFLICT (channel_id, slot_hour, slot_minute) DO UPDATE
|
||||||
|
SET label=$4, enabled=$5 RETURNING *`,
|
||||||
|
[req.params.id, slot_hour, slot_minute, label || null, enabled, sort_order]
|
||||||
|
);
|
||||||
|
res.json(rows[0]);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE /api/channels/admin/:id/slots/:slotId
|
||||||
|
router.delete('/admin/:id/slots/:slotId', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await query(`DELETE FROM publish_slots WHERE id=$1 AND channel_id=$2`,
|
||||||
|
[req.params.slotId, req.params.id]);
|
||||||
|
res.json({ ok: true });
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/channels/admin/:id/scheduled — запланированные посты
|
||||||
|
router.get('/admin/:id/scheduled', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { rows } = await query(
|
||||||
|
`SELECT sp.*, a.title as article_title, a.slug as article_slug
|
||||||
|
FROM scheduled_posts sp
|
||||||
|
LEFT JOIN articles a ON a.id = sp.article_id
|
||||||
|
WHERE sp.channel_id=$1
|
||||||
|
ORDER BY sp.scheduled_at DESC LIMIT 30`,
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
|
res.json(rows);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /api/channels/admin/:id/schedule — поставить пост в очередь
|
||||||
|
router.post('/admin/:id/schedule', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { article_id, custom_text, scheduled_at } = req.body;
|
||||||
|
if (!scheduled_at) return res.status(400).json({ error: 'scheduled_at required' });
|
||||||
|
const { rows } = await query(
|
||||||
|
`INSERT INTO scheduled_posts (channel_id, article_id, custom_text, scheduled_at)
|
||||||
|
VALUES ($1,$2,$3,$4) RETURNING *`,
|
||||||
|
[req.params.id, article_id || null, custom_text || null, scheduled_at]
|
||||||
|
);
|
||||||
|
res.json(rows[0]);
|
||||||
|
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||||
|
});
|
||||||
|
|||||||
+27
-6
@@ -129,15 +129,37 @@ async function runAutogenForCategory(category) {
|
|||||||
* Основная функция cron — проверяет какие категории нужно генерировать.
|
* Основная функция cron — проверяет какие категории нужно генерировать.
|
||||||
*/
|
*/
|
||||||
async function runAutogen({ forceCategory = null } = {}) {
|
async function runAutogen({ forceCategory = null } = {}) {
|
||||||
|
let whereClause, params = [];
|
||||||
|
|
||||||
|
if (forceCategory) {
|
||||||
|
// Ручной запуск — игнорируем время
|
||||||
|
whereClause = `WHERE enabled=true AND category=$1`;
|
||||||
|
params = [forceCategory];
|
||||||
|
} else {
|
||||||
|
// Автоматический запуск — проверяем время по расписанию
|
||||||
|
// Берём текущий час/минуту в московском времени (UTC+3)
|
||||||
|
const now = new Date();
|
||||||
|
const mskOffset = 3 * 60; // UTC+3
|
||||||
|
const mskTime = new Date(now.getTime() + mskOffset * 60000);
|
||||||
|
const currentHour = mskTime.getUTCHours();
|
||||||
|
const currentMinute = mskTime.getUTCMinutes();
|
||||||
|
|
||||||
|
console.log(`[Autogen] Check time MSK ${String(currentHour).padStart(2,'0')}:${String(currentMinute).padStart(2,'0')}`);
|
||||||
|
|
||||||
|
whereClause = `WHERE enabled=true
|
||||||
|
AND run_hour=$1
|
||||||
|
AND run_minute BETWEEN $2 AND $3
|
||||||
|
AND (last_run_at IS NULL OR last_run_at < NOW() - INTERVAL '6 hours')`;
|
||||||
|
params = [currentHour, currentMinute - 5, currentMinute + 5];
|
||||||
|
}
|
||||||
|
|
||||||
const { rows: settings } = await query(
|
const { rows: settings } = await query(
|
||||||
`SELECT * FROM autogen_settings WHERE enabled=true
|
`SELECT * FROM autogen_settings ${whereClause} ORDER BY category`,
|
||||||
${forceCategory ? `AND category=$1` : `AND (next_run_at IS NULL OR next_run_at <= NOW())`}
|
params
|
||||||
ORDER BY category`,
|
|
||||||
forceCategory ? [forceCategory] : []
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!settings.length) {
|
if (!settings.length) {
|
||||||
console.log('[Autogen] Nothing to generate');
|
console.log('[Autogen] Nothing to generate at this time');
|
||||||
return { processed: 0, results: [] };
|
return { processed: 0, results: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +167,6 @@ async function runAutogen({ forceCategory = null } = {}) {
|
|||||||
for (const s of settings) {
|
for (const s of settings) {
|
||||||
const result = await runAutogenForCategory(s.category);
|
const result = await runAutogenForCategory(s.category);
|
||||||
results.push({ category: s.category, ...result });
|
results.push({ category: s.category, ...result });
|
||||||
// Пауза между категориями чтобы не перегружать API
|
|
||||||
if (settings.indexOf(s) < settings.length - 1) {
|
if (settings.indexOf(s) < settings.length - 1) {
|
||||||
await new Promise(r => setTimeout(r, 5000));
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user