Files
service-finder/backend/app/scripts/migrate_rbac_phase1_enums.py

155 lines
5.7 KiB
Python

# /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())