Files
orion/alembic/versions/za0k1l2m3n4o5_add_admin_menu_config.py
Samir Boulahtit 9a828999fe feat: add admin menu configuration and sidebar improvements
- Add AdminMenuConfig model for per-platform menu customization
- Add menu registry for centralized menu configuration
- Add my-menu-config and platform-menu-config admin pages
- Update sidebar with improved layout and Alpine.js interactions
- Add FrontendType enum for admin/vendor menu separation
- Document self-contained module patterns in session note

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 20:34:58 +01:00

129 lines
3.9 KiB
Python

"""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)
"""
from alembic import op
import sqlalchemy as sa
# 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")