Files
docqube_backend/alembic/versions/b1_0_explicit_superadmin.py
2026-09-08 11:00:05 +05:30

63 lines
1.8 KiB
Python

"""B1.0 — make superadmin an explicit flag instead of a null tenant
Adds users.is_superadmin and backfills it from the signal it replaces, so the
operators who are superadmins today stay superadmins after deploy.
Until now privilege was inferred from `tenant_id IS NULL`. That made the
*absence* of tenant context a grant of authority: a bug that dropped the tenant
escalated instead of denying. The flag separates "has no tenant" from "may do
anything".
This migration only adds and backfills. It deliberately does not drop or alter
`tenant_id`, and the application honours both signals for one release, so this
can be reverted without locking anyone out.
Revision ID: b1_0_explicit_superadmin
Revises: dfae38e4ede3
"""
import sqlalchemy as sa
from alembic import op
revision = "b1_0_explicit_superadmin"
down_revision = "73dff81eed72"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"users",
sa.Column(
"is_superadmin",
sa.Boolean(),
nullable=False,
server_default=sa.false(),
),
)
# Backfill in the same migration, not a follow-up script. If this ran later
# there would be a window in which every superadmin was demoted.
op.execute(
"""
UPDATE users
SET is_superadmin = true
WHERE tenant_id IS NULL
AND is_deleted = false
"""
)
op.create_index(
"ix_users_is_superadmin",
"users",
["is_superadmin"],
postgresql_where=sa.text("is_superadmin"),
)
def downgrade() -> None:
# Safe to reverse: `tenant_id IS NULL` still identifies the same people,
# and the application still honours it.
op.drop_index("ix_users_is_superadmin", table_name="users")
op.drop_column("users", "is_superadmin")