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:
278
docs/architecture/frontend-detection.md
Normal file
278
docs/architecture/frontend-detection.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# 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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
RESERVED_SUBDOMAINS = {"www", "admin", "api", "vendor", "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_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):**
|
||||
```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_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
|
||||
@@ -120,35 +120,35 @@ Injects: request.state.vendor = <Vendor object>
|
||||
|
||||
**Note on Path-Based Routing:** Previous implementations used a `PathRewriteMiddleware` to rewrite paths at runtime. This has been replaced with **double router mounting** in `main.py`, where shop routes are registered twice with different prefixes (`/shop` and `/vendors/{vendor_code}/shop`). This approach is simpler and uses FastAPI's native routing capabilities.
|
||||
|
||||
### 3. Context Detection Middleware
|
||||
### 3. Frontend Type Detection Middleware
|
||||
|
||||
**Purpose**: Determine the type/context of the request
|
||||
**Purpose**: Determine which frontend the request targets
|
||||
|
||||
**What it does**:
|
||||
- Analyzes the request path (using clean_path)
|
||||
- Determines which interface is being accessed:
|
||||
- `API` - `/api/*` paths
|
||||
- `ADMIN` - `/admin/*` paths or `admin.*` subdomain
|
||||
- `VENDOR_DASHBOARD` - `/vendor/*` paths (management area)
|
||||
- `SHOP` - Storefront pages (has vendor + not admin/vendor/API)
|
||||
- `FALLBACK` - Unknown context
|
||||
- Injects `request.state.context_type`
|
||||
- Uses centralized `FrontendDetector` class for all detection logic
|
||||
- Determines which frontend is being accessed:
|
||||
- `ADMIN` - `/admin/*`, `/api/v1/admin/*` paths or `admin.*` subdomain
|
||||
- `VENDOR` - `/vendor/*`, `/api/v1/vendor/*` paths (management area)
|
||||
- `STOREFRONT` - Customer shop pages (`/storefront/*`, `/vendors/*`, vendor subdomains)
|
||||
- `PLATFORM` - Marketing pages (`/`, `/pricing`, `/about`)
|
||||
- Injects `request.state.frontend_type` (FrontendType enum)
|
||||
|
||||
**Detection Rules**:
|
||||
**Detection Priority** (handled by `FrontendDetector`):
|
||||
```python
|
||||
if path.startswith("/api/"):
|
||||
context = API
|
||||
elif path.startswith("/admin/") or host.startswith("admin."):
|
||||
context = ADMIN
|
||||
elif path.startswith("/vendor/"):
|
||||
context = VENDOR_DASHBOARD
|
||||
elif request.state.vendor exists:
|
||||
context = SHOP
|
||||
else:
|
||||
context = FALLBACK
|
||||
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
|
||||
```
|
||||
|
||||
**Why it's useful**: Error handlers and templates adapt based on context
|
||||
**Why it's useful**: Error handlers, templates, and language detection adapt based on frontend type
|
||||
|
||||
**See**: [Frontend Detection Architecture](frontend-detection.md) for complete details
|
||||
|
||||
### 4. Theme Context Middleware
|
||||
|
||||
@@ -221,15 +221,18 @@ Each middleware file contains one primary class or a tightly related set of clas
|
||||
class LoggingMiddleware(BaseHTTPMiddleware):
|
||||
"""Request/response logging middleware"""
|
||||
|
||||
# middleware/context.py
|
||||
class ContextManager: # Business logic
|
||||
class ContextMiddleware: # ASGI wrapper
|
||||
class RequestContext(Enum): # Related enum
|
||||
# middleware/frontend_type.py
|
||||
class FrontendTypeMiddleware: # ASGI wrapper for frontend detection
|
||||
# Uses FrontendDetector from app/core/frontend_detector.py
|
||||
|
||||
# middleware/auth.py
|
||||
class AuthManager: # Authentication logic
|
||||
```
|
||||
|
||||
> **Note**: The old `middleware/context.py` with `ContextMiddleware` and `RequestContext` is deprecated.
|
||||
> Use `FrontendTypeMiddleware` and `FrontendType` enum instead.
|
||||
> See [Frontend Detection Architecture](frontend-detection.md) for migration guide.
|
||||
|
||||
#### One Test File Per Component
|
||||
|
||||
Follow the Single Responsibility Principle - each test file tests exactly one component:
|
||||
@@ -368,8 +371,8 @@ async def get_products(request: Request):
|
||||
}
|
||||
</style>
|
||||
|
||||
{# Access context #}
|
||||
{% if request.state.context_type.value == "admin" %}
|
||||
{# Access frontend type #}
|
||||
{% if request.state.frontend_type.value == "admin" %}
|
||||
<div class="admin-badge">Admin Mode</div>
|
||||
{% endif %}
|
||||
```
|
||||
@@ -394,11 +397,11 @@ async def get_products(request: Request):
|
||||
↓ Sets: request.state.vendor_id = 1
|
||||
↓ Sets: request.state.clean_path = "/shop/products"
|
||||
|
||||
3. ContextDetectionMiddleware
|
||||
↓ Analyzes path: "/shop/products"
|
||||
↓ Has vendor: Yes
|
||||
↓ Not admin/api/vendor dashboard
|
||||
↓ Sets: request.state.context_type = RequestContext.SHOP
|
||||
3. FrontendTypeMiddleware
|
||||
↓ Uses FrontendDetector with path: "/shop/products"
|
||||
↓ Has vendor context: Yes
|
||||
↓ Detects storefront frontend
|
||||
↓ Sets: request.state.frontend_type = FrontendType.STOREFRONT
|
||||
|
||||
4. ThemeContextMiddleware
|
||||
↓ Loads theme for vendor_id = 1
|
||||
@@ -430,10 +433,10 @@ Each middleware component handles errors gracefully:
|
||||
- If database error: Logs error, allows request to continue
|
||||
- Fallback: Request proceeds without vendor context
|
||||
|
||||
### ContextDetectionMiddleware
|
||||
### FrontendTypeMiddleware
|
||||
- If clean_path missing: Uses original path
|
||||
- If vendor missing: Defaults to FALLBACK context
|
||||
- Always sets a context_type (never None)
|
||||
- If vendor missing: Defaults to PLATFORM frontend type
|
||||
- Always sets a frontend_type (never None)
|
||||
|
||||
### ThemeContextMiddleware
|
||||
- If vendor missing: Skips theme loading
|
||||
@@ -479,8 +482,10 @@ Middleware is registered in `main.py`:
|
||||
# Add in REVERSE order (LIFO execution)
|
||||
app.add_middleware(LoggingMiddleware)
|
||||
app.add_middleware(ThemeContextMiddleware)
|
||||
app.add_middleware(ContextDetectionMiddleware)
|
||||
app.add_middleware(LanguageMiddleware)
|
||||
app.add_middleware(FrontendTypeMiddleware)
|
||||
app.add_middleware(VendorContextMiddleware)
|
||||
app.add_middleware(PlatformContextMiddleware)
|
||||
```
|
||||
|
||||
**Note**: FastAPI's `add_middleware` executes in **reverse order** (Last In, First Out)
|
||||
|
||||
@@ -147,27 +147,25 @@ app.include_router(shop_pages.router, prefix="/vendors/{vendor_code}/shop")
|
||||
**Detection Logic**:
|
||||
|
||||
```python
|
||||
host = request.headers.get("host", "")
|
||||
path = request.state.clean_path # "/shop/products"
|
||||
has_vendor = hasattr(request.state, 'vendor') and request.state.vendor
|
||||
|
||||
if path.startswith("/api/"):
|
||||
context = RequestContext.API
|
||||
elif path.startswith("/admin/"):
|
||||
context = RequestContext.ADMIN
|
||||
elif path.startswith("/vendor/"):
|
||||
context = RequestContext.VENDOR_DASHBOARD
|
||||
elif hasattr(request.state, 'vendor') and request.state.vendor:
|
||||
context = RequestContext.SHOP # ← Our example
|
||||
else:
|
||||
context = RequestContext.FALLBACK
|
||||
# FrontendDetector handles all detection logic centrally
|
||||
frontend_type = FrontendDetector.detect(host, path, has_vendor)
|
||||
# Returns: FrontendType.STOREFRONT # ← Our example
|
||||
|
||||
request.state.context_type = context
|
||||
request.state.frontend_type = frontend_type
|
||||
```
|
||||
|
||||
**Request State After**:
|
||||
```python
|
||||
request.state.context_type = RequestContext.SHOP
|
||||
request.state.frontend_type = FrontendType.STOREFRONT
|
||||
```
|
||||
|
||||
> **Note**: Detection logic is centralized in `app/core/frontend_detector.py`.
|
||||
> See [Frontend Detection Architecture](frontend-detection.md) for details.
|
||||
|
||||
### 6. ThemeContextMiddleware
|
||||
|
||||
**What happens**:
|
||||
@@ -363,7 +361,7 @@ sequenceDiagram
|
||||
Context->>Router: Route request
|
||||
Router->>Handler: Call API handler
|
||||
Handler->>DB: Query products
|
||||
DB-->>Handler: Product data
|
||||
DB-->>Handler: Product data
|
||||
Handler-->>Router: JSON response
|
||||
Router-->>Client: {products: [...]}
|
||||
```
|
||||
@@ -390,7 +388,7 @@ sequenceDiagram
|
||||
Context->>Theme: Pass request
|
||||
Note over Theme: Skip theme<br/>(No vendor)
|
||||
Theme->>Router: Route request
|
||||
Router->>Handler: Call handler
|
||||
Router->>Handler: Call handler
|
||||
Handler->>Template: Render admin template
|
||||
Template-->>Client: Admin HTML page
|
||||
```
|
||||
@@ -423,7 +421,7 @@ sequenceDiagram
|
||||
Context->>Theme: Pass request
|
||||
Theme->>DB: Query theme
|
||||
DB-->>Theme: Theme config
|
||||
Note over Theme: Set theme in request.state
|
||||
Note over Theme: Set theme in request.state
|
||||
Theme->>Router: Route request
|
||||
Router->>Handler: Call handler
|
||||
Handler->>DB: Query products for vendor
|
||||
@@ -450,12 +448,12 @@ After VendorContextMiddleware:
|
||||
{
|
||||
vendor: <Vendor: Wizamart>,
|
||||
vendor_id: 1,
|
||||
clean_path: "/shop/products",
|
||||
clean_path: "/shop/products",
|
||||
frontend_type: FrontendType.STOREFRONT
|
||||
}
|
||||
|
||||
After ThemeContextMiddleware:
|
||||
{
|
||||
{
|
||||
vendor: <Vendor: Wizamart>,
|
||||
vendor_id: 1,
|
||||
clean_path: "/shop/products",
|
||||
@@ -463,7 +461,7 @@ After ThemeContextMiddleware:
|
||||
theme: {
|
||||
primary_color: "#3B82F6",
|
||||
secondary_color: "#10B981",
|
||||
logo_url: "/static/vendors/wizamart/logo.png",
|
||||
logo_url: "/static/vendors/wizamart/logo.png",
|
||||
custom_css: "..."
|
||||
}
|
||||
}
|
||||
@@ -481,7 +479,7 @@ Typical request timings:
|
||||
| - ThemeContextMiddleware | 2ms | 1% |
|
||||
| Database Queries | 15ms | 10% |
|
||||
| Business Logic | 50ms | 35% |
|
||||
| Template Rendering | 75ms | 52% |
|
||||
| Template Rendering | 75ms | 52% |
|
||||
| **Total** | **145ms** | **100%** |
|
||||
|
||||
## Error Handling in Flow
|
||||
@@ -550,7 +548,7 @@ async def debug_state(request: Request):
|
||||
"has_theme": bool(getattr(request.state, 'theme', None))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Check Middleware Order
|
||||
|
||||
In `main.py`, middleware registration order is critical:
|
||||
@@ -562,7 +560,8 @@ In `main.py`, middleware registration order is critical:
|
||||
app.add_middleware(LanguageMiddleware) # Runs fifth
|
||||
app.add_middleware(FrontendTypeMiddleware) # Runs fourth
|
||||
app.add_middleware(VendorContextMiddleware) # Runs second
|
||||
app.add_middleware(ThemeContextMiddleware) # Runs fifth
|
||||
app.add_middleware(ContextDetectionMiddleware) # Runs fourth
|
||||
```
|
||||
app.add_middleware(LanguageMiddleware) # Runs fifth
|
||||
app.add_middleware(FrontendTypeMiddleware) # Runs fourth
|
||||
app.add_middleware(VendorContextMiddleware) # Runs second
|
||||
```
|
||||
|
||||
@@ -66,51 +66,57 @@ ASGI middleware that wraps VendorContextManager for FastAPI integration.
|
||||
|
||||
---
|
||||
|
||||
## Request Context Detection
|
||||
## Frontend Type Detection
|
||||
|
||||
### RequestContext
|
||||
### FrontendType
|
||||
|
||||
Enum defining all possible request context types in the application.
|
||||
Enum defining all possible frontend types in the application.
|
||||
|
||||
::: middleware.context.RequestContext
|
||||
::: app.modules.enums.FrontendType
|
||||
options:
|
||||
show_source: false
|
||||
heading_level: 4
|
||||
show_root_heading: false
|
||||
members:
|
||||
- API
|
||||
- PLATFORM
|
||||
- ADMIN
|
||||
- VENDOR_DASHBOARD
|
||||
- SHOP
|
||||
- FALLBACK
|
||||
- VENDOR
|
||||
- STOREFRONT
|
||||
|
||||
### ContextManager
|
||||
### FrontendDetector
|
||||
|
||||
Detects the type of request (API, Admin, Vendor Dashboard, Shop) based on URL patterns.
|
||||
Centralized class for detecting which frontend a request targets based on URL patterns.
|
||||
|
||||
**Context Detection Rules:**
|
||||
- `/api/` → API context
|
||||
- `/admin/` → Admin context
|
||||
- `/vendor/` → Vendor Dashboard context
|
||||
- `/shop/` → Shop context
|
||||
- Default → Fallback context
|
||||
**Detection Rules (Priority Order):**
|
||||
1. Admin subdomain (`admin.*`) → ADMIN
|
||||
2. Path-based detection:
|
||||
- `/admin/*`, `/api/v1/admin/*` → ADMIN
|
||||
- `/vendor/*`, `/api/v1/vendor/*` → VENDOR
|
||||
- `/storefront/*`, `/shop/*`, `/vendors/*` → STOREFRONT
|
||||
- `/api/v1/platform/*` → PLATFORM
|
||||
3. Vendor subdomain → STOREFRONT
|
||||
4. Vendor context set → STOREFRONT
|
||||
5. Default → PLATFORM
|
||||
|
||||
::: middleware.context.ContextManager
|
||||
::: app.core.frontend_detector.FrontendDetector
|
||||
options:
|
||||
show_source: false
|
||||
heading_level: 4
|
||||
show_root_heading: false
|
||||
|
||||
### ContextMiddleware
|
||||
### FrontendTypeMiddleware
|
||||
|
||||
ASGI middleware for context detection. Must run AFTER VendorContextMiddleware.
|
||||
ASGI middleware for frontend type detection. Must run AFTER VendorContextMiddleware.
|
||||
|
||||
::: middleware.context.ContextMiddleware
|
||||
::: middleware.frontend_type.FrontendTypeMiddleware
|
||||
options:
|
||||
show_source: false
|
||||
heading_level: 4
|
||||
show_root_heading: false
|
||||
|
||||
> **Note**: The old `RequestContext` enum and `ContextMiddleware` are deprecated.
|
||||
> See [Frontend Detection Architecture](../architecture/frontend-detection.md) for migration guide.
|
||||
|
||||
---
|
||||
|
||||
## Theme Management
|
||||
@@ -235,20 +241,24 @@ The middleware stack must be configured in the correct order for proper function
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Request] --> B[LoggingMiddleware]
|
||||
B --> C[VendorContextMiddleware]
|
||||
C --> D[ContextMiddleware]
|
||||
D --> E[ThemeContextMiddleware]
|
||||
E --> F[Application Routes]
|
||||
F --> G[Response]
|
||||
B --> C[PlatformContextMiddleware]
|
||||
C --> D[VendorContextMiddleware]
|
||||
D --> E[FrontendTypeMiddleware]
|
||||
E --> F[LanguageMiddleware]
|
||||
F --> G[ThemeContextMiddleware]
|
||||
G --> H[Application Routes]
|
||||
H --> I[Response]
|
||||
```
|
||||
|
||||
**Critical Dependencies:**
|
||||
1. **LoggingMiddleware** runs first for request timing
|
||||
2. **VendorContextMiddleware** detects vendor and sets clean_path
|
||||
3. **ContextMiddleware** detects context type (API/Admin/Vendor/Shop)
|
||||
4. **ThemeContextMiddleware** loads vendor theme based on context
|
||||
2. **PlatformContextMiddleware** detects platform and sets platform context
|
||||
3. **VendorContextMiddleware** detects vendor and sets clean_path
|
||||
4. **FrontendTypeMiddleware** detects frontend type (ADMIN/VENDOR/STOREFRONT/PLATFORM)
|
||||
5. **LanguageMiddleware** resolves language based on frontend type
|
||||
6. **ThemeContextMiddleware** loads vendor theme based on context
|
||||
|
||||
**Note:** Path-based routing (e.g., `/vendors/{code}/shop/*`) is handled by double router mounting in `main.py`, not by middleware.
|
||||
**Note:** Path-based routing (e.g., `/vendors/{code}/storefront/*`) is handled by double router mounting in `main.py`, not by middleware.
|
||||
|
||||
---
|
||||
|
||||
@@ -258,22 +268,28 @@ Middleware components inject the following variables into `request.state`:
|
||||
|
||||
| Variable | Set By | Type | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `platform` | PlatformContextMiddleware | Platform | Current platform object |
|
||||
| `vendor` | VendorContextMiddleware | Vendor | Current vendor object |
|
||||
| `vendor_id` | VendorContextMiddleware | int | Current vendor ID |
|
||||
| `clean_path` | VendorContextMiddleware | str | Path without vendor prefix |
|
||||
| `context_type` | ContextMiddleware | RequestContext | Request context (API/Admin/Vendor/Shop) |
|
||||
| `frontend_type` | FrontendTypeMiddleware | FrontendType | Frontend type (ADMIN/VENDOR/STOREFRONT/PLATFORM) |
|
||||
| `language` | LanguageMiddleware | str | Detected language code |
|
||||
| `theme` | ThemeContextMiddleware | dict | Vendor theme configuration |
|
||||
|
||||
**Usage in Routes:**
|
||||
```python
|
||||
from fastapi import Request
|
||||
from app.modules.enums import FrontendType
|
||||
from middleware.frontend_type import get_frontend_type
|
||||
|
||||
@app.get("/shop/products")
|
||||
@app.get("/storefront/products")
|
||||
async def get_products(request: Request):
|
||||
vendor = request.state.vendor
|
||||
context = request.state.context_type
|
||||
frontend_type = get_frontend_type(request)
|
||||
theme = request.state.theme
|
||||
return {"vendor": vendor.name, "context": context}
|
||||
|
||||
if frontend_type == FrontendType.STOREFRONT:
|
||||
return {"vendor": vendor.name, "frontend": frontend_type.value}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -308,6 +324,8 @@ For testing examples, see the [Testing Guide](../testing/testing-guide.md).
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Frontend Detection Architecture](../architecture/frontend-detection.md) - Frontend type detection system
|
||||
- [Middleware Architecture](../architecture/middleware.md) - Middleware stack overview
|
||||
- [Authentication Guide](../api/authentication.md) - User authentication and JWT tokens
|
||||
- [RBAC Documentation](../api/rbac.md) - Role-based access control
|
||||
- [Error Handling](../api/error-handling.md) - Exception handling patterns
|
||||
|
||||
Reference in New Issue
Block a user