- Caddy: custom image with caddy-ratelimit plugin, security headers (X-Frame-Options, HSTS, CSP-adjacent, etc.), per-IP rate limiting on auth/scrape/global zones, static error pages (502/503/504), fix routing to remove /api/scrape/* and /api/chapter-text-preview/* direct-to-backend (were bypassing SvelteKit auth middleware) - docker-compose: Caddy build context + error volume, Watchtower service (label-enable mode, 5 min poll), watchtower labels on backend/runner/ui - Scraper: ScrapeChapterList uses retryGet (9 attempts, Retry-After backoff) to fix 429-induced chapter list failures; upTo param stops pagination early for range scrapes - UI: Browse→Catalogue rename (routes, API, links), admin scrape page Continue/Retry buttons, +error.svelte branded error page, type cleanup (removed dead exports, added BookPreviewMeta/BookPreviewResponse to scraper.ts) - Meilisearch: meta_updated field, sort=update fix, facet distribution - Docs: reorganise into docs/d2/ and docs/mermaid/ subdirectories, update all diagrams to reflect Caddy/Watchtower/routing changes, add api-routing.d2 ownership map with auth-level colour coding, regenerate SVGs
202 lines
7.0 KiB
Plaintext
202 lines
7.0 KiB
Plaintext
direction: right
|
|
|
|
# ─── Legend ───────────────────────────────────────────────────────────────────
|
|
|
|
legend: Legend {
|
|
style.fill: "#fafafa"
|
|
style.stroke: "#d4d4d8"
|
|
|
|
pub: public {
|
|
style.fill: "#f0fdf4"
|
|
style.font-color: "#15803d"
|
|
style.stroke: "#86efac"
|
|
}
|
|
user: user auth {
|
|
style.fill: "#eff6ff"
|
|
style.font-color: "#1d4ed8"
|
|
style.stroke: "#93c5fd"
|
|
}
|
|
adm: admin only {
|
|
style.fill: "#fff7ed"
|
|
style.font-color: "#c2410c"
|
|
style.stroke: "#fdba74"
|
|
}
|
|
}
|
|
|
|
# ─── Client ───────────────────────────────────────────────────────────────────
|
|
|
|
client: Browser / iOS App {
|
|
shape: person
|
|
style.fill: "#fff9e6"
|
|
}
|
|
|
|
# ─── Caddy ────────────────────────────────────────────────────────────────────
|
|
|
|
caddy: Caddy :443 {
|
|
shape: rectangle
|
|
style.fill: "#f1f5f9"
|
|
label: "Caddy :443\ncustom build · caddy-ratelimit\nsecurity headers · rate limiting\nstatic error pages"
|
|
}
|
|
|
|
# ─── SvelteKit UI ─────────────────────────────────────────────────────────────
|
|
# Handles: auth enforcement, session, all /api/* routes that have SK counterparts
|
|
|
|
sk: SvelteKit UI :3000 {
|
|
style.fill: "#fef3c7"
|
|
|
|
auth: Auth {
|
|
style.fill: "#fde68a"
|
|
style.stroke: "#f59e0b"
|
|
label: "POST /api/auth/login\nPOST /api/auth/register\nPOST /api/auth/change-password\nGET /api/auth/session"
|
|
}
|
|
|
|
catalogue_sk: Catalogue {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/catalogue-page\nGET /api/search"
|
|
}
|
|
|
|
book_sk: Book {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/book/{slug}\nGET /api/chapter/{slug}/{n}\nGET /api/chapter-text-preview/{slug}/{n}"
|
|
}
|
|
|
|
scrape_sk: Scrape (admin) {
|
|
style.fill: "#fff7ed"
|
|
style.stroke: "#fdba74"
|
|
label: "GET /api/scrape/status\nGET /api/scrape/tasks\nPOST /api/scrape\nPOST /api/scrape/range\nPOST /api/scrape/cancel/{id}"
|
|
}
|
|
|
|
audio_sk: Audio {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "POST /api/audio/{slug}/{n}\nGET /api/audio/status/{slug}/{n}\nGET /api/voices"
|
|
}
|
|
|
|
presign_sk: Presigned URLs {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/presign/chapter/{slug}/{n}\nGET /api/presign/audio/{slug}/{n}\nGET /api/presign/voice-sample/{voice}"
|
|
}
|
|
|
|
presign_user: Presigned URLs (user) {
|
|
style.fill: "#eff6ff"
|
|
style.stroke: "#93c5fd"
|
|
label: "GET /api/presign/avatar-upload/{userId}\nGET /api/presign/avatar/{userId}"
|
|
}
|
|
|
|
progress_sk: Progress {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/progress\nPOST /api/progress/{slug}\nDELETE /api/progress/{slug}"
|
|
}
|
|
|
|
library_sk: Library {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/library\nPOST /api/library/{slug}\nDELETE /api/library/{slug}"
|
|
}
|
|
|
|
comments_sk: Comments {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/comments/{slug}\nPOST /api/comments/{slug}"
|
|
}
|
|
}
|
|
|
|
# ─── Go Backend ───────────────────────────────────────────────────────────────
|
|
# Caddy proxies these paths directly — no SvelteKit auth layer
|
|
|
|
be: Backend API :8080 {
|
|
style.fill: "#eef3ff"
|
|
|
|
health_be: Health {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /health\nGET /api/version"
|
|
}
|
|
|
|
scrape_be: Scrape admin (direct) {
|
|
style.fill: "#fff7ed"
|
|
style.stroke: "#fdba74"
|
|
label: "POST /scrape\nPOST /scrape/book\nPOST /scrape/book/range"
|
|
}
|
|
|
|
catalogue_be: Catalogue {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/browse\nGET /api/catalogue\nGET /api/ranking\nGET /api/cover/{domain}/{slug}"
|
|
}
|
|
|
|
book_be: Book / Chapter {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/book-preview/{slug}\nGET /api/chapter-text/{slug}/{n}\nGET /api/chapter-markdown/{slug}/{n}\nPOST /api/reindex/{slug} ⚠ admin"
|
|
}
|
|
|
|
audio_be: Audio {
|
|
style.fill: "#f0fdf4"
|
|
style.stroke: "#86efac"
|
|
label: "GET /api/audio-proxy/{slug}/{n}\nGET /api/voices"
|
|
}
|
|
}
|
|
|
|
# ─── Storage ──────────────────────────────────────────────────────────────────
|
|
|
|
storage: Storage {
|
|
style.fill: "#eaf7ea"
|
|
|
|
pb: PocketBase :8090 {
|
|
shape: cylinder
|
|
label: "auth · books · progress\ncomments · library\nscrape_jobs · audio_cache"
|
|
}
|
|
mn: MinIO :9000 {
|
|
shape: cylinder
|
|
label: "chapters · audio\navatars · browse"
|
|
}
|
|
ms: Meilisearch :7700 {
|
|
shape: cylinder
|
|
label: "index: books"
|
|
}
|
|
vk: Valkey :6379 {
|
|
shape: cylinder
|
|
label: "presign URL cache"
|
|
}
|
|
}
|
|
|
|
# ─── Caddy routing ────────────────────────────────────────────────────────────
|
|
|
|
client -> caddy: HTTPS :443
|
|
|
|
caddy -> sk: "/* (catch-all)\n→ SvelteKit handles auth"
|
|
caddy -> be: "/health /scrape*\n/api/browse /api/book-preview/*\n/api/chapter-text/* /api/chapter-markdown/*\n/api/reindex/* /api/cover/*\n/api/audio-proxy/* /api/catalogue /api/ranking"
|
|
caddy -> storage.mn: "/avatars/*\n(presigned GETs)"
|
|
|
|
# ─── SvelteKit → Backend (server-side proxy) ──────────────────────────────────
|
|
|
|
sk.catalogue_sk -> be.catalogue_be: internal proxy
|
|
sk.book_sk -> be.book_be: internal proxy
|
|
sk.audio_sk -> be.audio_be: internal proxy
|
|
sk.presign_sk -> storage.vk: check cache
|
|
sk.presign_sk -> storage.mn: generate presign
|
|
sk.presign_user -> storage.mn: generate presign
|
|
|
|
# ─── SvelteKit → Storage (direct) ────────────────────────────────────────────
|
|
|
|
sk.auth -> storage.pb: sessions / users
|
|
sk.scrape_sk -> storage.pb: scrape job records
|
|
sk.progress_sk -> storage.pb
|
|
sk.library_sk -> storage.pb
|
|
sk.comments_sk -> storage.pb
|
|
|
|
# ─── Backend → Storage ────────────────────────────────────────────────────────
|
|
|
|
be.catalogue_be -> storage.ms: full-text search
|
|
be.catalogue_be -> storage.pb: ranking records
|
|
be.catalogue_be -> storage.mn: cover presign
|
|
be.book_be -> storage.mn: chapter objects
|
|
be.book_be -> storage.pb: book metadata
|
|
be.audio_be -> storage.mn: audio presign
|
|
be.audio_be -> storage.vk: presign cache
|