"""Add authentication and RBAC tables""" from __future__ import annotations from alembic import op import sqlalchemy as sa from passlib.context import CryptContext from sqlalchemy.sql import column, table # revision identifiers, used by Alembic. revision = "20251109_02" down_revision = "20251109_01" branch_labels = None depends_on = None password_context = CryptContext(schemes=["argon2"], deprecated="auto") def upgrade() -> None: op.create_table( "users", sa.Column("id", sa.Integer(), primary_key=True), sa.Column("email", sa.String(length=255), nullable=False), sa.Column("username", sa.String(length=128), nullable=False), sa.Column("password_hash", sa.String(length=255), nullable=False), sa.Column( "is_active", sa.Boolean(), nullable=False, server_default=sa.true(), ), sa.Column( "is_superuser", sa.Boolean(), nullable=False, server_default=sa.false(), ), sa.Column("last_login_at", sa.DateTime(timezone=True), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.UniqueConstraint("email", name="uq_users_email"), sa.UniqueConstraint("username", name="uq_users_username"), ) op.create_index( "ix_users_active_superuser", "users", ["is_active", "is_superuser"], unique=False, ) op.create_table( "roles", sa.Column("id", sa.Integer(), primary_key=True), sa.Column("name", sa.String(length=64), nullable=False), sa.Column("display_name", sa.String(length=128), nullable=False), sa.Column("description", sa.Text(), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.UniqueConstraint("name", name="uq_roles_name"), ) op.create_table( "user_roles", sa.Column("user_id", sa.Integer(), nullable=False), sa.Column("role_id", sa.Integer(), nullable=False), sa.Column( "granted_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column("granted_by", sa.Integer(), nullable=True), sa.ForeignKeyConstraint( ["user_id"], ["users.id"], ondelete="CASCADE", ), sa.ForeignKeyConstraint( ["role_id"], ["roles.id"], ondelete="CASCADE", ), sa.ForeignKeyConstraint( ["granted_by"], ["users.id"], ondelete="SET NULL", ), sa.PrimaryKeyConstraint("user_id", "role_id"), sa.UniqueConstraint("user_id", "role_id", name="uq_user_roles_user_role"), ) op.create_index( "ix_user_roles_role_id", "user_roles", ["role_id"], unique=False, ) # Seed default roles roles_table = table( "roles", column("id", sa.Integer()), column("name", sa.String()), column("display_name", sa.String()), column("description", sa.Text()), ) op.bulk_insert( roles_table, [ { "id": 1, "name": "admin", "display_name": "Administrator", "description": "Full platform access with user management rights.", }, { "id": 2, "name": "project_manager", "display_name": "Project Manager", "description": "Manage projects, scenarios, and associated data.", }, { "id": 3, "name": "analyst", "display_name": "Analyst", "description": "Review dashboards and scenario outputs.", }, { "id": 4, "name": "viewer", "display_name": "Viewer", "description": "Read-only access to assigned projects and reports.", }, ], ) admin_password_hash = password_context.hash("ChangeMe123!") users_table = table( "users", column("id", sa.Integer()), column("email", sa.String()), column("username", sa.String()), column("password_hash", sa.String()), column("is_active", sa.Boolean()), column("is_superuser", sa.Boolean()), ) op.bulk_insert( users_table, [ { "id": 1, "email": "admin@calminer.local", "username": "admin", "password_hash": admin_password_hash, "is_active": True, "is_superuser": True, } ], ) user_roles_table = table( "user_roles", column("user_id", sa.Integer()), column("role_id", sa.Integer()), column("granted_by", sa.Integer()), ) op.bulk_insert( user_roles_table, [ { "user_id": 1, "role_id": 1, "granted_by": 1, } ], ) def downgrade() -> None: op.drop_index("ix_user_roles_role_id", table_name="user_roles") op.drop_table("user_roles") op.drop_table("roles") op.drop_index("ix_users_active_superuser", table_name="users") op.drop_table("users")