middleware fix for path-based vendor url

This commit is contained in:
2025-11-09 18:47:53 +01:00
parent 79dfcab09f
commit adbcee4ce3
13 changed files with 2078 additions and 810 deletions

124
main.py
View File

@@ -1,4 +1,14 @@
# main.py
"""
Wizamart FastAPI Application
Multi-tenant e-commerce marketplace platform with:
- Three deployment modes (subdomain, custom domain, path-based)
- Three interfaces (platform administration (admin), vendor dashboard (vendor), customer shop (shop))
- Comprehensive exception handling
- Middleware stack for context injection
"""
import sys
import io
@@ -28,9 +38,11 @@ from app.core.database import get_db
from app.core.lifespan import lifespan
from app.exceptions.handler import setup_exception_handlers
from app.exceptions import ServiceUnavailableException
from middleware.context_middleware import context_middleware
from middleware.theme_context import theme_context_middleware
from middleware.vendor_context import vendor_context_middleware
# Import REFACTORED class-based middleware
from middleware.vendor_context import VendorContextMiddleware
from middleware.context_middleware import ContextMiddleware
from middleware.theme_context import ThemeContextMiddleware
from middleware.logging_middleware import LoggingMiddleware
logger = logging.getLogger(__name__)
@@ -63,23 +75,66 @@ app.add_middleware(
allow_headers=["*"],
)
# Add vendor context middleware (must be after CORS)
app.middleware("http")(vendor_context_middleware)
# ============================================================================
# MIDDLEWARE REGISTRATION (CORRECTED ORDER!)
# ============================================================================
#
# IMPORTANT: Middleware execution order with BaseHTTPMiddleware:
#
# When using app.add_middleware or wrapping with BaseHTTPMiddleware,
# the LAST added middleware runs FIRST (LIFO - Last In, First Out).
#
# 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. ThemeContextMiddleware (load theme)
# 4. LoggingMiddleware (log all requests)
#
# Therefore we add them in REVERSE:
# - Add ThemeContextMiddleware FIRST (runs LAST in request)
# - Add ContextMiddleware SECOND
# - Add VendorContextMiddleware THIRD
# - Add LoggingMiddleware LAST (runs FIRST for timing)
# ============================================================================
# Add middleware (AFTER vendor_context_middleware)
app.middleware("http")(context_middleware)
logger.info("=" * 80)
logger.info("MIDDLEWARE REGISTRATION")
logger.info("=" * 80)
# Add theme context middleware (must be after vendor context)
app.middleware("http")(theme_context_middleware)
# Add logging middleware (logs all requests/responses)
# Add logging middleware (runs first for timing, logs all requests/responses)
logger.info("Adding LoggingMiddleware (runs first for request timing)")
app.add_middleware(LoggingMiddleware)
# Add theme context middleware (runs last in request chain)
logger.info("Adding ThemeContextMiddleware (detects and loads theme)")
app.add_middleware(ThemeContextMiddleware)
# Add context detection middleware (runs after vendor context extraction)
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)")
app.add_middleware(VendorContextMiddleware)
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. ThemeContextMiddleware (theme loading)")
logger.info(" 5. FastAPI Router")
logger.info("=" * 80)
# ========================================
# MOUNT STATIC FILES - Use absolute path
# ========================================
if STATIC_DIR.exists():
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
logger.info(f"Mounted static files from: {STATIC_DIR}")
else:
logger.warning(f"Static directory not found at {STATIC_DIR}")
# ========================================
@@ -87,7 +142,6 @@ else:
# Include API router (JSON endpoints at /api/*)
app.include_router(api_router, prefix="/api")
# ============================================================================
# FAVICON ROUTES (Must be registered BEFORE page routers)
# ============================================================================
@@ -97,14 +151,12 @@ def serve_favicon() -> Response:
Serve favicon with caching headers.
Checks multiple possible locations for the favicon.
"""
# Possible favicon locations (in priority order)
possible_paths = [
STATIC_DIR / "favicon.ico",
STATIC_DIR / "images" / "favicon.ico",
STATIC_DIR / "assets" / "favicon.ico",
]
# Find first existing favicon
for favicon_path in possible_paths:
if favicon_path.exists():
return FileResponse(
@@ -115,7 +167,6 @@ def serve_favicon() -> Response:
}
)
# No favicon found - return 204 No Content
return Response(status_code=204)
@@ -136,30 +187,60 @@ async def vendor_favicon():
# ============================================================================
# Include HTML page routes (these return rendered templates, not JSON)
logger.info("=" * 80)
logger.info("ROUTE REGISTRATION")
logger.info("=" * 80)
# Admin pages
logger.info("Registering admin page routes: /admin/*")
app.include_router(
admin_pages.router,
prefix="/admin",
tags=["admin-pages"],
include_in_schema=False # Don't show HTML pages in API docs
include_in_schema=False
)
# Vendor pages
# Vendor management pages (dashboard, products, orders, etc.)
logger.info("Registering vendor page routes: /vendor/{code}/*")
app.include_router(
vendor_pages.router,
prefix="/vendor",
tags=["vendor-pages"],
include_in_schema=False # Don't show HTML pages in API docs
include_in_schema=False
)
# Shop pages
# Customer shop pages - Register at TWO prefixes:
# 1. /shop/* (for subdomain/custom domain modes)
# 2. /vendors/{code}/shop/* (for path-based development mode)
logger.info("Registering shop page routes:")
logger.info(" - /shop/* (subdomain/custom domain mode)")
logger.info(" - /vendors/{code}/shop/* (path-based development mode)")
app.include_router(
shop_pages.router,
prefix="/shop",
tags=["shop-pages"],
include_in_schema=False # Don't show HTML pages in API docs
include_in_schema=False
)
app.include_router(
shop_pages.router,
prefix="/vendors/{vendor_code}/shop",
tags=["shop-pages"],
include_in_schema=False
)
logger.info("=" * 80)
# Log all registered routes
logger.info("=" * 80)
logger.info("REGISTERED ROUTES SUMMARY")
logger.info("=" * 80)
for route in app.routes:
if hasattr(route, 'methods') and hasattr(route, 'path'):
methods = ', '.join(route.methods) if route.methods else 'N/A'
logger.info(f" {methods:<10} {route.path:<60}")
logger.info("=" * 80)
# ============================================================================
# API ROUTES (JSON Responses)
@@ -201,7 +282,8 @@ def health_check(db: Session = Depends(get_db)):
],
"deployment_modes": [
"Subdomain-based (production): vendor.platform.com",
"Path-based (development): /vendor/vendorname/",
"Custom domain (production): customvendordomain.com",
"Path-based (development): /vendors/vendorname/ or /vendor/vendorname/",
],
"auth_required": "Most endpoints require Bearer token authentication",
}