Merge pull request 'Alembic migration for synchronize Database' (#20) from dev into loaf-prod
Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
147
alembic/versions/013_sync_role_permissions.py
Normal file
147
alembic/versions/013_sync_role_permissions.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
"""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
|
||||||
Reference in New Issue
Block a user