# Request Flow

Complete journey of a request through the Orion platform, from client to response.

## Overview

This document traces how requests flow through the multi-tenant system, showing the path through middleware, routing, and response generation.

## High-Level Flow

```mermaid
graph TB
    A[Client Request] --> B{Request Type?}

    B -->|API| C[REST API Flow]
    B -->|HTML Page| D[Page Rendering Flow]

    C --> E[Middleware Stack]
    D --> E

    E --> F[Store Detection]
    F --> G[Context Detection]
    G --> H[Theme Loading]
    H --> I[Router]

    I -->|API| J[API Handler]
    I -->|Page| K[Route Handler]

    J --> L[JSON Response]
    K --> M[Jinja2 Template]
    M --> N[HTML Response]

    L --> O[Client]
    N --> O
```

## Detailed Request Flow

### 1. Client Sends Request

**Example Requests**:

```http
# Shop page request (subdomain mode)
GET https://orion.platform.com/storefront/products
Host: orion.platform.com

# API request
GET https://platform.com/api/v1/products?store_id=1
Authorization: Bearer eyJ0eXAi...
Host: platform.com

# Admin page request
GET https://platform.com/admin/stores
Authorization: Bearer eyJ0eXAi...
Host: platform.com
```

### 2. LoggingMiddleware (Entry Point)

**What happens**:
- Request enters the application
- Timer starts
- Request logged with method, path, client IP

**Request State**:
```python
# Start time recorded
start_time = time.time()

# Log entry
logger.info(f"Request: GET /storefront/products from 192.168.1.100")
```

**Output**: Nothing added to `request.state` yet

### 3. StoreContextMiddleware

**What happens**:
- Analyzes host header and path
- Checks platform domain guards: if host matches `platform.domain`, Methods 1 & 2 are skipped; if host is a subdomain of `platform.domain`, Method 1 is skipped
- Determines routing mode (custom domain / subdomain / path-based)
- Queries database for store
- Extracts clean path

**Example Processing** (Subdomain Mode — Two-Step Lookup):

```python
# Input
host = "wizatech-rewards.rewardflow.lu"
path = "/storefront/products"

# Detection logic — two-step subdomain lookup
subdomain = host.split('.')[0]  # "wizatech-rewards"

# Step 1: Check per-platform custom subdomain (StorePlatform.custom_subdomain)
store_platform = db.query(StorePlatform).filter(
    StorePlatform.custom_subdomain == subdomain
).first()

if store_platform:
    store = store_platform.store
else:
    # Step 2: Fall back to standard Store.subdomain
    store = db.query(Store).filter(
        Store.subdomain == subdomain
    ).first()

# Set request state
request.state.store = store
request.state.store_id = store.id
request.state.clean_path = "/storefront/products"  # Already clean
```

**Request State After**:
```python
request.state.store = <Store: Orion>
request.state.store_id = 1
request.state.clean_path = "/storefront/products"
```

### 4. Router Matching (FastAPI Native)

**What happens**:
- FastAPI matches the request path against registered routes
- Both storefront and store dashboard routes are registered with two prefixes each:
  - Storefront: `/storefront/*` and `/storefront/{store_code}/*`
  - Store dashboard: `/store/*` and `/store/{store_code}/*`

**Example** (Path-Based Mode):

```python
# In main.py - Double router mounting for storefront
app.include_router(storefront_pages.router, prefix="/storefront")
app.include_router(storefront_pages.router, prefix="/storefront/{store_code}")

# In main.py - Double router mounting for store dashboard
app.include_router(store_pages.router, prefix="/store")
app.include_router(store_pages.router, prefix="/store/{store_code}")

# Storefront request: /storefront/ACME/products
# Matches: Second storefront router (/storefront/{store_code})
# store_code = "ACME"

# Store dashboard request: /store/ACME/dashboard
# Matches: Second store router (/store/{store_code})
# store_code = "ACME"
```

Route handlers use the `get_resolved_store_code` dependency to transparently resolve the store code from either the path parameter (dev mode) or middleware state (production subdomain/custom domain).

**Note:** Previous implementations used `PathRewriteMiddleware` to rewrite paths. This has been replaced with FastAPI's native routing via double router mounting.

**Request State After**: No changes to state, but internal path updated

### 5. ContextDetectionMiddleware

**What happens**:
- Analyzes the clean path
- Determines request context type
- Sets context in request state

**Detection Logic**:

```python
host = request.headers.get("host", "")
path = request.state.clean_path  # "/storefront/products"
has_store = hasattr(request.state, 'store') and request.state.store

# FrontendDetector handles all detection logic centrally
frontend_type = FrontendDetector.detect(host, path, has_store)
# Returns: FrontendType.STOREFRONT  # ← Our example

request.state.frontend_type = frontend_type
```

