"""sync_role_permissions Revision ID: 013_sync_permissions Revises: 012_fix_remaining Create Date: 2026-01-05 Syncs role_permissions between DEV and PROD bidirectionally. - Adds 18 DEV-only permissions to PROD (new features) - Adds 6 PROD-only permissions to DEV (operational/security) Result: Both environments have identical 142 permission mappings """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision: str = '013_sync_permissions' down_revision: Union[str, None] = '012_fix_remaining' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: """Sync role_permissions bidirectionally""" from sqlalchemy import text conn = op.get_bind() print("Syncing role_permissions between environments...") # ============================================================ # STEP 1: Add missing permissions to ensure all exist # ============================================================ print("\n[1/2] Ensuring all permissions exist...") # Permissions that should exist (union of both environments) all_permissions = [ # From DEV-only list ('donations.export', 'Export Donations', 'donations'), ('donations.view', 'View Donations', 'donations'), ('financials.create', 'Create Financial Reports', 'financials'), ('financials.delete', 'Delete Financial Reports', 'financials'), ('financials.edit', 'Edit Financial Reports', 'financials'), ('financials.export', 'Export Financial Reports', 'financials'), ('financials.payments', 'Manage Financial Payments', 'financials'), ('settings.edit', 'Edit Settings', 'settings'), ('settings.email_templates', 'Manage Email Templates', 'settings'), ('subscriptions.activate', 'Activate Subscriptions', 'subscriptions'), ('subscriptions.cancel', 'Cancel Subscriptions', 'subscriptions'), ('subscriptions.create', 'Create Subscriptions', 'subscriptions'), ('subscriptions.edit', 'Edit Subscriptions', 'subscriptions'), ('subscriptions.export', 'Export Subscriptions', 'subscriptions'), ('subscriptions.plans', 'Manage Subscription Plans', 'subscriptions'), ('subscriptions.view', 'View Subscriptions', 'subscriptions'), ('events.calendar_export', 'Export Event Calendar', 'events'), ('events.rsvps', 'View Event RSVPs', 'events'), # From PROD-only list ('permissions.audit', 'Audit Permissions', 'permissions'), ('permissions.view', 'View Permissions', 'permissions'), ('settings.backup', 'Manage Backups', 'settings'), ] for code, name, module in all_permissions: # Insert if not exists conn.execute(text(f""" INSERT INTO permissions (id, code, name, description, module, created_at) SELECT gen_random_uuid(), '{code}', '{name}', '{name}', '{module}', NOW() WHERE NOT EXISTS ( SELECT 1 FROM permissions WHERE code = '{code}' ) """)) print(" ✓ Ensured all permissions exist") # ============================================================ # STEP 2: Add missing role-permission mappings # ============================================================ print("\n[2/2] Adding missing role-permission mappings...") # Mappings that should exist (union of both environments) role_permission_mappings = [ # DEV-only (add to PROD) ('admin', 'donations.export'), ('admin', 'donations.view'), ('admin', 'financials.create'), ('admin', 'financials.delete'), ('admin', 'financials.edit'), ('admin', 'financials.export'), ('admin', 'financials.payments'), ('admin', 'settings.edit'), ('admin', 'settings.email_templates'), ('admin', 'subscriptions.activate'), ('admin', 'subscriptions.cancel'), ('admin', 'subscriptions.create'), ('admin', 'subscriptions.edit'), ('admin', 'subscriptions.export'), ('admin', 'subscriptions.plans'), ('admin', 'subscriptions.view'), ('member', 'events.calendar_export'), ('member', 'events.rsvps'), # PROD-only (add to DEV) ('admin', 'permissions.audit'), ('admin', 'permissions.view'), ('admin', 'settings.backup'), ('finance', 'bylaws.view'), ('finance', 'events.view'), ('finance', 'newsletters.view'), ] added_count = 0 for role, perm_code in role_permission_mappings: result = conn.execute(text(f""" INSERT INTO role_permissions (id, role, permission_id, created_at) SELECT gen_random_uuid(), '{role}', p.id, NOW() FROM permissions p WHERE p.code = '{perm_code}' AND NOT EXISTS ( SELECT 1 FROM role_permissions rp WHERE rp.role = '{role}' AND rp.permission_id = p.id ) RETURNING id """)) if result.rowcount > 0: added_count += 1 print(f" ✓ Added {added_count} missing role-permission mappings") # Verify final count final_count = conn.execute(text("SELECT COUNT(*) FROM role_permissions")).scalar() print(f"\n✅ Role-permission mappings synchronized: {final_count} total") def downgrade() -> None: """Revert sync (not recommended)""" print("⚠️ Downgrade not supported - permissions are additive") pass