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 <noreply@anthropic.com>
This commit is contained in:
133
main.py
133
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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user