refactor: centralize frontend detection with FrontendDetector

Major architecture change to unify frontend detection:

## Problem Solved
- Eliminated code duplication across 3 middleware files
- Fixed incomplete path detection (now detects /api/v1/admin/*)
- Unified on FrontendType enum (deprecates RequestContext)
- Added request.state.frontend_type for all requests

## New Components
- app/core/frontend_detector.py: Centralized FrontendDetector class
- middleware/frontend_type.py: FrontendTypeMiddleware (replaces ContextMiddleware)
- docs/architecture/frontend-detection.md: Complete architecture documentation

## Changes
- main.py: Use FrontendTypeMiddleware instead of ContextMiddleware
- middleware/context.py: Deprecated (kept for backwards compatibility)
- middleware/platform_context.py: Use FrontendDetector.is_admin()
- middleware/vendor_context.py: Use FrontendDetector.is_admin()
- middleware/language.py: Use FrontendType instead of context_value
- app/exceptions/handler.py: Use FrontendType.STOREFRONT
- app/exceptions/error_renderer.py: Use FrontendType
- Customer routes: Cookie path changed from /shop to /storefront

## Documentation
- docs/architecture/frontend-detection.md: New comprehensive docs
- docs/architecture/middleware.md: Updated for new system
- docs/architecture/request-flow.md: Updated for FrontendType
- docs/backend/middleware-reference.md: Updated API reference

## Tests
- tests/unit/core/test_frontend_detector.py: 37 new tests
- tests/unit/middleware/test_frontend_type.py: 11 new tests
- tests/unit/middleware/test_context.py: Updated for compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 16:15:19 +01:00
parent e77535e2cd
commit b769f5a047
17 changed files with 1393 additions and 915 deletions

View File

@@ -16,7 +16,8 @@ from fastapi import HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse, RedirectResponse
from middleware.context import RequestContext, get_request_context
from app.modules.enums import FrontendType
from middleware.frontend_type import get_frontend_type
from .base import WizamartException
from .error_renderer import ErrorPageRenderer
@@ -382,17 +383,17 @@ def _is_html_page_request(request: Request) -> bool:
def _redirect_to_login(request: Request) -> RedirectResponse:
"""
Redirect to appropriate login page based on request context.
Redirect to appropriate login page based on request frontend type.
Uses context detection to determine admin vs vendor vs shop login.
Uses FrontendType detection to determine admin vs vendor vs storefront login.
Properly handles multi-access routing (domain, subdomain, path-based).
"""
context_type = get_request_context(request)
frontend_type = get_frontend_type(request)
if context_type == RequestContext.ADMIN:
if frontend_type == FrontendType.ADMIN:
logger.debug("Redirecting to /admin/login")
return RedirectResponse(url="/admin/login", status_code=302)
if context_type == RequestContext.VENDOR_DASHBOARD:
if frontend_type == FrontendType.VENDOR:
# Extract vendor code from the request path
# Path format: /vendor/{vendor_code}/...
path_parts = request.url.path.split("/")
@@ -417,8 +418,8 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
logger.debug(f"Redirecting to {login_url}")
return RedirectResponse(url=login_url, status_code=302)
if context_type == RequestContext.SHOP:
# For shop context, redirect to shop login (customer login)
if frontend_type == FrontendType.STOREFRONT:
# For storefront context, redirect to storefront login (customer login)
# Calculate base_url for proper routing (supports domain, subdomain, and path-based access)
vendor = getattr(request.state, "vendor", None)
vendor_context = getattr(request.state, "vendor_context", None)
@@ -437,11 +438,11 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
)
base_url = f"{full_prefix}{vendor.subdomain}/"
login_url = f"{base_url}shop/account/login"
login_url = f"{base_url}storefront/account/login"
logger.debug(f"Redirecting to {login_url}")
return RedirectResponse(url=login_url, status_code=302)
# Fallback to root for unknown contexts
logger.debug("Unknown context, redirecting to /")
# Fallback to root for unknown contexts (PLATFORM)
logger.debug("Platform context, redirecting to /")
return RedirectResponse(url="/", status_code=302)