Commit Graph
4 Commits
Author SHA1 Message Date
SansanandClaude Sonnet 5 786fdbb0d5 Dockerize the API with an embedded web UI, Fyne-for-Windows, and real releases
cmd/api/main.go: registerWebUI serves a built web/ as a single-page app -
Echo's static middleware in HTML5 mode, with a Skipper that exempts
/api/*, /hello, /docs, /schemas/* and openapi.json/.yaml so it can never
shadow a real API route. It reads WEB_DIST_DIR (default /frontend, where
the Dockerfile puts it) and no-ops if that directory doesn't exist, so
`go run ./cmd/api` and CI stay Node-free and API-only; only the Docker
image actually turns the web UI on.

Dockerfile: new node:22-alpine stage builds web/, copied into the final
image at /frontend alongside the existing Go-built binary. Added
.dockerignore (web/node_modules, web/dist, web/.angular, .git) now that a
second language's tooling is in the build context.

Looked at how your other Gitea projects (MusicServer et al.) structure
this, since I can't exercise Gitea Actions itself from here: MusicServer's
own Dockerfile already does "copy a prebuilt frontend into the Go image,
serve it from Go" (there, from a separately-published frontend image) -
registerWebUI does the same thing in spirit, just within one image rather
than two, since this UI isn't meant to be published separately.

release.yml:
- build-gui now also cross-compiles the Fyne GUI for windows/amd64 via
  mingw-w64, alongside the existing linux/amd64. Verified for real, not
  just written: installed mingw-w64 locally and cross-built cmd/gui,
  first plain (got a working PE32+ console exe), then with
  -ldflags "-H=windowsgui" (got the GUI-subsystem exe actually wanted, no
  console window). macOS isn't attempted - cgo has no equivalent
  cross-toolchain for Darwin and there's no registered macOS runner here.
- new build-docker job pushes the API image (now carrying the web build)
  to gitea.sanplex.xyz/sansan/completed, tagged with the version and
  latest.
- replaced the hand-rolled curl+jq Gitea-release creation with
  akkuman/gitea-release-action, and artifact upload with the Gitea generic
  package registry - both are what your other projects (HanjaKanaMun,
  MusicPlayer) actually use, found by reading their workflows rather than
  guessing. Same for the registry host, owner, and secrets.TOKEN name
  (previously secrets.GITHUB_TOKEN, which nothing else here uses).

ci.yml: new docker job builds the same image on every push to main/develop
(push: false) as a health check, matching MusicServer's build-vs-release
split.

Verified for real wherever I could: a full `docker build` of the new
Dockerfile, then running that image against a real Postgres container and
hitting it with curl - / serves the actual built Angular index.html,
/games (a client-side route) correctly falls back to it, /api/games and
/hello are untouched (still return real API responses, not shadowed by
the static handler), and register+list-games works end to end through the
container. Also ran it the documented way, `docker compose up --build`,
with the same results. Separately confirmed the Windows Fyne cross-build
produces a real, correctly-subsystemed .exe. What I could not run from
here is Gitea Actions itself - the workflow YAML is written to match your
other projects' established, working conventions, but its first real
execution on your Gitea instance is still the first real test of it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-03 12:49:36 +02:00
SansanandClaude Sonnet 5 9972537b63 Add a small, tested base for all three clients, and Gitea CI/release workflows
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>
2026-10-03 12:16:59 +02:00
SansanandClaude Sonnet 5 5072919b61 Rebuild the schema and API on a working, multi-user foundation
Steps 1-4 of the review plan (docs/plans not checked in; see conversation):

Step 1 - make it boot from a clean checkout:
- Replace the old schema.sql + single ALTER-TABLE migration (which never
  created the tables) with one init migration: users, sessions, platform,
  game, all with UUIDv7-ready uuid ids, soft deletes (deleted_at), and a
  shared change_seq sequence + trigger for a future sync feature.
- sqlc now reads the schema from db/migrations directly.
- Migrations are embedded in the binary (db/migrations/embed.go) instead of
  being copied around and found via path fallbacks.
- ensureDatabase/runMigrations failures are now fatal, and main pings the
  pool after connecting, instead of starting an API with no schema.
- Switched both the app and golang-migrate to pgx/v5 (dropped lib/pq).
- Fixed the Dockerfile's Go version (1.23 -> 1.25) and build target.
- Added a postgres healthcheck to docker-compose so the app waits for a
  ready database instead of racing it.
- go.mod tidied, bin/ added to .gitignore, Justfile's docker-compose calls
  switched to the v2 `docker compose` plugin.

Step 2 - auth and per-user data:
- internal/auth: argon2id password hashing, random session tokens stored
  only as a hash.
- POST /api/auth/{register,login,logout}, GET /api/auth/me. Sessions work
  via either an Authorization: Bearer header or an httpOnly cookie.
- Every platform/game query is scoped to the authenticated user.

Step 3 - API correctness:
- internal/model holds the DTOs, with no server-side dependencies, so
  clients can import it later without pulling in Huma/Echo.
- dbError() maps ErrNoRows -> 404, unique violation -> 409, FK violation ->
  409, instead of leaking Postgres error text with a 500.
- Soft-delete meant the FK on game.platform_id no longer blocks deleting a
  referenced platform on its own; added a NOT EXISTS guard plus an
  application-level check so that case now correctly returns 409 instead of
  silently succeeding (caught by testing the live server, not by a mock).
- Added GET /games/{id} and GET /platforms/{id}. All JSON routes moved
  under /api, freeing / for a future web client.
- ListGames/GetGame join platform_name in.
- cmd/openapi writes the generated spec to api/openapi.json.

Step 4 - tests and CI:
- Rewrote the mocks and handler tests for the new Querier shape; added
  coverage for 401 (no session), 404 (not found / another user's row),
  409 (duplicate email, platform referenced by games), 422 (score out of
  range), and that queries are actually scoped by the authenticated user's
  id, not just that the right status code comes back.
- .github/workflows/ci.yml: go vet, go test, go mod tidy -diff, sqlc diff.

Everything above was exercised against a real local Postgres (migrations
up/down, the trigger, constraints) and through the running server via curl
(register, login, scoping, every status-code case), not just unit tests.

Known regression, intentionally deferred to the next step: cmd/tui and
cmd/gui still call the old unauthenticated GET /games, which no longer
exists. They need internal/client and a login flow before they work again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-03 11:24:11 +02:00
Sansan 47a9719eeb Created a base to build on 2025-12-22 21:19:31 +01:00