refactor: remove deprecated menu_registry.py in favor of module-driven menus
- Delete app/config/menu_registry.py (centralized menu definitions) - Update app/config/__init__.py to remove menu_registry exports - Migrate menu_service.py to use menu_discovery_service exclusively - Add helper methods to menu_discovery_service.py: - get_all_menu_item_ids() - get all item IDs for a frontend type - get_menu_item() - get specific menu item by ID - is_super_admin_only_item() - check if item is super-admin only - get_menu_item_module() - get module code for a menu item Menu items are now defined in each module's definition.py file and aggregated by MenuDiscoveryService at runtime, enabling true module-driven architecture. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,8 @@
|
|||||||
# app/config/__init__.py
|
# app/config/__init__.py
|
||||||
"""Configuration modules for the application."""
|
"""Configuration modules for the application."""
|
||||||
|
|
||||||
from .menu_registry import (
|
# Note: menu_registry.py has been deprecated in favor of module-driven menus.
|
||||||
ADMIN_MENU_REGISTRY,
|
# Menu items are now defined in each module's definition.py file and aggregated
|
||||||
VENDOR_MENU_REGISTRY,
|
# by MenuDiscoveryService (app/modules/core/services/menu_discovery_service.py).
|
||||||
AdminMenuItem,
|
|
||||||
VendorMenuItem,
|
|
||||||
get_all_menu_item_ids,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = []
|
||||||
"ADMIN_MENU_REGISTRY",
|
|
||||||
"VENDOR_MENU_REGISTRY",
|
|
||||||
"AdminMenuItem",
|
|
||||||
"VendorMenuItem",
|
|
||||||
"get_all_menu_item_ids",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,546 +0,0 @@
|
|||||||
# app/config/menu_registry.py
|
|
||||||
"""
|
|
||||||
Menu registry for Admin and Vendor frontends.
|
|
||||||
|
|
||||||
This module defines the complete menu structure for both frontends.
|
|
||||||
Menu items are identified by unique IDs that are used for:
|
|
||||||
- Storing visibility configuration in AdminMenuConfig
|
|
||||||
- Checking access in require_menu_access() dependency
|
|
||||||
- Rendering the sidebar dynamically
|
|
||||||
|
|
||||||
The registry is the single source of truth for menu structure.
|
|
||||||
Database only stores visibility overrides (is_visible=False).
|
|
||||||
"""
|
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from app.modules.enums import FrontendType
|
|
||||||
|
|
||||||
|
|
||||||
class AdminMenuItem(str, Enum):
|
|
||||||
"""Admin frontend menu item identifiers."""
|
|
||||||
|
|
||||||
# Dashboard (always visible section)
|
|
||||||
DASHBOARD = "dashboard"
|
|
||||||
|
|
||||||
# Super Admin section
|
|
||||||
ADMIN_USERS = "admin-users"
|
|
||||||
|
|
||||||
# Platform Administration section
|
|
||||||
COMPANIES = "companies"
|
|
||||||
VENDORS = "vendors"
|
|
||||||
MESSAGES = "messages"
|
|
||||||
|
|
||||||
# Vendor Operations section
|
|
||||||
VENDOR_PRODUCTS = "vendor-products"
|
|
||||||
CUSTOMERS = "customers"
|
|
||||||
INVENTORY = "inventory"
|
|
||||||
ORDERS = "orders"
|
|
||||||
|
|
||||||
# Marketplace section
|
|
||||||
MARKETPLACE_LETZSHOP = "marketplace-letzshop"
|
|
||||||
|
|
||||||
# Billing & Subscriptions section
|
|
||||||
SUBSCRIPTION_TIERS = "subscription-tiers"
|
|
||||||
SUBSCRIPTIONS = "subscriptions"
|
|
||||||
BILLING_HISTORY = "billing-history"
|
|
||||||
|
|
||||||
# Content Management section
|
|
||||||
PLATFORMS = "platforms"
|
|
||||||
CONTENT_PAGES = "content-pages"
|
|
||||||
VENDOR_THEMES = "vendor-themes"
|
|
||||||
|
|
||||||
# Developer Tools section
|
|
||||||
COMPONENTS = "components"
|
|
||||||
ICONS = "icons"
|
|
||||||
|
|
||||||
# Platform Health section
|
|
||||||
PLATFORM_HEALTH = "platform-health"
|
|
||||||
TESTING = "testing"
|
|
||||||
CODE_QUALITY = "code-quality"
|
|
||||||
|
|
||||||
# Platform Monitoring section
|
|
||||||
IMPORTS = "imports"
|
|
||||||
BACKGROUND_TASKS = "background-tasks"
|
|
||||||
LOGS = "logs"
|
|
||||||
NOTIFICATIONS = "notifications"
|
|
||||||
|
|
||||||
# Platform Settings section
|
|
||||||
SETTINGS = "settings"
|
|
||||||
EMAIL_TEMPLATES = "email-templates"
|
|
||||||
MY_MENU = "my-menu" # Super admin only - personal menu configuration
|
|
||||||
|
|
||||||
|
|
||||||
class VendorMenuItem(str, Enum):
|
|
||||||
"""Vendor frontend menu item identifiers."""
|
|
||||||
|
|
||||||
# Main section (always visible)
|
|
||||||
DASHBOARD = "dashboard"
|
|
||||||
ANALYTICS = "analytics"
|
|
||||||
|
|
||||||
# Products & Inventory section
|
|
||||||
PRODUCTS = "products"
|
|
||||||
INVENTORY = "inventory"
|
|
||||||
MARKETPLACE = "marketplace"
|
|
||||||
|
|
||||||
# Sales & Orders section
|
|
||||||
ORDERS = "orders"
|
|
||||||
LETZSHOP = "letzshop"
|
|
||||||
INVOICES = "invoices"
|
|
||||||
|
|
||||||
# Customers section
|
|
||||||
CUSTOMERS = "customers"
|
|
||||||
MESSAGES = "messages"
|
|
||||||
NOTIFICATIONS = "notifications"
|
|
||||||
|
|
||||||
# Shop & Content section
|
|
||||||
CONTENT_PAGES = "content-pages"
|
|
||||||
MEDIA = "media"
|
|
||||||
|
|
||||||
# Account & Settings section
|
|
||||||
TEAM = "team"
|
|
||||||
PROFILE = "profile"
|
|
||||||
BILLING = "billing"
|
|
||||||
EMAIL_TEMPLATES = "email-templates"
|
|
||||||
SETTINGS = "settings"
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Admin Menu Registry
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
ADMIN_MENU_REGISTRY = {
|
|
||||||
"frontend_type": FrontendType.ADMIN,
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"id": "main",
|
|
||||||
"label": None, # No header, always at top
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.DASHBOARD.value,
|
|
||||||
"label": "Dashboard",
|
|
||||||
"icon": "home",
|
|
||||||
"url": "/admin/dashboard",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "superAdmin",
|
|
||||||
"label": "Super Admin",
|
|
||||||
"super_admin_only": True,
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.ADMIN_USERS.value,
|
|
||||||
"label": "Admin Users",
|
|
||||||
"icon": "shield",
|
|
||||||
"url": "/admin/admin-users",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "platformAdmin",
|
|
||||||
"label": "Platform Administration",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.COMPANIES.value,
|
|
||||||
"label": "Companies",
|
|
||||||
"icon": "office-building",
|
|
||||||
"url": "/admin/companies",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.VENDORS.value,
|
|
||||||
"label": "Vendors",
|
|
||||||
"icon": "shopping-bag",
|
|
||||||
"url": "/admin/vendors",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.MESSAGES.value,
|
|
||||||
"label": "Messages",
|
|
||||||
"icon": "chat-bubble-left-right",
|
|
||||||
"url": "/admin/messages",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "vendorOps",
|
|
||||||
"label": "Vendor Operations",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.VENDOR_PRODUCTS.value,
|
|
||||||
"label": "Products",
|
|
||||||
"icon": "cube",
|
|
||||||
"url": "/admin/vendor-products",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.CUSTOMERS.value,
|
|
||||||
"label": "Customers",
|
|
||||||
"icon": "user-group",
|
|
||||||
"url": "/admin/customers",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.INVENTORY.value,
|
|
||||||
"label": "Inventory",
|
|
||||||
"icon": "archive",
|
|
||||||
"url": "/admin/inventory",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.ORDERS.value,
|
|
||||||
"label": "Orders",
|
|
||||||
"icon": "clipboard-list",
|
|
||||||
"url": "/admin/orders",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "marketplace",
|
|
||||||
"label": "Marketplace",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.MARKETPLACE_LETZSHOP.value,
|
|
||||||
"label": "Letzshop",
|
|
||||||
"icon": "shopping-cart",
|
|
||||||
"url": "/admin/marketplace/letzshop",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "billing",
|
|
||||||
"label": "Billing & Subscriptions",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.SUBSCRIPTION_TIERS.value,
|
|
||||||
"label": "Subscription Tiers",
|
|
||||||
"icon": "tag",
|
|
||||||
"url": "/admin/subscription-tiers",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.SUBSCRIPTIONS.value,
|
|
||||||
"label": "Vendor Subscriptions",
|
|
||||||
"icon": "credit-card",
|
|
||||||
"url": "/admin/subscriptions",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.BILLING_HISTORY.value,
|
|
||||||
"label": "Billing History",
|
|
||||||
"icon": "document-text",
|
|
||||||
"url": "/admin/billing-history",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "contentMgmt",
|
|
||||||
"label": "Content Management",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.PLATFORMS.value,
|
|
||||||
"label": "Platforms",
|
|
||||||
"icon": "globe-alt",
|
|
||||||
"url": "/admin/platforms",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.CONTENT_PAGES.value,
|
|
||||||
"label": "Content Pages",
|
|
||||||
"icon": "document-text",
|
|
||||||
"url": "/admin/content-pages",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.VENDOR_THEMES.value,
|
|
||||||
"label": "Vendor Themes",
|
|
||||||
"icon": "color-swatch",
|
|
||||||
"url": "/admin/vendor-themes",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "devTools",
|
|
||||||
"label": "Developer Tools",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.COMPONENTS.value,
|
|
||||||
"label": "Components",
|
|
||||||
"icon": "view-grid",
|
|
||||||
"url": "/admin/components",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.ICONS.value,
|
|
||||||
"label": "Icons",
|
|
||||||
"icon": "photograph",
|
|
||||||
"url": "/admin/icons",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "platformHealth",
|
|
||||||
"label": "Platform Health",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.PLATFORM_HEALTH.value,
|
|
||||||
"label": "Capacity Monitor",
|
|
||||||
"icon": "chart-bar",
|
|
||||||
"url": "/admin/platform-health",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.TESTING.value,
|
|
||||||
"label": "Testing Hub",
|
|
||||||
"icon": "beaker",
|
|
||||||
"url": "/admin/testing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.CODE_QUALITY.value,
|
|
||||||
"label": "Code Quality",
|
|
||||||
"icon": "shield-check",
|
|
||||||
"url": "/admin/code-quality",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "monitoring",
|
|
||||||
"label": "Platform Monitoring",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.IMPORTS.value,
|
|
||||||
"label": "Import Jobs",
|
|
||||||
"icon": "cube",
|
|
||||||
"url": "/admin/imports",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.BACKGROUND_TASKS.value,
|
|
||||||
"label": "Background Tasks",
|
|
||||||
"icon": "collection",
|
|
||||||
"url": "/admin/background-tasks",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.LOGS.value,
|
|
||||||
"label": "Application Logs",
|
|
||||||
"icon": "document-text",
|
|
||||||
"url": "/admin/logs",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.NOTIFICATIONS.value,
|
|
||||||
"label": "Notifications",
|
|
||||||
"icon": "bell",
|
|
||||||
"url": "/admin/notifications",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "settingsSection",
|
|
||||||
"label": "Platform Settings",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.SETTINGS.value,
|
|
||||||
"label": "General",
|
|
||||||
"icon": "cog",
|
|
||||||
"url": "/admin/settings",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.EMAIL_TEMPLATES.value,
|
|
||||||
"label": "Email Templates",
|
|
||||||
"icon": "mail",
|
|
||||||
"url": "/admin/email-templates",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": AdminMenuItem.MY_MENU.value,
|
|
||||||
"label": "My Menu",
|
|
||||||
"icon": "view-grid",
|
|
||||||
"url": "/admin/my-menu",
|
|
||||||
"super_admin_only": True, # Only super admins can customize their menu
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Vendor Menu Registry
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
VENDOR_MENU_REGISTRY = {
|
|
||||||
"frontend_type": FrontendType.VENDOR,
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"id": "main",
|
|
||||||
"label": None, # No header, always at top
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.DASHBOARD.value,
|
|
||||||
"label": "Dashboard",
|
|
||||||
"icon": "home",
|
|
||||||
"url": "/dashboard", # Relative to /vendor/{code}/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.ANALYTICS.value,
|
|
||||||
"label": "Analytics",
|
|
||||||
"icon": "chart-bar",
|
|
||||||
"url": "/analytics",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "products",
|
|
||||||
"label": "Products & Inventory",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.PRODUCTS.value,
|
|
||||||
"label": "All Products",
|
|
||||||
"icon": "shopping-bag",
|
|
||||||
"url": "/products",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.INVENTORY.value,
|
|
||||||
"label": "Inventory",
|
|
||||||
"icon": "clipboard-list",
|
|
||||||
"url": "/inventory",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.MARKETPLACE.value,
|
|
||||||
"label": "Marketplace Import",
|
|
||||||
"icon": "download",
|
|
||||||
"url": "/marketplace",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sales",
|
|
||||||
"label": "Sales & Orders",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.ORDERS.value,
|
|
||||||
"label": "Orders",
|
|
||||||
"icon": "document-text",
|
|
||||||
"url": "/orders",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.LETZSHOP.value,
|
|
||||||
"label": "Letzshop Orders",
|
|
||||||
"icon": "external-link",
|
|
||||||
"url": "/letzshop",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.INVOICES.value,
|
|
||||||
"label": "Invoices",
|
|
||||||
"icon": "currency-euro",
|
|
||||||
"url": "/invoices",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "customers",
|
|
||||||
"label": "Customers",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.CUSTOMERS.value,
|
|
||||||
"label": "All Customers",
|
|
||||||
"icon": "user-group",
|
|
||||||
"url": "/customers",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.MESSAGES.value,
|
|
||||||
"label": "Messages",
|
|
||||||
"icon": "chat-bubble-left-right",
|
|
||||||
"url": "/messages",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.NOTIFICATIONS.value,
|
|
||||||
"label": "Notifications",
|
|
||||||
"icon": "bell",
|
|
||||||
"url": "/notifications",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "shop",
|
|
||||||
"label": "Shop & Content",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.CONTENT_PAGES.value,
|
|
||||||
"label": "Content Pages",
|
|
||||||
"icon": "document-text",
|
|
||||||
"url": "/content-pages",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.MEDIA.value,
|
|
||||||
"label": "Media Library",
|
|
||||||
"icon": "photograph",
|
|
||||||
"url": "/media",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "account",
|
|
||||||
"label": "Account & Settings",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.TEAM.value,
|
|
||||||
"label": "Team",
|
|
||||||
"icon": "user-group",
|
|
||||||
"url": "/team",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.PROFILE.value,
|
|
||||||
"label": "Profile",
|
|
||||||
"icon": "user",
|
|
||||||
"url": "/profile",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.BILLING.value,
|
|
||||||
"label": "Billing",
|
|
||||||
"icon": "credit-card",
|
|
||||||
"url": "/billing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.EMAIL_TEMPLATES.value,
|
|
||||||
"label": "Email Templates",
|
|
||||||
"icon": "mail",
|
|
||||||
"url": "/email-templates",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": VendorMenuItem.SETTINGS.value,
|
|
||||||
"label": "Settings",
|
|
||||||
"icon": "adjustments",
|
|
||||||
"url": "/settings",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Helper Functions
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_menu_item_ids(frontend_type: FrontendType) -> set[str]:
|
|
||||||
"""Get all menu item IDs for a frontend type."""
|
|
||||||
registry = (
|
|
||||||
ADMIN_MENU_REGISTRY if frontend_type == FrontendType.ADMIN else VENDOR_MENU_REGISTRY
|
|
||||||
)
|
|
||||||
items = set()
|
|
||||||
for section in registry["sections"]:
|
|
||||||
for item in section["items"]:
|
|
||||||
items.add(item["id"])
|
|
||||||
return items
|
|
||||||
|
|
||||||
|
|
||||||
def get_menu_item(frontend_type: FrontendType, menu_item_id: str) -> dict | None:
|
|
||||||
"""Get a menu item definition by ID."""
|
|
||||||
registry = (
|
|
||||||
ADMIN_MENU_REGISTRY if frontend_type == FrontendType.ADMIN else VENDOR_MENU_REGISTRY
|
|
||||||
)
|
|
||||||
for section in registry["sections"]:
|
|
||||||
for item in section["items"]:
|
|
||||||
if item["id"] == menu_item_id:
|
|
||||||
return {**item, "section_id": section["id"], "section_label": section.get("label")}
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def is_super_admin_only_item(menu_item_id: str) -> bool:
|
|
||||||
"""Check if a menu item is in a super_admin_only section."""
|
|
||||||
for section in ADMIN_MENU_REGISTRY["sections"]:
|
|
||||||
if section.get("super_admin_only"):
|
|
||||||
for item in section["items"]:
|
|
||||||
if item["id"] == menu_item_id:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@@ -372,6 +372,101 @@ class MenuDiscoveryService:
|
|||||||
|
|
||||||
return mandatory_ids
|
return mandatory_ids
|
||||||
|
|
||||||
|
def get_all_menu_item_ids(
|
||||||
|
self,
|
||||||
|
frontend_type: FrontendType,
|
||||||
|
) -> set[str]:
|
||||||
|
"""
|
||||||
|
Get set of all menu item IDs for a frontend type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frontend_type: Frontend type to get item IDs for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Set of all menu item IDs from all modules
|
||||||
|
"""
|
||||||
|
from app.modules.registry import MODULES
|
||||||
|
|
||||||
|
item_ids = set()
|
||||||
|
|
||||||
|
for module_def in MODULES.values():
|
||||||
|
for section in module_def.menus.get(frontend_type, []):
|
||||||
|
for item in section.items:
|
||||||
|
item_ids.add(item.id)
|
||||||
|
|
||||||
|
return item_ids
|
||||||
|
|
||||||
|
def get_menu_item(
|
||||||
|
self,
|
||||||
|
frontend_type: FrontendType,
|
||||||
|
menu_item_id: str,
|
||||||
|
) -> DiscoveredMenuItem | None:
|
||||||
|
"""
|
||||||
|
Get a specific menu item by ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frontend_type: Frontend type to search in
|
||||||
|
menu_item_id: Menu item ID to look up
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DiscoveredMenuItem if found, None otherwise
|
||||||
|
"""
|
||||||
|
from app.modules.registry import MODULES
|
||||||
|
|
||||||
|
for module_code, module_def in MODULES.items():
|
||||||
|
for section in module_def.menus.get(frontend_type, []):
|
||||||
|
for item in section.items:
|
||||||
|
if item.id == menu_item_id:
|
||||||
|
return DiscoveredMenuItem(
|
||||||
|
id=item.id,
|
||||||
|
label_key=item.label_key,
|
||||||
|
icon=item.icon,
|
||||||
|
route=item.route,
|
||||||
|
order=item.order,
|
||||||
|
is_mandatory=item.is_mandatory,
|
||||||
|
requires_permission=item.requires_permission,
|
||||||
|
badge_source=item.badge_source,
|
||||||
|
is_super_admin_only=item.is_super_admin_only,
|
||||||
|
module_code=module_code,
|
||||||
|
section_id=section.id,
|
||||||
|
section_label_key=section.label_key,
|
||||||
|
section_order=section.order,
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_super_admin_only_item(
|
||||||
|
self,
|
||||||
|
menu_item_id: str,
|
||||||
|
frontend_type: FrontendType = FrontendType.ADMIN,
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a menu item is super-admin only.
|
||||||
|
|
||||||
|
An item is super-admin only if either:
|
||||||
|
- The item itself has is_super_admin_only=True
|
||||||
|
- The section containing the item has is_super_admin_only=True
|
||||||
|
|
||||||
|
Args:
|
||||||
|
menu_item_id: Menu item ID to check
|
||||||
|
frontend_type: Frontend type to search in
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the item is super-admin only
|
||||||
|
"""
|
||||||
|
from app.modules.registry import MODULES
|
||||||
|
|
||||||
|
for module_def in MODULES.values():
|
||||||
|
for section in module_def.menus.get(frontend_type, []):
|
||||||
|
# Check if section is super-admin only
|
||||||
|
section_is_super_admin = section.is_super_admin_only
|
||||||
|
|
||||||
|
for item in section.items:
|
||||||
|
if item.id == menu_item_id:
|
||||||
|
return section_is_super_admin or item.is_super_admin_only
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def get_menu_item_module(
|
def get_menu_item_module(
|
||||||
self,
|
self,
|
||||||
menu_item_id: str,
|
menu_item_id: str,
|
||||||
|
|||||||
@@ -34,15 +34,8 @@ from dataclasses import dataclass
|
|||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.config.menu_registry import (
|
|
||||||
ADMIN_MENU_REGISTRY,
|
|
||||||
VENDOR_MENU_REGISTRY,
|
|
||||||
get_all_menu_item_ids,
|
|
||||||
get_menu_item,
|
|
||||||
is_super_admin_only_item,
|
|
||||||
)
|
|
||||||
from app.modules.service import module_service
|
from app.modules.service import module_service
|
||||||
from app.modules.core.models import AdminMenuConfig, MANDATORY_MENU_ITEMS
|
from app.modules.core.models import AdminMenuConfig
|
||||||
from app.modules.core.services.menu_discovery_service import menu_discovery_service
|
from app.modules.core.services.menu_discovery_service import menu_discovery_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
|
|
||||||
@@ -108,7 +101,7 @@ class MenuService:
|
|||||||
True if menu item is visible/accessible
|
True if menu item is visible/accessible
|
||||||
"""
|
"""
|
||||||
# Validate menu item exists in registry
|
# Validate menu item exists in registry
|
||||||
all_items = get_all_menu_item_ids(frontend_type)
|
all_items = menu_discovery_service.get_all_menu_item_ids(frontend_type)
|
||||||
if menu_item_id not in all_items:
|
if menu_item_id not in all_items:
|
||||||
logger.warning(f"Unknown menu item: {menu_item_id} for {frontend_type.value}")
|
logger.warning(f"Unknown menu item: {menu_item_id} for {frontend_type.value}")
|
||||||
return False
|
return False
|
||||||
@@ -121,7 +114,8 @@ class MenuService:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Mandatory items are always accessible (if module is enabled)
|
# Mandatory items are always accessible (if module is enabled)
|
||||||
if menu_item_id in MANDATORY_MENU_ITEMS.get(frontend_type, set()):
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(frontend_type)
|
||||||
|
if menu_item_id in mandatory_items:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# No scope specified - show all by default (fallback for unconfigured)
|
# No scope specified - show all by default (fallback for unconfigured)
|
||||||
@@ -161,8 +155,8 @@ class MenuService:
|
|||||||
Returns:
|
Returns:
|
||||||
Set of visible menu item IDs
|
Set of visible menu item IDs
|
||||||
"""
|
"""
|
||||||
all_items = get_all_menu_item_ids(frontend_type)
|
all_items = menu_discovery_service.get_all_menu_item_ids(frontend_type)
|
||||||
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(frontend_type)
|
||||||
|
|
||||||
# Filter by module enablement if platform is specified
|
# Filter by module enablement if platform is specified
|
||||||
if platform_id:
|
if platform_id:
|
||||||
@@ -294,60 +288,47 @@ class MenuService:
|
|||||||
Returns:
|
Returns:
|
||||||
List of MenuItemConfig with current visibility state and module info
|
List of MenuItemConfig with current visibility state and module info
|
||||||
"""
|
"""
|
||||||
from app.modules.registry import get_menu_item_module
|
|
||||||
|
|
||||||
registry = (
|
|
||||||
ADMIN_MENU_REGISTRY if frontend_type == FrontendType.ADMIN else VENDOR_MENU_REGISTRY
|
|
||||||
)
|
|
||||||
|
|
||||||
shown_items = self._get_shown_items(db, frontend_type, platform_id=platform_id)
|
shown_items = self._get_shown_items(db, frontend_type, platform_id=platform_id)
|
||||||
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(frontend_type)
|
||||||
|
|
||||||
# Get module-available items
|
# Get module-available items
|
||||||
|
all_item_ids = menu_discovery_service.get_all_menu_item_ids(frontend_type)
|
||||||
module_available_items = module_service.filter_menu_items_by_modules(
|
module_available_items = module_service.filter_menu_items_by_modules(
|
||||||
db, platform_id, get_all_menu_item_ids(frontend_type), frontend_type
|
db, platform_id, all_item_ids, frontend_type
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Get all menu items from discovery service
|
||||||
|
all_items = menu_discovery_service.get_all_menu_items(frontend_type)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for section in registry["sections"]:
|
for item in all_items:
|
||||||
section_id = section["id"]
|
# Check if module is enabled for this item
|
||||||
section_label = section.get("label")
|
is_module_enabled = item.id in module_available_items
|
||||||
is_super_admin_section = section.get("super_admin_only", False)
|
|
||||||
|
|
||||||
for item in section["items"]:
|
# If no config exists (shown_items is None), show all by default
|
||||||
item_id = item["id"]
|
# Otherwise, item is visible if in shown_items or mandatory
|
||||||
|
# Note: visibility config is independent of module enablement
|
||||||
|
is_visible = (
|
||||||
|
shown_items is None
|
||||||
|
or item.id in shown_items
|
||||||
|
or item.id in mandatory_items
|
||||||
|
)
|
||||||
|
|
||||||
# Check if module is enabled for this item
|
result.append(
|
||||||
is_module_enabled = item_id in module_available_items
|
MenuItemConfig(
|
||||||
module_code = get_menu_item_module(item_id, frontend_type)
|
id=item.id,
|
||||||
|
label=item.label_key,
|
||||||
# If no config exists (shown_items is None), show all by default
|
icon=item.icon,
|
||||||
# Otherwise, item is visible if in shown_items or mandatory
|
url=item.route,
|
||||||
# Note: visibility config is independent of module enablement
|
section_id=item.section_id,
|
||||||
is_visible = (
|
section_label=item.section_label_key,
|
||||||
shown_items is None
|
is_visible=is_visible,
|
||||||
or item_id in shown_items
|
is_mandatory=item.id in mandatory_items,
|
||||||
or item_id in mandatory_items
|
is_super_admin_only=item.is_super_admin_only,
|
||||||
)
|
is_module_enabled=is_module_enabled,
|
||||||
|
module_code=item.module_code,
|
||||||
# Item is super admin only if section or item is marked as such
|
|
||||||
is_item_super_admin_only = is_super_admin_section or item.get("super_admin_only", False)
|
|
||||||
|
|
||||||
result.append(
|
|
||||||
MenuItemConfig(
|
|
||||||
id=item_id,
|
|
||||||
label=item["label"],
|
|
||||||
icon=item["icon"],
|
|
||||||
url=item["url"],
|
|
||||||
section_id=section_id,
|
|
||||||
section_label=section_label,
|
|
||||||
is_visible=is_visible,
|
|
||||||
is_mandatory=item_id in mandatory_items,
|
|
||||||
is_super_admin_only=is_item_super_admin_only,
|
|
||||||
is_module_enabled=is_module_enabled,
|
|
||||||
module_code=module_code,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -369,47 +350,39 @@ class MenuService:
|
|||||||
Returns:
|
Returns:
|
||||||
List of MenuItemConfig with current visibility state
|
List of MenuItemConfig with current visibility state
|
||||||
"""
|
"""
|
||||||
from app.modules.registry import get_menu_item_module
|
|
||||||
|
|
||||||
shown_items = self._get_shown_items(
|
shown_items = self._get_shown_items(
|
||||||
db, FrontendType.ADMIN, user_id=user_id
|
db, FrontendType.ADMIN, user_id=user_id
|
||||||
)
|
)
|
||||||
mandatory_items = MANDATORY_MENU_ITEMS.get(FrontendType.ADMIN, set())
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(FrontendType.ADMIN)
|
||||||
|
|
||||||
|
# Get all menu items from discovery service
|
||||||
|
all_items = menu_discovery_service.get_all_menu_items(FrontendType.ADMIN)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for section in ADMIN_MENU_REGISTRY["sections"]:
|
for item in all_items:
|
||||||
section_id = section["id"]
|
# If no config exists (shown_items is None), show all by default
|
||||||
section_label = section.get("label")
|
# Otherwise, item is visible if in shown_items or mandatory
|
||||||
is_super_admin_section = section.get("super_admin_only", False)
|
is_visible = (
|
||||||
|
shown_items is None
|
||||||
|
or item.id in shown_items
|
||||||
|
or item.id in mandatory_items
|
||||||
|
)
|
||||||
|
|
||||||
for item in section["items"]:
|
result.append(
|
||||||
item_id = item["id"]
|
MenuItemConfig(
|
||||||
module_code = get_menu_item_module(item_id, FrontendType.ADMIN)
|
id=item.id,
|
||||||
|
label=item.label_key,
|
||||||
# If no config exists (shown_items is None), show all by default
|
icon=item.icon,
|
||||||
# Otherwise, item is visible if in shown_items or mandatory
|
url=item.route,
|
||||||
is_visible = (
|
section_id=item.section_id,
|
||||||
shown_items is None
|
section_label=item.section_label_key,
|
||||||
or item_id in shown_items
|
is_visible=is_visible,
|
||||||
or item_id in mandatory_items
|
is_mandatory=item.id in mandatory_items,
|
||||||
)
|
is_super_admin_only=item.is_super_admin_only,
|
||||||
# Item is super admin only if section or item is marked as such
|
is_module_enabled=True, # Super admins see all modules
|
||||||
is_item_super_admin_only = is_super_admin_section or item.get("super_admin_only", False)
|
module_code=item.module_code,
|
||||||
result.append(
|
|
||||||
MenuItemConfig(
|
|
||||||
id=item_id,
|
|
||||||
label=item["label"],
|
|
||||||
icon=item["icon"],
|
|
||||||
url=item["url"],
|
|
||||||
section_id=section_id,
|
|
||||||
section_label=section_label,
|
|
||||||
is_visible=is_visible,
|
|
||||||
is_mandatory=item_id in mandatory_items,
|
|
||||||
is_super_admin_only=is_item_super_admin_only,
|
|
||||||
is_module_enabled=True, # Super admins see all modules
|
|
||||||
module_code=module_code,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -443,12 +416,12 @@ class MenuService:
|
|||||||
ValueError: If user_id is provided for vendor frontend
|
ValueError: If user_id is provided for vendor frontend
|
||||||
"""
|
"""
|
||||||
# Validate menu item exists
|
# Validate menu item exists
|
||||||
all_items = get_all_menu_item_ids(frontend_type)
|
all_items = menu_discovery_service.get_all_menu_item_ids(frontend_type)
|
||||||
if menu_item_id not in all_items:
|
if menu_item_id not in all_items:
|
||||||
raise ValueError(f"Unknown menu item: {menu_item_id}")
|
raise ValueError(f"Unknown menu item: {menu_item_id}")
|
||||||
|
|
||||||
# Check if mandatory - mandatory items are always visible, no need to store
|
# Check if mandatory - mandatory items are always visible, no need to store
|
||||||
mandatory = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
mandatory = menu_discovery_service.get_mandatory_item_ids(frontend_type)
|
||||||
if menu_item_id in mandatory:
|
if menu_item_id in mandatory:
|
||||||
if not is_visible:
|
if not is_visible:
|
||||||
raise ValueError(f"Cannot hide mandatory menu item: {menu_item_id}")
|
raise ValueError(f"Cannot hide mandatory menu item: {menu_item_id}")
|
||||||
@@ -559,8 +532,8 @@ class MenuService:
|
|||||||
|
|
||||||
# Create records with is_visible=False for all non-mandatory items
|
# Create records with is_visible=False for all non-mandatory items
|
||||||
# This makes "reset" mean "hide everything except mandatory"
|
# This makes "reset" mean "hide everything except mandatory"
|
||||||
all_items = get_all_menu_item_ids(frontend_type)
|
all_items = menu_discovery_service.get_all_menu_item_ids(frontend_type)
|
||||||
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(frontend_type)
|
||||||
|
|
||||||
for item_id in all_items:
|
for item_id in all_items:
|
||||||
if item_id not in mandatory_items:
|
if item_id not in mandatory_items:
|
||||||
@@ -603,8 +576,8 @@ class MenuService:
|
|||||||
logger.info(f"Reset menu config for user {user_id}: deleted {deleted} rows")
|
logger.info(f"Reset menu config for user {user_id}: deleted {deleted} rows")
|
||||||
|
|
||||||
# Create records with is_visible=False for all non-mandatory items
|
# Create records with is_visible=False for all non-mandatory items
|
||||||
all_items = get_all_menu_item_ids(FrontendType.ADMIN)
|
all_items = menu_discovery_service.get_all_menu_item_ids(FrontendType.ADMIN)
|
||||||
mandatory_items = MANDATORY_MENU_ITEMS.get(FrontendType.ADMIN, set())
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(FrontendType.ADMIN)
|
||||||
|
|
||||||
for item_id in all_items:
|
for item_id in all_items:
|
||||||
if item_id not in mandatory_items:
|
if item_id not in mandatory_items:
|
||||||
@@ -650,8 +623,8 @@ class MenuService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Create records with is_visible=True for all non-mandatory items
|
# Create records with is_visible=True for all non-mandatory items
|
||||||
all_items = get_all_menu_item_ids(frontend_type)
|
all_items = menu_discovery_service.get_all_menu_item_ids(frontend_type)
|
||||||
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(frontend_type)
|
||||||
|
|
||||||
for item_id in all_items:
|
for item_id in all_items:
|
||||||
if item_id not in mandatory_items:
|
if item_id not in mandatory_items:
|
||||||
@@ -692,8 +665,8 @@ class MenuService:
|
|||||||
logger.info(f"Show all menu config for user {user_id}: deleted {deleted} rows")
|
logger.info(f"Show all menu config for user {user_id}: deleted {deleted} rows")
|
||||||
|
|
||||||
# Create records with is_visible=True for all non-mandatory items
|
# Create records with is_visible=True for all non-mandatory items
|
||||||
all_items = get_all_menu_item_ids(FrontendType.ADMIN)
|
all_items = menu_discovery_service.get_all_menu_item_ids(FrontendType.ADMIN)
|
||||||
mandatory_items = MANDATORY_MENU_ITEMS.get(FrontendType.ADMIN, set())
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(FrontendType.ADMIN)
|
||||||
|
|
||||||
for item_id in all_items:
|
for item_id in all_items:
|
||||||
if item_id not in mandatory_items:
|
if item_id not in mandatory_items:
|
||||||
@@ -762,8 +735,8 @@ class MenuService:
|
|||||||
logger.info(f"Cleaned up {deleted} old menu config records before initialization")
|
logger.info(f"Cleaned up {deleted} old menu config records before initialization")
|
||||||
|
|
||||||
# Get all menu items for this frontend
|
# Get all menu items for this frontend
|
||||||
all_items = get_all_menu_item_ids(frontend_type)
|
all_items = menu_discovery_service.get_all_menu_item_ids(frontend_type)
|
||||||
mandatory_items = MANDATORY_MENU_ITEMS.get(frontend_type, set())
|
mandatory_items = menu_discovery_service.get_mandatory_item_ids(frontend_type)
|
||||||
|
|
||||||
# Create visible records for all non-mandatory items
|
# Create visible records for all non-mandatory items
|
||||||
for item_id in all_items:
|
for item_id in all_items:
|
||||||
|
|||||||
Reference in New Issue
Block a user