# Frontend Detection Architecture This document describes the centralized frontend detection system that identifies which frontend (ADMIN, STORE, 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/*` | | **STORE** | Store dashboard | `/store/*`, `/api/v1/store/*` | | **STOREFRONT** | Customer-facing shop | `/storefront/*`, `/stores/*`, `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. StoreContextMiddleware → Sets request.state.store │ │ │ │ 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 ```python class FrontendType(str, Enum): PLATFORM = "platform" # Marketing pages (/, /pricing, /about) ADMIN = "admin" # Admin panel (/admin/*) STORE = "store" # Store dashboard (/store/*) STOREFRONT = "storefront" # Customer shop (/storefront/*, /stores/*) ``` ## 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 - /store/* or /api/v1/store/* → STORE - /storefront/*, /shop/*, /stores/* → STOREFRONT - /api/v1/platform/* → PLATFORM 3. Store subdomain (wizamart.oms.lu) → STOREFRONT 4. Store context set by middleware → STOREFRONT 5. Default → PLATFORM ``` ### Path Patterns ```python # Admin paths ADMIN_PATH_PREFIXES = ("/admin", "/api/v1/admin") # Store dashboard paths STORE_PATH_PREFIXES = ("/store/", "/api/v1/store") # Storefront paths STOREFRONT_PATH_PREFIXES = ( "/storefront", "/api/v1/storefront", "/shop", # Legacy support "/api/v1/shop", # Legacy support "/stores/", # Path-based store access ) # Platform paths PLATFORM_PATH_PREFIXES = ("/api/v1/platform",) ``` ### Reserved Subdomains These subdomains are NOT treated as store storefronts: ```python RESERVED_SUBDOMAINS = {"www", "admin", "api", "store", "portal"} ``` ## Usage ### In Middleware/Routes ```python 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) ```python 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_store_context=True ) # Returns: FrontendType.STOREFRONT # Convenience methods if FrontendDetector.is_admin(host, path): # Admin logic pass if FrontendDetector.is_storefront(host, path, has_store_context=True): # Storefront logic pass ``` ## Detection Scenarios ### Development Mode (localhost) | Request | Host | Path | Frontend | |---------|------|------|----------| | Admin page | localhost | /admin/stores | ADMIN | | Admin API | localhost | /api/v1/admin/users | ADMIN | | Store dashboard | localhost | /store/settings | STORE | | Store API | localhost | /api/v1/store/products | STORE | | Storefront | localhost | /storefront/products | STOREFRONT | | Storefront (path-based) | localhost | /stores/wizamart/products | STOREFRONT | | Marketing | localhost | /pricing | PLATFORM | ### Production Mode (domains) | Request | Host | Path | Frontend | |---------|------|------|----------| | Admin subdomain | admin.oms.lu | /dashboard | ADMIN | | Store 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` | | `STORE_DASHBOARD` | `FrontendType.STORE` | | `SHOP` | `FrontendType.STOREFRONT` | | `FALLBACK` | `FrontendType.PLATFORM` | ### Code Migration **Before (deprecated):** ```python from middleware.context import RequestContext, get_request_context context = get_request_context(request) if context == RequestContext.SHOP: # Storefront logic pass ``` **After:** ```python 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: ```python 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 ```bash # 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:** ```python 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/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 | ## Related Documentation - [Middleware Stack](middleware.md) - Overall middleware architecture - [Request Flow](request-flow.md) - How requests are processed - [URL Routing](url-routing/overview.md) - URL structure and routing patterns - [Multi-Tenant Architecture](multi-tenant.md) - Tenant detection and isolation