fix(clinics): 3 bugs de unicidade e wipe corrigidos
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
1. admin-clinics.js — bloqueia cadastro de clinic_name + pc_name duplicados: Antes só validava email único. Agora verifica se já existe dispositivo com a mesma combinação clinic_name + pc_name (case-insensitive). Sem isso, dois PCs com mesmo nome na mesma clínica sobrescreveriam arquivos um do outro no Wasabi (caminho idêntico). 2. admin-clinics.js — wipe usava client_name ao invés de clinic_name no banco: 'DELETE FROM images WHERE client_name = clinic_name' deletava o conjunto errado de imagens (client_name é o nome do PC, não da clínica). Corrigido para 'WHERE clinic_name = $1'. Wasabi continua usando sanitize(clinic_name)/ como prefixo (correto). 3. server.js — verificação de duplicata no connect incluía clinicName: O check usava só clientName (pc_name), causando falso positivo: dois PCs de clínicas diferentes com o mesmo nome (ex: ambos "PC-SALA-1") — o segundo era desconectado. Agora verifica clinicName + clientName juntos. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user