fix(thumbnail): serve image bytes directly + on-demand thumb generation with Wasabi backfill
continuous-integration/webhook Falha no deploy (VPS4)
continuous-integration/webhook Falha no deploy (VPS4)
O endpoint /api/images/:id/thumbnail retornava JSON {url}, mas o React aponta
<img src> direto para ele, causando placeholders cinza (sem erro no devtools).
Agora serve image/jpeg diretamente. Quando thumb_filename é null, puxa a original,
gera a thumb na hora, serve imediatamente e faz backfill no Wasabi + atualiza o banco.
This commit is contained in:
+40
-16
@@ -524,32 +524,56 @@ app.use(checkInstallationMiddleware);
|
||||
|
||||
// Middleware que pula autenticação para rotas públicas de imagens
|
||||
const publicImageRoutes = express.Router();
|
||||
|
||||
// Serve os BYTES da thumbnail diretamente (image/jpeg), para uso direto em <img src>.
|
||||
// Se a imagem ainda não tem thumbnail, gera na hora a partir da original,
|
||||
// serve imediatamente e faz backfill (upload pro Wasabi + atualiza banco) em background.
|
||||
publicImageRoutes.get('/:id/thumbnail', async (req, res) => {
|
||||
try {
|
||||
const image = await db.get('SELECT thumb_filename, filename, client_name, patient_name FROM images WHERE id = $1', [req.params.id]);
|
||||
const image = await db.get(
|
||||
'SELECT id, thumb_filename, filename, client_name, patient_name FROM images WHERE id = $1',
|
||||
[req.params.id]
|
||||
);
|
||||
if (!image) return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||
|
||||
const fileToCheck = image.thumb_filename || image.filename;
|
||||
let url;
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
|
||||
const fsSync = require('fs');
|
||||
const path = require('path');
|
||||
const localPath = path.join(process.env.UPLOAD_DIR || '/app/uploads', fileToCheck);
|
||||
|
||||
if (fsSync.existsSync(localPath)) {
|
||||
url = `/uploads/${fileToCheck}`;
|
||||
} else {
|
||||
// 1. Já existe thumbnail — serve direto (local ou Wasabi)
|
||||
if (image.thumb_filename) {
|
||||
try {
|
||||
const wasabiUrl = await storage.getImageUrl(fileToCheck, image.client_name, image.patient_name, false);
|
||||
url = wasabiUrl || `/uploads/${fileToCheck}`;
|
||||
const buf = await storage.getImageBuffer(image.thumb_filename, image.client_name, image.patient_name);
|
||||
if (buf) {
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
return res.end(buf);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erro ao gerar URL Wasabi para thumbnail:', e);
|
||||
url = `/uploads/${fileToCheck}`;
|
||||
console.error(`⚠️ [thumbnail] Falha ao ler thumb existente (id ${image.id}):`, e.message);
|
||||
// cai para regeneração abaixo
|
||||
}
|
||||
}
|
||||
res.json({ url });
|
||||
|
||||
// 2. Sem thumbnail — puxa a original, gera a thumb na hora
|
||||
const originalBuf = await storage.getImageBuffer(image.filename, image.client_name, image.patient_name);
|
||||
if (!originalBuf) {
|
||||
return res.status(404).json({ error: 'Imagem original não encontrada' });
|
||||
}
|
||||
|
||||
const thumbBuf = await imageProcessor.getThumbnail(originalBuf, 500, 500);
|
||||
|
||||
// Serve imediatamente para não travar a UI
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
res.end(thumbBuf);
|
||||
|
||||
// Backfill em background: salva a thumb no Wasabi e atualiza o banco
|
||||
const thumbFilename = `thumb_${image.filename.replace(/\.[^.]+$/, '')}.jpg`;
|
||||
storage.saveImage(thumbFilename, thumbBuf, image.client_name, image.patient_name, false)
|
||||
.then(() => db.run('UPDATE images SET thumb_filename = $1 WHERE id = $2', [thumbFilename, image.id]))
|
||||
.then(() => console.log(`✅ [thumb-backfill] Thumb gerada/salva: ${thumbFilename} (id ${image.id})`))
|
||||
.catch(err => console.error(`⚠️ [thumb-backfill] Falha id ${image.id}:`, err.message));
|
||||
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
console.error(`❌ [thumbnail] Erro id ${req.params.id}:`, e.message);
|
||||
if (!res.headersSent) res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user