forked from admin/zeropost-engine
feat: auto-retry SVG covers every 30 min
coverRetry.js: сканирует articles с cover < 30KB (SVG-заглушки), перегенерирует через covers.generateCover(). При недоступности провайдера (timeout/502) прерывает цикл до следующего запуска. Первый запуск через 5 мин после старта engine, далее каждые 30 мин.
This commit is contained in:
@@ -91,6 +91,10 @@ const start = async () => {
|
|||||||
await migrate();
|
await migrate();
|
||||||
await config.reloadAi();
|
await config.reloadAi();
|
||||||
console.log('[Engine] AI config loaded from app_settings: text=' + config.ai.baseUrl + ', images=' + config.ai.imageBaseUrl);
|
console.log('[Engine] AI config loaded from app_settings: text=' + config.ai.baseUrl + ', images=' + config.ai.imageBaseUrl);
|
||||||
|
|
||||||
|
// Автоматический ретрай SVG-заглушек
|
||||||
|
require('./src/services/coverRetry').start();
|
||||||
|
|
||||||
app.listen(config.port, () => {
|
app.listen(config.port, () => {
|
||||||
console.log(`[Engine] Running on port ${config.port}`);
|
console.log(`[Engine] Running on port ${config.port}`);
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ const covers = require('./src/services/covers');
|
|||||||
const config = require('./src/config');
|
const config = require('./src/config');
|
||||||
const { query } = require('./src/config/db');
|
const { query } = require('./src/config/db');
|
||||||
|
|
||||||
const DELAY_MS = 8000;
|
const DELAY_MS = 20000;
|
||||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
// Фоновый ретрай SVG-обложек.
|
||||||
|
// Запускается каждые 30 минут, ищет статьи с маленькими обложками (< 30KB),
|
||||||
|
// перегенерирует их. Если провайдер недоступен — пропускает до следующего запуска.
|
||||||
|
|
||||||
|
const { query } = require('../config/db');
|
||||||
|
const covers = require('../services/covers');
|
||||||
|
const config = require('../config');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const UPLOADS_DIR = process.env.UPLOADS_DIR || '/var/www/zeropost-uploads';
|
||||||
|
const SVG_MAX_BYTES = 30 * 1024; // < 30KB = скорее всего SVG-заглушка
|
||||||
|
const DELAY_MS = 25000; // пауза между генерациями
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
|
async function findSvgCovers() {
|
||||||
|
const { rows } = await query(
|
||||||
|
`SELECT id, title, tags, cover_url FROM articles
|
||||||
|
WHERE cover_url IS NOT NULL AND cover_url != ''
|
||||||
|
ORDER BY id DESC`
|
||||||
|
);
|
||||||
|
const candidates = [];
|
||||||
|
for (const a of rows) {
|
||||||
|
const file = path.join(UPLOADS_DIR, a.cover_url.replace('/uploads/', ''));
|
||||||
|
try {
|
||||||
|
const stat = fs.statSync(file);
|
||||||
|
if (stat.size < SVG_MAX_BYTES) candidates.push(a);
|
||||||
|
} catch (_) { /* файл не найден — пропускаем */ }
|
||||||
|
}
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retryCovers() {
|
||||||
|
let candidates;
|
||||||
|
try { candidates = await findSvgCovers(); }
|
||||||
|
catch (err) { console.warn('[CoverRetry] DB error:', err.message); return; }
|
||||||
|
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
console.log('[CoverRetry] no SVG covers found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`[CoverRetry] found ${candidates.length} SVG cover(s), retrying...`);
|
||||||
|
|
||||||
|
let ok = 0, fail = 0;
|
||||||
|
for (const a of candidates) {
|
||||||
|
try {
|
||||||
|
await covers.generateCover({ articleId: a.id, title: a.title, tags: a.tags || [], channelId: 1 });
|
||||||
|
// Проверяем что новый файл реально больше
|
||||||
|
const { rows } = await query('SELECT cover_url FROM articles WHERE id=$1', [a.id]);
|
||||||
|
const newFile = path.join(UPLOADS_DIR, (rows[0]?.cover_url || '').replace('/uploads/', ''));
|
||||||
|
const newSize = fs.existsSync(newFile) ? fs.statSync(newFile).size : 0;
|
||||||
|
if (newSize > SVG_MAX_BYTES) {
|
||||||
|
console.log(`[CoverRetry] ✓ article=${a.id} new size=${Math.round(newSize/1024)}KB`);
|
||||||
|
ok++;
|
||||||
|
} else {
|
||||||
|
console.log(`[CoverRetry] ✗ article=${a.id} still small (${Math.round(newSize/1024)}KB)`);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`[CoverRetry] ✗ article=${a.id} error:`, err.message.slice(0, 80));
|
||||||
|
fail++;
|
||||||
|
// Если провайдер не отвечает — прерываем цикл до следующего запуска
|
||||||
|
if (err.message.includes('timeout') || err.message.includes('502')) {
|
||||||
|
console.warn('[CoverRetry] provider unavailable, stopping until next run');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok + fail < candidates.length) await sleep(DELAY_MS);
|
||||||
|
}
|
||||||
|
console.log(`[CoverRetry] done: ${ok} fixed, ${fail} failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск каждые 30 минут
|
||||||
|
function start() {
|
||||||
|
// Первый запуск через 5 минут после старта engine (чтобы не грузить при рестарте)
|
||||||
|
setTimeout(async () => {
|
||||||
|
await retryCovers();
|
||||||
|
setInterval(retryCovers, 30 * 60 * 1000);
|
||||||
|
}, 5 * 60 * 1000);
|
||||||
|
console.log('[CoverRetry] Auto-retry started (every 30 min, first run in 5 min)');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { start, retryCovers };
|
||||||
Reference in New Issue
Block a user