middleware fix for path-based vendor url
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
# middleware/context_middleware.py
|
||||
"""
|
||||
Context Detection Middleware
|
||||
Context Detection Middleware (Class-Based)
|
||||
|
||||
Detects the request context type (API, Admin, Vendor Dashboard, Shop, or Fallback)
|
||||
and injects it into request.state for use by error handlers and other components.
|
||||
|
||||
This middleware runs independently and complements vendor_context_middleware.
|
||||
MUST run AFTER vendor_context_middleware to have access to clean_path.
|
||||
MUST run BEFORE theme_context_middleware (which needs context_type).
|
||||
|
||||
Class-based middleware provides:
|
||||
- Better state management
|
||||
- Easier testing
|
||||
- More organized code
|
||||
- Standard ASGI pattern
|
||||
"""
|
||||
|
||||
import logging
|
||||
from enum import Enum
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from fastapi import Request
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -38,42 +47,68 @@ class ContextManager:
|
||||
4. Shop → Vendor storefront (custom domain, subdomain, or shop paths)
|
||||
5. Fallback → Unknown/generic context
|
||||
|
||||
CRITICAL: Uses clean_path (if available) instead of original path.
|
||||
This ensures correct context detection for path-based routing.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
|
||||
Returns:
|
||||
RequestContext enum value
|
||||
"""
|
||||
path = request.url.path
|
||||
# Use clean_path if available (extracted by vendor_context_middleware)
|
||||
# Falls back to original path if clean_path not set
|
||||
# This is critical for correct context detection with path-based routing
|
||||
path = getattr(request.state, 'clean_path', request.url.path)
|
||||
|
||||
host = request.headers.get("host", "")
|
||||
|
||||
# Remove port from host if present
|
||||
if ":" in host:
|
||||
host = host.split(":")[0]
|
||||
|
||||
logger.debug(
|
||||
f"[CONTEXT] Detecting context",
|
||||
extra={
|
||||
"original_path": request.url.path,
|
||||
"clean_path": getattr(request.state, 'clean_path', 'NOT SET'),
|
||||
"path_to_check": path,
|
||||
"host": host,
|
||||
}
|
||||
)
|
||||
|
||||
# 1. API context (highest priority)
|
||||
if path.startswith("/api/"):
|
||||
logger.debug("[CONTEXT] Detected as API", extra={"path": path})
|
||||
return RequestContext.API
|
||||
|
||||
# 2. Admin context
|
||||
if ContextManager._is_admin_context(request, host, path):
|
||||
logger.debug("[CONTEXT] Detected as ADMIN", extra={"path": path, "host": host})
|
||||
return RequestContext.ADMIN
|
||||
|
||||
# 3. Vendor Dashboard context (vendor management area)
|
||||
if ContextManager._is_vendor_dashboard_context(path):
|
||||
logger.debug("[CONTEXT] Detected as VENDOR_DASHBOARD", extra={"path": path})
|
||||
return RequestContext.VENDOR_DASHBOARD
|
||||
|
||||
# 4. Shop context (vendor storefront)
|
||||
# Check if vendor context exists (set by vendor_context_middleware)
|
||||
if hasattr(request.state, 'vendor') and request.state.vendor:
|
||||
# If we have a vendor and it's not admin or vendor dashboard, it's shop
|
||||
logger.debug(
|
||||
"[CONTEXT] Detected as SHOP (has vendor context)",
|
||||
extra={"vendor": request.state.vendor.name}
|
||||
)
|
||||
return RequestContext.SHOP
|
||||
|
||||
# Also check shop-specific paths
|
||||
if path.startswith("/shop/"):
|
||||
logger.debug("[CONTEXT] Detected as SHOP (from path)", extra={"path": path})
|
||||
return RequestContext.SHOP
|
||||
|
||||
# 5. Fallback for unknown contexts
|
||||
logger.debug("[CONTEXT] Detected as FALLBACK", extra={"path": path})
|
||||
return RequestContext.FALLBACK
|
||||
|
||||
@staticmethod
|
||||
@@ -92,43 +127,59 @@ class ContextManager:
|
||||
@staticmethod
|
||||
def _is_vendor_dashboard_context(path: str) -> bool:
|
||||
"""Check if request is in vendor dashboard context."""
|
||||
# Vendor dashboard paths (/vendor/*)
|
||||
# Vendor dashboard paths (/vendor/{code}/*)
|
||||
# Note: This is the vendor management area, not the shop
|
||||
if path.startswith("/vendor/"):
|
||||
# Important: /vendors/{code}/shop/* should NOT match this
|
||||
if path.startswith("/vendor/") and not path.startswith("/vendors/"):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
async def context_middleware(request: Request, call_next):
|
||||
class ContextMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
Middleware to detect and inject request context into request.state.
|
||||
|
||||
This should run AFTER vendor_context_middleware to have access to
|
||||
vendor information if available.
|
||||
Class-based middleware provides:
|
||||
- Better lifecycle management
|
||||
- Easier to test and extend
|
||||
- Standard ASGI pattern
|
||||
- Clear separation of concerns
|
||||
|
||||
Injects:
|
||||
Runs SECOND in middleware chain (after vendor_context_middleware).
|
||||
Depends on:
|
||||
request.state.clean_path (set by vendor_context_middleware)
|
||||
request.state.vendor (set by vendor_context_middleware)
|
||||
|
||||
Sets:
|
||||
request.state.context_type: RequestContext enum value
|
||||
"""
|
||||
# Detect context
|
||||
context_type = ContextManager.detect_context(request)
|
||||
|
||||
# Inject into request state
|
||||
request.state.context_type = context_type
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
"""
|
||||
Detect context and inject into request state.
|
||||
"""
|
||||
# Detect context
|
||||
context_type = ContextManager.detect_context(request)
|
||||
|
||||
# Log context detection (debug level)
|
||||
logger.debug(
|
||||
f"[CONTEXT] Request context detected: {context_type.value}",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"host": request.headers.get("host", ""),
|
||||
"context": context_type.value,
|
||||
}
|
||||
)
|
||||
# Inject into request state
|
||||
request.state.context_type = context_type
|
||||
|
||||
# Continue processing
|
||||
response = await call_next(request)
|
||||
return response
|
||||
# Log context detection with full details
|
||||
logger.debug(
|
||||
f"[CONTEXT_MIDDLEWARE] Context detected: {context_type.value}",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"clean_path": getattr(request.state, 'clean_path', 'NOT SET'),
|
||||
"host": request.headers.get("host", ""),
|
||||
"context": context_type.value,
|
||||
"has_vendor": hasattr(request.state, 'vendor') and request.state.vendor is not None,
|
||||
}
|
||||
)
|
||||
|
||||
# Continue processing
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
|
||||
def get_request_context(request: Request) -> RequestContext:
|
||||
|
||||
63
middleware/path_rewrite_middleware.py
Normal file
63
middleware/path_rewrite_middleware.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# middleware/path_rewrite_middleware.py
|
||||
"""
|
||||
Path Rewrite Middleware
|
||||
|
||||
Rewrites request paths for path-based vendor routing.
|
||||
This allows /vendor/VENDORCODE/shop/products to be routed as /shop/products
|
||||
|
||||
MUST run AFTER vendor_context_middleware and BEFORE context_middleware.
|
||||
"""
|
||||
import logging
|
||||
from fastapi import Request
|
||||
from starlette.datastructures import URL
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def path_rewrite_middleware(request: Request, call_next):
|
||||
"""
|
||||
Middleware to rewrite request paths for vendor context.
|
||||
|
||||
If vendor_context_middleware set request.state.clean_path, this middleware
|
||||
will rewrite the request path to use the clean path instead.
|
||||
|
||||
This allows FastAPI route matching to work correctly with path-based routing.
|
||||
|
||||
Example:
|
||||
Original: /vendor/WIZAMART/shop/products
|
||||
Clean path: /shop/products
|
||||
After rewrite: Request is routed as if path was /shop/products
|
||||
|
||||
MUST run after vendor_context_middleware (which sets clean_path)
|
||||
MUST run before context_middleware (which needs to see the clean path)
|
||||
"""
|
||||
|
||||
# Check if vendor_context_middleware set a clean_path
|
||||
if hasattr(request.state, 'clean_path'):
|
||||
clean_path = request.state.clean_path
|
||||
original_path = request.url.path
|
||||
|
||||
# Only rewrite if clean_path is different from original path
|
||||
if clean_path != original_path:
|
||||
logger.debug(
|
||||
f"[PATH_REWRITE] Rewriting path",
|
||||
extra={
|
||||
"original_path": original_path,
|
||||
"clean_path": clean_path,
|
||||
"vendor": getattr(request.state, 'vendor', 'NOT SET'),
|
||||
}
|
||||
)
|
||||
|
||||
# Rewrite the path by modifying the request's scope
|
||||
# This affects how FastAPI's router will see the path
|
||||
request.scope['path'] = clean_path
|
||||
|
||||
# Also update request._url to reflect the change
|
||||
# This ensures request.url.path returns the rewritten path
|
||||
old_url = request.url
|
||||
new_url = old_url.replace(path=clean_path)
|
||||
request._url = new_url
|
||||
|
||||
# Continue to next middleware/handler
|
||||
response = await call_next(request)
|
||||
return response
|
||||
@@ -1,9 +1,17 @@
|
||||
# middleware/theme_context.py
|
||||
"""
|
||||
Theme Context Middleware
|
||||
Injects vendor-specific theme into request context
|
||||
Theme Context Middleware (Class-Based)
|
||||
|
||||
Injects vendor-specific theme into request context.
|
||||
|
||||
Class-based middleware provides:
|
||||
- Better state management
|
||||
- Easier testing
|
||||
- Standard ASGI pattern
|
||||
"""
|
||||
|
||||
import logging
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from fastapi import Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -31,7 +39,7 @@ class ThemeContextManager:
|
||||
return theme.to_dict()
|
||||
|
||||
# Return default theme
|
||||
return get_default_theme()
|
||||
return ThemeContextManager.get_default_theme()
|
||||
|
||||
@staticmethod
|
||||
def get_default_theme() -> dict:
|
||||
@@ -76,40 +84,68 @@ class ThemeContextManager:
|
||||
}
|
||||
|
||||
|
||||
async def theme_context_middleware(request: Request, call_next):
|
||||
class ThemeContextMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
Middleware to inject theme context into request state.
|
||||
|
||||
This runs AFTER vendor_context_middleware has set request.state.vendor
|
||||
Class-based middleware provides:
|
||||
- Better state management
|
||||
- Easier testing
|
||||
- Standard ASGI pattern
|
||||
|
||||
Runs LAST in middleware chain (after vendor_context_middleware and context_middleware).
|
||||
Depends on:
|
||||
request.state.vendor (set by vendor_context_middleware)
|
||||
|
||||
Sets:
|
||||
request.state.theme: Theme dictionary
|
||||
"""
|
||||
# Only inject theme for shop pages (not admin or API)
|
||||
if hasattr(request.state, 'vendor') and request.state.vendor:
|
||||
vendor = request.state.vendor
|
||||
|
||||
# Get database session
|
||||
db_gen = get_db()
|
||||
db = next(db_gen)
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
"""
|
||||
Load and inject theme context.
|
||||
"""
|
||||
# Only inject theme for shop pages (not admin or API)
|
||||
if hasattr(request.state, 'vendor') and request.state.vendor:
|
||||
vendor = request.state.vendor
|
||||
|
||||
try:
|
||||
# Get vendor theme
|
||||
theme = ThemeContextManager.get_vendor_theme(db, vendor.id)
|
||||
request.state.theme = theme
|
||||
# Get database session
|
||||
db_gen = get_db()
|
||||
db = next(db_gen)
|
||||
|
||||
logger.debug(
|
||||
f"Theme loaded for vendor {vendor.name}: {theme['theme_name']}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load theme for vendor {vendor.id}: {e}")
|
||||
# Fallback to default theme
|
||||
try:
|
||||
# Get vendor theme
|
||||
theme = ThemeContextManager.get_vendor_theme(db, vendor.id)
|
||||
request.state.theme = theme
|
||||
|
||||
logger.debug(
|
||||
f"[THEME] Theme loaded for vendor",
|
||||
extra={
|
||||
"vendor_id": vendor.id,
|
||||
"vendor_name": vendor.name,
|
||||
"theme_name": theme.get('theme_name', 'default'),
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[THEME] Failed to load theme for vendor {vendor.id}: {e}",
|
||||
exc_info=True
|
||||
)
|
||||
# Fallback to default theme
|
||||
request.state.theme = ThemeContextManager.get_default_theme()
|
||||
finally:
|
||||
db.close()
|
||||
else:
|
||||
# No vendor context, use default theme
|
||||
request.state.theme = ThemeContextManager.get_default_theme()
|
||||
finally:
|
||||
db.close()
|
||||
else:
|
||||
# No vendor context, use default theme
|
||||
request.state.theme = ThemeContextManager.get_default_theme()
|
||||
logger.debug(
|
||||
"[THEME] No vendor context, using default theme",
|
||||
extra={"has_vendor": False}
|
||||
)
|
||||
|
||||
response = await call_next(request)
|
||||
return response
|
||||
# Continue processing
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
|
||||
def get_current_theme(request: Request) -> dict:
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
# middleware/vendor_context.py
|
||||
"""
|
||||
Vendor Context Middleware (Class-Based)
|
||||
|
||||
Detects vendor from host/domain/path and injects into request.state.
|
||||
Handles three routing modes:
|
||||
1. Custom domains (customdomain1.com → Vendor 1)
|
||||
2. Subdomains (vendor1.platform.com → Vendor 1)
|
||||
3. Path-based (/vendor/vendor1/ or /vendors/vendor1/ → Vendor 1)
|
||||
|
||||
Also extracts clean_path for nested routing patterns.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
from fastapi import Request
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy import func
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from fastapi import Request
|
||||
|
||||
from app.core.database import get_db
|
||||
from models.database.vendor import Vendor
|
||||
@@ -23,7 +36,7 @@ class VendorContextManager:
|
||||
Priority order:
|
||||
1. Custom domain (customdomain1.com)
|
||||
2. Subdomain (vendor1.platform.com)
|
||||
3. Path-based (/vendor/vendor1/)
|
||||
3. Path-based (/vendor/vendor1/ or /vendors/vendor1/)
|
||||
|
||||
Returns dict with vendor info or None if not found.
|
||||
"""
|
||||
@@ -48,7 +61,6 @@ class VendorContextManager:
|
||||
)
|
||||
|
||||
if is_custom_domain:
|
||||
# This could be a custom domain like customdomain1.com
|
||||
normalized_domain = VendorDomain.normalize_domain(host)
|
||||
return {
|
||||
"domain": normalized_domain,
|
||||
@@ -69,15 +81,23 @@ class VendorContextManager:
|
||||
"host": host
|
||||
}
|
||||
|
||||
# Method 3: Path-based detection (/vendor/vendorname/) - for development
|
||||
if path.startswith("/vendor/"):
|
||||
path_parts = path.split("/")
|
||||
if len(path_parts) >= 3:
|
||||
subdomain = path_parts[2]
|
||||
# Method 3: Path-based detection (/vendor/vendorname/ or /vendors/vendorname/)
|
||||
# Support BOTH patterns for flexibility
|
||||
if path.startswith("/vendor/") or path.startswith("/vendors/"):
|
||||
# Determine which pattern
|
||||
if path.startswith("/vendors/"):
|
||||
prefix_len = len("/vendors/")
|
||||
else:
|
||||
prefix_len = len("/vendor/")
|
||||
|
||||
path_parts = path[prefix_len:].split("/")
|
||||
if len(path_parts) >= 1 and path_parts[0]:
|
||||
vendor_code = path_parts[0]
|
||||
return {
|
||||
"subdomain": subdomain,
|
||||
"subdomain": vendor_code,
|
||||
"detection_method": "path",
|
||||
"path_prefix": f"/vendor/{subdomain}",
|
||||
"path_prefix": path[:prefix_len + len(vendor_code)],
|
||||
"full_prefix": path[:prefix_len], # /vendor/ or /vendors/
|
||||
"host": host
|
||||
}
|
||||
|
||||
@@ -102,7 +122,6 @@ class VendorContextManager:
|
||||
if context.get("detection_method") == "custom_domain":
|
||||
domain = context.get("domain")
|
||||
if domain:
|
||||
# Look up vendor by custom domain
|
||||
vendor_domain = (
|
||||
db.query(VendorDomain)
|
||||
.filter(VendorDomain.domain == domain)
|
||||
@@ -113,12 +132,11 @@ class VendorContextManager:
|
||||
|
||||
if vendor_domain:
|
||||
vendor = vendor_domain.vendor
|
||||
# Check if vendor is active
|
||||
if not vendor or not vendor.is_active:
|
||||
logger.warning(f"Vendor for domain {domain} is not active")
|
||||
return None
|
||||
|
||||
logger.info(f"[OK] Vendor found via custom domain: {domain} -> {vendor.name}")
|
||||
logger.info(f"[OK] Vendor found via custom domain: {domain} → {vendor.name}")
|
||||
return vendor
|
||||
else:
|
||||
logger.warning(f"No active vendor found for custom domain: {domain}")
|
||||
@@ -127,7 +145,6 @@ class VendorContextManager:
|
||||
# Method 2 & 3: Subdomain or path-based lookup
|
||||
if "subdomain" in context:
|
||||
subdomain = context["subdomain"]
|
||||
# Query vendor by subdomain (case-insensitive)
|
||||
vendor = (
|
||||
db.query(Vendor)
|
||||
.filter(func.lower(Vendor.subdomain) == subdomain.lower())
|
||||
@@ -137,7 +154,7 @@ class VendorContextManager:
|
||||
|
||||
if vendor:
|
||||
method = context.get("detection_method", "unknown")
|
||||
logger.info(f"[OK] Vendor found via {method}: {subdomain} -> {vendor.name}")
|
||||
logger.info(f"[OK] Vendor found via {method}: {subdomain} → {vendor.name}")
|
||||
else:
|
||||
logger.warning(f"No active vendor found for subdomain: {subdomain}")
|
||||
|
||||
@@ -145,14 +162,19 @@ class VendorContextManager:
|
||||
|
||||
@staticmethod
|
||||
def extract_clean_path(request: Request, vendor_context: Optional[dict]) -> str:
|
||||
"""Extract clean path without vendor prefix for routing."""
|
||||
"""
|
||||
Extract clean path without vendor prefix for routing.
|
||||
|
||||
Supports both /vendor/ and /vendors/ prefixes.
|
||||
"""
|
||||
if not vendor_context:
|
||||
return request.url.path
|
||||
|
||||
# Only strip path prefix for path-based detection
|
||||
if vendor_context.get("detection_method") == "path":
|
||||
path_prefix = vendor_context.get("path_prefix", "")
|
||||
path = request.url.path
|
||||
path_prefix = vendor_context.get("path_prefix", "")
|
||||
|
||||
if path.startswith(path_prefix):
|
||||
clean_path = path[len(path_prefix):]
|
||||
return clean_path if clean_path else "/"
|
||||
@@ -165,15 +187,12 @@ class VendorContextManager:
|
||||
host = request.headers.get("host", "")
|
||||
path = request.url.path
|
||||
|
||||
# Remove port from host
|
||||
if ":" in host:
|
||||
host = host.split(":")[0]
|
||||
|
||||
# Check for admin subdomain
|
||||
if host.startswith("admin."):
|
||||
return True
|
||||
|
||||
# Check for admin path
|
||||
if path.startswith("/admin"):
|
||||
return True
|
||||
|
||||
@@ -189,82 +208,118 @@ class VendorContextManager:
|
||||
"""Check if request is for static files."""
|
||||
path = request.url.path.lower()
|
||||
|
||||
# Static file extensions
|
||||
static_extensions = (
|
||||
'.ico', '.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.svg',
|
||||
'.woff', '.woff2', '.ttf', '.eot', '.webp', '.map', '.json',
|
||||
'.xml', '.txt', '.pdf', '.webmanifest'
|
||||
)
|
||||
|
||||
# Static paths
|
||||
static_paths = ('/static/', '/media/', '/assets/', '/.well-known/')
|
||||
|
||||
# Check if it's a static file by extension
|
||||
if path.endswith(static_extensions):
|
||||
return True
|
||||
|
||||
# Check if it's in a static directory
|
||||
if any(path.startswith(static_path) for static_path in static_paths):
|
||||
return True
|
||||
|
||||
# Special case: favicon.ico at any level
|
||||
if 'favicon.ico' in path:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
async def vendor_context_middleware(request: Request, call_next):
|
||||
class VendorContextMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
Middleware to inject vendor context into request state.
|
||||
|
||||
Handles three routing modes:
|
||||
1. Custom domains (customdomain1.com -> Vendor 1)
|
||||
2. Subdomains (vendor1.platform.com -> Vendor 1)
|
||||
3. Path-based (/vendor/vendor1/ -> Vendor 1)
|
||||
Class-based middleware provides:
|
||||
- Better state management
|
||||
- Easier testing
|
||||
- More organized code
|
||||
- Standard ASGI pattern
|
||||
|
||||
Runs FIRST in middleware chain.
|
||||
Sets:
|
||||
request.state.vendor: Vendor object
|
||||
request.state.vendor_context: Detection metadata
|
||||
request.state.clean_path: Path without vendor prefix
|
||||
"""
|
||||
# Skip vendor detection for admin, API, static files, and system requests
|
||||
if (VendorContextManager.is_admin_request(request) or
|
||||
VendorContextManager.is_api_request(request) or
|
||||
VendorContextManager.is_static_file_request(request) or
|
||||
request.url.path in ["/", "/health", "/docs", "/redoc", "/openapi.json"]):
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
"""
|
||||
Detect and inject vendor context.
|
||||
"""
|
||||
# Skip vendor detection for admin, API, static files, and system requests
|
||||
if (
|
||||
VendorContextManager.is_admin_request(request) or
|
||||
VendorContextManager.is_api_request(request) or
|
||||
VendorContextManager.is_static_file_request(request) or
|
||||
request.url.path in ["/", "/health", "/docs", "/redoc", "/openapi.json"]
|
||||
):
|
||||
logger.debug(
|
||||
f"[VENDOR] Skipping vendor detection: {request.url.path}",
|
||||
extra={"path": request.url.path, "reason": "admin/api/static/system"}
|
||||
)
|
||||
request.state.vendor = None
|
||||
request.state.vendor_context = None
|
||||
request.state.clean_path = request.url.path
|
||||
return await call_next(request)
|
||||
|
||||
# Detect vendor context
|
||||
vendor_context = VendorContextManager.detect_vendor_context(request)
|
||||
|
||||
if vendor_context:
|
||||
db_gen = get_db()
|
||||
db = next(db_gen)
|
||||
try:
|
||||
vendor = VendorContextManager.get_vendor_from_context(db, vendor_context)
|
||||
|
||||
if vendor:
|
||||
request.state.vendor = vendor
|
||||
request.state.vendor_context = vendor_context
|
||||
request.state.clean_path = VendorContextManager.extract_clean_path(
|
||||
request, vendor_context
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"[VENDOR_CONTEXT] Vendor detected",
|
||||
extra={
|
||||
"vendor_id": vendor.id,
|
||||
"vendor_name": vendor.name,
|
||||
"vendor_subdomain": vendor.subdomain,
|
||||
"detection_method": vendor_context.get("detection_method"),
|
||||
"original_path": request.url.path,
|
||||
"clean_path": request.state.clean_path,
|
||||
}
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"[WARNING] Vendor context detected but vendor not found",
|
||||
extra={
|
||||
"context": vendor_context,
|
||||
"detection_method": vendor_context.get("detection_method"),
|
||||
}
|
||||
)
|
||||
request.state.vendor = None
|
||||
request.state.vendor_context = vendor_context
|
||||
request.state.clean_path = request.url.path
|
||||
finally:
|
||||
db.close()
|
||||
else:
|
||||
logger.debug(
|
||||
f"[VENDOR] No vendor context detected",
|
||||
extra={
|
||||
"path": request.url.path,
|
||||
"host": request.headers.get("host", ""),
|
||||
}
|
||||
)
|
||||
request.state.vendor = None
|
||||
request.state.vendor_context = None
|
||||
request.state.clean_path = request.url.path
|
||||
|
||||
# Continue to next middleware
|
||||
return await call_next(request)
|
||||
|
||||
# Detect vendor context
|
||||
vendor_context = VendorContextManager.detect_vendor_context(request)
|
||||
|
||||
if vendor_context:
|
||||
db_gen = get_db()
|
||||
db = next(db_gen)
|
||||
try:
|
||||
vendor = VendorContextManager.get_vendor_from_context(db, vendor_context)
|
||||
|
||||
if vendor:
|
||||
request.state.vendor = vendor
|
||||
request.state.vendor_context = vendor_context
|
||||
request.state.clean_path = VendorContextManager.extract_clean_path(
|
||||
request, vendor_context
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"[VENDOR] Vendor context: {vendor.name} ({vendor.subdomain}) "
|
||||
f"via {vendor_context['detection_method']}"
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"[WARNING] Vendor not found for context: {vendor_context}"
|
||||
)
|
||||
request.state.vendor = None
|
||||
request.state.vendor_context = vendor_context
|
||||
finally:
|
||||
db.close()
|
||||
else:
|
||||
request.state.vendor = None
|
||||
request.state.vendor_context = None
|
||||
request.state.clean_path = request.url.path
|
||||
|
||||
return await call_next(request)
|
||||
|
||||
|
||||
def get_current_vendor(request: Request) -> Optional[Vendor]:
|
||||
"""Helper function to get current vendor from request state."""
|
||||
|
||||
Reference in New Issue
Block a user