fix: cron enabled=1 + plan-b reenvio automático de imagens ausentes
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
Bug fix: - server.js e routes/images.js: 'enabled = true' → 'enabled = 1' (PostgreSQL rejeita smallint = boolean — causava 'operator does not exist') Plan B — imagens ausentes no Wasabi: - server.js: pendingReupload (Map clientName → Set<original_filename>) quando getImageBufferWithFallback retorna null no endpoint /thumbnail, scheduleReupload() registra o original_filename e emite 'reupload-files' imediatamente se o cliente estiver online, ou na próxima conexão. - server.js: client-identify agora drena a fila pendente do cliente ao reconectar, antes de emitir clients-list-updated. - original_filename adicionado ao SELECT do endpoint /thumbnail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -571,7 +571,7 @@ router.post('/backfill-thumbs', async (req, res) => {
|
||||
const rows = await db.all(
|
||||
`SELECT id, filename, clinic_name, client_name, patient_name
|
||||
FROM images
|
||||
WHERE thumb_filename IS NULL AND enabled = true
|
||||
WHERE thumb_filename IS NULL AND enabled = 1
|
||||
ORDER BY created_at DESC`
|
||||
);
|
||||
res.json({ queued: rows.length, message: 'Backfill iniciado em background' });
|
||||
|
||||
+44
-5
@@ -522,6 +522,34 @@ app.use(checkInstallationMiddleware);
|
||||
// ROTAS PÚBLICAS (SEM AUTENTICAÇÃO)
|
||||
// ================================================================
|
||||
|
||||
// ================================================================
|
||||
// PLAN B — Fila de reenvio por cliente
|
||||
// Quando nem thumb nem original existem no Wasabi, o servidor
|
||||
// registra o original_filename na fila do cliente que enviou a imagem.
|
||||
// Na próxima conexão (ou imediatamente se já estiver online) o servidor
|
||||
// emite 'reupload-files' para que o cliente reenvie os arquivos ausentes.
|
||||
// ================================================================
|
||||
const pendingReupload = new Map(); // clientName → Set<original_filename>
|
||||
|
||||
function scheduleReupload(image) {
|
||||
if (!image.original_filename || !image.client_name) return;
|
||||
const key = image.client_name;
|
||||
if (!pendingReupload.has(key)) pendingReupload.set(key, new Set());
|
||||
pendingReupload.get(key).add(image.original_filename);
|
||||
|
||||
// Se o cliente já estiver online, notifica imediatamente
|
||||
const clientInfo = connectedClients.find(c => c.name === key);
|
||||
if (clientInfo) {
|
||||
const clientSocket = io.sockets.sockets.get(clientInfo.socketId);
|
||||
if (clientSocket) {
|
||||
const files = [...pendingReupload.get(key)];
|
||||
clientSocket.emit('reupload-files', { files });
|
||||
pendingReupload.delete(key);
|
||||
console.log(`📤 [plan-b] ${files.length} arquivo(s) solicitado(s) para reenvio → ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware que pula autenticação para rotas públicas de imagens
|
||||
const publicImageRoutes = express.Router();
|
||||
|
||||
@@ -549,7 +577,7 @@ async function getImageBufferWithFallback(filename, clinicName, clientName, pati
|
||||
publicImageRoutes.get('/:id/thumbnail', async (req, res) => {
|
||||
try {
|
||||
const image = await db.get(
|
||||
'SELECT id, thumb_filename, filename, clinic_name, client_name, patient_name FROM images WHERE id = $1',
|
||||
'SELECT id, thumb_filename, filename, original_filename, clinic_name, client_name, patient_name FROM images WHERE id = $1',
|
||||
[req.params.id]
|
||||
);
|
||||
if (!image) return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||
@@ -574,7 +602,9 @@ publicImageRoutes.get('/:id/thumbnail', async (req, res) => {
|
||||
// 2. Sem thumbnail — puxa a original (com fallback legado), gera a thumb na hora
|
||||
const originalBuf = await getImageBufferWithFallback(image.filename, image.clinic_name, image.client_name, image.patient_name);
|
||||
if (!originalBuf) {
|
||||
return res.status(404).json({ error: 'Imagem original não encontrada no storage' });
|
||||
// Plan B: solicita ao cliente que reenvie o arquivo original ausente
|
||||
scheduleReupload(image);
|
||||
return res.status(404).json({ error: 'Imagem original não encontrada no storage. Reenvio solicitado ao cliente.' });
|
||||
}
|
||||
|
||||
const thumbBuf = await imageProcessor.getThumbnail(originalBuf, 500, 500);
|
||||
@@ -863,7 +893,7 @@ io.on('connection', (socket) => {
|
||||
connectedClients.push(clientInfo);
|
||||
socket.isIdentified = true;
|
||||
console.log('✅ Cliente adicionado à lista. Total conectados:', connectedClients.length);
|
||||
|
||||
|
||||
// Confirmar para o cliente
|
||||
socket.emit('server-identified', {
|
||||
success: true,
|
||||
@@ -871,7 +901,16 @@ io.on('connection', (socket) => {
|
||||
clientInfo: clientInfo
|
||||
});
|
||||
console.log('✅ Confirmação enviada para cliente:', socket.id);
|
||||
|
||||
|
||||
// Plan B: enviar fila de reenvio pendente para este cliente
|
||||
const pending = pendingReupload.get(clientName);
|
||||
if (pending && pending.size > 0) {
|
||||
const files = [...pending];
|
||||
socket.emit('reupload-files', { files });
|
||||
pendingReupload.delete(clientName);
|
||||
console.log(`📤 [plan-b] ${files.length} arquivo(s) pendente(s) enviados para ${clientName} ao reconectar`);
|
||||
}
|
||||
|
||||
// Atualizar lista para todos
|
||||
io.emit('clients-list-updated', connectedClients);
|
||||
});
|
||||
@@ -1313,7 +1352,7 @@ async function start() {
|
||||
const { runThumbBackfill } = require('./routes/images');
|
||||
const missing = await db.all(
|
||||
`SELECT id, filename, clinic_name, client_name, patient_name FROM images
|
||||
WHERE thumb_filename IS NULL AND enabled = true ORDER BY created_at DESC`
|
||||
WHERE thumb_filename IS NULL AND enabled = 1 ORDER BY created_at DESC`
|
||||
);
|
||||
if (missing.length > 0) {
|
||||
console.log(`[cron:thumbs] ${missing.length} imagem(ns) sem thumbnail — processando...`);
|
||||
|
||||
Reference in New Issue
Block a user