"""Reseller role + tenant ownership + company is_active + RLS extension Revision ID: 0034 Revises: 0033 Create Date: 2026-06-23 - Neuer Enum-Wert UserRole.RESELLER - companies.is_active (Mandanten-Login-Sperre) - companies.reseller_id (Besitz durch Reseller; NULL = Plattform/SUPER_ADMIN) - RLS-Policies für companies + users so erweitert, dass ein Reseller (app.reseller_id) genau seine eigenen Firmen und deren User sieht/verwaltet (keine personenbezogenen Zeit-/Abwesenheitsdaten – diese Tabellen bleiben unverändert auf company_id gefenced). """ from alembic import op from sqlalchemy import text revision = "0034" down_revision = "0033" branch_labels = None depends_on = None # ── RLS-Ausdrücke (analog 0024) ────────────────────────────────────────────── _BYPASS = "COALESCE(current_setting('app.bypass_rls', true), 'off') = 'on'" _CID = "company_id = NULLIF(current_setting('app.company_id', true), '')::uuid" _IID = "id = NULLIF(current_setting('app.company_id', true), '')::uuid" _RID = "reseller_id = NULLIF(current_setting('app.reseller_id', true), '')::uuid" # Firmen, die dem aktuellen Reseller gehören (Subquery unterliegt selbst der # companies-Policy, liefert dem Reseller also genau seine eigenen Firmen). _USER_RESELLER = ( "company_id IN (SELECT id FROM companies WHERE " "reseller_id = NULLIF(current_setting('app.reseller_id', true), '')::uuid)" ) def _exec(sql: str) -> None: op.execute(text(sql)) def _recreate_policies(table: str, using: str) -> None: for op_name in ("select", "insert", "update", "delete"): _exec(f"DROP POLICY IF EXISTS rls_{table}_{op_name} ON {table}") _exec(f"CREATE POLICY rls_{table}_select ON {table} FOR SELECT USING {using}") _exec(f"CREATE POLICY rls_{table}_insert ON {table} FOR INSERT WITH CHECK {using}") _exec(f"CREATE POLICY rls_{table}_update ON {table} FOR UPDATE USING {using} WITH CHECK {using}") _exec(f"CREATE POLICY rls_{table}_delete ON {table} FOR DELETE USING {using}") def upgrade() -> None: # 1) Enum-Wert RESELLER (ADD VALUE darf in TX laufen, wird hier nicht verwendet) _exec("ALTER TYPE userrole ADD VALUE IF NOT EXISTS 'RESELLER'") # 2) Neue Spalten _exec("ALTER TABLE companies ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT TRUE") _exec("ALTER TABLE companies ADD COLUMN IF NOT EXISTS reseller_id UUID NULL " "REFERENCES users(id) ON DELETE SET NULL") _exec("CREATE INDEX IF NOT EXISTS ix_companies_reseller_id ON companies (reseller_id)") # 3) RLS erweitern _recreate_policies("companies", f"({_BYPASS} OR {_IID} OR {_RID})") _recreate_policies("users", f"({_BYPASS} OR {_CID} OR {_USER_RESELLER})") def downgrade() -> None: # RLS auf 0024-Stand zurücksetzen _recreate_policies("companies", f"({_BYPASS} OR {_IID})") _recreate_policies("users", f"({_BYPASS} OR {_CID})") _exec("DROP INDEX IF EXISTS ix_companies_reseller_id") _exec("ALTER TABLE companies DROP COLUMN IF EXISTS reseller_id") _exec("ALTER TABLE companies DROP COLUMN IF EXISTS is_active") # Enum-Wert RESELLER bleibt (PostgreSQL kann Enum-Werte nicht entfernen)