feat: add mobile API routes (download-all ZIP, upload-file multipart, auth/me, settings)
This commit is contained in:
@@ -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
|
||||
// ================================================================
|
||||
|
||||
Reference in New Issue
Block a user