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>
279 lines
9.3 KiB
Markdown
279 lines
9.3 KiB
Markdown
# 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
|