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-l4 · caddy-ratelimit\nCrowdSec bouncer · security headers\nrate limiting · static error pages\nRedis TCP proxy :6380" } # ─── SvelteKit UI ───────────────────────────────────────────────────────────── # All routes here pass through SvelteKit — auth is enforced server-side. 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 (infinite scroll)\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/book\nPOST /api/scrape/book/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 (public) { 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 — bypasses SvelteKit entirely. be: Backend API :8080 { style.fill: "#eef3ff" health_be: Health / Version { 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/catalogue (Meilisearch)\nGET /api/browse (legacy MinIO cache)\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: "POST /api/audio/{slug}/{n}\nGET /api/audio/status/{slug}/{n}\nGET /api/audio-proxy/{slug}/{n}\nGET /api/voices" } presign_be: 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}\nGET /api/presign/avatar-upload/{userId}\nGET /api/presign/avatar/{userId}" } } # ─── Storage ────────────────────────────────────────────────────────────────── storage: Storage { style.fill: "#eaf7ea" pb: PocketBase :8090 { shape: cylinder label: "auth · books · progress\ncomments · library\nscrape_jobs · audio_cache\nranking" } mn: MinIO :9000 { shape: cylinder label: "chapters · audio\navatars · catalogue (browse)" } ms: Meilisearch :7700 { shape: cylinder label: "index: books\nfilterable: status · genres\nsortable: rank · rating\n total_chapters · meta_updated" } vk: Valkey :6379 { shape: cylinder label: "presign URL cache (TTL ~55 min)\nAsynq job queue (runner)" } } # ─── Caddy routing ──────────────────────────────────────────────────────────── client -> caddy: HTTPS :443 caddy -> sk: "/* (catch-all)\n→ SvelteKit enforces auth" caddy -> be: "/health /scrape*\n/api/browse /api/catalogue /api/ranking\n/api/version /api/book-preview/*\n/api/chapter-text/* /api/chapter-markdown/*\n/api/reindex/* /api/cover/*\n/api/audio* /api/voices /api/presign/*" caddy -> storage.mn: "/avatars/* /audio/* /chapters/*\n(presigned MinIO 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 -> be.presign_be: internal proxy sk.presign_user -> be.presign_be: internal proxy # ─── 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 + facets 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 be.presign_be -> storage.vk: check / set presign cache be.presign_be -> storage.mn: generate presigned URL