revamping documentation

This commit is contained in:
2025-11-17 22:59:42 +01:00
parent bbd64a6f21
commit 807033be16
107 changed files with 11973 additions and 28413 deletions

View File

@@ -0,0 +1,448 @@
# Middleware Stack
The middleware stack is the backbone of the multi-tenant system, handling tenant detection, context injection, and theme loading for all requests.
## Overview
The application uses a custom middleware stack that processes **every request** regardless of whether it's:
- REST API calls (`/api/*`)
- Admin interface pages (`/admin/*`)
- Vendor dashboard pages (`/vendor/*`)
- Shop pages (`/shop/*` or custom domains)
This middleware layer is **system-wide** and enables the multi-tenant architecture to function seamlessly.
## Middleware Components
### 1. Logging Middleware
**Purpose**: Request/response logging and performance monitoring
**What it does**:
- Logs every incoming request with method, path, and client IP
- Measures request processing time
- Logs response status codes
- Adds `X-Process-Time` header with processing duration
- Logs errors with stack traces
**Example Log Output**:
```
INFO Request: GET /admin/dashboard from 192.168.1.100
INFO Response: 200 for GET /admin/dashboard (0.143s)
```
**Configuration**: Runs first to capture full request timing
### 2. Vendor Context Middleware
**Purpose**: Detect which vendor's shop the request is for (multi-tenant core)
**What it does**:
- Detects vendor from:
- Custom domain (e.g., `customdomain.com`)
- Subdomain (e.g., `vendor1.platform.com`)
- Path prefix (e.g., `/vendor/vendor1/` or `/vendors/vendor1/`)
- Queries database to find vendor by domain or code
- Injects vendor object into `request.state.vendor`
- Extracts "clean path" (path without vendor prefix)
- Sets `request.state.clean_path` for routing
**Example**:
```
Request: https://wizamart.platform.com/shop/products
Middleware detects: vendor_code = "wizamart"
Queries database: SELECT * FROM vendors WHERE code = 'wizamart'
Injects: request.state.vendor = <Vendor object>
request.state.vendor_id = 1
request.state.clean_path = "/shop/products"
```
**Why it's critical**: Without this, the system wouldn't know which vendor's data to show
**See**: [Multi-Tenant System](multi-tenant.md) for routing modes
### 3. Path Rewrite Middleware
**Purpose**: Rewrite request paths for proper FastAPI routing
**What it does**:
- Uses the `clean_path` extracted by VendorContextMiddleware
- Rewrites `request.scope['path']` to remove vendor prefix
- Allows FastAPI routes to match correctly
**Example**:
```
Original path: /vendor/WIZAMART/shop/products
Clean path: /shop/products (set by VendorContextMiddleware)
Path Rewrite Middleware changes request path to: /shop/products
FastAPI router can now match: @app.get("/shop/products")
```
**Why it's needed**: FastAPI routes don't include vendor prefix, so we strip it
### 4. Context Detection Middleware
**Purpose**: Determine the type/context of the request
**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`
**Detection Rules**:
```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
```
**Why it's useful**: Error handlers and templates adapt based on context
### 5. Theme Context Middleware
**Purpose**: Load vendor-specific theme settings
**What it does**:
- Checks if request has a vendor (from VendorContextMiddleware)
- Queries database for vendor's theme settings
- Injects theme configuration into `request.state.theme`
- Provides default theme if vendor has no custom theme
**Theme Data Structure**:
```python
{
"primary_color": "#3B82F6",
"secondary_color": "#10B981",
"logo_url": "/static/vendors/wizamart/logo.png",
"favicon_url": "/static/vendors/wizamart/favicon.ico",
"custom_css": "/* vendor-specific styles */"
}
```
**Why it's needed**: Each vendor shop can have custom branding
## Middleware Execution Order
### The Stack (First to Last)
```mermaid
graph TD
A[Client Request] --> B[1. LoggingMiddleware]
B --> C[2. VendorContextMiddleware]
C --> D[3. PathRewriteMiddleware]
D --> E[4. ContextDetectionMiddleware]
E --> F[5. ThemeContextMiddleware]
F --> G[6. FastAPI Router]
G --> H[Route Handler]
H --> I[Response]
I --> J[Client]
```
### Why This Order Matters
**Critical Dependencies**:
1. **LoggingMiddleware first**
- Needs to wrap everything to measure total time
- Must log errors from all other middleware
2. **VendorContextMiddleware second**
- Must run before PathRewriteMiddleware (provides clean_path)
- Must run before ContextDetectionMiddleware (provides vendor)
- Must run before ThemeContextMiddleware (provides vendor_id)
3. **PathRewriteMiddleware third**
- Depends on clean_path from VendorContextMiddleware
- Must run before ContextDetectionMiddleware (rewrites path)
4. **ContextDetectionMiddleware fourth**
- Uses clean_path from VendorContextMiddleware
- Uses rewritten path from PathRewriteMiddleware
- Provides context_type for ThemeContextMiddleware
5. **ThemeContextMiddleware last**
- Depends on vendor from VendorContextMiddleware
- Depends on context_type from ContextDetectionMiddleware
**Breaking this order will break the application!**
## Request State Variables
Middleware components inject these variables into `request.state`:
| Variable | Set By | Type | Used By | Description |
|----------|--------|------|---------|-------------|
| `vendor` | VendorContextMiddleware | Vendor | Theme, Templates | Current vendor object |
| `vendor_id` | VendorContextMiddleware | int | Queries, Theme | Current vendor ID |
| `clean_path` | VendorContextMiddleware | str | PathRewrite, Context | Path without vendor prefix |
| `context_type` | ContextDetectionMiddleware | RequestContext | Theme, Error handlers | Request context enum |
| `theme` | ThemeContextMiddleware | dict | Templates | Vendor theme config |
### Using in Route Handlers
```python
from fastapi import Request
@app.get("/shop/products")
async def get_products(request: Request):
# Access vendor
vendor = request.state.vendor
vendor_id = request.state.vendor_id
# Access context
context = request.state.context_type
# Access theme
theme = request.state.theme
# Use in queries
products = db.query(Product).filter(
Product.vendor_id == vendor_id
).all()
return {"vendor": vendor.name, "products": products}
```
### Using in Templates
```jinja2
{# Access vendor #}
<h1>{{ request.state.vendor.name }}</h1>
{# Access theme #}
<style>
:root {
--primary-color: {{ request.state.theme.primary_color }};
--secondary-color: {{ request.state.theme.secondary_color }};
}
</style>
{# Access context #}
{% if request.state.context_type.value == "admin" %}
<div class="admin-badge">Admin Mode</div>
{% endif %}
```
## Request Flow Example
### Example: Shop Product Page Request
**URL**: `https://wizamart.myplatform.com/shop/products`
**Middleware Processing**:
```
1. LoggingMiddleware
↓ Starts timer
↓ Logs: "Request: GET /shop/products from 192.168.1.100"
2. VendorContextMiddleware
↓ Detects subdomain: "wizamart"
↓ Queries DB: vendor = get_vendor_by_code("wizamart")
↓ Sets: request.state.vendor = <Vendor: Wizamart>
↓ Sets: request.state.vendor_id = 1
↓ Sets: request.state.clean_path = "/shop/products"
3. PathRewriteMiddleware
↓ Path already clean (no rewrite needed for subdomain mode)
↓ request.scope['path'] = "/shop/products"
4. ContextDetectionMiddleware
↓ Analyzes path: "/shop/products"
↓ Has vendor: Yes
↓ Not admin/api/vendor dashboard
↓ Sets: request.state.context_type = RequestContext.SHOP
5. ThemeContextMiddleware
↓ Loads theme for vendor_id = 1
↓ Sets: request.state.theme = {...theme config...}
6. FastAPI Router
↓ Matches route: @app.get("/shop/products")
↓ Calls handler function
7. Route Handler
↓ Accesses: request.state.vendor_id
↓ Queries: products WHERE vendor_id = 1
↓ Renders template with vendor data
8. Response
↓ Returns HTML with vendor theme
9. LoggingMiddleware (response phase)
↓ Logs: "Response: 200 for GET /shop/products (0.143s)"
↓ Adds header: X-Process-Time: 0.143
```
## Error Handling in Middleware
Each middleware component handles errors gracefully:
### VendorContextMiddleware
- If vendor not found: Sets `request.state.vendor = None`
- If database error: Logs error, allows request to continue
- Fallback: Request proceeds without vendor context
### ContextDetectionMiddleware
- If clean_path missing: Uses original path
- If vendor missing: Defaults to FALLBACK context
- Always sets a context_type (never None)
### ThemeContextMiddleware
- If vendor missing: Skips theme loading
- If theme query fails: Uses default theme
- If no theme exists: Returns empty theme dict
**Design Philosophy**: Middleware should never crash the application. Degrade gracefully.
## Performance Considerations
### Database Queries
**Per Request**:
- 1 query in VendorContextMiddleware (vendor lookup) - cached by DB
- 1 query in ThemeContextMiddleware (theme lookup) - cached by DB
**Total**: ~2 DB queries per request
**Optimization Opportunities**:
- Implement Redis caching for vendor lookups
- Cache theme data in memory
- Use connection pooling (already enabled)
### Memory Usage
Minimal per-request overhead:
- Small objects stored in `request.state`
- No global state maintained
- Garbage collected after response
### Latency
Typical overhead: **< 5ms** per request
- Vendor lookup: ~2ms
- Theme lookup: ~2ms
- Context detection: <1ms
## Configuration
Middleware is registered in `main.py`:
```python
# Add in REVERSE order (LIFO execution)
app.add_middleware(LoggingMiddleware)
app.add_middleware(ThemeContextMiddleware)
app.add_middleware(ContextDetectionMiddleware)
app.add_middleware(VendorContextMiddleware)
```
**Note**: FastAPI's `add_middleware` executes in **reverse order** (Last In, First Out)
## Testing Middleware
### Unit Testing
Test each middleware component in isolation:
```python
from middleware.vendor_context import VendorContextManager
def test_vendor_detection_subdomain():
# Mock request
request = create_mock_request(host="wizamart.platform.com")
# Test detection
manager = VendorContextManager()
vendor = manager.detect_vendor_from_subdomain(request)
assert vendor.code == "wizamart"
```
### Integration Testing
Test the full middleware stack:
```python
def test_shop_request_flow(client):
response = client.get(
"/shop/products",
headers={"Host": "wizamart.platform.com"}
)
assert response.status_code == 200
assert "Wizamart" in response.text
```
**See**: [Testing Guide](../testing/testing-guide.md)
## Debugging Middleware
### Enable Debug Logging
```python
import logging
logging.getLogger("middleware").setLevel(logging.DEBUG)
```
### Check Request State
In route handlers:
```python
@app.get("/debug")
async def debug_state(request: Request):
return {
"vendor": request.state.vendor.name if hasattr(request.state, 'vendor') else None,
"vendor_id": getattr(request.state, 'vendor_id', None),
"clean_path": getattr(request.state, 'clean_path', None),
"context_type": request.state.context_type.value if hasattr(request.state, 'context_type') else None,
"theme": bool(getattr(request.state, 'theme', None))
}
```
### Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| Vendor not detected | Wrong host header | Check domain configuration |
| Context is FALLBACK | Path doesn't match patterns | Check route prefix |
| Theme not loading | Vendor ID missing | Check VendorContextMiddleware runs first |
| Sidebar broken | Variable name conflict | See frontend troubleshooting |
## Related Documentation
- [Multi-Tenant System](multi-tenant.md) - Detailed routing modes
- [Request Flow](request-flow.md) - Complete request journey
- [Authentication & RBAC](auth-rbac.md) - Security middleware
- [Backend API Reference](../backend/middleware-reference.md) - Technical API docs
- [Frontend Development](../frontend/overview.md) - Using middleware state in frontend
## Technical Reference
For detailed API documentation of middleware classes and methods, see:
- [Backend Middleware Reference](../backend/middleware-reference.md)
This includes:
- Complete class documentation
- Method signatures
- Parameter details
- Return types
- Auto-generated from source code