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:
@@ -34,31 +34,32 @@ router.post('/upload', async (req, res) => {
|
||||
|
||||
await imageProcessor.validateImage(imageBuffer);
|
||||
|
||||
const clinicName = 'Upload Manual';
|
||||
const clientName = 'Upload Manual';
|
||||
const patientName = patientData.name;
|
||||
const timestamp = Date.now();
|
||||
const guid = Math.random().toString(36).substring(2, 15);
|
||||
const filename = `${timestamp}_${guid}.png`;
|
||||
|
||||
await storage.saveImage(filename, imageBuffer, clientName, patientName, false);
|
||||
|
||||
|
||||
await storage.saveImage(filename, imageBuffer, clinicName, clientName, patientName, false);
|
||||
|
||||
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 (e) {
|
||||
console.error('Erro ao gerar thumbnail manual:', e);
|
||||
}
|
||||
|
||||
|
||||
const doctor = patientData.doctor || '';
|
||||
const remark = patientData.remark || '';
|
||||
|
||||
|
||||
const result = await db.run(
|
||||
`INSERT INTO images (
|
||||
image_guid, patient_name, client_name, original_filename, filename, thumb_filename, enabled, doctor, remark
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, 1, $7, $8)`,
|
||||
[guid, patientName, clientName, 'manual_upload.png', filename, thumbFilename, doctor, remark]
|
||||
image_guid, patient_name, clinic_name, client_name, original_filename, filename, thumb_filename, enabled, doctor, remark
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, 1, $8, $9)`,
|
||||
[guid, patientName, clinicName, clientName, 'manual_upload.png', filename, thumbFilename, doctor, remark]
|
||||
);
|
||||
|
||||
// Notificar clientes socket para atualizar galeria automaticamente
|
||||
@@ -217,9 +218,9 @@ router.delete('/by-patient', async (req, res) => {
|
||||
const images = await db.all('SELECT * FROM images WHERE patient_name = $1', [patientName]);
|
||||
|
||||
for (const image of images) {
|
||||
await storage.deleteImage(image.filename, image.client_name, image.patient_name);
|
||||
await storage.deleteImage(image.filename, image.clinic_name, image.client_name, image.patient_name);
|
||||
}
|
||||
|
||||
|
||||
await db.run('DELETE FROM images WHERE patient_name = $1', [patientName]);
|
||||
res.json({ success: true, count: images.length });
|
||||
} catch (error) {
|
||||
@@ -280,11 +281,11 @@ router.get('/:id/transformations', async (req, res) => {
|
||||
|
||||
let originalBuffer;
|
||||
try {
|
||||
originalBuffer = await storage.getImageBuffer(originalImage.filename, originalImage.client_name, originalImage.patient_name, false);
|
||||
originalBuffer = await storage.getImageBuffer(originalImage.filename, originalImage.clinic_name, originalImage.client_name, originalImage.patient_name, false);
|
||||
} catch (e) {
|
||||
originalBuffer = await storage.getImageBuffer(originalImage.filename, originalImage.client_name, originalImage.patient_name, true);
|
||||
originalBuffer = await storage.getImageBuffer(originalImage.filename, originalImage.clinic_name, originalImage.client_name, originalImage.patient_name, true);
|
||||
}
|
||||
|
||||
|
||||
const transformations = [
|
||||
{ name: 'Original', rotation: 0, flipH: false, flipV: false },
|
||||
{ name: 'Flip Horizontal', rotation: 0, flipH: true, flipV: false },
|
||||
@@ -336,18 +337,18 @@ router.post('/:id/transform', async (req, res) => {
|
||||
// Processar a imagem
|
||||
let originalBuffer;
|
||||
try {
|
||||
originalBuffer = await storage.getImageBuffer(realOriginal.filename, realOriginal.client_name, realOriginal.patient_name, false);
|
||||
originalBuffer = await storage.getImageBuffer(realOriginal.filename, realOriginal.clinic_name, realOriginal.client_name, realOriginal.patient_name, false);
|
||||
} catch (e) {
|
||||
originalBuffer = await storage.getImageBuffer(realOriginal.filename, realOriginal.client_name, realOriginal.patient_name, true);
|
||||
originalBuffer = await storage.getImageBuffer(realOriginal.filename, realOriginal.clinic_name, realOriginal.client_name, realOriginal.patient_name, true);
|
||||
}
|
||||
|
||||
|
||||
let processed = await imageProcessor.rotate(originalBuffer, rotation);
|
||||
processed = await imageProcessor.flip(processed, flipH, flipV);
|
||||
|
||||
|
||||
// Salvar nova versão em processed (local e nuvem)
|
||||
const timestamp = Date.now();
|
||||
const processedFilename = `${realOriginal.image_guid}_${timestamp}.png`;
|
||||
await storage.saveImage(processedFilename, processed, realOriginal.client_name, realOriginal.patient_name, true);
|
||||
await storage.saveImage(processedFilename, processed, realOriginal.clinic_name, realOriginal.client_name, realOriginal.patient_name, true);
|
||||
|
||||
// Desabilitar a imagem active atual para que não fique duplicada na interface
|
||||
await db.run('UPDATE images SET enabled = 0 WHERE id = $1', [req.params.id]);
|
||||
@@ -357,12 +358,13 @@ router.post('/:id/transform', async (req, res) => {
|
||||
// Inserir a nova imagem como a versão ativa
|
||||
const result = await db.run(
|
||||
`INSERT INTO images (
|
||||
image_guid, patient_name, client_name, original_filename, filename,
|
||||
image_guid, patient_name, clinic_name, client_name, original_filename, filename,
|
||||
enabled, rotation, flip_horizontal, flip_vertical, original_image_id, created_at, remark, doctor
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`,
|
||||
[
|
||||
`${realOriginal.image_guid}_${timestamp}`,
|
||||
realOriginal.patient_name,
|
||||
realOriginal.clinic_name,
|
||||
realOriginal.client_name,
|
||||
realOriginal.filename,
|
||||
processedFilename,
|
||||
@@ -457,8 +459,8 @@ router.delete('/:id', async (req, res) => {
|
||||
}
|
||||
|
||||
// Apagar arquivo local e do S3
|
||||
await storage.deleteImage(image.filename, image.client_name, image.patient_name);
|
||||
|
||||
await storage.deleteImage(image.filename, image.clinic_name, image.client_name, image.patient_name);
|
||||
|
||||
// Apagar do banco
|
||||
await db.run('DELETE FROM images WHERE id = $1', [req.params.id]);
|
||||
|
||||
@@ -475,7 +477,7 @@ router.delete('/:id', async (req, res) => {
|
||||
// ================================================================
|
||||
router.get('/:id/file-url', async (req, res) => {
|
||||
try {
|
||||
const image = await db.get('SELECT filename, client_name, patient_name FROM images WHERE id = $1', [req.params.id]);
|
||||
const image = await db.get('SELECT 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' });
|
||||
|
||||
let url;
|
||||
@@ -484,7 +486,7 @@ router.get('/:id/file-url', async (req, res) => {
|
||||
} else {
|
||||
// Se o arquivo está no Wasabi, reconstroem a URL
|
||||
try {
|
||||
const wasabiUrl = await storage.getImageUrl(image.filename, image.client_name, image.patient_name);
|
||||
const wasabiUrl = await storage.getImageUrl(image.filename, image.clinic_name, image.client_name, image.patient_name);
|
||||
url = wasabiUrl || `/uploads/${image.filename}`;
|
||||
} catch (e) {
|
||||
console.error('Erro ao gerar URL Wasabi:', e);
|
||||
@@ -524,7 +526,7 @@ router.put('/:id/toggle-enabled', async (req, res) => {
|
||||
router.post('/backfill-thumbs', async (req, res) => {
|
||||
try {
|
||||
const rows = await db.all(
|
||||
`SELECT id, filename, client_name, patient_name
|
||||
`SELECT id, filename, clinic_name, client_name, patient_name
|
||||
FROM images
|
||||
WHERE thumb_filename IS NULL AND enabled = true
|
||||
ORDER BY created_at DESC`
|
||||
@@ -551,7 +553,7 @@ async function runThumbBackfill(rows) {
|
||||
if (!imgBuffer) { failed++; continue; }
|
||||
const thumbBuffer = await imageProcessor.getThumbnail(imgBuffer);
|
||||
const thumbFilename = `thumb_${row.filename.replace(/\.[^.]+$/, '')}.jpg`;
|
||||
await storage.saveImage(thumbFilename, thumbBuffer, row.client_name, row.patient_name, false);
|
||||
await storage.saveImage(thumbFilename, thumbBuffer, row.clinic_name, row.client_name, row.patient_name, false);
|
||||
await db.run('UPDATE images SET thumb_filename = $1 WHERE id = $2', [thumbFilename, row.id]);
|
||||
done++;
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user