feat(storage): reorganiza Wasabi com estrutura clinic_name/pc_name/patient_name
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
- Adiciona coluna clinic_name em images (ALTER TABLE IF NOT EXISTS) - Todas as funções de storage recebem clinicName como parâmetro: saveImage, getImageBuffer, getImageUrl, deleteImage, getDownloadPresignedUrl, getUploadPresignedUrl - Chave Wasabi passa de pc_name/patient/arquivo → clinic_name/pc_name/patient/arquivo - server.js: usa socket.user.clinicName (preenchido via machine_token auth do DB) como fonte primária do nome da clínica nos 3 handlers de upload e na rota thumbnail - Todos os INSERTs em images passam a gravar clinic_name - Corrige bug oculto: wipe de clínica agora apaga o prefixo correto no Wasabi - Scripts utilitários atualizados: backfill-wasabi-thumbs, recreate-all-thumbs, migrate-to-wasabi - client-monitor.js: envia clinicName no client-identify (fallback para clientes legados) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+36
-28
@@ -531,7 +531,7 @@ const publicImageRoutes = express.Router();
|
||||
publicImageRoutes.get('/:id/thumbnail', async (req, res) => {
|
||||
try {
|
||||
const image = await db.get(
|
||||
'SELECT id, thumb_filename, filename, client_name, patient_name FROM images WHERE id = $1',
|
||||
'SELECT id, thumb_filename, 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' });
|
||||
@@ -543,7 +543,7 @@ publicImageRoutes.get('/:id/thumbnail', async (req, res) => {
|
||||
// 1. Já existe thumbnail — serve direto (local ou Wasabi)
|
||||
if (image.thumb_filename) {
|
||||
try {
|
||||
const buf = await storage.getImageBuffer(image.thumb_filename, image.client_name, image.patient_name);
|
||||
const buf = await storage.getImageBuffer(image.thumb_filename, image.clinic_name, image.client_name, image.patient_name);
|
||||
if (buf) {
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
return res.end(buf);
|
||||
@@ -555,7 +555,7 @@ publicImageRoutes.get('/:id/thumbnail', async (req, res) => {
|
||||
}
|
||||
|
||||
// 2. Sem thumbnail — puxa a original, gera a thumb na hora
|
||||
const originalBuf = await storage.getImageBuffer(image.filename, image.client_name, image.patient_name);
|
||||
const originalBuf = await storage.getImageBuffer(image.filename, image.clinic_name, image.client_name, image.patient_name);
|
||||
if (!originalBuf) {
|
||||
return res.status(404).json({ error: 'Imagem original não encontrada' });
|
||||
}
|
||||
@@ -568,7 +568,7 @@ publicImageRoutes.get('/:id/thumbnail', async (req, res) => {
|
||||
|
||||
// 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)
|
||||
storage.saveImage(thumbFilename, thumbBuf, image.clinic_name, 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));
|
||||
@@ -830,6 +830,7 @@ io.on('connection', (socket) => {
|
||||
socketId: socket.id,
|
||||
type: data.type || 'unknown',
|
||||
name: clientName,
|
||||
clinicName: socket.user?.clinicName || (data.clinicName || '').trim() || 'Clínica não identificada',
|
||||
version: data.version || '1.0.0',
|
||||
system: data.system || 'unknown',
|
||||
clientId: clientId,
|
||||
@@ -906,20 +907,21 @@ io.on('connection', (socket) => {
|
||||
|
||||
// Buscar nome do cliente se identificado
|
||||
const clientInfo = connectedClients.find(c => c.socketId === socket.id);
|
||||
const clientName = clientInfo ? clientInfo.name : 'Cliente não identificado';
|
||||
const clientName = clientInfo ? clientInfo.name : 'PC não identificado';
|
||||
const clinicName = socket.user?.clinicName || (clientInfo ? clientInfo.clinicName : 'Clínica não identificada');
|
||||
const patientName = data.patientData ? `${data.patientData.firstName || ''} ${data.patientData.lastName || ''}`.trim() || 'Desconhecido' : 'Desconhecido';
|
||||
|
||||
|
||||
// Salvar imagem original local e na nuvem
|
||||
const timestamp = Date.now();
|
||||
const filename = `${timestamp}_${data.metadata.guid}.png`;
|
||||
await storage.saveImage(filename, imageBuffer, clientName, patientName, false);
|
||||
|
||||
await storage.saveImage(filename, imageBuffer, clinicName, clientName, patientName, false);
|
||||
|
||||
// Gerar e salvar miniatura (Thumbnail) — 500x500, qualidade 80%
|
||||
let thumbFilename = null;
|
||||
try {
|
||||
const thumbBuffer = await imageProcessor.getThumbnail(imageBuffer, 500, 500);
|
||||
thumbFilename = `thumb_${filename}`;
|
||||
await storage.saveImage(thumbFilename, thumbBuffer, clientName, patientName, false);
|
||||
await storage.saveImage(thumbFilename, thumbBuffer, clinicName, clientName, patientName, false);
|
||||
} catch (thumbErr) {
|
||||
console.error('Erro ao gerar thumbnail:', thumbErr);
|
||||
// Continua mesmo se falhar o thumbnail, usando fallback no front
|
||||
@@ -936,20 +938,22 @@ io.on('connection', (socket) => {
|
||||
// Salvar no banco de dados
|
||||
const result = await db.run(
|
||||
`INSERT INTO images (
|
||||
image_guid,
|
||||
image_guid,
|
||||
patient_name,
|
||||
clinic_name,
|
||||
client_name,
|
||||
original_filename,
|
||||
filename,
|
||||
original_filename,
|
||||
filename,
|
||||
enabled,
|
||||
doctor,
|
||||
remark,
|
||||
thumb_filename,
|
||||
created_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id`,
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
|
||||
[
|
||||
data.metadata.guid,
|
||||
data.patientData.name || 'Paciente não identificado',
|
||||
clinicName,
|
||||
clientName,
|
||||
data.metadata.fileName,
|
||||
filename,
|
||||
@@ -960,7 +964,7 @@ io.on('connection', (socket) => {
|
||||
createdAt
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
console.log('✅ Imagem salva no banco. ID:', result.lastID);
|
||||
console.log('✅ Arquivo submetido ao storage (Wasabi/Local):', filename);
|
||||
|
||||
@@ -1015,15 +1019,16 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
|
||||
const clientInfo = connectedClients.find(c => c.socketId === socket.id);
|
||||
const clientName = clientInfo ? clientInfo.name : 'Cliente não identificado';
|
||||
const clientName = clientInfo ? clientInfo.name : 'PC não identificado';
|
||||
const clinicName = socket.user?.clinicName || (clientInfo ? clientInfo.clinicName : 'Clínica não identificada');
|
||||
const patientName = data.patientData ? `${data.patientData.firstName || ''} ${data.patientData.lastName || ''}`.trim() || 'Desconhecido' : 'Desconhecido';
|
||||
|
||||
|
||||
const timestamp = Date.now();
|
||||
const filename = `${timestamp}_${data.metadata.guid}.png`;
|
||||
const thumbFilename = `${timestamp}_${data.metadata.guid}_thumb.jpg`;
|
||||
|
||||
const originalUrl = await storage.getUploadPresignedUrl(filename, clientName, patientName, 'image/png');
|
||||
const thumbnailUrl = await storage.getUploadPresignedUrl(thumbFilename, clientName, patientName, 'image/jpeg');
|
||||
|
||||
const originalUrl = await storage.getUploadPresignedUrl(filename, clinicName, clientName, patientName, 'image/png');
|
||||
const thumbnailUrl = await storage.getUploadPresignedUrl(thumbFilename, clinicName, clientName, patientName, 'image/jpeg');
|
||||
|
||||
if (!originalUrl || !thumbnailUrl) {
|
||||
if (typeof callback === 'function') callback({ success: false, error: 'Falha ao gerar URLs no S3.' });
|
||||
@@ -1051,33 +1056,36 @@ io.on('connection', (socket) => {
|
||||
console.log('📥 [image-upload-direct] Notificação de upload recebida de:', socket.id);
|
||||
|
||||
const clientInfo = connectedClients.find(c => c.socketId === socket.id);
|
||||
const clientName = clientInfo ? clientInfo.name : 'Cliente não identificado';
|
||||
|
||||
const clientName = clientInfo ? clientInfo.name : 'PC não identificado';
|
||||
const clinicName = socket.user?.clinicName || (clientInfo ? clientInfo.clinicName : 'Clínica não identificada');
|
||||
|
||||
// Determinar data de criação
|
||||
const formatLocalDateTime = (date) => {
|
||||
const pad = (num) => String(num).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
};
|
||||
|
||||
|
||||
const createdAt = (data.patientData && data.patientData.photographTime) || formatLocalDateTime(new Date());
|
||||
|
||||
|
||||
// Salvar no banco de dados com a referência para a thumbnail
|
||||
const result = await db.run(
|
||||
`INSERT INTO images (
|
||||
image_guid,
|
||||
image_guid,
|
||||
patient_name,
|
||||
clinic_name,
|
||||
client_name,
|
||||
original_filename,
|
||||
filename,
|
||||
original_filename,
|
||||
filename,
|
||||
enabled,
|
||||
doctor,
|
||||
remark,
|
||||
thumb_filename,
|
||||
created_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id`,
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
|
||||
[
|
||||
data.metadata.guid,
|
||||
data.patientData.name || 'Paciente não identificado',
|
||||
clinicName,
|
||||
clientName,
|
||||
data.metadata.fileName,
|
||||
data.filename,
|
||||
@@ -1253,7 +1261,7 @@ async function start() {
|
||||
try {
|
||||
const { runThumbBackfill } = require('./routes/images');
|
||||
const missing = await db.all(
|
||||
`SELECT id, filename, client_name, patient_name FROM images
|
||||
`SELECT id, filename, clinic_name, client_name, patient_name FROM images
|
||||
WHERE thumb_filename IS NULL AND enabled = true ORDER BY created_at DESC`
|
||||
);
|
||||
if (missing.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user