docs: reorganize and trim README

This commit is contained in:
2026-06-21 02:25:14 +02:00
committed by Milas Holsting
parent 8ebaac758c
commit ce64efaf5f

282
README.md
View File

@@ -4,6 +4,10 @@
<img src="/static/assets/logo.png" alt="MyAnimeList logo" width="120" />
</p>
<p align="center">
<strong>A local-first anime catalog, watchlist, recommendation, and playback app.</strong>
</p>
<p align="center">
<img alt="Go" src="https://img.shields.io/badge/go-1.25-00ADD8?style=flat-square&logo=go" />
<img alt="SQLite" src="https://img.shields.io/badge/database-sqlite-003B57?style=flat-square&logo=sqlite" />
@@ -13,99 +17,93 @@
<img alt="License" src="https://img.shields.io/badge/license-MIT-green?style=flat-square" />
</p>
MyAnimeList is a self-hosted anime tracker, catalog browser, watchlist, and playback app built as a
portfolio-grade full-stack project. It brings the parts of an anime discovery workflow that usually
live across several services into one local application: browsing titles, saving a watchlist,
tracking progress, continuing episodes, reading metadata, finding recommendations, and watching
through a browser player backed by provider integrations.
---
The project is intentionally built like a small real-world product instead of a throwaway demo. The
backend is a Go application with feature-oriented modules, SQLite persistence, schema migrations,
startup data fixes, provider clients, and tests around the parts that are easy to regress. The UI is
mostly server-rendered with Go templates and enhanced with HTMX where partial updates make sense.
TypeScript is reserved for browser-heavy behavior such as the video player, search interactions,
theme handling, progress updates, subtitles, skip segments, and carousel controls.
MyAnimeList is a self-hosted media app for browsing anime, managing a watchlist, resuming episodes,
and playing streams through a browser-based player. It collects the parts of an anime workflow that
usually live across several products and keeps them in one small Go application backed by SQLite.
## Why This Exists
I built it as a portfolio project, but the goal was never to make a disposable demo. The interesting
part of the project is the product shape: server-rendered pages, a local database, provider
integrations, playback proxying, recommendations, migrations, tests, and a TypeScript player that
only appears where browser state actually earns its place.
I made this project to explore what a focused, personal media app can look like when it is treated
with the same care as a production product. Anime tracking is a good problem space because it touches
many practical engineering concerns without needing a large team or cloud platform:
> [!NOTE]
> This is a personal, local-first project. It is written to demonstrate product engineering choices,
> not to present itself as an official MyAnimeList client or a hosted streaming platform.
- local authentication and user-owned data
- catalog search, browse filters, details pages, and recommendations
- long-lived state such as watchlists, playback progress, and continue-watching rows
- server-rendered pages with small, deliberate frontend islands
- integration boundaries around external metadata and playback providers
- background refresh policies, cache behavior, migrations, and data repair jobs
- observability, request context, error handling, and testable service boundaries
## Contents
The goal is not to replace a commercial anime platform. The goal is to show how I think about product
engineering: keeping the architecture understandable, making features feel cohesive, choosing boring
tools where possible, and building enough operational structure that the app feels like it could keep
growing.
- [What This Project Is](#what-this-project-is)
- [What It Includes](#what-it-includes)
- [How It Is Built](#how-it-is-built)
- [Working Locally](#working-locally)
- [Repository Map](#repository-map)
- [Project Documents](#project-documents)
## What It Can Do
## What This Project Is
- Browse and search anime using external catalog data.
- View detail pages with metadata, synopsis, reviews, characters, statistics, related titles, themes,
and watch-order information.
- Maintain a local watchlist with status-oriented user state.
- Continue watching from stored playback progress.
- Play episodes through an HLS-capable web player.
- Rewrite playlists and proxy playback/subtitle requests through the server.
- Track progress, completion, episode navigation, quality, keyboard controls, and player state in
TypeScript.
- Support subtitles, subtitle caching, and VTT parsing.
- Manage skip segments and local skip-segment overrides.
- Generate personalized top picks from watchlist taste signals and recommendation data.
- Run SQLite migrations and one-off data fixes as the data model evolves.
- Create local users and run maintenance commands from `cmd/user`.
This project started from a simple idea: anime tracking becomes more interesting when catalog data,
personal progress, and playback live in the same interface. A user should be able to discover a
title, inspect its metadata, add it to a watchlist, watch an episode, come back later, and continue
from the right place without stitching that flow together manually.
## Architecture
That makes the app a useful playground for real application concerns. It has authentication,
long-lived user state, external APIs, background refresh behavior, migrations, data fixes, cache
boundaries, provider-specific code, and enough frontend complexity to justify TypeScript without
turning the whole product into a single-page app.
The repository is organized around product features and integration boundaries:
The project is also intentionally modest. It uses a single Go server and a SQLite database because
those choices make the system easy to run, inspect, and reason about. The architecture is more about
clear ownership than novelty: feature packages own their handlers and services, integrations stay at
the edges, and the UI is mostly rendered by the server.
| Path | Purpose |
## What It Includes
| Area | What it does |
| --- | --- |
| `cmd/server` | Application entry point for the web server. |
| `cmd/user` | Local admin and maintenance commands. |
| `internal/anime` | Catalog, details, browse, search, reviews, and recommendations wiring. |
| `internal/auth` | Local authentication, middleware, and user session behavior. |
| `internal/watchlist` | Watchlist handlers, service logic, and persistence access. |
| `internal/playback` | Playback state, progress, proxy tokens, skip segments, and watch data. |
| `internal/episodes` | Episode refresh and provider mapping logic. |
| `internal/database` | SQLite setup, migrations, and startup data fixes. |
| `internal/db` | Generated and helper database access code. |
| `integrations/jikan` | Jikan API client, rate limiting, query helpers, and catalog types. |
| `integrations/playback/allanime` | Playback provider client and extraction logic. |
| `templates` | Server-rendered pages and reusable Go template components. |
| `static` | TypeScript source for client-side interactions and player behavior. |
| `scripts` | Bun-powered development and maintenance scripts. |
| Catalog | Browse, search, and inspect anime metadata from external catalog sources. |
| Details | Render synopsis, reviews, characters, statistics, relations, themes, and watch-order data. |
| Watchlist | Store local user state for saved titles, statuses, and progress-driven flows. |
| Playback | Serve watch pages, proxy streams/subtitles, rewrite playlists, and track progress. |
| Player | Handle HLS playback, quality selection, subtitles, keyboard controls, episode navigation, and skip segments. |
| Recommendations | Generate personal top picks from watchlist signals and recommendation data. |
| Maintenance | Run migrations, startup fixes, local user commands, and data repair scripts. |
The application uses Go for the long-running server, domain services, provider clients, and data
access. SQLite keeps the app simple to run locally while still supporting real schema evolution
through migrations. Bun is used for the frontend toolchain, TypeScript checks, formatting, linting,
and small developer scripts. `mise` pins the tool versions so a fresh checkout can get to a working
environment quickly.
<details>
<summary><strong>Implementation notes</strong></summary>
## Technology Choices
The backend is written in Go with Gin for HTTP routing and Fx for module wiring. SQLite is used for
local persistence, with migrations and data fixes committed alongside the application. Templates are
rendered on the server, HTMX handles small partial updates, and TypeScript powers the interactive
parts of the browser experience.
- **Go** keeps the backend fast, explicit, and easy to ship as a single binary.
- **Gin** provides HTTP routing and middleware without hiding the request lifecycle.
- **Uber Fx** wires modules together in a way that keeps feature packages independent.
- **SQLite** makes the project self-contained and easy to run without external infrastructure.
- **Goose** manages migrations as the schema changes.
- **Go templates** keep most UI rendering close to the server data model.
- **HTMX** adds small partial updates without turning the app into a full SPA.
- **TypeScript** handles the browser behavior that benefits from strong client-side structure.
- **Tailwind CSS** provides a compact styling workflow.
- **Bun** runs the frontend build, lint, format, and script tasks.
- **mise** pins local tool versions for reproducible development.
The most stateful frontend code lives under `static/player`, where the app handles playback mode,
source loading, progress storage, subtitles, timelines, quality changes, keyboard shortcuts, skip
segments, episode completion, and thumbnail navigation.
## Developer Experience
</details>
This project is set up around a small number of repeatable commands. The main workflow is:
## How It Is Built
The application is organized around product boundaries rather than framework layers. `internal/anime`
owns catalog-facing behavior, `internal/watchlist` owns saved user state, `internal/playback` owns
watch data and proxy behavior, and `integrations` contains provider clients. This keeps the core app
from depending directly on the details of a specific metadata or playback source.
Server-rendered templates are the default because most pages are content-heavy and benefit from
simple request-response rendering. TypeScript is used where the browser has real ongoing state:
search interactions, theme handling, carousels, watchlist actions, toast messages, and especially
the video player.
The result is a codebase that behaves like a small product rather than a tutorial project: it has a
repeatable toolchain, database evolution, local maintenance commands, focused tests, and a clear
split between app code and external integrations.
## Working Locally
The local workflow assumes [`mise`](https://mise.jdx.dev/) for tool versions and `just` for common
commands.
```bash
mise install
@@ -113,80 +111,86 @@ bun install
just dev
```
`just dev` runs the app through Air, which rebuilds the Go server and frontend assets when relevant
files change. The default server address is `http://localhost:3000`.
The development server runs on `http://localhost:3000` by default. `just dev` uses Air to rebuild the
Go server and frontend assets when relevant files change.
Configuration is read from environment variables, and a local `.env` file is loaded automatically.
Useful variables include:
| Variable | Default | Purpose |
| --- | --- | --- |
| `PORT` | `3000` | HTTP port for the local server. |
| `DATABASE_FILE` | `mal.db` | SQLite database path. |
| `GIN_MODE` | release default | Gin runtime mode. |
| `MAL_CORS_ALLOW_ALL` | disabled | Allows any origin when set to `1`; useful only for local or proxy setups. |
| `PLAYBACK_PROXY_SECRET` | empty | Enables signed playback proxy tokens when set. |
| `EPISODE_AVAILABILITY_MODE` | `auto` | Episode availability strategy: `auto`, `legacy`, or `jikan`. |
| `MAL_JIKAN_TRACE` | disabled | Enables optional Jikan client tracing when truthy. |
## Common Commands
The project uses `just` as the command runner:
| Command | Description |
| --- | --- |
| `just setup` | Install pinned tools with `mise` and install Bun dependencies. |
| `just dev` | Start the local development server with live rebuilds. |
| `just build` | Build the Go server, Tailwind CSS, and TypeScript assets. |
| `just test` | Run the Go test suite. |
| `just fmt` | Format Go code. |
| `bun run format` | Format TypeScript and related frontend files with `oxfmt`. |
| `just lint-go` | Run `golangci-lint` across Go packages. |
| `just lint-ts` | Run type-aware `oxlint` against the TypeScript source. |
| `just typecheck` | Run `tsc` without emitting files. |
| `just check` | Run linting, tests, typechecking, and a full build. |
| `just run` | Build and run the compiled server binary. |
| `just clean` | Remove generated build output. |
| `just new-data-fix name` | Scaffold a new data-fix file. |
| `just run-fixes` | Run registered data fixes through the user command. |
| `just fix-all` | Run the Bun maintenance script for data fixes. |
To create a local user after setup:
Create a local user with:
```bash
go run ./cmd/user <username> <password>
```
## Quality Bar
### Commands
The codebase aims for a practical production style:
| Command | Use it for |
| --- | --- |
| `just setup` | Install pinned tools and Bun dependencies. |
| `just dev` | Run the app locally with live rebuilds. |
| `just build` | Build the Go binary, CSS, and TypeScript assets. |
| `just test` | Run the Go test suite. |
| `just check` | Run linting, tests, typechecking, and a full build. |
| `just lint-go` / `just lint-ts` | Run backend or frontend linting separately. |
| `just typecheck` | Run TypeScript without emitting files. |
| `just run` | Build and run the compiled server. |
| `just clean` | Remove generated build output. |
- feature packages own their handlers, services, repositories, and module wiring;
- external provider behavior is isolated in `integrations`;
- migrations are versioned and committed with the application code;
- template components use named props instead of implicit positional state;
- tests focus on database helpers, provider behavior, rendering helpers, recommendations, playback,
observability, and request handling;
- linting and typechecking are part of the normal local workflow;
- maintenance scripts are kept in the repo instead of living as undocumented one-off commands.
<details>
<summary><strong>Configuration</strong></summary>
## Project Status
Configuration is loaded from environment variables, and a local `.env` file is read automatically.
This is a personal portfolio project and local-first application. It is still evolving, but the repo
is structured to make future work straightforward: add migrations when the data model changes, keep
provider-specific logic behind integration packages, prefer server-rendered flows by default, and use
TypeScript only where browser state is genuinely doing work.
| Variable | Default | Purpose |
| --- | --- | --- |
| `PORT` | `3000` | HTTP port for the server. |
| `DATABASE_FILE` | `mal.db` | SQLite database path. |
| `GIN_MODE` | release default | Gin runtime mode. |
| `MAL_CORS_ALLOW_ALL` | disabled | Allows any origin when set to `1`; intended for local/proxy setups. |
| `PLAYBACK_PROXY_SECRET` | empty | Enables signed playback proxy tokens when set. |
| `EPISODE_AVAILABILITY_MODE` | `auto` | Episode availability strategy: `auto`, `legacy`, or `jikan`. |
| `MAL_JIKAN_TRACE` | disabled | Enables optional Jikan client tracing when truthy. |
## Community And Security
</details>
Please read the project docs before contributing or using code from this repository:
<details>
<summary><strong>Maintenance commands</strong></summary>
- [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) describes the expected standard for respectful
participation.
- [`SECURITY.md`](SECURITY.md) explains how to report security issues and how this project treats
sensitive playback, auth, and local data concerns.
- [`LICENSE`](LICENSE) contains the MIT license for this project.
| Command | Use it for |
| --- | --- |
| `just new-data-fix name` | Scaffold a new data-fix file. |
| `just run-fixes` | Run registered data fixes through `cmd/user`. |
| `just fix-all` | Run the Bun maintenance script for data fixes. |
| `bun run format` | Format TypeScript and related frontend files with `oxfmt`. |
## License
</details>
This project is released under the MIT License. See [`LICENSE`](LICENSE) for the full text.
## Repository Map
| Path | Responsibility |
| --- | --- |
| `cmd/server` | Web server entry point. |
| `cmd/user` | Local user and maintenance commands. |
| `internal/anime` | Catalog, details, browse, search, reviews, and recommendations. |
| `internal/auth` | Authentication, middleware, and local user handling. |
| `internal/watchlist` | Watchlist handlers, service logic, and persistence. |
| `internal/playback` | Watch data, progress, proxy tokens, and skip segments. |
| `internal/episodes` | Episode refresh and provider mapping. |
| `internal/database` | SQLite setup, migrations, and startup data fixes. |
| `integrations/jikan` | Jikan API client and catalog types. |
| `integrations/playback/allanime` | Playback provider client and extraction logic. |
| `templates` | Server-rendered pages and reusable components. |
| `static` | TypeScript source for client-side behavior. |
| `scripts` | Bun-powered development and maintenance scripts. |
## Project Documents
This repository includes the usual project-level documents for public review and reuse:
| Document | Purpose |
| --- | --- |
| [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) | Sets expectations for respectful project participation. |
| [`SECURITY.md`](SECURITY.md) | Explains how to report vulnerabilities and what is in scope. |
| [`LICENSE`](LICENSE) | MIT license terms. |
---
Released under the [MIT License](LICENSE).