diff --git a/dental-server/routes/admin-clinics.js b/dental-server/routes/admin-clinics.js index a907097..ad30134 100644 --- a/dental-server/routes/admin-clinics.js +++ b/dental-server/routes/admin-clinics.js @@ -48,6 +48,21 @@ router.post('/', requireAdmin, async (req, res) => { return res.status(400).json({ success: false, message: 'Email já cadastrado para outra clínica' }); } + // Verificar se já existe um dispositivo com mesmo clinic_name + pc_name + // (evita dois PCs com caminho idêntico no Wasabi, sobrescrevendo arquivos um do outro) + if (pc_name) { + const dupPath = await db.get( + `SELECT id FROM clinics_devices WHERE LOWER(clinic_name) = LOWER($1) AND LOWER(pc_name) = LOWER($2)`, + [clinic_name, pc_name] + ); + if (dupPath) { + return res.status(400).json({ + success: false, + message: `Já existe um dispositivo com a clínica "${clinic_name}" e PC "${pc_name}". Use um nome de PC diferente.` + }); + } + } + // Gerar hash da senha e token UUID da máquina const password_hash = bcrypt.hashSync(password, 10); const machine_token = crypto.randomUUID(); @@ -111,25 +126,29 @@ router.delete('/:id/wipe', requireAdmin, async (req, res) => { const clinic = await db.get('SELECT * FROM clinics_devices WHERE id = $1', [id]); if (!clinic) return res.status(404).json({ success: false, message: 'Clínica não encontrada' }); - const clientName = clinic.clinic_name; - log.push(`Clínica: "${clientName}"`); + const clinicName = clinic.clinic_name; + const pcName = clinic.pc_name; + log.push(`Clínica: "${clinicName}" | PC: "${pcName}"`); - // 2. Apaga imagens do banco (e GTOs vinculadas via CASCADE) - const images = await db.all('SELECT id FROM images WHERE client_name = $1', [clientName]); + // 2. Apaga imagens do banco cujo clinic_name bate com este dispositivo + // (clinic_name nas imagens é o nome da clínica; client_name é o nome do PC) + const images = await db.all( + 'SELECT id FROM images WHERE clinic_name = $1', + [clinicName] + ); if (images.length > 0) { const ids = images.map(r => r.id); - // gto_images tem ON DELETE CASCADE, mas garantimos: await db.run(`DELETE FROM gto_images WHERE image_id = ANY($1::int[])`, [ids]); - await db.run('DELETE FROM images WHERE client_name = $1', [clientName]); + await db.run('DELETE FROM images WHERE clinic_name = $1', [clinicName]); log.push(`Banco: ${images.length} imagem(ns) apagada(s)`); } else { log.push('Banco: nenhuma imagem encontrada para esta clínica'); } - // 3. Apaga pasta inteira no Wasabi (prefixo = sanitize(clinic_name)/) + // 3. Apaga pasta inteira no Wasabi + // Estrutura: sanitize(clinic_name)/sanitize(pc_name)/... → prefixo = clinic_name/ const wasabiStatus = storage.getWasabiStatus(); if (wasabiStatus.enabled) { - // Reutiliza a config do storage carregada em memória via internal accessor const cfg = JSON.parse( (await db.get("SELECT value FROM system_config WHERE key='wasabi_config'")).value ); @@ -142,9 +161,9 @@ router.delete('/:id/wipe', requireAdmin, async (req, res) => { forcePathStyle: true, }); - // sanitize igual ao storage.js - const cleanClient = clientName.trim().replace(/[\/\\?#%*:"<>|]/g, '_'); - const prefix = `${cleanClient}/`; + // sanitize igual ao storage.js: remove chars inválidos, mantém o nome + const sanitize = (s) => s ? s.trim().replace(/[\/\\?#%*:"<>|]/g, '_') : 'desconhecido'; + const prefix = `${sanitize(clinicName)}/`; let totalDeleted = 0; let continuationToken; diff --git a/dental-server/server.js b/dental-server/server.js index b39e134..e701a73 100644 --- a/dental-server/server.js +++ b/dental-server/server.js @@ -865,11 +865,14 @@ io.on('connection', (socket) => { // Se autenticado via machine_token, o pc_name do cadastro é autoritativo const clientName = (socket.user?.pcName || data.name || 'PC não identificado').trim(); - const clientId = data.clientId || ''; - - // 1. Validar unicidade em relação aos clientes já conectados - const duplicate = connectedClients.find(c => - c.name.toLowerCase() === clientName.toLowerCase() && + const clientId = data.clientId || ''; + const clinicName = socket.user?.clinicName || (data.clinicName || '').trim() || 'Clínica não identificada'; + + // 1. Validar unicidade: mesmo PC da mesma clínica já conectado (clientId diferente) + // Usa clinicName + clientName para não bloquear PCs homônimos de clínicas diferentes. + const duplicate = connectedClients.find(c => + c.name.toLowerCase() === clientName.toLowerCase() && + c.clinicName.toLowerCase() === clinicName.toLowerCase() && c.clientId !== clientId );