"""Add roles system Revision ID: 001_add_roles Revises: Create Date: 2024-12-14 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy import inspect # revision identifiers, used by Alembic. revision: str = '001_add_roles' down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def column_exists(table_name: str, column_name: str) -> bool: bind = op.get_bind() inspector = inspect(bind) columns = [col['name'] for col in inspector.get_columns(table_name)] return column_name in columns def constraint_exists(table_name: str, constraint_name: str) -> bool: bind = op.get_bind() inspector = inspect(bind) fks = inspector.get_foreign_keys(table_name) return any(fk['name'] == constraint_name for fk in fks) def upgrade() -> None: # Add role column to users table if not column_exists('users', 'role'): op.add_column('users', sa.Column('role', sa.String(20), nullable=False, server_default='user')) # Add role column to participants table if not column_exists('participants', 'role'): op.add_column('participants', sa.Column('role', sa.String(20), nullable=False, server_default='participant')) # Rename organizer_id to creator_id in marathons table if column_exists('marathons', 'organizer_id') and not column_exists('marathons', 'creator_id'): op.alter_column('marathons', 'organizer_id', new_column_name='creator_id') # Update existing participants: set role='organizer' for marathon creators # This is idempotent - running multiple times is safe op.execute(""" UPDATE participants p SET role = 'organizer' FROM marathons m WHERE p.marathon_id = m.id AND p.user_id = m.creator_id """) # Add status column to games table if not column_exists('games', 'status'): op.add_column('games', sa.Column('status', sa.String(20), nullable=False, server_default='approved')) # Rename added_by_id to proposed_by_id in games table if column_exists('games', 'added_by_id') and not column_exists('games', 'proposed_by_id'): op.alter_column('games', 'added_by_id', new_column_name='proposed_by_id') # Add approved_by_id column to games table if not column_exists('games', 'approved_by_id'): op.add_column('games', sa.Column('approved_by_id', sa.Integer(), nullable=True)) if not constraint_exists('games', 'fk_games_approved_by_id'): op.create_foreign_key( 'fk_games_approved_by_id', 'games', 'users', ['approved_by_id'], ['id'], ondelete='SET NULL' ) def downgrade() -> None: # Remove approved_by_id from games if constraint_exists('games', 'fk_games_approved_by_id'): op.drop_constraint('fk_games_approved_by_id', 'games', type_='foreignkey') if column_exists('games', 'approved_by_id'): op.drop_column('games', 'approved_by_id') # Rename proposed_by_id back to added_by_id if column_exists('games', 'proposed_by_id'): op.alter_column('games', 'proposed_by_id', new_column_name='added_by_id') # Remove status from games if column_exists('games', 'status'): op.drop_column('games', 'status') # Rename creator_id back to organizer_id if column_exists('marathons', 'creator_id'): op.alter_column('marathons', 'creator_id', new_column_name='organizer_id') # Remove role from participants if column_exists('participants', 'role'): op.drop_column('participants', 'role') # Remove role from users if column_exists('users', 'role'): op.drop_column('users', 'role')