**Request State After**:
```python
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**:
- Checks if request has a store
- Loads theme configuration from database
- Injects theme into request state

**Theme Loading**:

```python
if hasattr(request.state, 'store_id'):
    theme = db.query(StoreTheme).filter(
        StoreTheme.store_id == request.state.store_id
    ).first()

    request.state.theme = {
        "primary_color": theme.primary_color,
        "secondary_color": theme.secondary_color,
        "logo_url": theme.logo_url,
        "custom_css": theme.custom_css
    }
```

**Request State After**:
```python
request.state.theme = {
    "primary_color": "#3B82F6",
    "secondary_color": "#10B981",
    "logo_url": "/static/stores/orion/logo.png",
    "custom_css": "..."
}
```

### 7. FastAPI Router

**What happens**:
- Request reaches FastAPI's router
- Router matches path to registered route
- Route dependencies are resolved
- Handler function is called

**Route Matching**:

```python
# Request path (after rewrite): "/storefront/products"

# Matches this route
@app.get("/storefront/products")
async def get_storefront_products(request: Request):
    # Handler code
    pass
```

### 8. Route Handler Execution

**Example Handler**:

```python
# Routes are defined in modules: app/modules/<module>/routes/pages/storefront.py

@router.get("/products")
async def shop_products_page(
    request: Request,
    db: Session = Depends(get_db)
):
    # Access store from request state
    store = request.state.store
    store_id = request.state.store_id

    # Query products for this store
    products = db.query(Product).filter(
        Product.store_id == store_id
    ).all()

    # Render template with context
    return templates.TemplateResponse(
        "shop/products.html",
        {
            "request": request,
            "store": store,
            "products": products,
            "theme": request.state.theme
        }
    )
```

### 9. Template Rendering (Jinja2)

**Template** (`templates/storefront/products.html`):

```jinja2
<!DOCTYPE html>
<html>
<head>
    <title>{{ store.name }} - Products</title>
    <style>
        :root {
            --primary-color: {{ theme.primary_color }};
            --secondary-color: {{ theme.secondary_color }};
        }
    </style>
</head>
<body>
    <h1>{{ store.name }} Shop</h1>

    <div class="products">
        {% for product in products %}
        <div class="product-card">
            <h2>{{ product.name }}</h2>
            <p>{{ product.price }}</p>
        </div>
        {% endfor %}
    </div>
</body>
</html>
```

**Rendered HTML**:

```html
<!DOCTYPE html>
<html>
<head>
    <title>Orion - Products</title>
    <style>
        :root {
            --primary-color: #3B82F6;
            --secondary-color: #10B981;
        }
    </style>
</head>
<body>
    <h1>Orion Shop</h1>
    <div class="products">
        <div class="product-card">
            <h2>Product 1</h2>
            <p>$29.99</p>
        </div>
        <!-- More products... -->
    </div>
</body>
</html>
```

### 10. Response Sent Back

**LoggingMiddleware (Response Phase)**:
- Calculates total request time
- Logs response status and duration
- Adds performance header

**Logging**:
```python
duration = time.time() - start_time  # 0.143 seconds

logger.info(
    f"Response: 200 for GET /storefront/products (0.143s)"
)

# Add header
response.headers["X-Process-Time"] = "0.143"
```

**Final Response**:
```http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Process-Time: 0.143
Content-Length: 2847

<!DOCTYPE html>
<html>
...
</html>
```

## Flow Diagrams by Request Type

### API Request Flow

```mermaid
sequenceDiagram
    participant Client
    participant Logging
    participant Store
    participant Context
    participant Router
    participant Handler
    participant DB

    Client->>Logging: GET /api/v1/products?store_id=1
    Logging->>Store: Pass request
    Note over Store: No store detection<br/>(API uses query param)
    Store->>Context: Pass request
    Context->>Context: Detect API context
    Note over Context: frontend_type = API (via FrontendDetector)
    Context->>Router: Route request
    Router->>Handler: Call API handler
    Handler->>DB: Query products
    DB-->>Handler: Product data
    Handler-->>Router: JSON response
    Router-->>Client: {products: [...]}
```

### Admin Page Flow

```mermaid
sequenceDiagram
    participant Client
    participant Logging
    participant Store
    participant Context
    participant Theme
    participant Router
    participant Handler
    participant Template

    Client->>Logging: GET /admin/stores
    Logging->>Store: Pass request
    Note over Store: No store<br/>(Admin area)
    Store->>Context: Pass request
    Context->>Context: Detect Admin context
    Note over Context: frontend_type = ADMIN
    Context->>Theme: Pass request
    Note over Theme: Skip theme<br/>(No store)
    Theme->>Router: Route request
    Router->>Handler: Call handler
    Handler->>Template: Render admin template
    Template-->>Client: Admin HTML page
