fix(cron): adiciona cron verifyStorage + corrige dispatch manual
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
- Novo cron 'verifyStorage' (30min): percorre as 200 imagens mais recentes, tenta buscar no storage com fallback legado; para cada arquivo ausente aciona scheduleReupload() (Plan B) imediatamente. Primeira execução 15s após o boot para cobrir os IDs 404 sem esperar o usuário abrir o card. - cronState agora tem dois jobs; /api/cron/status retorna ambos. - POST /api/cron/run/:key usa mapa de funções; suporta 'thumbBackfill' e 'verifyStorage' (botão "Executar agora" na tela de Configurações). - lastResult do verifyStorage inclui: checked, missing, requested. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+64
-13
@@ -1326,22 +1326,27 @@ async function start() {
|
||||
redis.initRedis();
|
||||
|
||||
// ================================================================
|
||||
// CRON — Geração automática de thumbnails ausentes
|
||||
// CRON — Tarefas automáticas em background
|
||||
// ================================================================
|
||||
const THUMB_CRON_INTERVAL_MS = 10 * 60 * 1000; // 10 minutos
|
||||
const VERIFY_CRON_INTERVAL_MS = 30 * 60 * 1000; // 30 minutos
|
||||
|
||||
const cronState = {
|
||||
thumbBackfill: {
|
||||
name: 'Geração de Thumbnails',
|
||||
description: 'Gera e envia ao Wasabi as miniaturas de RX que ainda não possuem thumbnail.',
|
||||
description: 'Gera e envia ao Wasabi as miniaturas de RX que ainda não possuem thumbnail (thumb_filename IS NULL).',
|
||||
intervalMs: THUMB_CRON_INTERVAL_MS,
|
||||
running: false,
|
||||
lastRun: null,
|
||||
nextRun: null,
|
||||
lastResult: null,
|
||||
}
|
||||
running: false, lastRun: null, nextRun: null, lastResult: null,
|
||||
},
|
||||
verifyStorage: {
|
||||
name: 'Verificação de Integridade',
|
||||
description: 'Verifica se os arquivos de imagem existem no Wasabi; para os ausentes aciona Plan B (reenvio pelo cliente Windows).',
|
||||
intervalMs: VERIFY_CRON_INTERVAL_MS,
|
||||
running: false, lastRun: null, nextRun: null, lastResult: null,
|
||||
},
|
||||
};
|
||||
|
||||
// ── Cron 1: thumb backfill (images sem thumb_filename) ──────────
|
||||
async function runThumbCron() {
|
||||
if (cronState.thumbBackfill.running) return;
|
||||
cronState.thumbBackfill.running = true;
|
||||
@@ -1370,13 +1375,60 @@ async function start() {
|
||||
}
|
||||
}
|
||||
|
||||
// Rodar na inicialização após 5s e depois a cada intervalo
|
||||
// ── Cron 2: verificar se arquivos existem no storage ────────────
|
||||
// Percorre imagens com thumb_filename preenchido e verifica se o
|
||||
// arquivo (original ou thumb) está realmente no Wasabi. Para os
|
||||
// ausentes, chama scheduleReupload() para acionar o Plan B.
|
||||
async function runVerifyStorageCron() {
|
||||
if (cronState.verifyStorage.running) return;
|
||||
cronState.verifyStorage.running = true;
|
||||
cronState.verifyStorage.lastRun = new Date().toISOString();
|
||||
cronState.verifyStorage.nextRun = new Date(Date.now() + VERIFY_CRON_INTERVAL_MS).toISOString();
|
||||
console.log('[cron:verify] Iniciando verificação de integridade do storage...');
|
||||
let checked = 0, missing = 0, requested = 0;
|
||||
try {
|
||||
// Limita a 200 por execução para não sobrecarregar
|
||||
const rows = await db.all(
|
||||
`SELECT id, filename, thumb_filename, original_filename, clinic_name, client_name, patient_name
|
||||
FROM images WHERE enabled = 1 ORDER BY created_at DESC LIMIT 200`
|
||||
);
|
||||
for (const row of rows) {
|
||||
checked++;
|
||||
const buf = await getImageBufferWithFallback(
|
||||
row.thumb_filename || row.filename,
|
||||
row.clinic_name, row.client_name, row.patient_name
|
||||
);
|
||||
if (!buf) {
|
||||
missing++;
|
||||
scheduleReupload(row);
|
||||
requested++;
|
||||
console.log(`[cron:verify] ⚠️ Arquivo ausente id=${row.id} (${row.original_filename}) → Plan B acionado`);
|
||||
}
|
||||
}
|
||||
cronState.verifyStorage.lastResult = { checked, missing, requested, status: 'ok' };
|
||||
console.log(`[cron:verify] Concluído: ${checked} verificadas, ${missing} ausentes, ${requested} reenvios solicitados.`);
|
||||
} catch (e) {
|
||||
console.error('[cron:verify] Erro:', e.message);
|
||||
cronState.verifyStorage.lastResult = { checked, missing, requested, status: 'error', error: e.message };
|
||||
} finally {
|
||||
cronState.verifyStorage.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Rodar na inicialização e depois em intervalos
|
||||
setTimeout(() => {
|
||||
cronState.thumbBackfill.nextRun = new Date(Date.now() + THUMB_CRON_INTERVAL_MS).toISOString();
|
||||
runThumbCron();
|
||||
}, 5000);
|
||||
setInterval(runThumbCron, THUMB_CRON_INTERVAL_MS);
|
||||
|
||||
// Verificação de storage: 1ª vez após 15s (depois do boot estabilizar)
|
||||
setTimeout(() => {
|
||||
cronState.verifyStorage.nextRun = new Date(Date.now() + VERIFY_CRON_INTERVAL_MS).toISOString();
|
||||
runVerifyStorageCron();
|
||||
}, 15000);
|
||||
setInterval(runVerifyStorageCron, VERIFY_CRON_INTERVAL_MS);
|
||||
|
||||
// GET /api/cron/status — estado dos cron jobs (admin)
|
||||
app.get('/api/cron/status', authenticateToken, (req, res) => {
|
||||
res.json(
|
||||
@@ -1395,12 +1447,11 @@ async function start() {
|
||||
|
||||
// POST /api/cron/run/:key — dispara um cron job manualmente (admin)
|
||||
app.post('/api/cron/run/:key', authenticateToken, async (req, res) => {
|
||||
if (req.params.key === 'thumbBackfill') {
|
||||
const runners = { thumbBackfill: runThumbCron, verifyStorage: runVerifyStorageCron };
|
||||
const fn = runners[req.params.key];
|
||||
if (!fn) return res.status(404).json({ error: 'Cron job não encontrado.' });
|
||||
res.json({ success: true, message: 'Cron iniciado em background.' });
|
||||
runThumbCron();
|
||||
} else {
|
||||
res.status(404).json({ error: 'Cron job não encontrado.' });
|
||||
}
|
||||
fn();
|
||||
});
|
||||
|
||||
server.listen(PORT, () => {
|
||||
|
||||
Reference in New Issue
Block a user