import postgres, { createPool, PgDb, withTenant } from "./pg.ts"; import { config } from "./config.ts"; /** * panels_product, esquema iam: identidad/roles/permisos DE CADA TENANT. * Aislado de `core` a propósito (ver reglas del monolito modular) -- este * módulo no debe importar nada de db.ts (core) ni viceversa. */ let appPool: postgres.Sql | null = null; let authPool: postgres.Sql | null = null; function getAppPool(): postgres.Sql { if (!appPool) { appPool = createPool(config.databaseUrlIam, { max: 10 }); } return appPool; } /** Pool con credenciales _owner: SOLO para resolver login por username (el * username es único globalmente, no por tenant, así que hay que buscarlo * ANTES de saber a qué tenant pertenece -- RLS con tenant_id no puede * aplicar todavía en ese punto). El dueño de la tabla omite RLS de forma * nativa en Postgres (a menos que se use FORCE ROW LEVEL SECURITY, que a * propósito no se activó -- ver db/iam/changesets/002-rls.sql). No usar * este pool para nada más que esa resolución puntual. */ function getAuthPool(): postgres.Sql { if (!authPool) { authPool = createPool(config.databaseUrlIamOwner || config.databaseUrlIam, { max: 3 }); } return authPool; } /** Health-check de arranque: falla rápido si Postgres no responde. */ export async function pingIamDb(): Promise { await getAppPool()`SELECT 1`; } /** Busca un usuario por username en CUALQUIER tenant (paso previo al login, * antes de conocer el tenant_id). Devuelve la fila cruda; el caller decide * qué hacer con tenant_id/role_code. */ export async function findUserByUsernameAnyTenant(username: string) { const rows = await getAuthPool()` SELECT id, username, password_hash, display_name, company_id, tenant_id, role_code, must_change_password, email, created_at FROM iam.users WHERE username = ${username}`; return rows[0] as Record | undefined; } /** Ejecuta `fn` con una conexión ya acotada por tenant_id via RLS (ver * pg.ts#withTenant). Todo el resto del código de iam (fuera del login) * debe pasar por aquí. */ export async function withIamTenant( tenantId: number | null, fn: (db: PgDb) => Promise, ): Promise { return await withTenant(getAppPool(), tenantId, fn); } /** Uso puntual sin transacción/RLS explícito -- reservado para el bootstrap * (scripts/bootstrap-admin.ts) y para lecturas del propio rol _owner que * necesitan ver todos los tenants a propósito (soporte/administración). */ export async function withIamOwner(fn: (db: PgDb) => Promise): Promise { return await fn(new PgDb(getAuthPool())); } export async function closeIamDb(): Promise { await appPool?.end({ timeout: 5 }); await authPool?.end({ timeout: 5 }); appPool = null; authPool = null; }