feat(rx): tenant_id (conta/dono) acima de clinica_id — multi-tenant 2 níveis

Hierarquia tenant_id (usuarios.id do dono) / clinica_id / pc / paciente, espelhando
o scoreodonto (conta multi-clínica planejada: um dono gerencia várias clínicas).

- Colunas tenant_id em clinics_devices e images (+índices); backfill device + 2776 imgs.
- Cadastro de device, socket (machine_token), JWT de client e uploads gravam tenant_id.
- Listagens (/images, /patients, /by-patient, /unique-clinics) filtram por tenant_id e/ou clinica_id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-06-12 17:26:06 +02:00
parent 31022c6a6f
commit b6da56617a
5 changed files with 61 additions and 25 deletions
+29 -7
View File
@@ -53,19 +53,24 @@ router.get('/unique-clients', async (req, res) => {
});
// ================================================================
// GET /api/images/unique-clinics - Tenants (clinica_id) com contagem + rótulo
// GET /api/images/unique-clinics - Clínicas (clinica_id) de uma conta (tenant_id),
// com rótulo e contagens. ?tenant_id=<conta> filtra; sem ele, todas.
// ================================================================
router.get('/unique-clinics', async (req, res) => {
try {
const tenantId = req.query.tenant_id || '';
const rows = await db.all(
`SELECT clinica_id,
`SELECT tenant_id,
clinica_id,
MAX(clinic_name) AS clinic_name,
COUNT(*)::integer AS image_count,
COUNT(DISTINCT patient_name)::integer AS patient_count
FROM images
WHERE clinica_id IS NOT NULL AND enabled = 1
GROUP BY clinica_id
ORDER BY clinic_name`
AND ($1 = '' OR tenant_id = $1)
GROUP BY tenant_id, clinica_id
ORDER BY clinic_name`,
[tenantId]
);
res.json(rows);
} catch (error) {
@@ -254,13 +259,19 @@ router.get('/', async (req, res) => {
const showDisabled = req.query.disabled === 'true';
const search = req.query.search || '';
const clientName = req.query.client || '';
const clinicaId = req.query.clinica_id || ''; // tenant do scoreodonto (isolamento)
const tenantId = req.query.tenant_id || ''; // conta/dono (isolamento por conta)
const clinicaId = req.query.clinica_id || ''; // clínica (isolamento por unidade)
const clinicName = req.query.clinic || ''; // rótulo legível (filtro opcional)
let query = `SELECT * FROM images WHERE enabled = $1`;
let params = [showDisabled ? 0 : 1];
let paramIndex = 2;
if (tenantId) {
query += ` AND tenant_id = $${paramIndex++}`;
params.push(tenantId);
}
if (clinicaId) {
query += ` AND clinica_id = $${paramIndex++}`;
params.push(clinicaId);
@@ -300,7 +311,8 @@ router.get('/patients', async (req, res) => {
const showDisabled = req.query.disabled === 'true';
const search = req.query.search || '';
const clientName = req.query.client || '';
const clinicaId = req.query.clinica_id || ''; // tenant do scoreodonto (isolamento)
const tenantId = req.query.tenant_id || ''; // conta/dono (isolamento por conta)
const clinicaId = req.query.clinica_id || ''; // clínica (isolamento por unidade)
const clinicName = req.query.clinic || ''; // rótulo legível (filtro opcional)
const enabledVal = showDisabled ? 0 : 1;
@@ -313,6 +325,11 @@ router.get('/patients', async (req, res) => {
const offset = (page - 1) * limit;
let whereStr = `WHERE 1=1`;
if (tenantId) {
whereStr += ` AND tenant_id = $${paramIndex}`;
params.push(tenantId);
paramIndex++;
}
if (clinicaId) {
whereStr += ` AND clinica_id = $${paramIndex}`;
params.push(clinicaId);
@@ -398,7 +415,8 @@ router.get('/by-patient', async (req, res) => {
try {
const patientName = req.query.name || '';
const showDisabled = req.query.disabled === 'true';
const clinicaId = req.query.clinica_id || ''; // tenant do scoreodonto (isolamento)
const tenantId = req.query.tenant_id || ''; // conta/dono (isolamento por conta)
const clinicaId = req.query.clinica_id || ''; // clínica (isolamento por unidade)
if (!patientName) {
return res.status(400).json({ error: 'Parâmetro name obrigatório' });
@@ -406,6 +424,10 @@ router.get('/by-patient', async (req, res) => {
let sql = `SELECT * FROM images WHERE patient_name = $1 AND enabled = $2`;
const params = [patientName, showDisabled ? 0 : 1];
if (tenantId) {
sql += ` AND tenant_id = $${params.length + 1}`;
params.push(tenantId);
}
if (clinicaId) {
sql += ` AND clinica_id = $${params.length + 1}`;
params.push(clinicaId);