Your material, turned into practice.
Turns any document, video or podcast into summaries, flashcards and quizzes, scheduled with FSRS-4.5 — one .NET API behind web, admin and mobile apps.
Personal product · toto-study.com · 2025 – 2026 · source on GitHub

- 4
- DeployablesAPI, web, admin, mobile
- 234
- Document typesincl. OCR for scans
- 9
- LLM providersswitchable in settings
- 1,512
- Backend testsxUnit, all passing
Under the hood
Clean Architecture + CQRS
Dependencies point inward: API → Application → Domain ← Infrastructure. Handlers depend only on interfaces, never on a vendor.
Dependencies point inward; the pipeline hangs off Infrastructure
Solid arrows are calls, dashed are dependency inversion. The AI key arrives in X-AI-* headers and lives only for that request, so tokens stream straight back as SSE.
The stack, layer by layer
Arrows are compile-time references. Domain references nothing.
Captions from the cloud
It worked on a laptop. On AWS, YouTube asked it to sign in.
From the production box, every yt-dlp caption request came back “Sign in to confirm you’re not a bot”. The fix: stop looking like a server.
What counts as a bot
Where it comes from
An AWS address — cloud IP ranges are public, so it reads as a script.
Instead: Residential proxies instead of the server's own IP.
Who is asking
Nobody — no session cookies, so YouTube asks it to sign in.
Instead: Cookies exported from real signed-in browsers.
How often
Too many requests from one IP or session — HTTP 429 or 403.
Instead: Round-robin over the pool; a flagged pair sits out 5 minutes.
One caption request, from a cloud IP
Each failure is classified, so the retry swaps only what was blamed: a dead proxy costs a proxy, a bot check costs a cookie set. Five attempts, then Whisper.
All systems degrade gracefully
Degrade,
don’t fail.
Every dependency has a fallback. Slower, never broken.
- Redis is downPostgres serves the cacheproduction runs without Redis
- A video has no captionsWhisper transcribes the audio
- Semantic search failskeyword search answers
- An export job stallsit is requeued after 30 minutes
How it works
From upload to review.
- 1
Upload
Hashed for duplicates, stored in S3.
- 2
Extract
Parsed by type; video via captions, else a queued Whisper job.
- 3
Embed
Chunks indexed in pgvector.
- 4
Generate
Streamed over SSE, with citations.
- 5
Review
Scheduled by FSRS-4.5 on every client.