PMFlow/backend/alembic/versions/add_users_table_rbac.py

84 lines
3.7 KiB
Python

"""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'
# 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)
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', sa.String(50) if is_sqlite else sa.Enum('BOSS', 'ADMIN', 'AI', 'FE', 'BE', name='userrole'), 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', sa.String(50) if is_sqlite else sa.Enum('active', 'away', 'offline', name='userstatus'), 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)