"""Add admin menu configuration table Revision ID: za0k1l2m3n4o5 Revises: z9j0k1l2m3n4 Create Date: 2026-01-25 Adds configurable admin sidebar menus: - Platform-level config: Controls which menu items platform admins see - User-level config: Controls which menu items super admins see - Opt-out model: All items visible by default - Mandatory items enforced at application level (companies, vendors, users, settings) """ import sqlalchemy as sa from alembic import op # revision identifiers, used by Alembic. revision = "za0k1l2m3n4o5" down_revision = "z9j0k1l2m3n4" branch_labels = None depends_on = None def upgrade() -> None: # Create admin_menu_configs table op.create_table( "admin_menu_configs", sa.Column("id", sa.Integer(), nullable=False), sa.Column( "platform_id", sa.Integer(), nullable=True, comment="Platform scope - applies to all platform admins of this platform", ), sa.Column( "user_id", sa.Integer(), nullable=True, comment="User scope - applies to this specific super admin", ), sa.Column( "menu_item_id", sa.String(50), nullable=False, comment="Menu item identifier from registry (e.g., 'products', 'inventory')", ), sa.Column( "is_visible", sa.Boolean(), nullable=False, server_default="true", comment="Whether this menu item is visible (False = hidden)", ), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now(), ), # Foreign keys sa.ForeignKeyConstraint( ["platform_id"], ["platforms.id"], ondelete="CASCADE", ), sa.ForeignKeyConstraint( ["user_id"], ["users.id"], ondelete="CASCADE", ), sa.PrimaryKeyConstraint("id"), # Unique constraints sa.UniqueConstraint("platform_id", "menu_item_id", name="uq_platform_menu_config"), sa.UniqueConstraint("user_id", "menu_item_id", name="uq_user_menu_config"), # Check constraint: exactly one scope must be set sa.CheckConstraint( "(platform_id IS NOT NULL AND user_id IS NULL) OR " "(platform_id IS NULL AND user_id IS NOT NULL)", name="ck_admin_menu_config_scope", ), ) # Create indexes for performance op.create_index( "idx_admin_menu_configs_platform_id", "admin_menu_configs", ["platform_id"], ) op.create_index( "idx_admin_menu_configs_user_id", "admin_menu_configs", ["user_id"], ) op.create_index( "idx_admin_menu_configs_menu_item_id", "admin_menu_configs", ["menu_item_id"], ) op.create_index( "idx_admin_menu_config_platform_visible", "admin_menu_configs", ["platform_id", "is_visible"], ) op.create_index( "idx_admin_menu_config_user_visible", "admin_menu_configs", ["user_id", "is_visible"], ) def downgrade() -> None: # Drop indexes op.drop_index("idx_admin_menu_config_user_visible", table_name="admin_menu_configs") op.drop_index("idx_admin_menu_config_platform_visible", table_name="admin_menu_configs") op.drop_index("idx_admin_menu_configs_menu_item_id", table_name="admin_menu_configs") op.drop_index("idx_admin_menu_configs_user_id", table_name="admin_menu_configs") op.drop_index("idx_admin_menu_configs_platform_id", table_name="admin_menu_configs") # Drop table op.drop_table("admin_menu_configs")