diff --git a/README.md b/README.md index 373f26e..8fac801 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,42 @@ # MyAnimeList - - - - - -
- - - MyAnimeList logo - - - MyAnimeList
- My personal anime tracker, built because nothing else felt right. -
+

+ + + MyAnimeList logo + +
+ My personal anime tracker, built because nothing else felt right. +

Go SQLite - Tailwind + Tailwind HTMX

---- +MyAnimeList home page screenshot -## Why this project exists +I was frustrated with every anime tracker I tried. Decent UI but awkward UX. Good features but missing the ones I actually use. So I built my own: search fast, get context fast, update your status fast, and move on. -I built this for myself. +This project is personal first — I put it on GitHub because I like shipping in the open. It also doubles as proof that a small, server-rendered Go app can stay reliable even when upstream anime APIs are inconsistent. -I was frustrated with the UI and UX of every tracker I tried. Even when something looked decent, it still felt awkward to use day-to-day, or it was missing pieces I considered essential. I wanted one place that matched how I actually watch anime: search fast, get context fast, update status fast, and move on. - -So this project is personal first and public second. I put it on GitHub because I like shipping in the open, not because it was originally designed as a general-purpose product for everyone. - -Technically, I also wanted to prove that a small, server-rendered Go app could stay reliable even when upstream anime APIs are inconsistent. A lot of this code exists because real APIs rate-limit, timeout, and occasionally fail at the worst possible moment. - -## What the application offers - -For my own workflow, MyAnimeList combines catalog browsing, seasonal discovery, quick search, detail pages with recommendations and relations, watchlist management, continue-watching, and in-app playback in one server-rendered interface. - -The interface is minimal and functional, featuring a dark theme and quick access to tracking tools. +- **Catalog browsing & seasonal discovery** — explore what's airing, filter by season +- **Quick search** — find anime, get mal links, open the detail page in seconds +- **Detail pages** — synopsis, stats, recommendations, related entries +- **Watchlist management** — track your progress across statuses +- **Continue watching** — pick up where you left off +- **In-app playback** — proxy-based video player ## Technical approach -The application is written in Go and rendered on the server with `html/template`, with SQLite as the primary datastore and `sqlc` for typed query generation. Styling uses Tailwind CSS v4. HTMX and small TypeScript modules handle incremental interactions, which keeps the interface responsive without moving the entire product into a heavy client-side architecture. +Written in Go with server-rendered `html/template`, SQLite + `sqlc` for typed queries, Tailwind CSS v4 for styling, and HTMX backed by small TypeScript modules for incremental interactions. -The external anime data source is Jikan (`https://api.jikan.moe/v4`). Because reliability is a first-class concern, the client layer includes request pacing, bounded retries, backoff behavior, stale-cache fallback, and a persisted retry queue for failed fetches. Playback proxying uses uTLS to bypass Cloudflare protections. - -Upstream APIs can fail transiently with `429` and `5xx` responses, so the app favors graceful degradation over hard failure. Cached values are used when fresh requests fail, retryable failures are persisted and replayed in a background worker, and relation synchronization is incremental so one bad fetch does not block the rest of the graph. +The external anime data source is [Jikan](https://api.jikan.moe/v4). The client layer handles request pacing, bounded retries, backoff, stale-cache fallback, and a persisted retry queue. Playback proxying uses uTLS to bypass Cloudflare. The system is built to degrade gracefully under `429` and `5xx` responses rather than fail hard. ## Repository structure -The codebase follows standard Go project layout conventions. - | Path | Purpose | | ----------------- | ------------------------------------------------ | | `api/*` | Feature routes: anime, auth, playback, watchlist | @@ -63,74 +48,18 @@ The codebase follows standard Go project layout conventions. | `migrations` | Schema evolution | | `static` / `dist` | Frontend assets | -## Getting started +## Running locally -Requires Go `1.25+`, Bun, and [just](https://github.com/casey/just) (`brew install just`). +Requires Go `1.25+`, Bun, and [just](https://github.com/casey/just). Migrations run on startup. Configuration lives in environment variables — see `cmd/server/main.go` for the full list. ```bash -git clone https://github.com/mkelvers/mal.git && cd mal -openssl rand -base32 32 -PLAYBACK_PROXY_SECRET="your-32-char-secret" go run ./cmd/server -go run ./cmd/user +just dev ``` -The app runs at `http://localhost:3000`. +## Contributing -### Tasks - -The justfile automates common tasks: - -```bash -just fmt # format go code -just lint # go fmt && go vet -just test # run go tests -just build # build go binary + frontend -just check # lint, test, typecheck, build -just dev # build and run -just install-hooks # install pre-push hooks -``` - -### Docker - -```bash -docker build -t mal . -docker run --rm -p 3000:3000 -e PLAYBACK_PROXY_SECRET="$(openssl rand -base32 32)" mal - -# persistent data -docker run --rm -p 3000:3000 \ - -e DATABASE_FILE=/app/data/mal.db \ - -e PLAYBACK_PROXY_SECRET="your-secret" \ - -v "$(pwd)/data:/app/data" \ - mal - -docker exec mal ./cmd/user -``` - -## Configuration - -| Variable | Default | Description | -| ----------------------- | ------------------- | ----------------------------------------------------------- | -| `PORT` | `3000` | HTTP listen port | -| `DATABASE_FILE` | `mal.db` | SQLite database file path | -| `ENV` | _(empty)_ | Set to `production` to enable secure session cookies | -| `MIGRATIONS_DIR` | _(auto-discovered)_ | Optional explicit path to migration files | -| `PLAYBACK_PROXY_SECRET` | _(required)_ | HMAC secret for signed playback proxy tokens (min 32 chars) | -| `MAL_JIKAN_TRACE` | `false` | Log all Jikan cache/upstream timings when enabled | - -## Testing - -Run locally with `just check` or manually: - -```bash -go test ./... -``` - -Migrations run automatically on startup. - -## Security - -Keep secrets out of version control, do not publish real credentials in documentation or screenshots, and report security issues privately before public disclosure. +Bug reports and pull requests are welcome. This is a personal project, so there is no strict roadmap or issue triage cycle. If something is broken or missing, open an issue or send a PR. ## License -This project is released under the MIT License. See `LICENSE` for details. +MIT. See `LICENSE`. diff --git a/static/assets/screenshot-home.png b/static/assets/screenshot-home.png new file mode 100644 index 0000000..fec6f47 Binary files /dev/null and b/static/assets/screenshot-home.png differ