internal/client (new): a typed Go HTTP client for the API - login, register, logout, me, and full CRUD for games/platforms - shared by the TUI and the Fyne GUI so request-building, auth and error handling (an APIError with the HTTP status) live in one tested place instead of being duplicated. 8 tests against an httptest server covering success, 401, 403 and that the token is stored/cleared at the right times. cmd/tui (rewrite): drops the old unauthenticated read-only viewer. Now a login screen (bubbles/textinput) -> a scrollable games table (bubbles/table) with 'r' to refresh, built as an explicit state machine (screenLogin/screenLoading/screenGames) so Update's transitions are testable without a real terminal. 9 tests driving Update() directly. cmd/gui (rewrite): drops raylib for Fyne. Same shape as the TUI - a login form, then a games table, with networking on a goroutine and fyne.Do for the UI updates. The table-cell and login-validation logic are plain functions with no Fyne/display dependency, so they're tested directly (7 tests) rather than needing Fyne's headless test driver. internal/api: tagged every operation (Auth/Platforms/Games/Misc) so the generated OpenAPI spec groups into named services instead of one bucket; api/openapi.json regenerated. Added CORS middleware (wildcard origin, no credentials needed since clients use a bearer token, not the cookie) - needed for the new web client's browser requests to the API's a different origin. web/ (new): an Angular 22 + Material app, API client generated from api/openapi.json via ng-openapi-gen (services: true for per-resource classes - AuthService/GamesService/PlatformsService - instead of one generic Api facade). A Session service holds the token/user as signals, persists the token to localStorage, and is the only thing that calls the generated AuthService; an HttpInterceptorFn attaches the bearer token to every request; a CanActivateFn guard protects /games. Login and Games are standalone, lazy-loaded route components. 21 tests (Vitest, Angular's new default - no browser/Karma needed) covering the service, interceptor, guard and both components, run via `npm test` exactly as CI will call it. Justfile: fixed run-tui/run-gui, which used `go run .../main.go` - broken now that both packages span multiple files, since that only compiles the one named file. Added web-install/web-dev/web-build/web-test. .gitea/workflows/ci.yml (moved from .github, which Gitea doesn't read): on push to main/develop - gofmt, go vet, go build, go test, go mod tidy diff (with the Fyne build's system deps installed), sqlc diff, and the web app's build+test. .gitea/workflows/release.yml (new): on a vX.Y.Z tag, cross-compiles api+tui for linux/darwin/windows (amd64+arm64, pure Go, no cgo), builds the Fyne GUI for linux/amd64 only (cgo + its OpenGL/X11 link means it can't cross-compile from one Linux runner the way api/tui do, and this repo has no registered macOS/Windows runner to build it on natively - documented in the workflow's comments), builds the web app, and creates a Gitea release with all of it attached via the Gitea API. Written to Gitea's documented GitHub-Actions-compatible syntax but not executable from here - there's no Gitea server in this environment to run it against, unlike everything else in this commit, which was. Verified: every Go package builds/vets/tests clean; sqlc diff and go mod tidy -diff are clean; the Angular app builds for production and its test suite passes via `npm test`/`npm run build` (the exact commands CI runs), including after a clean `npm ci` from the committed lockfile; the new CORS config was checked with real preflight and GET requests against a running server, not just read. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Completed
A tracker for a video game collection: what you own, which platform it's on, your score for it, and whether (and when) you finished it.
Status
Each account has its own private game list, but all accounts share one platform list (only an admin can add/rename/delete a platform - the first account ever registered becomes an admin automatically). The API is usable. There are three small-base clients: a terminal UI, a desktop GUI (Fyne), and a web app (Angular) — each can log in and show your games, with more to come.
Running it
just docker-up # starts Postgres and the API on :8080
just docker-down # stops them
Or against a Postgres you already have running locally:
export DATABASE_URL="postgres://postgres:password@localhost:5432/gamedb?sslmode=disable"
just run-api
The API creates its database and runs migrations on startup if needed.
Browse http://localhost:8080/docs for the generated API docs.
Using the API
# Register (also logs you in; the first account ever registered becomes
# an admin, which is required to create platforms - see Status above)
curl -X POST localhost:8080/api/auth/register -H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"a long password","name":"You"}'
# -> {"token": "...", "user": {...}}
TOKEN=... # the token from above
curl -X POST localhost:8080/api/platforms -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"name":"PC"}'
curl localhost:8080/api/games -H "Authorization: Bearer $TOKEN"
A browser-based client can skip the Authorization header: register/login
also set an httpOnly session cookie.
Clients
just run-tui— a terminal UI (Bubble Tea). Log in, see your games in a scrollable table,rto refresh.just run-gui— a desktop GUI (Fyne). Log in, see your games in a table, refresh.just web-dev— a web app (Angular + Material) athttp://localhost:4200, proxying nothing itself; it talks directly to the API atlocalhost:8080(CORS is permissive enough for local dev as-is). Log in, see your games.
All three point at http://localhost:8080 by default. The TUI and GUI read
COMPLETED_API_URL to use a different API; the web app's URL is a build-time
constant in web/src/app/app.config.ts.
Each client is a small base - listing is there, but adding/editing a game or managing platforms isn't yet.
Development
just test # go test ./...
just sqlc # regenerate internal/db after changing db/query.sql or a migration
just openapi # regenerate api/openapi.json after changing a route
just web-test # web app unit tests (Vitest)
just web-build # production build of the web app
Schema changes go in db/migrations as a new <timestamp>_<name>.up.sql /
.down.sql pair; sqlc.yaml reads the schema from that directory directly,
so there's no separate schema file to keep in sync.
The TUI and GUI share internal/client, a typed Go HTTP client for the API.
The web app's API client (web/src/app/api/) is generated from
api/openapi.json by ng-openapi-gen (cd web && npx ng-openapi-gen) and
shouldn't be hand-edited.
CI/CD
.gitea/workflows/ci.yml builds and tests everything (Go + web) on every
push to main or develop. .gitea/workflows/release.yml builds all three
clients and attaches them to a Gitea release when a vX.Y.Z tag is pushed;
see that file's comments for its one real limitation (the desktop GUI is
only built for linux/amd64, since it needs cgo and this repo has no
registered macOS/Windows runner to cross-build it on).