xtablo-source/backend
Arthur Belleville 62e5e3eb60
feat(06-01): add river dependency and ListOrphanFiles sqlc query
- go get github.com/riverqueue/river@v0.37.0 + riverpgxv5@v0.37.0
- append ListOrphanFiles :many query to files.sql (orphan tablo_files rows)
- regenerate sqlc: ListOrphanFilesRow{ID, TabloID, S3Key} exported
- go build ./... exits 0
2026-05-15 16:32:48 +02:00
..
bin feat(01-01): create directory skeleton and per-package doc.go placeholders 2026-05-14 17:53:55 +02:00
cmd fix(05-WR-01): raise ReadTimeout/WriteTimeout to 120s for large uploads 2026-05-15 12:50:25 +02:00
internal feat(06-01): add river dependency and ListOrphanFiles sqlc query 2026-05-15 16:32:48 +02:00
migrations feat(05-01): add aws-sdk-go-v2 modules, 0005_files migration, sqlc queries, and files.Store 2026-05-15 12:18:16 +02:00
static chore(04-01): Sortable.js bootstrap and soft-danger button CSS 2026-05-15 09:24:44 +02:00
templates test(05-files): add pure unit tests for formatBytes, byteCountReader, and content-type sniff 2026-05-15 13:29:08 +02:00
.air.toml feat(01-01): sqlc config, tailwind input CSS, air live-reload config 2026-05-14 17:54:35 +02:00
.env.example feat(02-01): create internal/auth package skeleton, test DB harness, env docs 2026-05-14 21:56:45 +02:00
.gitignore feat(01-01): compose file, env example, gitignore, bootstrap migration 2026-05-14 17:54:18 +02:00
compose.yaml test(05-01): add RED test scaffold for FILE-01..06 and MinIO in compose.yaml 2026-05-15 12:19:23 +02:00
go.mod feat(06-01): add river dependency and ListOrphanFiles sqlc query 2026-05-15 16:32:48 +02:00
go.sum feat(06-01): add river dependency and ListOrphanFiles sqlc query 2026-05-15 16:32:48 +02:00
justfile chore(04-01): Sortable.js bootstrap and soft-danger button CSS 2026-05-15 09:24:44 +02:00
README.md docs(01-04): add backend/README.md quickstart (closes FOUND-05) 2026-05-14 19:31:19 +02:00
sqlc.yaml feat(02-01): add sqlc queries + citext/uuid overrides; generate bindings 2026-05-14 21:52:48 +02:00
tailwind.input.css fix(01): guard sqlc on empty queries and correct tailwind paths 2026-05-14 20:09:39 +02:00

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 https://github.com/casey/just)
  • 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.jsjust 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 ~510 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