```

### Storefront Page Flow (Full Multi-Tenant)

```mermaid
sequenceDiagram
    participant Client
    participant Logging
    participant Store
    participant Path
    participant Context
    participant Theme
    participant Router
    participant Handler
    participant DB
    participant Template

    Client->>Logging: GET /storefront/products<br/>Host: orion.platform.com
    Logging->>Store: Pass request
    Store->>DB: Query store by subdomain
    DB-->>Store: Store object
    Note over Store: Set store, store_id, clean_path
    Store->>Path: Pass request
    Note over Path: Path already clean
    Path->>Context: Pass request
    Context->>Context: Detect Storefront context
    Note over Context: frontend_type = STOREFRONT
    Context->>Theme: Pass request
    Theme->>DB: Query theme
    DB-->>Theme: Theme config
    Note over Theme: Set theme in request.state
    Theme->>Router: Route request
    Router->>Handler: Call handler
    Handler->>DB: Query products for store
    DB-->>Handler: Product list
    Handler->>Template: Render with theme
    Template-->>Client: Themed storefront HTML
```

## Request State Timeline

Showing how `request.state` is built up through the middleware stack:

```
Initial State: {}

After StoreContextMiddleware:
{
    store: <Store: Orion>,
    store_id: 1,
    clean_path: "/storefront/products"
}

After FrontendTypeMiddleware:
{
    store: <Store: Orion>,
    store_id: 1,
    clean_path: "/storefront/products",
    frontend_type: FrontendType.STOREFRONT
}

After ThemeContextMiddleware:
{
    store: <Store: Orion>,
    store_id: 1,
    clean_path: "/storefront/products",
    frontend_type: FrontendType.STOREFRONT,
    theme: {
        primary_color: "#3B82F6",
        secondary_color: "#10B981",
        logo_url: "/static/stores/orion/logo.png",
        custom_css: "..."
    }
}
```

## Performance Metrics

Typical request timings:

| Component | Time | Percentage |
|-----------|------|------------|
| Middleware Stack | 5ms | 3% |
| - StoreContextMiddleware | 2ms | 1% |
| - FrontendTypeMiddleware | <1ms | <1% |
| - ThemeContextMiddleware | 2ms | 1% |
| Database Queries | 15ms | 10% |
| Business Logic | 50ms | 35% |
| Template Rendering | 75ms | 52% |
| **Total** | **145ms** | **100%** |

## Error Handling in Flow

### Middleware Errors

If middleware encounters an error:

```python
try:
    # Middleware logic
    store = detect_store(request)
except Exception as e:
    logger.error(f"Store detection failed: {e}")
    # Set default/None
    request.state.store = None
    # Continue to next middleware
```

### Handler Errors

If route handler raises an exception:

```python
try:
    response = await handler(request)
except HTTPException as e:
    # FastAPI handles HTTP exceptions
    return error_response(e.status_code, e.detail)
except Exception as e:
    # Custom exception handler
    logger.error(f"Handler error: {e}")
    return error_response(500, "Internal Server Error")
```

## Related Documentation

- [Middleware Stack](middleware.md) - Detailed middleware documentation
- [Multi-Tenant System](multi-tenant.md) - Tenant routing modes
- [Authentication & RBAC](auth-rbac.md) - Security flow
- [Architecture Overview](overview.md) - System architecture

## Debugging Request Flow

### Enable Request Logging

```python
import logging

logging.getLogger("middleware").setLevel(logging.DEBUG)
logging.getLogger("fastapi").setLevel(logging.DEBUG)
```

### Add Debug Endpoint

```python
@app.get("/debug/request-state")
async def debug_state(request: Request):
    return {
        "path": request.url.path,
        "host": request.headers.get("host"),
        "store": request.state.store.name if hasattr(request.state, 'store') else None,
        "store_id": getattr(request.state, 'store_id', None),
        "clean_path": getattr(request.state, 'clean_path', None),
        "frontend_type": request.state.frontend_type.value if hasattr(request.state, 'frontend_type') else None,
        "has_theme": bool(getattr(request.state, 'theme', None))
    }
```

### Check Middleware Order

In `main.py`, middleware registration order is critical:

```python
# REVERSE order (Last In, First Out)
app.add_middleware(LoggingMiddleware)         # Runs first
app.add_middleware(ThemeContextMiddleware)    # Runs sixth
app.add_middleware(LanguageMiddleware)        # Runs fifth
app.add_middleware(FrontendTypeMiddleware)    # Runs fourth
app.add_middleware(StoreContextMiddleware)   # Runs second
```
