go-doc/data/docs/go-places-developer.md
2026-10-07 15:05:48 +02:00

8.6 KiB

Imported from DEVELOPER.md in the go-places repository.


Developer Workflow Guide for Go Places Project

This document outlines the standard operating procedure for building, testing, and deploying go-places, the service in this repository. It authenticates callers against go-loose, a separate SSO service that is not part of this repository.

🚀 Core Dependencies & Structure

The system relies on the following structure:

  • Root: /home/dnoulet/go/go-places/
  • API: api/ — the Go module root (module go-places-api)
  • Dependency: go-loose — the SSO/authentication service. It lives in a separate repository and is not present here; only its Helm chart (charts/go-loose/) is kept in this repository.
  • Web: web/ — React front-end
  • Infrastructure: all Kubernetes deployment definitions are in charts/

🛠️ Local Development & Building (The Makefile)

All development tasks should begin by referencing the Makefile in the project root.

1. Building Local Binaries (For testing/local runs)

This simulates the container image build process locally.

make build
  • make build-api: compiles the service to bin/go-places.
  • make build-loose: not available here. go-loose is a separate repository, so this target reports that clearly instead of failing obscurely.

2. Running Locally

make run

Starts the API with AUTH_MODE=dev on :8080, backed by a local SQLite file and ./data/media. Open http://localhost:8080/graphql in a browser for a small query console.

3. Testing

make test    # go test ./...
make race    # go test -race ./...
make vet     # go vet ./...
make fmt     # go fmt ./...
make check   # everything CI runs: fmt, vet, race, web typecheck, helm lint

4. Validating Infrastructure

Before attempting deployment, always lint the Helm charts to ensure all YAML templates are valid.

make validate

This runs helm lint against charts/api (go-places) and charts/go-loose.

5. Container Image

docker build -t go-places:latest .

The build context is the repository root, because the Dockerfile copies api/. The container runs unprivileged and exposes the API on port 8080.

☁️ Deployment to Cluster (Helm/Rancher)

The production deployment strategy leverages Kubernetes via Helm, managed by Rancher.

make deploy

This runs helm upgrade --install for the go-places chart (from charts/api, whose internal name is go-places) and then the go-loose chart, both into the dev namespace.

📡 HTTP Surface

Route Auth Purpose
GET /healthz no Liveness probe.
GET /readyz no Readiness probe; pings the database.
POST /graphql bearer Queries and mutations. This is the only route that executes a query.
GET /graphql no Browser query console. A ?query= parameter is rejected, because this route is unauthenticated.
POST /places/{placeId}/images bearer Multipart picture upload (image field, optional caption).
POST /upload bearer Same upload, with placeId given as a form field.
GET /media/{key} no Serves a stored picture.
GET /api/places bearer REST list, ENABLE_REST=true.
GET /api/places/{placeId} bearer REST read, including relatedPlaces and each picture's linkedImages.
POST /api/places bearer REST create.
GET /api/places/{placeId}/images bearer REST picture list.

Health probes and /media/ are the only unauthenticated data routes, and neither carries user data: a kubelet cannot present a token, and an <img> tag cannot attach one. Everything else requires a bearer token.

Every setting is read from the environment by internal/config; see api/internal/config/config.go for the full list and its defaults.

Authentication. With AUTH_MODE=dev, the token valid_token identifies the user dev. With AUTH_MODE=jwt, an HS256 token issued by go-loose must carry sub and exp, and may carry username and email. The algorithm is pinned to HS256, so a token cannot select none or an asymmetric algorithm.

Uploads. Analysis runs inline: the picture is stored, decoded, and turned into badges before the response is written. A picture whose bytes cannot be decoded is still stored, with analysisStatus: failed and the reason in analysisError.

Linking. Image.linkedImages and Place.relatedPlaces are computed on read from the pictures' own coordinates (EXIF GPS when present, otherwise the place's coordinate). Nothing is persisted, so links always reflect the current set of pictures.

💾 Storage and durability

SQLite and the uploaded pictures share a single volume, mounted at /app/data (persistence.mountPath). The chart requests a ReadWriteOnce claim and forces a Recreate strategy, because SQLite permits one writer and a rolling update would briefly run two pods against the same database file. replicaCount must stay at 1.

The volume is what makes the travel log survive a pod being rescheduled. To confirm:

kubectl delete pod -n dev -l app.kubernetes.io/name=go-places

A place and its pictures are still there afterwards. The image runs as uid 10001, and securityContext.fsGroup is set to the same value so the non-root process can create the database file inside the volume.

Two limits worth knowing:

  • The claim cannot be resized. The cluster's local-path StorageClass does not allow volume expansion, so editing persistence.size makes the API server reject the resized claim. helm upgrade then aborts part-way and leaves the release in STATUS=failed (the running pod and its data are unaffected); recover with helm rollback or by upgrading again without the size change. Grow the storage or add an expandable StorageClass first.
  • local-path is node-local. Data lives on the node's own disk under /var/lib/rancher/k3s/storage, so it survives pod replacement but not loss of that node. It also means the service is pinned to one node. Its reclaim policy is Retain, so deleting the claim does not destroy the data directory.

Set persistence.enabled=false to fall back to an emptyDir. That is useful for a throwaway demo and wrong for anything real: the database and every picture are lost on each pod replacement.

📚 Workflow Summary

  1. Develop: make code changes in api/ or web/.
  2. Test: make build && make check.
  3. Run: make run, then cd web && npm run dev.
  4. Validate: make validate.
  5. Deploy: make deploy.

🖥️ Web front-end

web/ is a Vite + React client for the REST routes above, not for GraphQL.

cd web
npm install
npm run dev      # http://localhost:5173

The dev server proxies /api, /media, /healthz and /graphql to the API, so the browser only ever makes same-origin requests and CORS never enters the picture. It expects the API on http://localhost:8080; override with VITE_PROXY_TARGET.

The client has no login flow: identity belongs to go-loose, so it asks for a token and keeps it in localStorage. VITE_API_TOKEN bakes one into the bundle at build time, and VITE_API_BASE points the client at an API on another origin. A build without either asks for a token, which is what you want in production — a token in a public bundle is public.

cd web
npm run build     # -> web/dist, a static bundle
npx tsc --noEmit  # type check

web/dist is not served by the API image and charts/api does not deploy it. See the front-end section of INFRASTRUCTURE.md for the two ways to publish it.

🚫 Known Gaps / TODOs

  • There is no Docker Compose setup, so RabbitMQ and a remote inference endpoint are not exercised locally by default. Without AMQP_URL the service logs events instead.
  • Object recognition is heuristic. Detecting an arbitrary subject ("a dog") needs the remote analyzer at ANALYZER_URL/v1/analyze; the local analyzer only produces scene and property badges such as Water, Sky, Vivid and Nature.
  • shared-interfaces/schema/models.graphql documents the domain structs. It is maintained by hand, so the executable schema in api/internal/transport/schema.go stays the source of truth and a drift check is still worth adding.
  • go-loose is consumed purely as an HTTP/SSO dependency; there is no in-process client to it.
  • Only comments can be read over REST. Adding one, or deleting a place or picture from the UI, needs a GraphQL call or new REST routes.

This guide assumes familiarity with Go, Docker, and Kubernetes concepts.