feat(rx): multi-tenant por clinica_id + página /clients de clientes ativos

- clinica_id (tenant do scoreodonto, clinicas.id) como eixo de isolamento em
  clinics_devices e images (+índices); clinic_name vira rótulo legível.
- Cadastro de device, socket (machine_token) e uploads (direct + legado) gravam clinica_id.
- Listagens (/images, /patients, /by-patient, /unique-clinics) filtram por clinica_id.
- /clients: lista clientes Windows ativos (inclui autenticados sem client-identify),
  tempo real + polling, exibe a clínica.
- send-image marcado como legado (client real usa Direct Upload).

Inclui trabalho de dev acumulado no working tree (não havia commits desde origin/main).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-06-12 16:17:09 +02:00
parent d9182cff09
commit 31022c6a6f
55 changed files with 6773 additions and 1350 deletions
+13 -2
View File
@@ -226,7 +226,13 @@ async function getImageBuffer(filename, clinicName = null, clientName = null, pa
if (response.Body) {
const bytes = await response.Body.transformToByteArray();
return Buffer.from(bytes);
const buffer = Buffer.from(bytes);
// Cache local — evita round-trip ao Wasabi nas próximas requisições
try {
await fs.mkdir(path.dirname(localPath), { recursive: true });
await fs.writeFile(localPath, buffer);
} catch (_) {}
return buffer;
}
} catch (error) {
if (error.name === 'NoSuchKey') {
@@ -235,7 +241,12 @@ async function getImageBuffer(filename, clinicName = null, clientName = null, pa
const response = await s3.send(new GetObjectCommand({ Bucket: currentBucket, Key: filename }));
if (response.Body) {
const bytes = await response.Body.transformToByteArray();
return Buffer.from(bytes);
const buffer = Buffer.from(bytes);
try {
await fs.mkdir(path.dirname(localPath), { recursive: true });
await fs.writeFile(localPath, buffer);
} catch (_) {}
return buffer;
}
} catch (fallbackError) {
console.error(`❌ [Wasabi] Arquivo não encontrado no S3: ${filename}`);