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
|
from middleware.theme_context import ThemeContextMiddleware
|
||||||
|
|
||||||
# Import REFACTORED class-based middleware
|
# Import REFACTORED class-based middleware
|
||||||
|
from middleware.platform_context import PlatformContextMiddleware
|
||||||
from middleware.vendor_context import VendorContextMiddleware
|
from middleware.vendor_context import VendorContextMiddleware
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -114,17 +115,19 @@ app.add_middleware(
|
|||||||
# So we add them in REVERSE order of desired execution:
|
# So we add them in REVERSE order of desired execution:
|
||||||
#
|
#
|
||||||
# Desired execution order:
|
# Desired execution order:
|
||||||
# 1. VendorContextMiddleware (detect vendor, extract clean_path)
|
# 1. PlatformContextMiddleware (detect platform from domain/path)
|
||||||
# 2. ContextMiddleware (detect context using clean_path)
|
# 2. VendorContextMiddleware (detect vendor, uses platform_clean_path)
|
||||||
# 3. LanguageMiddleware (detect language based on context)
|
# 3. ContextMiddleware (detect context using clean_path)
|
||||||
# 4. ThemeContextMiddleware (load theme)
|
# 4. LanguageMiddleware (detect language based on context)
|
||||||
# 5. LoggingMiddleware (log all requests)
|
# 5. ThemeContextMiddleware (load theme)
|
||||||
|
# 6. LoggingMiddleware (log all requests)
|
||||||
#
|
#
|
||||||
# Therefore we add them in REVERSE:
|
# Therefore we add them in REVERSE:
|
||||||
# - Add ThemeContextMiddleware FIRST (runs LAST in request)
|
# - Add ThemeContextMiddleware FIRST (runs LAST in request)
|
||||||
# - Add LanguageMiddleware SECOND (runs after context)
|
# - Add LanguageMiddleware SECOND
|
||||||
# - Add ContextMiddleware THIRD
|
# - Add ContextMiddleware THIRD
|
||||||
# - Add VendorContextMiddleware FOURTH
|
# - Add VendorContextMiddleware FOURTH
|
||||||
|
# - Add PlatformContextMiddleware FIFTH
|
||||||
# - Add LoggingMiddleware LAST (runs FIRST for timing)
|
# - 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)")
|
logger.info("Adding ContextMiddleware (detects context type using clean_path)")
|
||||||
app.add_middleware(ContextMiddleware)
|
app.add_middleware(ContextMiddleware)
|
||||||
|
|
||||||
# Add vendor context middleware (runs first in request chain)
|
# Add vendor context middleware (runs after platform context)
|
||||||
logger.info("Adding VendorContextMiddleware (detects vendor, extracts clean_path)")
|
logger.info("Adding VendorContextMiddleware (detects vendor, uses platform_clean_path)")
|
||||||
app.add_middleware(VendorContextMiddleware)
|
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("=" * 80)
|
||||||
logger.info("MIDDLEWARE ORDER SUMMARY:")
|
logger.info("MIDDLEWARE ORDER SUMMARY:")
|
||||||
logger.info(" Execution order (request →):")
|
logger.info(" Execution order (request →):")
|
||||||
logger.info(" 1. LoggingMiddleware (timing)")
|
logger.info(" 1. LoggingMiddleware (timing)")
|
||||||
logger.info(" 2. VendorContextMiddleware (vendor detection)")
|
logger.info(" 2. PlatformContextMiddleware (platform detection)")
|
||||||
logger.info(" 3. ContextMiddleware (context detection)")
|
logger.info(" 3. VendorContextMiddleware (vendor detection)")
|
||||||
logger.info(" 4. LanguageMiddleware (language detection)")
|
logger.info(" 4. ContextMiddleware (context detection)")
|
||||||
logger.info(" 5. ThemeContextMiddleware (theme loading)")
|
logger.info(" 5. LanguageMiddleware (language detection)")
|
||||||
logger.info(" 6. FastAPI Router")
|
logger.info(" 6. ThemeContextMiddleware (theme loading)")
|
||||||
|
logger.info(" 7. FastAPI Router")
|
||||||
logger.info("=" * 80)
|
logger.info("=" * 80)
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
@@ -331,6 +339,7 @@ async def vendor_root_path(
|
|||||||
"""Handle vendor root path (e.g., /vendors/wizamart/)"""
|
"""Handle vendor root path (e.g., /vendors/wizamart/)"""
|
||||||
# Vendor should already be in request.state from middleware
|
# Vendor should already be in request.state from middleware
|
||||||
vendor = getattr(request.state, "vendor", None)
|
vendor = getattr(request.state, "vendor", None)
|
||||||
|
platform = getattr(request.state, "platform", None)
|
||||||
|
|
||||||
if not vendor:
|
if not vendor:
|
||||||
raise HTTPException(status_code=404, detail=f"Vendor '{vendor_code}' not found")
|
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.routes.shop_pages import get_shop_context
|
||||||
from app.services.content_page_service import content_page_service
|
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(
|
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:
|
if not landing_page:
|
||||||
landing_page = content_page_service.get_page_for_vendor(
|
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:
|
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
|
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.
|
Falls back to default static template if not found.
|
||||||
"""
|
"""
|
||||||
from app.services.content_page_service import content_page_service
|
from app.services.content_page_service import content_page_service
|
||||||
|
|
||||||
logger.debug("[PLATFORM] Homepage requested")
|
logger.debug("[PLATFORM] Homepage requested")
|
||||||
|
|
||||||
# Try to load platform homepage from CMS
|
# Get platform from middleware (multi-platform support)
|
||||||
homepage = content_page_service.get_page_for_vendor(
|
platform = getattr(request.state, "platform", None)
|
||||||
db,
|
|
||||||
slug="platform_homepage",
|
|
||||||
vendor_id=None, # Platform-level page
|
|
||||||
include_unpublished=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Load header and footer navigation
|
if platform:
|
||||||
header_pages = content_page_service.list_pages_for_vendor(
|
# Try to load platform homepage from CMS (platform marketing page)
|
||||||
db, vendor_id=None, header_only=True, include_unpublished=False
|
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(
|
# Also try platform_homepage slug for backwards compatibility
|
||||||
db, vendor_id=None, footer_only=True, include_unpublished=False
|
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
|
# Get language from request state and build i18n context
|
||||||
language = getattr(request.state, "language", "fr")
|
language = getattr(request.state, "language", "fr")
|
||||||
@@ -438,7 +472,7 @@ async def platform_content_page(
|
|||||||
"""
|
"""
|
||||||
Platform content pages: /about, /faq, /terms, /contact, etc.
|
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.
|
Returns 404 if page not found.
|
||||||
|
|
||||||
This route MUST be defined LAST to avoid conflicts with other routes.
|
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}")
|
logger.debug(f"[PLATFORM] Content page requested: /{slug}")
|
||||||
|
|
||||||
# Load page from CMS
|
# Get platform from middleware (multi-platform support)
|
||||||
page = content_page_service.get_page_for_vendor(
|
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,
|
db,
|
||||||
|
platform_id=platform.id,
|
||||||
slug=slug,
|
slug=slug,
|
||||||
vendor_id=None,
|
include_unpublished=False,
|
||||||
include_unpublished=False, # Platform pages only
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not page:
|
if not page:
|
||||||
logger.warning(f"[PLATFORM] Content page not found: {slug}")
|
logger.warning(f"[PLATFORM] Content page not found: {slug}")
|
||||||
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
||||||
|
|
||||||
# Load header and footer navigation
|
# Load header and footer navigation (platform marketing pages)
|
||||||
header_pages = content_page_service.list_pages_for_vendor(
|
header_pages = content_page_service.list_platform_pages(
|
||||||
db, vendor_id=None, header_only=True, include_unpublished=False
|
db, platform_id=platform.id, header_only=True, include_unpublished=False
|
||||||
)
|
)
|
||||||
|
|
||||||
footer_pages = content_page_service.list_pages_for_vendor(
|
footer_pages = content_page_service.list_platform_pages(
|
||||||
db, vendor_id=None, footer_only=True, include_unpublished=False
|
db, platform_id=platform.id, footer_only=True, include_unpublished=False
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"[PLATFORM] Rendering content page: {page.title} (/{slug})")
|
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
|
- If no vendor (platform root): Redirect to documentation
|
||||||
"""
|
"""
|
||||||
vendor = getattr(request.state, "vendor", None)
|
vendor = getattr(request.state, "vendor", None)
|
||||||
|
platform = getattr(request.state, "platform", None)
|
||||||
|
|
||||||
if vendor:
|
if vendor:
|
||||||
# Vendor context detected - serve landing page
|
# Vendor context detected - serve landing page
|
||||||
from app.services.content_page_service import content_page_service
|
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(
|
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:
|
if not landing_page:
|
||||||
# Try 'home' slug as fallback
|
# Try 'home' slug as fallback
|
||||||
landing_page = content_page_service.get_page_for_vendor(
|
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:
|
if landing_page:
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ Handles three routing modes:
|
|||||||
3. Path-based (/vendor/vendor1/ or /vendors/vendor1/ → Vendor 1)
|
3. Path-based (/vendor/vendor1/ or /vendors/vendor1/ → Vendor 1)
|
||||||
|
|
||||||
Also extracts clean_path for nested routing patterns.
|
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
|
import logging
|
||||||
@@ -39,10 +42,14 @@ class VendorContextManager:
|
|||||||
2. Subdomain (vendor1.platform.com)
|
2. Subdomain (vendor1.platform.com)
|
||||||
3. Path-based (/vendor/vendor1/ or /vendors/vendor1/)
|
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.
|
Returns dict with vendor info or None if not found.
|
||||||
"""
|
"""
|
||||||
host = request.headers.get("host", "")
|
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)
|
# Remove port from host if present (e.g., localhost:8000 -> localhost)
|
||||||
if ":" in host:
|
if ":" in host:
|
||||||
@@ -393,7 +400,9 @@ class VendorContextMiddleware(BaseHTTPMiddleware):
|
|||||||
- More organized code
|
- More organized code
|
||||||
- Standard ASGI pattern
|
- 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:
|
Sets:
|
||||||
request.state.vendor: Vendor object
|
request.state.vendor: Vendor object
|
||||||
request.state.vendor_context: Detection metadata
|
request.state.vendor_context: Detection metadata
|
||||||
|
|||||||
Reference in New Issue
Block a user