Move 9 init/seed scripts into scripts/seed/ and 7 validation scripts (+ validators/ subfolder) into scripts/validate/ to reduce clutter in the root scripts/ directory. Update all references across Makefile, CI/CD configs, pre-commit hooks, docs (~40 files), and Python imports. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.3 KiB
9.3 KiB
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
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
# 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:
RESERVED_SUBDOMAINS = {"www", "admin", "api", "store", "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_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):
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 strategyErrorRenderer- to select appropriate error templatesExceptionHandler- 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 teststests/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
- Use
get_frontend_type(request)in route handlers - Use
FrontendDetector.detect()when you have host/path but no request - Use convenience methods like
is_admin(),is_storefront()for boolean checks - 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
- Don't use
RequestContext- it's deprecated - Don't duplicate path detection logic - use FrontendDetector
- Don't hardcode path patterns in middleware - they're centralized in FrontendDetector
- Don't check
request.state.context_type- userequest.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 - Overall middleware architecture
- Request Flow - How requests are processed
- URL Routing - URL structure and routing patterns
- Multi-Tenant Architecture - Tenant detection and isolation