174 lines
7.0 KiB
Python
174 lines
7.0 KiB
Python
"""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")
|