Files
orion/docs/architecture/frontend-detection.md
Samir Boulahtit b769f5a047 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>
2026-02-03 16:15:19 +01:00

9.3 KiB

Frontend Detection Architecture

This document describes the centralized frontend detection system that identifies which frontend (ADMIN, VENDOR, STOREFRONT, or PLATFORM) a request targets.

Overview

The application serves multiple frontends from a single codebase:

Frontend Description Example URLs
ADMIN Platform administration /admin/*, /api/v1/admin/*, admin.oms.lu/*
VENDOR Vendor dashboard /vendor/*, /api/v1/vendor/*
STOREFRONT Customer-facing shop /storefront/*, /vendors/*, wizamart.oms.lu/*
PLATFORM Marketing pages /, /pricing, /about

The FrontendDetector class provides centralized, consistent detection of which frontend a request targets.

Architecture

Components

┌─────────────────────────────────────────────────────────────────┐
│                     Request Processing                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1. PlatformContextMiddleware  → Sets request.state.platform     │
│                                                                   │
│  2. VendorContextMiddleware    → Sets request.state.vendor       │
│                                                                   │
│  3. FrontendTypeMiddleware     → Sets request.state.frontend_type│
│           │                                                       │
│           └──→ Uses FrontendDetector.detect()                    │
│                                                                   │
│  4. LanguageMiddleware         → Uses frontend_type for language │
│                                                                   │
│  5. ThemeContextMiddleware     → Uses frontend_type for theming  │
│                                                                   │
│  6. FastAPI Router             → Handles request                 │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

Key Files

File Purpose
app/core/frontend_detector.py Centralized detection logic
middleware/frontend_type.py Middleware that sets request.state.frontend_type
app/modules/enums.py Defines FrontendType enum

FrontendType Enum

class FrontendType(str, Enum):
    PLATFORM = "platform"    # Marketing pages (/, /pricing, /about)
    ADMIN = "admin"          # Admin panel (/admin/*)
    VENDOR = "vendor"        # Vendor dashboard (/vendor/*)
    STOREFRONT = "storefront"  # Customer shop (/storefront/*, /vendors/*)

Detection Priority

The FrontendDetector uses the following priority order:

1. Admin subdomain (admin.oms.lu)           → ADMIN
2. Path-based detection:
   - /admin/* or /api/v1/admin/*            → ADMIN
   - /vendor/* or /api/v1/vendor/*          → VENDOR
   - /storefront/*, /shop/*, /vendors/*     → STOREFRONT
   - /api/v1/platform/*                     → PLATFORM
3. Vendor subdomain (wizamart.oms.lu)       → STOREFRONT
4. Vendor context set by middleware         → STOREFRONT
5. Default                                  → PLATFORM

Path Patterns

# Admin paths
ADMIN_PATH_PREFIXES = ("/admin", "/api/v1/admin")

# Vendor dashboard paths
VENDOR_PATH_PREFIXES = ("/vendor/", "/api/v1/vendor")

# Storefront paths
STOREFRONT_PATH_PREFIXES = (
    "/storefront",
    "/api/v1/storefront",
    "/shop",           # Legacy support
    "/api/v1/shop",    # Legacy support
    "/vendors/",       # Path-based vendor access
)

# Platform paths
PLATFORM_PATH_PREFIXES = ("/api/v1/platform",)

Reserved Subdomains

These subdomains are NOT treated as vendor storefronts:

RESERVED_SUBDOMAINS = {"www", "admin", "api", "vendor", "portal"}

Usage

In Middleware/Routes

from middleware.frontend_type import get_frontend_type
from app.modules.enums import FrontendType

@router.get("/some-route")
async def some_route(request: Request):
    frontend_type = get_frontend_type(request)

    if frontend_type == FrontendType.ADMIN:
        # Admin-specific logic
        pass
    elif frontend_type == FrontendType.STOREFRONT:
        # Storefront-specific logic
        pass

Direct Detection (without request)

from app.core.frontend_detector import FrontendDetector
from app.modules.enums import FrontendType

# Full detection
frontend_type = FrontendDetector.detect(
    host="wizamart.oms.lu",
    path="/products",
    has_vendor_context=True
)
# Returns: FrontendType.STOREFRONT

# Convenience methods
if FrontendDetector.is_admin(host, path):
    # Admin logic
    pass

if FrontendDetector.is_storefront(host, path, has_vendor_context=True):
    # Storefront logic
    pass

Detection Scenarios

Development Mode (localhost)

Request Host Path Frontend
Admin page localhost /admin/vendors ADMIN
Admin API localhost /api/v1/admin/users ADMIN
Vendor dashboard localhost /vendor/settings VENDOR
Vendor API localhost /api/v1/vendor/products VENDOR
Storefront localhost /storefront/products STOREFRONT
Storefront (path-based) localhost /vendors/wizamart/products STOREFRONT
Marketing localhost /pricing PLATFORM

Production Mode (domains)

Request Host Path Frontend
Admin subdomain admin.oms.lu /dashboard ADMIN
Vendor subdomain wizamart.oms.lu /products STOREFRONT
Custom domain mybakery.lu /products STOREFRONT
Platform root oms.lu /pricing PLATFORM

Migration from RequestContext

The previous RequestContext enum is deprecated. Here's the mapping:

Old (RequestContext) New (FrontendType)
API Use FrontendDetector.is_api_request() + FrontendType
ADMIN FrontendType.ADMIN
VENDOR_DASHBOARD FrontendType.VENDOR
SHOP FrontendType.STOREFRONT
FALLBACK FrontendType.PLATFORM

Code Migration

Before (deprecated):

from middleware.context import RequestContext, get_request_context

context = get_request_context(request)
if context == RequestContext.SHOP:
    # Storefront logic
    pass

After:

from middleware.frontend_type import get_frontend_type
from app.modules.enums import FrontendType

frontend_type = get_frontend_type(request)
if frontend_type == FrontendType.STOREFRONT:
    # Storefront logic
    pass

Request State

After FrontendTypeMiddleware runs, the following is available:

request.state.frontend_type  # FrontendType enum value

This is used by:

  • LanguageMiddleware - to determine language resolution strategy
  • ErrorRenderer - to select appropriate error templates
  • ExceptionHandler - to redirect to correct login page
  • Route handlers - for frontend-specific logic

Testing

Unit Tests

Tests are located in:

  • tests/unit/core/test_frontend_detector.py - FrontendDetector tests
  • tests/unit/middleware/test_frontend_type.py - Middleware tests

Running Tests

# Run all frontend detection tests
pytest tests/unit/core/test_frontend_detector.py tests/unit/middleware/test_frontend_type.py -v

# Run with coverage
pytest tests/unit/core/test_frontend_detector.py tests/unit/middleware/test_frontend_type.py --cov=app.core.frontend_detector --cov=middleware.frontend_type

Best Practices

DO

  1. Use get_frontend_type(request) in route handlers
  2. Use FrontendDetector.detect() when you have host/path but no request
  3. Use convenience methods like is_admin(), is_storefront() for boolean checks
  4. Import from the correct location:
    from app.modules.enums import FrontendType
    from middleware.frontend_type import get_frontend_type
    from app.core.frontend_detector import FrontendDetector
    

DON'T

  1. Don't use RequestContext - it's deprecated
  2. Don't duplicate path detection logic - use FrontendDetector
  3. Don't hardcode path patterns in middleware - they're centralized in FrontendDetector
  4. Don't check request.state.context_type - use request.state.frontend_type

Architecture Rules

These rules are enforced by scripts/validate_architecture.py:

Rule Description
MID-001 Use FrontendDetector for frontend detection
MID-002 Don't hardcode path patterns in middleware
MID-003 Use FrontendType enum, not RequestContext