A refatoração do GET /api/gtos para retornar { rows, total, pages }
quebrou o TransformModal que esperava um array direto.
Correções:
- routes/gtos.js: restaura suporte a ?patient=NOME (match exato) para
o TransformModal continuar filtrando só as GTOs do paciente exibido,
sem retornar resultados de outros pacientes via ILIKE.
- TransformModal.jsx: lê data.rows (novo formato) com fallback para
array direto (compatibilidade durante transição).
As duas superfícies (TransformModal e GtosPage) agora usam a mesma
API, mesmos dados, sem lógica duplicada ou conflitante.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Bug fix:
- server.js e routes/images.js: 'enabled = true' → 'enabled = 1'
(PostgreSQL rejeita smallint = boolean — causava 'operator does not exist')
Plan B — imagens ausentes no Wasabi:
- server.js: pendingReupload (Map clientName → Set<original_filename>)
quando getImageBufferWithFallback retorna null no endpoint /thumbnail,
scheduleReupload() registra o original_filename e emite 'reupload-files'
imediatamente se o cliente estiver online, ou na próxima conexão.
- server.js: client-identify agora drena a fila pendente do cliente ao
reconectar, antes de emitir clients-list-updated.
- original_filename adicionado ao SELECT do endpoint /thumbnail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- server.js: cron thumb-backfill a cada 10min com rastreio de estado
(lastRun, nextRun, running, lastResult). GET /api/cron/status e
POST /api/cron/run/:key para consulta e disparo manual via painel.
- routes/images.js: PUT /api/images/patients/:name — edita patient_name,
doctor e remark em todas as imagens do paciente de uma vez.
Frontend:
- PatientsPage.jsx (/patients): tabela com colunas Nome (+ miniatura),
Dentista, Imagens, Última atualização, Observações, Ações. Suporta
busca, paginação infinita, editar e excluir por linha.
- SettingsPage.jsx (/settings): seção de Cron Jobs com status, resultado
e botão "Executar agora"; seção de informações do servidor.
- EditPatientModal.jsx: edita nome, dentista e observações do paciente.
- Sidebar.jsx: add "Pacientes" (todos usuários) e "Configurações" (admin).
- App.jsx: rotas /patients (protegida) e /settings (admin).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Causa raiz: request-presigned-urls usava firstName/lastName para montar
o patientName do caminho no Wasabi, mas o client envia apenas name.
Resultado: toda imagem era salva em '.../Desconhecido/arquivo.png' no
Wasabi, mas o banco guardava o nome real do paciente — na recuperação,
a key ficava diferente e resultava em NoSuchKey → 500.
Correções:
1. server.js: getImageBufferWithFallback() — tenta o path correto (nome
real) e, se falhar, tenta o path legado 'Desconhecido'. Usado tanto
para ler thumb existente quanto para gerar do original.
2. server.js: patientName corrigido em send-image e request-presigned-urls
para usar data.patientData.name (o campo que o client realmente envia),
garantindo consistência entre Wasabi e banco em novos uploads.
3. routes/images.js: runThumbBackfill agora usa storage.getImageBuffer
com fallback 'Desconhecido' em vez de leitura apenas do disco local
— o boot backfill passa a funcionar para imagens no Wasabi.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Adiciona coluna clinic_name em images (ALTER TABLE IF NOT EXISTS)
- Todas as funções de storage recebem clinicName como parâmetro: saveImage,
getImageBuffer, getImageUrl, deleteImage, getDownloadPresignedUrl, getUploadPresignedUrl
- Chave Wasabi passa de pc_name/patient/arquivo → clinic_name/pc_name/patient/arquivo
- server.js: usa socket.user.clinicName (preenchido via machine_token auth do DB) como
fonte primária do nome da clínica nos 3 handlers de upload e na rota thumbnail
- Todos os INSERTs em images passam a gravar clinic_name
- Corrige bug oculto: wipe de clínica agora apaga o prefixo correto no Wasabi
- Scripts utilitários atualizados: backfill-wasabi-thumbs, recreate-all-thumbs, migrate-to-wasabi
- client-monitor.js: envia clinicName no client-identify (fallback para clientes legados)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Form não enviava password → servidor retornava 400 em todo cadastro
- Adicionado input password no modal de cadastro
- pc_name tornado opcional no servidor (já era opcional no UI)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DELETE /admin/clinics/:id/wipe: apaga images/gto_images do banco (WHERE client_name),
lista e deleta em batch todos os objetos Wasabi com prefixo 'clinic_name/',
e remove o registro do dispositivo
- UI: botão 'Limpar dados' (🧹) separado do 🗑️ (remove só o cadastro),
modal com aviso, confirmação e log da operação
- Sidebar: remove link 'Zerar Sistema'