"""add users table RBAC Revision ID: add_users_table Revises: Create Date: 2026-02-22 """ from alembic import op import sqlalchemy as sa from sqlalchemy.dialects.postgresql import UUID, ENUM import uuid # revision identifiers, used by Alembic. revision = 'add_users_table' down_revision = '380f4e61b41c' # Changed from None to 380f4e61b41c branch_labels = None depends_on = None def upgrade(): bind = op.get_bind() is_sqlite = bind.dialect.name == 'sqlite' role_column_type = sa.String(50) status_column_type = sa.String(50) # Create enums (only for Postgres) if not is_sqlite: user_role_enum = ENUM('BOSS', 'ADMIN', 'AI', 'FE', 'BE', name='userrole', create_type=True) user_status_enum = ENUM('active', 'away', 'offline', name='userstatus', create_type=True) role_column_type = ENUM('BOSS', 'ADMIN', 'AI', 'FE', 'BE', name='userrole', create_type=False) status_column_type = ENUM('active', 'away', 'offline', name='userstatus', create_type=False) user_role_enum.create(bind, checkfirst=True) user_status_enum.create(bind, checkfirst=True) # Create users table op.create_table( 'users', sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True) if is_sqlite else sa.Column('id', UUID(as_uuid=True), primary_key=True, default=uuid.uuid4), sa.Column('username', sa.String(50), unique=True, nullable=False, index=True), sa.Column('password_hash', sa.String(255), nullable=False), sa.Column('name', sa.String(100), nullable=False), sa.Column('email', sa.String(255), unique=True, nullable=True), sa.Column('role', role_column_type, nullable=False, server_default='FE'), sa.Column('assigned_domains', sa.JSON(), nullable=True, server_default='[]'), sa.Column('workload', sa.Integer(), nullable=False, server_default='0'), sa.Column('status', status_column_type, nullable=False, server_default='active'), sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.func.now()), sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.func.now()), ) # Create initial users (with simple password hashes for demo) # In production, these should be created with proper password hashing from passlib.context import CryptContext pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') admin_hash = pwd_context.hash('admin123') user_hash = pwd_context.hash('user123') if is_sqlite: op.execute(f""" INSERT INTO users (username, password_hash, name, email, role, assigned_domains, workload, status, created_at, updated_at) VALUES ('admin', '{admin_hash}', 'System Administrator', 'admin@neural-sync.ai', 'BOSS', '[]', 0, 'active', datetime('now'), datetime('now')), ('user', '{user_hash}', 'Test User', 'user@neural-sync.ai', 'FE', '[]', 0, 'active', datetime('now'), datetime('now')) """) else: op.execute(f""" INSERT INTO users (id, username, password_hash, name, email, role, assigned_domains, workload, status, created_at, updated_at) VALUES ('{uuid.uuid4()}', 'admin', '{admin_hash}', 'System Administrator', 'admin@neural-sync.ai', 'BOSS', '[]', 0, 'active', NOW(), NOW()), ('{uuid.uuid4()}', 'user', '{user_hash}', 'Test User', 'user@neural-sync.ai', 'FE', '[]', 0, 'active', NOW(), NOW()) """) def downgrade(): op.drop_table('users') bind = op.get_bind() is_sqlite = bind.dialect.name == 'sqlite' # Drop enums (only for Postgres) if not is_sqlite: user_role_enum = sa.dialects.postgresql.ENUM(name='userrole') user_status_enum = sa.dialects.postgresql.ENUM(name='userstatus') user_role_enum.drop(bind, checkfirst=True) user_status_enum.drop(bind, checkfirst=True)