const express = require('express'); const router = express.Router(); const db = require('../database'); const imageProcessor = require('../image-processor'); const path = require('path'); const fs = require('fs').promises; // ================================================================ // GET /api/images - Listar imagens // ================================================================ router.get('/', async (req, res) => { try { const showDisabled = req.query.disabled === 'true'; const search = req.query.search || ''; const clientName = req.query.client || ''; let query = `SELECT * FROM images WHERE enabled = ?`; let params = [showDisabled ? 0 : 1]; if (clientName) { query += ` AND client_name = ?`; params.push(clientName); } if (search) { query += ` AND (patient_name LIKE ? OR image_guid LIKE ? OR filename LIKE ?)`; params.push(`%${search}%`, `%${search}%`, `%${search}%`); } query += ` ORDER BY created_at DESC LIMIT 100`; const images = await db.all(query, params); res.json(images); } catch (error) { console.error('Erro ao listar imagens:', error); res.status(500).json({ error: error.message }); } }); // ================================================================ // GET /api/images/patients - Listar pacientes agrupados // ================================================================ router.get('/patients', async (req, res) => { try { const showDisabled = req.query.disabled === 'true'; const search = req.query.search || ''; const clientName = req.query.client || ''; const enabledVal = showDisabled ? 0 : 1; let where = `WHERE enabled = ${enabledVal}`; let params = []; if (clientName) { where += ` AND client_name = ?`; params.push(clientName); } if (search) { where += ` AND (patient_name LIKE ? OR image_guid LIKE ?)`; params.push(`%${search}%`, `%${search}%`); } // Group by patient_name — pick thumbnail from most recent image const rows = await db.all( `SELECT patient_name, doctor, remark, client_name, COUNT(*) AS image_count, MAX(created_at) AS last_date, (SELECT filename FROM images i2 WHERE i2.patient_name = images.patient_name AND i2.enabled = ${enabledVal} ORDER BY i2.created_at DESC LIMIT 1) AS thumb_filename FROM images ${where} GROUP BY patient_name ORDER BY MAX(created_at) DESC LIMIT 200`, params ); res.json(rows); } catch (error) { console.error('Erro ao listar pacientes:', error); res.status(500).json({ error: error.message }); } }); // ================================================================ // GET /api/images/by-patient - Imagens de um paciente // ================================================================ router.get('/by-patient', async (req, res) => { try { const patientName = req.query.name || ''; const showDisabled = req.query.disabled === 'true'; if (!patientName) { return res.status(400).json({ error: 'Parâmetro name obrigatório' }); } const images = await db.all( `SELECT * FROM images WHERE patient_name = ? AND enabled = ? ORDER BY created_at DESC`, [patientName, showDisabled ? 0 : 1] ); res.json(images); } catch (error) { console.error('Erro ao buscar imagens do paciente:', error); res.status(500).json({ error: error.message }); } }); // ================================================================ // GET /api/images/:id - Buscar imagem por ID // ================================================================ router.get('/:id', async (req, res) => { try { const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]); if (!image) { return res.status(404).json({ error: 'Imagem não encontrada' }); } res.json(image); } catch (error) { res.status(500).json({ error: error.message }); } }); // ================================================================ // GET /api/images/stats - Estatísticas // ================================================================ router.get('/stats', async (req, res) => { try { const total = await db.get('SELECT COUNT(*) as count FROM images'); const enabled = await db.get('SELECT COUNT(*) as count FROM images WHERE enabled = 1'); const withTooth = await db.get('SELECT COUNT(*) as count FROM images WHERE tooth_number IS NOT NULL'); res.json({ total: total.count, enabled: enabled.count, disabled: total.count - enabled.count, withToothInfo: withTooth.count, withoutToothInfo: total.count - withTooth.count }); } catch (error) { res.status(500).json({ error: error.message }); } }); // ================================================================ // GET /api/images/:id/transformations - Gerar transformações // ================================================================ router.get('/:id/transformations', async (req, res) => { try { const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]); if (!image) { return res.status(404).json({ error: 'Imagem não encontrada' }); } // Buscar a imagem original se esta é uma transformação const originalId = image.original_image_id || image.id; const originalImage = await db.get('SELECT * FROM images WHERE id = ?', [originalId]); const originalPath = path.join(__dirname, '../uploads', originalImage.filename); let originalBuffer; try { originalBuffer = await fs.readFile(originalPath); } catch (e) { if (e.code === 'ENOENT') { // Fallback for images corrupted by previous bug const fallbackPath = path.join(__dirname, '../processed', originalImage.filename); originalBuffer = await fs.readFile(fallbackPath); } else { throw e; } } const transformations = [ { name: 'Original', rotation: 0, flipH: false, flipV: false }, { name: 'Flip Horizontal', rotation: 0, flipH: true, flipV: false }, { name: 'Flip Vertical', rotation: 0, flipH: false, flipV: true }, { name: 'Rotação +90°', rotation: 90, flipH: false, flipV: false }, { name: 'Rotação -90°', rotation: -90, flipH: false, flipV: false } ]; const results = []; for (const t of transformations) { try { let processed = await imageProcessor.rotate(originalBuffer, t.rotation); processed = await imageProcessor.flip(processed, t.flipH, t.flipV); const base64 = `data:image/png;base64,${processed.toString('base64')}`; results.push({ ...t, base64, size: processed.length }); } catch (error) { console.error(`Erro ao processar transformação ${t.name}:`, error); } } res.json(results); } catch (error) { console.error('Erro ao gerar transformações:', error); res.status(500).json({ error: error.message }); } }); // ================================================================ // POST /api/images/:id/transform - Salvar transformação // ================================================================ router.post('/:id/transform', async (req, res) => { try { const { rotation, flipH, flipV, remark } = req.body; const originalImage = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]); if (!originalImage) { return res.status(404).json({ error: 'Imagem não encontrada' }); } // Buscar a imagem original real se esta é uma transformação const realOriginalId = originalImage.original_image_id || originalImage.id; const realOriginal = await db.get('SELECT * FROM images WHERE id = ?', [realOriginalId]); // Processar a imagem const originalPath = path.join(__dirname, '../uploads', realOriginal.filename); let originalBuffer; try { originalBuffer = await fs.readFile(originalPath); } catch (e) { if (e.code === 'ENOENT') { const fallbackPath = path.join(__dirname, '../processed', realOriginal.filename); originalBuffer = await fs.readFile(fallbackPath); } else { throw e; } } let processed = await imageProcessor.rotate(originalBuffer, rotation); processed = await imageProcessor.flip(processed, flipH, flipV); // Salvar nova versão em processed const timestamp = Date.now(); const processedFilename = `${realOriginal.image_guid}_${timestamp}.png`; const processedPath = path.join(__dirname, '../processed', processedFilename); await fs.writeFile(processedPath, processed); // Desabilitar a imagem atual para que não fique duplicada na interface await db.run('UPDATE images SET enabled = 0 WHERE id = ?', [req.params.id]); const newRemark = remark !== undefined ? remark : originalImage.remark; // 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, enabled, rotation, flip_horizontal, flip_vertical, original_image_id, created_at, remark, doctor ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ `${realOriginal.image_guid}_${timestamp}`, realOriginal.patient_name, realOriginal.client_name, realOriginal.filename, processedFilename, 1, // Nova imagem fica ativa rotation, flipH ? 1 : 0, flipV ? 1 : 0, realOriginalId, originalImage.created_at, // Manter a data de criação original newRemark, realOriginal.doctor ] ); res.json({ success: true, imageId: result.lastID, message: 'Orientação atualizada com sucesso!' }); } catch (error) { console.error('Erro ao salvar transformação:', error); res.status(500).json({ error: error.message }); } }); // ================================================================ // PUT /api/images/:id/patient-info - Atualizar dados do paciente // ================================================================ router.put('/:id/patient-info', async (req, res) => { try { const { patientNameResumed, toothNumber, toothSide } = req.body; await db.run( `UPDATE images SET patient_name_resumed = ?, tooth_number = ?, tooth_side = ?, enabled = 1 WHERE id = ?`, [patientNameResumed, toothNumber, toothSide, req.params.id] ); res.json({ success: true, message: 'Dados do paciente atualizados!' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // ================================================================ // GET /api/images/:id/download - Download da imagem // ================================================================ router.get('/:id/download', async (req, res) => { try { const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]); if (!image) { return res.status(404).json({ error: 'Imagem não encontrada' }); } // Nome: nome-resumido_numero_lado.png if (image.patient_name_resumed && image.tooth_number && image.tooth_side) { const filename = `${image.patient_name_resumed}_${image.tooth_number}_${image.tooth_side}.png`; const filePath = path.join(__dirname, '../processed', image.filename); res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.download(filePath, filename); } else { res.status(400).json({ error: 'Dados do paciente incompletos' }); } } catch (error) { console.error('Erro no download:', error); res.status(500).json({ error: error.message }); } }); // ================================================================ // DELETE /api/images/:id - Apagar versão da imagem // ================================================================ router.delete('/:id', async (req, res) => { try { const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]); if (!image) { return res.status(404).json({ error: 'Imagem não encontrada' }); } // NUNCA apagar a original if (!image.original_image_id) { return res.status(400).json({ error: 'Não é possível apagar a imagem original', code: 'ORIGINAL_NOT_DELETABLE' }); } // Apagar arquivo const filePath = path.join(__dirname, '../processed', image.filename); try { await fs.unlink(filePath); } catch (e) { console.warn('Arquivo não encontrado para deletar:', filePath); } // Apagar do banco await db.run('DELETE FROM images WHERE id = ?', [req.params.id]); res.json({ success: true, message: 'Imagem apagada com sucesso' }); } catch (error) { console.error('Erro ao apagar imagem:', error); res.status(500).json({ error: error.message }); } }); // ================================================================ // PUT /api/images/:id/toggle-enabled - Habilitar/Desabilitar // ================================================================ router.put('/:id/toggle-enabled', async (req, res) => { try { const image = await db.get('SELECT * FROM images WHERE id = ?', [req.params.id]); if (!image) { return res.status(404).json({ error: 'Imagem não encontrada' }); } const newEnabled = image.enabled ? 0 : 1; await db.run('UPDATE images SET enabled = ? WHERE id = ?', [newEnabled, req.params.id]); res.json({ success: true, enabled: newEnabled === 1 }); } catch (error) { res.status(500).json({ error: error.message }); } }); module.exports = router;