From fe49008fefd526c123c5db7195135be59fdc7307 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sun, 18 Jan 2026 20:02:38 +0100 Subject: [PATCH] feat: integrate PlatformContextMiddleware and update routes for multi-platform Phase 2 implementation: - Register PlatformContextMiddleware in main.py (runs before VendorContextMiddleware) - Update VendorContextMiddleware to use platform_clean_path - Update platform homepage route to use three-tier CMS resolution - Update platform content page routes with platform context - Update vendor root path handlers with platform_id support Middleware execution order: 1. LoggingMiddleware 2. PlatformContextMiddleware (detect platform from domain/path) 3. VendorContextMiddleware (detect vendor, uses platform_clean_path) 4. ContextMiddleware 5. LanguageMiddleware 6. ThemeContextMiddleware Co-Authored-By: Claude Opus 4.5 --- main.py | 133 +++++++++++++++++++++++------------ middleware/vendor_context.py | 13 +++- 2 files changed, 100 insertions(+), 46 deletions(-) diff --git a/main.py b/main.py index 688f78bb..7633ce50 100644 --- a/main.py +++ b/main.py @@ -70,6 +70,7 @@ from middleware.logging import LoggingMiddleware from middleware.theme_context import ThemeContextMiddleware # Import REFACTORED class-based middleware +from middleware.platform_context import PlatformContextMiddleware from middleware.vendor_context import VendorContextMiddleware logger = logging.getLogger(__name__) @@ -114,17 +115,19 @@ app.add_middleware( # So we add them in REVERSE order of desired execution: # # Desired execution order: -# 1. VendorContextMiddleware (detect vendor, extract clean_path) -# 2. ContextMiddleware (detect context using clean_path) -# 3. LanguageMiddleware (detect language based on context) -# 4. ThemeContextMiddleware (load theme) -# 5. LoggingMiddleware (log all requests) +# 1. PlatformContextMiddleware (detect platform from domain/path) +# 2. VendorContextMiddleware (detect vendor, uses platform_clean_path) +# 3. ContextMiddleware (detect context using clean_path) +# 4. LanguageMiddleware (detect language based on context) +# 5. ThemeContextMiddleware (load theme) +# 6. LoggingMiddleware (log all requests) # # Therefore we add them in REVERSE: # - Add ThemeContextMiddleware FIRST (runs LAST in request) -# - Add LanguageMiddleware SECOND (runs after context) +# - Add LanguageMiddleware SECOND # - Add ContextMiddleware THIRD # - Add VendorContextMiddleware FOURTH +# - Add PlatformContextMiddleware FIFTH # - Add LoggingMiddleware LAST (runs FIRST for timing) # ============================================================================ @@ -148,19 +151,24 @@ app.add_middleware(LanguageMiddleware) logger.info("Adding ContextMiddleware (detects context type using clean_path)") app.add_middleware(ContextMiddleware) -# Add vendor context middleware (runs first in request chain) -logger.info("Adding VendorContextMiddleware (detects vendor, extracts clean_path)") +# Add vendor context middleware (runs after platform context) +logger.info("Adding VendorContextMiddleware (detects vendor, uses platform_clean_path)") app.add_middleware(VendorContextMiddleware) +# Add platform context middleware (runs first in request chain, before vendor) +logger.info("Adding PlatformContextMiddleware (detects platform from domain/path)") +app.add_middleware(PlatformContextMiddleware) + logger.info("=" * 80) logger.info("MIDDLEWARE ORDER SUMMARY:") logger.info(" Execution order (request →):") logger.info(" 1. LoggingMiddleware (timing)") -logger.info(" 2. VendorContextMiddleware (vendor detection)") -logger.info(" 3. ContextMiddleware (context detection)") -logger.info(" 4. LanguageMiddleware (language detection)") -logger.info(" 5. ThemeContextMiddleware (theme loading)") -logger.info(" 6. FastAPI Router") +logger.info(" 2. PlatformContextMiddleware (platform detection)") +logger.info(" 3. VendorContextMiddleware (vendor detection)") +logger.info(" 4. ContextMiddleware (context detection)") +logger.info(" 5. LanguageMiddleware (language detection)") +logger.info(" 6. ThemeContextMiddleware (theme loading)") +logger.info(" 7. FastAPI Router") logger.info("=" * 80) # ======================================== @@ -331,6 +339,7 @@ async def vendor_root_path( """Handle vendor root path (e.g., /vendors/wizamart/)""" # Vendor should already be in request.state from middleware vendor = getattr(request.state, "vendor", None) + platform = getattr(request.state, "platform", None) if not vendor: raise HTTPException(status_code=404, detail=f"Vendor '{vendor_code}' not found") @@ -338,14 +347,17 @@ async def vendor_root_path( from app.routes.shop_pages import get_shop_context from app.services.content_page_service import content_page_service - # Try to find landing page + # Get platform_id (use platform from context or default to 1 for OMS) + platform_id = platform.id if platform else 1 + + # Try to find landing page (with three-tier resolution) landing_page = content_page_service.get_page_for_vendor( - db, slug="landing", vendor_id=vendor.id, include_unpublished=False + db, platform_id=platform_id, slug="landing", vendor_id=vendor.id, include_unpublished=False ) if not landing_page: landing_page = content_page_service.get_page_for_vendor( - db, slug="home", vendor_id=vendor.id, include_unpublished=False + db, platform_id=platform_id, slug="home", vendor_id=vendor.id, include_unpublished=False ) if landing_page: @@ -373,29 +385,51 @@ async def platform_homepage(request: Request, db: Session = Depends(get_db)): """ Platform homepage at localhost:8000 or platform.com - Looks for CMS page with slug='platform_homepage' (vendor_id=NULL) + Uses multi-platform CMS with three-tier resolution: + 1. Platform marketing pages (is_platform_page=True) + 2. Vendor default pages (fallback) + 3. Vendor override pages + Falls back to default static template if not found. """ from app.services.content_page_service import content_page_service logger.debug("[PLATFORM] Homepage requested") - # Try to load platform homepage from CMS - homepage = content_page_service.get_page_for_vendor( - db, - slug="platform_homepage", - vendor_id=None, # Platform-level page - include_unpublished=False, - ) + # Get platform from middleware (multi-platform support) + platform = getattr(request.state, "platform", None) - # Load header and footer navigation - header_pages = content_page_service.list_pages_for_vendor( - db, vendor_id=None, header_only=True, include_unpublished=False - ) + if platform: + # Try to load platform homepage from CMS (platform marketing page) + homepage = content_page_service.get_platform_page( + db, + platform_id=platform.id, + slug="home", + include_unpublished=False, + ) - footer_pages = content_page_service.list_pages_for_vendor( - db, vendor_id=None, footer_only=True, include_unpublished=False - ) + # Also try platform_homepage slug for backwards compatibility + if not homepage: + homepage = content_page_service.get_platform_page( + db, + platform_id=platform.id, + slug="platform_homepage", + include_unpublished=False, + ) + + # Load header and footer navigation (platform marketing pages) + 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 + ) + else: + # Fallback for when no platform context (shouldn't happen normally) + homepage = None + header_pages = [] + footer_pages = [] # Get language from request state and build i18n context language = getattr(request.state, "language", "fr") @@ -438,7 +472,7 @@ async def platform_content_page( """ Platform content pages: /about, /faq, /terms, /contact, etc. - Loads content from CMS with slug (vendor_id=NULL for platform pages). + Uses multi-platform CMS with three-tier resolution. Returns 404 if page not found. This route MUST be defined LAST to avoid conflicts with other routes. @@ -447,25 +481,32 @@ async def platform_content_page( logger.debug(f"[PLATFORM] Content page requested: /{slug}") - # Load page from CMS - page = content_page_service.get_page_for_vendor( + # Get platform from middleware (multi-platform support) + platform = getattr(request.state, "platform", None) + + if not platform: + logger.warning(f"[PLATFORM] No platform context for content page: {slug}") + raise HTTPException(status_code=404, detail=f"Page not found: {slug}") + + # Load platform marketing page from CMS + page = content_page_service.get_platform_page( db, + platform_id=platform.id, slug=slug, - vendor_id=None, - include_unpublished=False, # Platform pages only + include_unpublished=False, ) if not page: logger.warning(f"[PLATFORM] Content page not found: {slug}") raise HTTPException(status_code=404, detail=f"Page not found: {slug}") - # Load header and footer navigation - header_pages = content_page_service.list_pages_for_vendor( - db, vendor_id=None, header_only=True, include_unpublished=False + # Load header and footer navigation (platform marketing pages) + 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_pages_for_vendor( - db, vendor_id=None, footer_only=True, include_unpublished=False + footer_pages = content_page_service.list_platform_pages( + db, platform_id=platform.id, footer_only=True, include_unpublished=False ) logger.info(f"[PLATFORM] Rendering content page: {page.title} (/{slug})") @@ -511,20 +552,24 @@ async def root(request: Request, db: Session = Depends(get_db)): - If no vendor (platform root): Redirect to documentation """ vendor = getattr(request.state, "vendor", None) + platform = getattr(request.state, "platform", None) if vendor: # Vendor context detected - serve landing page from app.services.content_page_service import content_page_service - # Try to find landing page (slug='landing' or 'home') + # Get platform_id (use platform from context or default to 1 for OMS) + platform_id = platform.id if platform else 1 + + # Try to find landing page (slug='landing' or 'home') with three-tier resolution landing_page = content_page_service.get_page_for_vendor( - db, slug="landing", vendor_id=vendor.id, include_unpublished=False + db, platform_id=platform_id, slug="landing", vendor_id=vendor.id, include_unpublished=False ) if not landing_page: # Try 'home' slug as fallback landing_page = content_page_service.get_page_for_vendor( - db, slug="home", vendor_id=vendor.id, include_unpublished=False + db, platform_id=platform_id, slug="home", vendor_id=vendor.id, include_unpublished=False ) if landing_page: diff --git a/middleware/vendor_context.py b/middleware/vendor_context.py index 0efd58a6..7aefb574 100644 --- a/middleware/vendor_context.py +++ b/middleware/vendor_context.py @@ -9,6 +9,9 @@ Handles three routing modes: 3. Path-based (/vendor/vendor1/ or /vendors/vendor1/ → Vendor 1) Also extracts clean_path for nested routing patterns. + +IMPORTANT: This middleware runs AFTER PlatformContextMiddleware. +Uses request.state.platform_clean_path when available (set by PlatformContextMiddleware). """ import logging @@ -39,10 +42,14 @@ class VendorContextManager: 2. Subdomain (vendor1.platform.com) 3. Path-based (/vendor/vendor1/ or /vendors/vendor1/) + Uses platform_clean_path from PlatformContextMiddleware when available. + This path has the platform prefix stripped (e.g., /oms/vendors/foo → /vendors/foo). + Returns dict with vendor info or None if not found. """ host = request.headers.get("host", "") - path = request.url.path + # Use platform_clean_path if available (set by PlatformContextMiddleware) + path = getattr(request.state, "platform_clean_path", None) or request.url.path # Remove port from host if present (e.g., localhost:8000 -> localhost) if ":" in host: @@ -393,7 +400,9 @@ class VendorContextMiddleware(BaseHTTPMiddleware): - More organized code - Standard ASGI pattern - Runs FIRST in middleware chain. + Runs AFTER PlatformContextMiddleware in the request chain. + Uses request.state.platform_clean_path for path-based vendor detection. + Sets: request.state.vendor: Vendor object request.state.vendor_context: Detection metadata