"""remove legacy qa role Revision ID: 20260521_01 Revises: 20260520_01 Create Date: 2026-05-21 09:00:00.000000 """ from typing import Sequence, Union from alembic import op revision: str = "20260521_01" down_revision: Union[str, None] = "20260520_01" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None USER_ROLE_VALUES = ("ADMIN", "PM", "CRA", "PV", "IMP", "MEDICAL_REVIEW") def upgrade() -> None: op.execute("CREATE TEMP TABLE qa_user_ids AS SELECT id FROM users WHERE role = 'QA'") op.execute("DELETE FROM permission_access_logs WHERE role = 'QA'") op.execute("DELETE FROM api_endpoint_permissions WHERE role = 'QA'") op.execute("DELETE FROM study_members WHERE role_in_study = 'QA'") op.execute( """ UPDATE studies SET active_roles = ( SELECT COALESCE(json_agg(role), '[]'::json) FROM json_array_elements_text(active_roles) AS role WHERE role <> 'QA' ) WHERE active_roles::text LIKE '%QA%' """ ) op.execute( """ DELETE FROM permission_template_versions WHERE template_id IN ( SELECT id FROM permission_templates WHERE category = 'QA' OR recommended_roles = 'QA' OR (permissions::jsonb ? 'QA') ) """ ) op.execute( """ DELETE FROM permission_templates WHERE category = 'QA' OR recommended_roles = 'QA' OR (permissions::jsonb ? 'QA') """ ) op.execute( """ UPDATE permission_templates SET created_by = NULL WHERE created_by IN (SELECT id FROM qa_user_ids) """ ) op.execute( """ DELETE FROM audit_logs WHERE operator_role = 'QA' OR operator_id IN (SELECT id FROM users WHERE role = 'QA') """ ) op.execute( """ DO $$ DECLARE rel record; BEGIN FOR rel IN SELECT quote_ident(ns.nspname) AS schema_name, quote_ident(cls.relname) AS table_name, quote_ident(att.attname) AS column_name, att.attnotnull AS not_null FROM pg_constraint con JOIN pg_class cls ON cls.oid = con.conrelid JOIN pg_namespace ns ON ns.oid = cls.relnamespace JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ANY(con.conkey) WHERE con.contype = 'f' AND con.confrelid = 'users'::regclass AND array_length(con.conkey, 1) = 1 LOOP IF rel.table_name = 'users' AND rel.column_name = 'id' THEN CONTINUE; END IF; IF rel.not_null THEN EXECUTE format( 'DELETE FROM %s.%s WHERE %s IN (SELECT id FROM qa_user_ids)', rel.schema_name, rel.table_name, rel.column_name ); ELSE EXECUTE format( 'UPDATE %s.%s SET %s = NULL WHERE %s IN (SELECT id FROM qa_user_ids)', rel.schema_name, rel.table_name, rel.column_name, rel.column_name ); END IF; END LOOP; END $$; """ ) # Delete legacy QA users after dependent project-role and audit rows have been removed. op.execute("DELETE FROM users WHERE id IN (SELECT id FROM qa_user_ids)") op.execute("DROP TABLE qa_user_ids") allowed = ", ".join(f"'{role}'" for role in USER_ROLE_VALUES) op.execute( f""" ALTER TABLE users ALTER COLUMN role TYPE text USING role::text """ ) op.execute("DROP TYPE user_role") op.execute(f"CREATE TYPE user_role AS ENUM ({allowed})") op.execute( """ ALTER TABLE users ALTER COLUMN role TYPE user_role USING role::user_role """ ) def downgrade() -> None: allowed = ", ".join(f"'{role}'" for role in USER_ROLE_VALUES) op.execute( """ ALTER TABLE users ALTER COLUMN role TYPE text USING role::text """ ) op.execute("DROP TYPE user_role") op.execute(f"CREATE TYPE user_role AS ENUM ({allowed})") op.execute( """ ALTER TABLE users ALTER COLUMN role TYPE user_role USING role::user_role """ )