diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..736acab --- /dev/null +++ b/backend/README.md @@ -0,0 +1,195 @@ +# Xtablo backend + +Go + HTMX + Postgres. Phase 1: Walking Skeleton. + +This README is the contract for FOUND-05: a developer with the prerequisites below +should be able to clone the repo, follow the Quickstart, and see the HTMX-driven +page within ~5 minutes. + +## Prerequisites + +Install these on your dev machine before starting: + +- **Go** ≥ 1.22 (this project's `go.mod` declares 1.26) +- **just** — task runner (`brew install just` on macOS, `cargo install just`, or see + ) +- **podman** with `podman compose` (preferred per D-11) **or** **docker** with + `docker compose` +- **curl** +- **git** + +You do **not** need to install `goose`, `templ`, `sqlc`, `air`, the Tailwind CLI, or +`htmx.min.js` — `just bootstrap` installs the Go tools into `$GOBIN` and +bootstrap-downloads the Tailwind binary and HTMX script into local, gitignored +paths. + +## Quickstart + +Clone-to-running-page in ~5 minutes. Run from inside `backend/`. + +``` +cd backend +cp .env.example .env # adjust DATABASE_URL if Postgres is not on localhost:5432 +just bootstrap # installs goose/templ/sqlc/air; bootstrap-downloads tailwindcss + htmx.min.js +just db-up # starts postgres via podman compose (see fallback below) +just migrate up # applies migrations from ./migrations +just dev # terminal 1: brings up db, runs generate, then air on :8080 + +# in a SECOND terminal: +just styles-watch # rebuilds static/tailwind.css on .templ / .go changes + +# open http://localhost:8080 +``` + +The page should render with a "Fetch server time" button. Clicking it swaps an +ISO-8601 timestamp into the page via HTMX. If the page shows "No time fetched +yet." and nothing happens on click, see Troubleshooting. + +`bootstrap` is the slowest step (Go tool installs + two HTTP downloads). It only +needs to run once per clone. + +## docker compose fallback + +`compose.yaml` is portable across podman and docker — the service definition is +identical. If you don't have podman: + +- Replace `podman compose` with `docker compose` mentally throughout this README. +- The `just db-up` / `just db-down` recipes call `podman compose` directly. Run + `docker compose up -d postgres` / `docker compose down` instead, and continue + with the rest of the Quickstart unchanged. + +(Decision D-11.) + +## Project layout + +``` +backend/ + cmd/ + web/main.go # HTTP server entry point + worker/main.go # background worker (skeleton — boot/log/shutdown only) + internal/ + db/ # pgxpool wiring + sqlc-generated queries + web/ # chi router, handlers, middleware, design-system + ui/ # custom templ component library (Button, Card, Badge) + session/ # placeholder — Phase 2 + tablos/ # placeholder — Phase 3 + tasks/ # placeholder — Phase 4 + files/ # placeholder — Phase 5 + migrations/ # goose .sql migrations + templates/ # .templ files (layout, index, fragments) + static/ + htmx.min.js # bootstrap-downloaded by `just bootstrap`; gitignored; no runtime CDN + tailwind.css # generated by the Tailwind standalone CLI + bin/ # gitignored — tailwindcss CLI binary, etc. + .air.toml # air live-reload config + .env.example # committed; copy to .env + compose.yaml # local Postgres + go.mod / go.sum + justfile # task runner recipes — the source of truth for commands + sqlc.yaml + tailwind.input.css + README.md +``` + +HTMX is served from `/static/htmx.min.js` at runtime — no CDN. The justfile's +bootstrap-time `unpkg.com` URL is the single authoritative version pin (D-10). + +## Environment variables + +`backend/.env` is gitignored; `backend/.env.example` is committed and lists the +three keys consumed by `cmd/web` (and `cmd/worker` for `DATABASE_URL`): + +| Variable | Description | Default | +| -------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------- | +| `DATABASE_URL` | Postgres DSN used by the web + worker binaries and by `just migrate` | `postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable` | +| `PORT` | HTTP port for `cmd/web` | `8080` | +| `ENV` | `development` enables slog's text handler; `production` switches to JSON | `development` | + +## Common commands + +Every command in this table is a recipe in `backend/justfile`. + +| Recipe | What it does | When to use | +| ----------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------- | +| `just bootstrap` | Installs Go CLI tools (`goose`, `templ`, `sqlc`, `air`); bootstrap-downloads `bin/tailwindcss` and `static/htmx.min.js` | Once per clone; re-run after deleting `bin/` or `static/htmx.min.js` | +| `just db-up` | Starts the local Postgres container | Before `just migrate up` / `just dev` if not already running | +| `just db-down` | Stops the local Postgres container | When you're done for the day | +| `just migrate up` / `migrate down` / `migrate status` | Applies / reverts / inspects goose migrations against `DATABASE_URL` | After `just db-up`, or any time you change `migrations/` | +| `just generate` | One-shot: `templ generate`, `sqlc generate`, Tailwind compile to `static/tailwind.css` | After editing `.templ`, query SQL, or `tailwind.input.css` | +| `just styles-watch` | Tailwind standalone CLI in `--watch` mode | In a second terminal alongside `just dev` (D-14) | +| `just dev` | Brings up Postgres, runs `just generate`, then runs `air` for Go live-reload on `:8080` | Main dev loop, terminal 1 | +| `just test` | `templ generate` then `go test ./...` | Before committing | +| `just lint` | `go vet ./...` and `gofmt -l` check | Before committing | +| `just build` | Generates assets, then builds `bin/web` and `bin/worker` | Producing release binaries locally | +| `just clean` | Removes `bin/`, `tmp/`, `static/htmx.min.js`, `static/tailwind.css`, and `*_templ.go` files | Reset to a fresh-clone state without dropping the Postgres volume | + +## Worker (skeleton — Phase 1 only) + +`cmd/worker` in Phase 1 boots, logs `worker ready`, and idles waiting for a +signal. Real job runtime lands in Phase 6 (D-03). To run it manually: + +``` +DATABASE_URL=postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable \ + go run ./cmd/worker +``` + +Ctrl-C to exit. + +## Troubleshooting + +The three issues most likely to trip you up on a fresh clone: + +- **"Fresh clone fails to build with `undefined: templates.Index`"** — Templ + generates `*_templ.go` files from `.templ` sources, and those generated files + are not committed. Run `just generate` (or `just dev`, which calls it) before + invoking `go build` directly. (Pitfall 1.) + +- **"First request to `/healthz` returns 503 right after `just db-up`"** — The + Postgres container needs ~5–10 seconds to become healthy after `podman compose + up -d` returns. Check `podman compose ps` (or `docker compose ps`) for the + `healthy` status, or just wait and retry. Subsequent calls succeed. The 503 + during warm-up is correct behavior, not a bug. (Pitfall 2.) + +- **"Tailwind classes used in `.templ` files don't appear in the compiled CSS"** — + Tailwind v4 only scans content paths declared via `@source` in + `tailwind.input.css`. Confirm the file contains `@source + "../templates/**/*.templ";` (and equivalent globs for `internal/web/**/*.go`). + Re-run `just styles-watch` so the watcher picks up the config change. + (Pitfall 3.) + +If something else is wrong and you want a clean slate without dropping the +Postgres volume: + +``` +just clean # removes bin/, tmp/, static/htmx.min.js, static/tailwind.css, *_templ.go +just bootstrap # re-download tools and assets +just dev # back to a working state +``` + +Run `just db-down` first if you also want to drop the Postgres container. + +## What Phase 1 ships (and doesn't) + +**Ships:** + +- Project scaffold (`go.mod`, justfile, `.air.toml`, `tailwind.input.css`, + `sqlc.yaml`, `compose.yaml`) +- Local Postgres via `compose.yaml` (`pg_isready` healthcheck) +- goose migration pipeline (`migrations/0001_init.sql` is a no-op bootstrap) +- chi router with `/`, `/healthz`, `/demo/time`, `/static/*` +- slog-based structured logging with RequestID middleware +- Graceful HTTP shutdown +- pgxpool wiring exercised by `/healthz` +- templ + HTMX demo (root page + `hx-get` round-trip to a templ fragment) +- Custom templ design-system package at `internal/web/ui/` (Button, Card, Badge) +- Live-reload dev loop (`just dev` + `just styles-watch`) +- `cmd/worker` skeleton (boot, log, idle, shutdown) + +**Does not ship — deferred:** + +- Authentication, sessions, users → Phase 2 +- Tablos CRUD → Phase 3 +- Tasks / kanban → Phase 4 +- File uploads + R2/S3 → Phase 5 +- Real worker jobs → Phase 6 +- Production deploy, Dockerfile, `/readyz` → Phase 7