diff --git a/dental-server/routes/images.js b/dental-server/routes/images.js index e265fe9..a9ea7d0 100644 --- a/dental-server/routes/images.js +++ b/dental-server/routes/images.js @@ -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' }); diff --git a/dental-server/server.js b/dental-server/server.js index 1e3974d..c722e7a 100644 --- a/dental-server/server.js +++ b/dental-server/server.js @@ -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 + +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...`);