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:
@@ -1 +1 @@
|
|||||||
2.1.14
|
2.1.15
|
||||||
|
|||||||
+40
-16
@@ -524,32 +524,56 @@ app.use(checkInstallationMiddleware);
|
|||||||
|
|
||||||
// Middleware que pula autenticação para rotas públicas de imagens
|
// Middleware que pula autenticação para rotas públicas de imagens
|
||||||
const publicImageRoutes = express.Router();
|
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) => {
|
publicImageRoutes.get('/:id/thumbnail', async (req, res) => {
|
||||||
try {
|
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' });
|
if (!image) return res.status(404).json({ error: 'Imagem não encontrada' });
|
||||||
|
|
||||||
const fileToCheck = image.thumb_filename || image.filename;
|
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||||
let url;
|
|
||||||
|
|
||||||
const fsSync = require('fs');
|
// 1. Já existe thumbnail — serve direto (local ou Wasabi)
|
||||||
const path = require('path');
|
if (image.thumb_filename) {
|
||||||
const localPath = path.join(process.env.UPLOAD_DIR || '/app/uploads', fileToCheck);
|
|
||||||
|
|
||||||
if (fsSync.existsSync(localPath)) {
|
|
||||||
url = `/uploads/${fileToCheck}`;
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
const wasabiUrl = await storage.getImageUrl(fileToCheck, image.client_name, image.patient_name, false);
|
const buf = await storage.getImageBuffer(image.thumb_filename, image.client_name, image.patient_name);
|
||||||
url = wasabiUrl || `/uploads/${fileToCheck}`;
|
if (buf) {
|
||||||
|
res.setHeader('Content-Type', 'image/jpeg');
|
||||||
|
return res.end(buf);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Erro ao gerar URL Wasabi para thumbnail:', e);
|
console.error(`⚠️ [thumbnail] Falha ao ler thumb existente (id ${image.id}):`, e.message);
|
||||||
url = `/uploads/${fileToCheck}`;
|
// 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) {
|
} 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