feat: add mobile API routes (download-all ZIP, upload-file multipart, auth/me, settings)

This commit is contained in:
VPS 4 Deploy Agent
2026-06-03 17:02:07 +02:00
parent e0de5af1f4
commit 68dc2965da
7 changed files with 807 additions and 1 deletions
+105
View File
@@ -52,6 +52,111 @@ router.get('/unique-clients', async (req, res) => {
}
});
// ================================================================
// GET /api/images/download-all — Baixar todas as imagens de um paciente em ZIP
// Usado pela tela Galeria do mobile (Sheet → "Baixar todas")
// ================================================================
router.get('/download-all', async (req, res) => {
try {
const patientName = req.query.patient;
if (!patientName) return res.status(400).json({ error: 'Parâmetro patient obrigatório' });
const images = await db.all(
'SELECT id, filename, original_filename, clinic_name, client_name, patient_name FROM images WHERE patient_name = $1 AND enabled = 1',
[patientName]
);
if (images.length === 0) return res.status(404).json({ error: 'Nenhuma imagem encontrada para este paciente' });
const archiver = require('archiver');
const sanitized = patientName.replace(/[^a-zA-Z0-9À-ÿ _-]/g, '_');
res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', `attachment; filename="${sanitized}_imagens.zip"`);
const archive = archiver('zip', { zlib: { level: 5 } });
archive.pipe(res);
for (const img of images) {
try {
let buf = null;
try { buf = await storage.getImageBuffer(img.filename, img.clinic_name, img.client_name, img.patient_name); } catch (_) {}
if (!buf && img.patient_name !== 'Desconhecido') {
try { buf = await storage.getImageBuffer(img.filename, img.clinic_name, img.client_name, 'Desconhecido'); } catch (_) {}
}
if (buf) {
const ext = path.extname(img.original_filename || img.filename) || '.png';
archive.append(buf, { name: `${img.original_filename || img.id}${ext === img.original_filename ? '' : ''}` });
}
} catch (e) {
console.error(`[download-all] Falha ao incluir imagem id ${img.id}:`, e.message);
}
}
await archive.finalize();
} catch (error) {
console.error('Erro ao gerar ZIP:', error);
if (!res.headersSent) res.status(500).json({ error: error.message });
}
});
// ================================================================
// POST /api/images/upload-file — Upload via multipart/form-data (mobile)
// Aceita arquivo via campo "image" + campos patientName, doctor, remark
// ================================================================
router.post('/upload-file', async (req, res) => {
try {
const multer = require('multer');
// Configura multer em memória (max 50MB)
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } }).single('image');
upload(req, res, async function (err) {
if (err) return res.status(400).json({ error: `Erro no upload: ${err.message}` });
if (!req.file) return res.status(400).json({ error: 'Nenhum arquivo enviado no campo "image"' });
const patientName = req.body.patientName || req.body.patient || 'Desconhecido';
const doctor = req.body.doctor || '';
const remark = req.body.remark || '';
const imageBuffer = req.file.buffer;
await imageProcessor.validateImage(imageBuffer);
const clinicName = 'Upload Mobile';
const clientName = 'Upload Mobile';
const timestamp = Date.now();
const guid = Math.random().toString(36).substring(2, 15);
const ext = path.extname(req.file.originalname) || '.png';
const filename = `${timestamp}_${guid}${ext}`;
await storage.saveImage(filename, imageBuffer, clinicName, clientName, patientName, false);
let thumbFilename = null;
try {
const thumbBuffer = await imageProcessor.getThumbnail(imageBuffer, 500, 500);
thumbFilename = `thumb_${timestamp}_${guid}.jpg`;
await storage.saveImage(thumbFilename, thumbBuffer, clinicName, clientName, patientName, false);
} catch (e) {
console.error('Erro ao gerar thumbnail (upload-file):', e);
}
await db.run(
`INSERT INTO images (
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, req.file.originalname, filename, thumbFilename, doctor, remark]
);
if (req.app.get('io') || global.io) {
const io = req.app.get('io') || global.io;
if (io) io.emit('new-image', { filename, thumb: thumbFilename, patientName });
}
res.json({ success: true, filename, thumbFilename });
});
} catch (error) {
console.error('Erro no upload-file:', error);
res.status(500).json({ error: error.message });
}
});
// ================================================================
// POST /api/images/upload - Envio Manual via Web
// ================================================================