chore: adding docs
This commit is contained in:
parent
23816d00f7
commit
81c5112409
39 changed files with 7257 additions and 2 deletions
168
data/docs/go-places-developer.md
Normal file
168
data/docs/go-places-developer.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<!-- go-doc:generated:begin source=go-places:DEVELOPER.md signature=0fcd11f2310a851002924fdb59e5ad85 generated=2026-10-05 18:14 UTC -->
|
||||
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.
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
make validate
|
||||
```
|
||||
|
||||
This runs `helm lint` against `charts/api` (go-places) and `charts/go-loose`.
|
||||
|
||||
### 5. Container Image
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
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.
|
||||
<!-- go-doc:generated:end -->
|
||||
Loading…
Add table
Add a link
Reference in a new issue