# Request Flow

Two representative request paths through the stack: a **page load** (SSR) and a
**media playback** (presigned URL → direct MinIO stream).

## SSR Page Load — Catalogue / Book Detail

```mermaid
sequenceDiagram
    actor C as Browser / iOS App
    participant CD as Caddy :443
    participant UI as SvelteKit UI :3000
    participant BE as Backend API :8080
    participant MS as Meilisearch :7700
    participant PB as PocketBase :8090
    participant VK as Valkey :6379

    C->>CD: HTTPS GET /catalogue
    CD->>UI: proxy /* (SvelteKit catch-all)
    UI->>BE: GET /api/catalogue?page=1&sort=popular
    BE->>MS: search(query, filters, sort)
    MS-->>BE: [{slug, title, …}, …]
    BE-->>UI: {books[], page, total, has_next}
    UI-->>CD: SSR HTML
    CD-->>C: 200 HTML

    Note over C,UI: Infinite scroll — client fetches next page via SvelteKit API route
    C->>CD: HTTPS GET /api/catalogue-page?page=2
    CD->>UI: proxy /* (SvelteKit /api/catalogue-page server route)
    UI->>BE: GET /api/catalogue?page=2
    BE->>MS: search(…)
    MS-->>BE: next page
    BE-->>UI: {books[], …}
    UI-->>C: JSON
```

## Audio Playback — Presigned URL Flow

```mermaid
sequenceDiagram
    actor C as Browser / iOS App
    participant CD as Caddy :443
    participant UI as SvelteKit UI :3000
    participant BE as Backend API :8080
    participant VK as Valkey :6379
    participant MN as MinIO :9000

    C->>CD: GET /api/presign/audio/{slug}/{n}?voice=af_bella
    CD->>UI: proxy /* (SvelteKit /api/presign/audio route)
    UI->>BE: GET /api/presign/audio/{slug}/{n}?voice=af_bella
    BE->>VK: GET presign:audio:{slug}:{n}:{voice}
    alt cache hit
        VK-->>BE: presigned URL (TTL remaining)
        BE-->>UI: 302 redirect → presigned URL
        UI-->>C: 302 redirect
    else cache miss
        BE->>MN: GeneratePresignedURL(audio-bucket, key, 1h)
        MN-->>BE: presigned URL
        BE->>VK: SET presign:audio:… EX 3500
        BE-->>UI: 302 redirect → presigned URL
        UI-->>C: 302 redirect
    end
    C->>MN: GET presigned URL (direct, no proxy)
    MN-->>C: audio/mpeg stream
```

## Chapter Read — SSR + Content Fetch

```mermaid
sequenceDiagram
    actor C as Browser / iOS App
    participant CD as Caddy :443
    participant UI as SvelteKit UI :3000
    participant BE as Backend API :8080
    participant PB as PocketBase :8090
    participant MN as MinIO :9000

    C->>CD: HTTPS GET /books/{slug}/chapters/{n}
    CD->>UI: proxy /* (SvelteKit catch-all)
    UI->>PB: getBook(slug) + listChapterIdx(slug)
    PB-->>UI: book meta + chapter list
    UI->>BE: GET /api/chapter-text/{slug}/{n}
    BE->>MN: GetObject(chapters-bucket, {slug}/{n}.md)
    MN-->>BE: markdown text
    BE-->>UI: plain text (markdown stripped)
    Note over UI: marked() → HTML
    UI-->>CD: SSR HTML
    CD-->>C: 200 HTML
```

## Caddy Request Lifecycle

Shows how security hardening applies before a request reaches any upstream.

```mermaid
flowchart TD
    A([Incoming HTTPS request]) --> B[TLS termination\nLet's Encrypt cert]
    B --> C{Rate limit check\ncaddy-ratelimit}
    C -- over limit --> D[429 Too Many Requests]
    C -- ok --> E[Add security headers\nX-Frame-Options · X-Content-Type-Options\nReferrer-Policy · Permissions-Policy\nHSTS · X-XSS-Protection\nremove Server header]
    E --> F{Route match}
    F -- "/health /scrape*\n/api/browse /api/book-preview/*\n/api/chapter-text/*\n/api/reindex/* /api/cover/*\n/api/audio-proxy/*" --> G[reverse_proxy → backend:8080]
    F -- "/avatars/*" --> H[reverse_proxy → minio:9000]
    F -- "/* everything else\n(incl. /api/scrape/*\n/api/chapter-text-preview/*)" --> I[reverse_proxy → ui:3000\nSvelteKit auth middleware runs]
    G --> J{Upstream healthy?}
    H --> J
    I --> J
    J -- yes --> K([Response to client])
    J -- "502/503/504" --> L[handle_errors\nstatic HTML from /srv/errors/]
    L --> K
```
