Some checks failed
CI / v3 / Check ui (pull_request) Failing after 15s
CI / v3 / Test backend (pull_request) Failing after 16s
CI / v3 / Docker / backend (pull_request) Has been skipped
CI / v3 / Docker / runner (pull_request) Has been skipped
CI / v3 / Docker / ui (pull_request) Has been skipped
- Remove all pre-v3 code: scraper, ui-v2, backend v1, ios v1+v2, legacy CI workflows - Flatten v3/ contents to repo root - Add Doppler secrets management (project=libnovel, config=prd) - Add justfile with doppler run wrappers for all docker compose commands - Strip hardcoded env fallbacks from docker-compose.yml - Add minimal README.md - Clean up .gitignore
112 lines
3.8 KiB
Markdown
112 lines
3.8 KiB
Markdown
# 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
|
|
```
|