# app/modules/cms/definition.py """ CMS module definition. Defines the CMS module including its features, menu items, route configurations, and self-contained component paths. This is a self-contained module with: - Services: app.modules.cms.services - Models: app.modules.cms.models - Exceptions: app.modules.cms.exceptions - Templates: app.modules.cms.templates (namespaced as cms/) """ import logging from typing import Any from app.modules.base import ( MenuItemDefinition, MenuSectionDefinition, ModuleDefinition, PermissionDefinition, ) from app.modules.enums import FrontendType logger = logging.getLogger(__name__) # ============================================================================= # Context Providers # ============================================================================= def _get_platform_context(request: Any, db: Any, platform: Any) -> dict[str, Any]: """ Provide CMS context for platform/marketing pages. Returns header and footer navigation pages for the marketing site. """ from app.modules.cms.services import content_page_service if not platform: return {"header_pages": [], "footer_pages": [], "legal_pages": []} platform_id = platform.id header_pages = [] footer_pages = [] legal_pages = [] try: header_pages = content_page_service.list_platform_pages( db, platform_id=platform_id, header_only=True, include_unpublished=False ) footer_pages = content_page_service.list_platform_pages( db, platform_id=platform_id, footer_only=True, include_unpublished=False ) legal_pages = content_page_service.list_platform_pages( db, platform_id=platform_id, legal_only=True, include_unpublished=False ) logger.debug( f"[CMS] Platform context: {len(header_pages)} header, {len(footer_pages)} footer, {len(legal_pages)} legal pages" ) except Exception as e: logger.warning(f"[CMS] Failed to load platform navigation pages: {e}") return { "header_pages": header_pages, "footer_pages": footer_pages, "legal_pages": legal_pages, } def _get_storefront_context(request: Any, db: Any, platform: Any) -> dict[str, Any]: """ Provide CMS context for storefront (customer shop) pages. Returns header and footer navigation pages for the store's shop. """ from app.modules.cms.services import content_page_service store = getattr(request.state, "store", None) if not platform: return {"header_pages": [], "footer_pages": [], "legal_pages": []} platform_id = platform.id header_pages = [] footer_pages = [] if store: try: header_pages = content_page_service.list_pages_for_store( db, platform_id=platform_id, store_id=store.id, header_only=True, include_unpublished=False, ) footer_pages = content_page_service.list_pages_for_store( db, platform_id=platform_id, store_id=store.id, footer_only=True, include_unpublished=False, ) logger.debug( f"[CMS] Storefront context for store {store.id}: " f"{len(header_pages)} header, {len(footer_pages)} footer pages" ) except Exception as e: logger.warning(f"[CMS] Failed to load storefront navigation pages: {e}") return { "header_pages": header_pages, "footer_pages": footer_pages, } # ============================================================================= # Router Lazy Imports # ============================================================================= def _get_admin_router(): """Lazy import of admin router to avoid circular imports.""" from app.modules.cms.routes.admin import router return router def _get_store_router(): """Lazy import of store router to avoid circular imports.""" from app.modules.cms.routes.api.store import router return router def _get_metrics_provider(): """Lazy import of metrics provider to avoid circular imports.""" from app.modules.cms.services.cms_metrics import cms_metrics_provider return cms_metrics_provider def _get_feature_provider(): """Lazy import of feature provider to avoid circular imports.""" from app.modules.cms.services.cms_features import cms_feature_provider return cms_feature_provider # CMS module definition - Self-contained module (pilot) cms_module = ModuleDefinition( code="cms", name="Content Management", description="Content pages, media library, and store themes.", version="1.0.0", features=[ "cms_basic", # Basic page editing "cms_custom_pages", # Custom page creation "cms_unlimited_pages", # No page limit "cms_templates", # Page templates "cms_seo", # SEO tools "media_library", # Media file management ], # Module-driven permissions permissions=[ PermissionDefinition( id="cms.view_pages", label_key="cms.permissions.view_pages", description_key="cms.permissions.view_pages_desc", category="cms", ), PermissionDefinition( id="cms.manage_pages", label_key="cms.permissions.manage_pages", description_key="cms.permissions.manage_pages_desc", category="cms", ), PermissionDefinition( id="cms.view_media", label_key="cms.permissions.view_media", description_key="cms.permissions.view_media_desc", category="cms", ), PermissionDefinition( id="cms.manage_media", label_key="cms.permissions.manage_media", description_key="cms.permissions.manage_media_desc", category="cms", ), PermissionDefinition( id="cms.manage_themes", label_key="cms.permissions.manage_themes", description_key="cms.permissions.manage_themes_desc", category="cms", ), ], menu_items={ FrontendType.ADMIN: [ "content-pages", # Platform content pages "store-themes", # Theme management ], FrontendType.STORE: [ "content-pages", # Store content pages "media", # Media library ], }, # New module-driven menu definitions menus={ FrontendType.ADMIN: [ MenuSectionDefinition( id="contentMgmt", label_key="cms.menu.content_management", icon="document-text", order=70, items=[ MenuItemDefinition( id="content-pages", label_key="cms.menu.content_pages", icon="document-text", route="/admin/content-pages", order=20, ), MenuItemDefinition( id="store-themes", label_key="cms.menu.store_themes", icon="color-swatch", route="/admin/store-themes", order=30, ), ], ), ], FrontendType.STORE: [ MenuSectionDefinition( id="shop", label_key="cms.menu.shop_content", icon="document-text", order=40, items=[ MenuItemDefinition( id="content-pages", label_key="cms.menu.content_pages", icon="document-text", route="/store/{store_code}/content-pages", order=10, requires_permission="cms.view_pages", ), MenuItemDefinition( id="media", label_key="cms.menu.media_library", icon="photograph", route="/store/{store_code}/media", order=20, requires_permission="cms.view_media", ), ], ), ], }, is_core=True, # CMS is a core module - content management is fundamental # Context providers for dynamic page context context_providers={ FrontendType.PLATFORM: _get_platform_context, FrontendType.STOREFRONT: _get_storefront_context, }, # Self-contained module configuration is_self_contained=True, services_path="app.modules.cms.services", models_path="app.modules.cms.models", exceptions_path="app.modules.cms.exceptions", migrations_path="migrations", # Module templates (namespaced as cms/admin/*.html and cms/store/*.html) templates_path="templates", # Module-specific translations (accessible via cms.* keys) locales_path="locales", # Metrics provider for dashboard statistics metrics_provider=_get_metrics_provider, feature_provider=_get_feature_provider, ) def get_cms_module_with_routers() -> ModuleDefinition: """ Get CMS module with routers attached. This function attaches the routers lazily to avoid circular imports during module initialization. """ cms_module.admin_router = _get_admin_router() cms_module.store_router = _get_store_router() return cms_module __all__ = ["cms_module", "get_cms_module_with_routers"]