From 799816f66a45a0d6352fa6ffeefb123360924d87 Mon Sep 17 00:00:00 2001 From: Aleksei Pavlov Date: Sun, 21 Jun 2026 16:36:47 +0300 Subject: [PATCH] =?UTF-8?q?fix(drafts):=20=D0=B0=D0=B2=D1=82=D0=BE-=D0=BE?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D1=80=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B2=D1=87=D0=B5=D1=80=D0=B0=D1=88?= =?UTF-8?q?=D0=BD=D0=B8=D1=85=20=D1=87=D0=B5=D1=80=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=20=E2=86=92=20=D1=81=D0=BB=D0=BE=D1=82=D1=8B?= =?UTF-8?q?=20=D1=81=D0=B5=D0=B3=D0=BE=D0=B4=D0=BD=D1=8F=D1=88=D0=BD=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=B4=D0=BD=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Было: runDraftAutoApprove брал ВСЕ черновики подряд и вызывал nextDripSlot() для каждого — слоты набивались на несколько дней вперёд. Теперь: - берём только черновики созданные за последние 36 часов (вчерашние) - слоты — только сегодняшний день (getTodaySlots из SITE_PUBLISH_SLOTS) - занятые слоты пропускаем, не затираем уже запланированное - если слотов меньше черновиков — ставим оставшихся в последний слот Механика как у заметок Зеро: 17:00 → генерация 4 черновиков (лежат в /admin/drafts, можно редактировать) 07:00 след.дня → авто-одобрение, слоты сегодняшнего дня (08:11/12:11/16:11/20:11) Ручное одобрение → nextDripSlot (ближайший свободный) --- draftAutoApprove.js | 89 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/draftAutoApprove.js b/draftAutoApprove.js index d9655f9..b533f2f 100644 --- a/draftAutoApprove.js +++ b/draftAutoApprove.js @@ -1,37 +1,102 @@ // draftAutoApprove.js -// Каждый день в 07:00 МСК переводит все статьи status='draft' → 'published' -// и ставит их на ближайшие publish_slots канала 1. +// Каждый день в 07:00 МСК переводит черновики вчерашней генерации → 'published' +// и ставит их в слоты ТЕКУЩЕГО дня (08:11/12:11/16:11/20:11). +// +// Механика (как у заметок Зеро): +// 17:00 — автогенерация создаёт 4 черновика (по 1 на категорию) +// 07:00 след.дня — авто-одобрение: берём только черновики созданные ВЧЕРА, +// раскладываем по слотам сегодняшнего дня по порядку +// В любой момент — редактор может одобрить черновик вручную (кнопка в /admin/drafts) +// тогда статья получает ближайший свободный слот сегодня const { query } = require('./src/config/db'); const { scheduleForArticle } = require('./src/services/articleAutoPublish'); -const { nextDripSlot } = require('./src/services/dripScheduler'); const AUTO_APPROVE_HOUR_MSK = 7; let lastRunDate = null; +/** + * Возвращает слоты сегодняшнего дня в UTC (из SITE_PUBLISH_SLOTS настройки). + * Слоты заданы в МСК (UTC+3). + */ +async function getTodaySlots() { + const { rows } = await query( + `SELECT value FROM app_settings WHERE key='SITE_PUBLISH_SLOTS' LIMIT 1` + ); + const raw = rows[0]?.value || '08:11,12:11,16:11,20:11'; + const parts = raw.split(',').map(s => s.trim()).filter(Boolean); + + const now = new Date(); + // Сегодня в МСК + const mskNow = new Date(now.getTime() + 3 * 60 * 60 * 1000); + const y = mskNow.getUTCFullYear(); + const m = mskNow.getUTCMonth(); + const d = mskNow.getUTCDate(); + + return parts.map(p => { + const [h, min] = p.split(':').map(Number); + // Конвертируем МСК → UTC (MSK = UTC+3) + const slot = new Date(Date.UTC(y, m, d, h - 3, min, 0, 0)); + return slot; + }).sort((a, b) => a - b); +} + async function runDraftAutoApprove() { try { + // Берём только черновики созданные ВЧЕРА (между 00:00 и 23:59 вчера МСК) + // Это именно те, что сгенерировались накануне и ждут одобрения const { rows: drafts } = await query( - `SELECT id, title, category FROM articles WHERE status='draft' ORDER BY created_at ASC` + `SELECT id, title, category, created_at FROM articles + WHERE status='draft' + AND created_at >= NOW() - INTERVAL '36 hours' + ORDER BY created_at ASC` ); if (!drafts.length) { - console.log('[DraftApprove] no drafts to approve'); + console.log('[DraftApprove] нет черновиков для авто-одобрения'); return; } - console.log(`[DraftApprove] approving ${drafts.length} drafts`); + console.log(`[DraftApprove] авто-одобряем ${drafts.length} черновиков → слоты сегодняшнего дня`); + + // Получаем слоты сегодняшнего дня + const todaySlots = await getTodaySlots(); + const now = new Date(); + + // Проверяем какие слоты уже заняты (pending или published сегодня) + const { rows: takenRows } = await query( + `SELECT scheduled_at FROM scheduled_posts + WHERE status IN ('pending','sent','sending') + AND scheduled_at >= NOW()::date + AND scheduled_at < (NOW()::date + INTERVAL '1 day')`, + ); + const takenTimes = new Set(takenRows.map(r => new Date(r.scheduled_at).getTime())); + + // Ищем свободные слоты (только в будущем и не занятые) + const freeSlots = todaySlots.filter(s => s > now && !takenTimes.has(s.getTime())); + + if (freeSlots.length === 0) { + console.log('[DraftApprove] все сегодняшние слоты заняты — черновики остаются'); + return; + } + + // Раскладываем черновики по свободным слотам (1 к 1) + for (let i = 0; i < drafts.length; i++) { + const draft = drafts[i]; + const slot = freeSlots[i] || freeSlots[freeSlots.length - 1]; // если черновиков больше слотов — ставим в последний - for (const draft of drafts) { - const slot = await nextDripSlot(); await query( `UPDATE articles SET status='published', published_at=$2 WHERE id=$1`, [draft.id, slot] ); await scheduleForArticle(draft.id); - const mskLabel = slot.toLocaleString('ru-RU', { timeZone: 'Europe/Moscow', day:'2-digit', month:'2-digit', hour:'2-digit', minute:'2-digit' }); - console.log(`[DraftApprove] slot ${mskLabel} for article=${draft.id}`); - console.log(`[DraftApprove] approved article=${draft.id} "${draft.title.slice(0, 50)}"`); + + const mskLabel = slot.toLocaleString('ru-RU', { + timeZone: 'Europe/Moscow', + day: '2-digit', month: '2-digit', + hour: '2-digit', minute: '2-digit', + }); + console.log(`[DraftApprove] article=${draft.id} "${draft.title.slice(0, 50)}" → ${mskLabel} МСК`); } } catch (err) { console.error('[DraftApprove] error:', err.message); @@ -39,7 +104,7 @@ async function runDraftAutoApprove() { } function startDraftAutoApproveScheduler() { - console.log('[DraftApprove] scheduler started (auto-approve at 07:00 MSK)'); + console.log('[DraftApprove] scheduler started (auto-approve at 07:00 MSK, только вчерашние черновики)'); setInterval(() => { const now = new Date();