"""create initial foundation tables Revision ID: 0001_foundation Revises: Create Date: 2026-07-08 """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql revision: str = "0001_foundation" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.execute('CREATE EXTENSION IF NOT EXISTS "pgcrypto";') op.create_table( "roles", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")), sa.Column("name", sa.Text(), nullable=False, unique=True), sa.Column("description", sa.Text(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), ) op.create_table( "permissions", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")), sa.Column("key", sa.Text(), nullable=False, unique=True), sa.Column("description", sa.Text(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), ) op.create_table( "role_permissions", sa.Column("role_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("roles.id", ondelete="CASCADE"), nullable=False), sa.Column("permission_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("permissions.id", ondelete="CASCADE"), nullable=False), sa.PrimaryKeyConstraint("role_id", "permission_id"), ) op.create_table( "users", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")), sa.Column("username", sa.Text(), nullable=False, unique=True), sa.Column("display_name", sa.Text(), nullable=False), sa.Column("email", sa.Text(), nullable=True), sa.Column("password_hash", sa.Text(), nullable=False), sa.Column("role_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("roles.id"), nullable=False), sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")), sa.Column("last_login_at", sa.DateTime(timezone=True), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), ) op.create_index("idx_users_role_id", "users", ["role_id"]) op.create_index("idx_users_is_active", "users", ["is_active"]) op.create_table( "sites", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")), sa.Column("name", sa.Text(), nullable=False, unique=True), sa.Column("description", sa.Text(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), ) op.create_table( "rooms", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")), sa.Column("site_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("sites.id", ondelete="CASCADE"), nullable=False), sa.Column("name", sa.Text(), nullable=False), sa.Column("description", sa.Text(), nullable=True), sa.Column("sort_order", sa.Integer(), nullable=False, server_default=sa.text("0")), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), sa.UniqueConstraint("site_id", "name", name="uq_rooms_site_name"), ) op.create_index("idx_rooms_site_id", "rooms", ["site_id"]) op.create_table( "app_settings", sa.Column("key", sa.Text(), primary_key=True), sa.Column("value_json", postgresql.JSONB(), nullable=False), sa.Column("is_sensitive", sa.Boolean(), nullable=False, server_default=sa.text("false")), sa.Column("updated_by_user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), ) op.execute(""" INSERT INTO roles (name, description) VALUES ('Admin', 'Full system administrator'), ('Technician', 'Technician user for repair and operational tasks'), ('Viewer', 'Read-only dashboard and miner viewer'), ('Auditor', 'Read-only audit and reporting user'); """) op.execute(""" INSERT INTO permissions (key, description) VALUES ('admin.access', 'Access administrator features'), ('users.manage', 'Create and manage users'), ('miners.view', 'View miners'), ('miners.create', 'Create miners'), ('miners.update', 'Update miners'), ('miners.delete', 'Delete miners'), ('scan_ranges.manage', 'Manage scan ranges'), ('scans.run', 'Run scans'), ('credentials.manage', 'Manage miner credentials'), ('backups.create', 'Create backups'), ('exports.create', 'Create exports'), ('audit.view', 'View audit logs'); """) op.execute(""" INSERT INTO role_permissions (role_id, permission_id) SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.name = 'Admin'; """) op.execute(""" INSERT INTO role_permissions (role_id, permission_id) SELECT r.id, p.id FROM roles r JOIN permissions p ON p.key IN ( 'miners.view', 'miners.update', 'scans.run', 'exports.create' ) WHERE r.name = 'Technician'; """) op.execute(""" INSERT INTO role_permissions (role_id, permission_id) SELECT r.id, p.id FROM roles r JOIN permissions p ON p.key IN ('miners.view') WHERE r.name = 'Viewer'; """) op.execute(""" INSERT INTO role_permissions (role_id, permission_id) SELECT r.id, p.id FROM roles r JOIN permissions p ON p.key IN ('miners.view', 'audit.view', 'exports.create') WHERE r.name = 'Auditor'; """) op.execute(""" INSERT INTO sites (name, description) VALUES ('Main Mining Site', 'Default mining site'); """) def downgrade() -> None: op.drop_table("app_settings") op.drop_index("idx_rooms_site_id", table_name="rooms") op.drop_table("rooms") op.drop_table("sites") op.drop_index("idx_users_is_active", table_name="users") op.drop_index("idx_users_role_id", table_name="users") op.drop_table("users") op.drop_table("role_permissions") op.drop_table("permissions") op.drop_table("roles")