feat: adicionar gerenciamento de clínicas e tokens

This commit is contained in:
VPS 4 Builder
2026-05-26 02:59:11 +02:00
parent 5e86717d12
commit 75c4bf3f87
14 changed files with 1241 additions and 2123 deletions
+24 -114
View File
@@ -5,7 +5,6 @@ const fs = require('fs').promises;
const path = require('path');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const sharp = require('sharp');
require('dotenv').config();
// Importar módulos
@@ -13,7 +12,6 @@ const imageProcessor = require('./image-processor');
const { authenticateToken, hashPassword, JWT_SECRET } = require('./auth');
const jwt = require('jsonwebtoken');
const db = require('./database');
const storage = require('./storage');
const authRoutes = require('./routes/auth');
const imageRoutes = require('./routes/images');
const patientsRouter = require('./routes/patients');
@@ -328,9 +326,9 @@ app.post('/api/install', async (req, res) => {
await db.run(
`INSERT INTO users (username, email, password_hash, created_at)
VALUES (?, ?, ?, NOW())
ON CONFLICT (username) DO UPDATE SET
email = EXCLUDED.email,
password_hash = EXCLUDED.password_hash`,
ON DUPLICATE KEY UPDATE
email = VALUES(email),
password_hash = VALUES(password_hash)`,
[admin.username, admin.email, passwordHash]
);
console.log('✅ Usuário admin criado:', admin.username);
@@ -442,44 +440,8 @@ app.use('/login', express.static('auth'));
// Serve static files
app.use(express.static('public'));
app.get('/uploads/:filename', async (req, res) => {
const filename = req.params.filename;
// Enviar ETag e Cache-Control imutável
res.setHeader('ETag', `"${filename}"`);
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
const clientEtag = req.headers['if-none-match'];
if (clientEtag === `"${filename}"` || clientEtag === filename || clientEtag === `W/"${filename}"`) {
return res.status(304).end();
}
// Tentar primeiro obter da pasta de uploads (imagens originais)
let buffer = await storage.getImageBuffer(filename, false);
// Se não encontrar, tentar da pasta de processed (imagens rotacionadas/transformadas)
if (!buffer) {
buffer = await storage.getImageBuffer(filename, true);
}
if (!buffer) {
return res.status(404).send('Imagem não encontrada');
}
const ext = path.extname(filename).toLowerCase();
let contentType = 'image/png';
if (ext === '.jpg' || ext === '.jpeg') {
contentType = 'image/jpeg';
} else if (ext === '.webp') {
contentType = 'image/webp';
} else if (ext === '.svg') {
contentType = 'image/svg+xml';
}
res.setHeader('Content-Type', contentType);
res.send(buffer);
});
app.use('/uploads', express.static(UPLOAD_DIR));
app.use('/uploads', express.static(PROCESSED_DIR));
// ================================================================
// MIDDLEWARE DE VERIFICAÇÃO DE INSTALAÇÃO (APENAS PARA APIs)
@@ -534,29 +496,10 @@ app.use('/api/images', imageRoutes);
app.use('/api/gtos', authenticateToken);
app.use('/api/gtos', gtosRouter);
// APIs do Sistema - requerem autenticação
app.use('/api/system', authenticateToken);
app.use('/api/system', systemRoutes);
// Endpoint para solicitar a sincronização aos dispositivos conectados
app.post('/api/system/trigger-sync', authenticateToken, (req, res) => {
try {
const windowsClients = connectedClients.filter(c => c.type === 'windows');
// Envia o evento 'sync-request' com timestamp para todos os dispositivos clientes identificados
io.emit('sync-request', { timestamp: Date.now() });
console.log(`🔄 [Sync] Sinal de sincronização enviado para ${windowsClients.length} dispositivo(s) Windows.`);
res.json({
success: true,
devicesTriggered: windowsClients.length,
message: `Sinal de sincronização enviado com sucesso para ${windowsClients.length} dispositivo(s) Windows.`
});
} catch (error) {
console.error('Erro ao solicitar sincronização:', error);
res.status(500).json({ error: error.message });
}
});
// ================================================================
// SOCKET.IO - CONEXÕES EM TEMPO REAL
// ================================================================
@@ -746,37 +689,16 @@ io.on('connection', (socket) => {
// Validar imagem
await imageProcessor.validateImage(imageBuffer);
// Detectar formato da imagem e gerar extensão adequada
const imgMetadata = await sharp(imageBuffer).metadata();
const format = imgMetadata.format || 'png';
const ext = format === 'jpeg' ? 'jpg' : format;
// Salvar imagem original
const timestamp = Date.now();
const filename = `${timestamp}_${data.metadata.guid}.${ext}`;
const filename = `${timestamp}_${data.metadata.guid}.png`;
const filePath = path.join(UPLOAD_DIR, filename);
await fs.writeFile(filePath, imageBuffer);
// 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 patientName = data.patientData.name || 'Paciente não identificado';
await storage.saveImage(filename, imageBuffer, clientName, patientName, false);
// Gerar e salvar thumbnail WebP
let thumbFilename = null;
try {
const thumbBuffer = await sharp(imageBuffer)
.resize({ width: 400, height: 400, fit: 'inside', withoutEnlargement: true })
.webp({ quality: 80, effort: 4 })
.toBuffer();
thumbFilename = `thumb_${timestamp}_${data.metadata.guid}.webp`;
await storage.saveImage(thumbFilename, thumbBuffer, clientName, patientName, false);
console.log(`✅ Thumbnail gerado e salvo no Wasabi: ${thumbFilename}`);
} catch (thumbErr) {
console.error('⚠️ Falha ao gerar thumbnail, prosseguindo sem ele:', thumbErr.message);
}
// Determinar data de criação
const formatLocalDateTime = (date) => {
@@ -797,9 +719,8 @@ io.on('connection', (socket) => {
enabled,
doctor,
remark,
thumb_filename,
created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
data.metadata.guid,
data.patientData.name || 'Paciente não identificado',
@@ -809,7 +730,6 @@ io.on('connection', (socket) => {
1,
data.patientData.doctor || null,
data.patientData.remark || null,
thumbFilename,
createdAt
]
);
@@ -841,7 +761,6 @@ io.on('connection', (socket) => {
doctor: data.patientData.doctor || null,
remark: data.patientData.remark || null,
filename: filename,
thumb_filename: thumbFilename,
created_at: createdAt
});
}
@@ -969,35 +888,26 @@ async function initDirectories() {
}
async function start() {
const fsSync = require('fs');
try {
// Inicializar diretórios
await initDirectories();
// Inicializar banco se as variáveis já existirem no ambiente
if (process.env.DB_TYPE === 'sqlite' || (process.env.DB_HOST && process.env.DB_NAME)) {
// Tentar inicializar banco apenas se .env já existe
const envPath = path.join(__dirname, '.env');
const fsSync = require('fs');
if (fsSync.existsSync(envPath)) {
try {
await db.initDatabase();
console.log('✅ Banco de dados inicializado a partir do ambiente');
await storage.loadConfigFromDb();
} catch (error) {
console.log('❌ Falha ao inicializar o banco de dados:', error.message);
}
} else {
// Fallback para arquivo .env físico
const envPath = path.join(__dirname, '.env');
const fsSync = require('fs');
if (fsSync.existsSync(envPath)) {
try {
require('dotenv').config();
if (process.env.DB_TYPE === 'sqlite' || (process.env.DB_HOST && process.env.DB_NAME)) {
await db.initDatabase();
console.log('✅ Banco de dados inicializado a partir do .env');
await storage.loadConfigFromDb();
}
} catch (error) {
console.log('⚠️ Banco de dados não configurado. Execute a instalação em /install');
// Recarregar .env se foi atualizado
require('dotenv').config();
// Inicializar banco de dados apenas se configurações existem
if (process.env.DB_TYPE === 'sqlite' || (process.env.DB_HOST && process.env.DB_NAME)) {
await db.initDatabase();
console.log('✅ Banco de dados inicializado');
}
} catch (error) {
console.log('⚠️ Banco de dados não configurado. Execute a instalação em /install');
}
}