# /opt/docker/dev/service_finder/backend/app/scripts/migrate_rbac_phase1_enums.py """ πŸ”„ RBAC Phase 1: PostgreSQL Enum Migration This script handles the PostgreSQL enum migration for: 1. identity.userrole -> new values: SUPERADMIN, ADMIN, MODERATOR, SALES_REP, SERVICE_MGR, USER 2. fleet.org_roles table seeding (delegated to seed_org_roles.py) Since PostgreSQL doesn't support ALTER TYPE ... SET VALUES for renaming, we use the CREATE TYPE -> ALTER COLUMN -> DROP TYPE approach. FuttatΓ‘s: docker compose exec sf_api python3 /app/app/scripts/migrate_rbac_phase1_enums.py """ import asyncio import logging from app.database import AsyncSessionLocal from sqlalchemy import text logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s]: %(message)s') logger = logging.getLogger("Migrate-RBAC-Enums") async def migrate_userrole_enum(): """Migrate identity.userrole enum to new values. Old values: superadmin, admin, moderator, sales_agent, user, service_owner, fleet_manager, driver New values: SUPERADMIN, ADMIN, MODERATOR, SALES_REP, SERVICE_MGR, USER Mapping: superadmin -> SUPERADMIN admin -> ADMIN moderator -> MODERATOR sales_agent -> SALES_REP user -> USER service_owner -> USER (fallback) fleet_manager -> USER (fallback) driver -> USER (fallback) """ async with AsyncSessionLocal() as session: logger.info("πŸ” Checking current identity.userrole enum...") # Check current enum values result = await session.execute( text("SELECT unnest(enum_range(NULL::identity.userrole))::text as val") ) current_values = {row[0] for row in result.fetchall()} logger.info(f"Current enum values: {current_values}") new_values = {"SUPERADMIN", "ADMIN", "MODERATOR", "SALES_REP", "SERVICE_MGR", "USER"} # If already migrated, skip if new_values.issubset(current_values): logger.info("βœ… Enum already has the new values. Skipping migration.") return logger.info("πŸ”„ Starting enum migration...") # Step 1: Create new enum type logger.info("Step 1: Creating new enum type 'userrole_new'...") await session.execute(text(""" CREATE TYPE identity.userrole_new AS ENUM ( 'SUPERADMIN', 'ADMIN', 'MODERATOR', 'SALES_REP', 'SERVICE_MGR', 'USER' ) """)) # Step 2: Alter the users table to use text temporarily logger.info("Step 2: Altering column to text type...") await session.execute(text(""" ALTER TABLE identity.users ALTER COLUMN role TYPE text USING role::text """)) # Step 3: Update old values to new values logger.info("Step 3: Updating role values...") await session.execute(text(""" UPDATE identity.users SET role = 'SUPERADMIN' WHERE role = 'superadmin' """)) await session.execute(text(""" UPDATE identity.users SET role = 'ADMIN' WHERE role = 'admin' """)) await session.execute(text(""" UPDATE identity.users SET role = 'MODERATOR' WHERE role = 'moderator' """)) await session.execute(text(""" UPDATE identity.users SET role = 'SALES_REP' WHERE role = 'sales_agent' """)) await session.execute(text(""" UPDATE identity.users SET role = 'USER' WHERE role IN ('user', 'service_owner', 'fleet_manager', 'driver') """)) # Step 4: Drop default before altering type logger.info("Step 4: Dropping default value...") await session.execute(text(""" ALTER TABLE identity.users ALTER COLUMN role DROP DEFAULT """)) # Step 5: Alter column to use new enum logger.info("Step 5: Altering column to new enum type...") await session.execute(text(""" ALTER TABLE identity.users ALTER COLUMN role TYPE identity.userrole_new USING role::text::identity.userrole_new """)) # Step 6: Set default logger.info("Step 6: Setting default value...") await session.execute(text(""" ALTER TABLE identity.users ALTER COLUMN role SET DEFAULT 'USER'::identity.userrole_new """)) # Step 7: Drop old enum and rename new one logger.info("Step 7: Dropping old enum and renaming new one...") await session.execute(text("DROP TYPE IF EXISTS identity.userrole")) await session.execute(text(""" ALTER TYPE identity.userrole_new RENAME TO userrole """)) await session.commit() logger.info("βœ… Enum migration completed successfully!") # Verify result = await session.execute( text("SELECT unnest(enum_range(NULL::identity.userrole))::text as val") ) final_values = [row[0] for row in result.fetchall()] logger.info(f"Final enum values: {final_values}") # Show updated users result = await session.execute( text("SELECT id, email, role::text FROM identity.users ORDER BY id") ) users = result.fetchall() logger.info(f"\nUpdated {len(users)} users:") for row in users: logger.info(f" id={row[0]:4d} | email={row[1]:30s} | role={row[2]}") async def main(): logger.info("=" * 60) logger.info("πŸ”„ RBAC Phase 1: PostgreSQL Enum Migration") logger.info("=" * 60) await migrate_userrole_enum() logger.info("=" * 60) logger.info("βœ… Migration completed.") logger.info("=" * 60) if __name__ == "__main__": asyncio.run(main())