# Fiesta backend (`nearle`) The Go/Fiber API behind the Nearle Daily merchant console, the customer app, the rider app and the in-store POS terminals. Postgres (`nearledb`) for tenants, stores, products, stock and orders; a separate pgvector database for the global product catalogue; Redis for POS presence; MQTT for the tills. This page is the map. Each section says what a thing is, how to use it, and where the detail lives. ## Run it ```sh export PATH="$PATH:$HOME/go/bin" # Go 1.24 lives there on the dev Macs docker compose -f docker-compose.local.yml up -d # postgres :5433, pgvector :5434, redis :6379 go run . # APP_ENV unset → .env.local, listens on :1122 go test ./... ``` An empty database is not enough — startup runs migrations that assume the live schema. `init/README.md` explains loading a schema dump first. Startup prints what it loaded and where it is pointed: ``` config: loaded .env.local config: APP_ENV=local, listening on :1122, database nearle@localhost:5433/nearledb scan: product search uses openai/all-minilm # or: EMBEDDING_PROVIDER not set, text-only ``` ## Configuration Everything comes from environment variables, read once by `config.Load()`. | You want to… | Do this | |---|---| | Run locally | Nothing — `.env.local` is loaded by default | | Run against production settings | `APP_ENV=production go run .` (⚠ every write is real) | | See every variable and what it does | `.env.example` | | Add a new setting | Add it to `config.Config` + `Load()` + `.env.example`, **and to the cluster** (`nearle-config` ConfigMap or `app-secrets` Secret in namespace `nearle`) — a variable in the file and not in the cluster is unset in production | | Find out why it won't boot | Read the message — it lists *every* missing variable at once | Precedence is `real environment > .env. > .env`. The container gets no `.env` file at all (`.dockerignore`); the `Dockerfile` sets `APP_ENV=production` and the values come from Kubernetes. Full detail, including the committed-credentials situation: **`docs/ENVIRONMENT.md`**. ## Layout ``` main.go boot: config → databases → migrations → routes → MQTT → listen config/ env-file loading, typed Config, validation db/ Postgres (nearledb + catalogue), Redis, S3 image store facade/ wires repositories → services → controllers (add new modules here) routes/ one file per module; /live/api/v1/web/... (console) and /v1/mob/... (apps) controllers/ HTTP in, HTTP out — parse, call the service, shape the envelope services/ the rules; no SQL, no HTTP repositories/ the SQL; nothing else models/ request/response and table shapes messaging/ MQTT ingest from tills, console live stream utils/ small shared helpers (tokens, geo, embeddings, geocoding) docs/ integration specs for the frontends and handoff notes scratch/ one-off read/verify tools run with `go run ./scratch/` init/ schema/seed for the local database ``` Every response uses the same envelope: `{ "code": 200, "status": true, "message": "…", "details": … }`. Business outcomes ("out of stock", "not registered") are 200s with a reason in the body; HTTP errors mean the request could not be served at all. ## Adding an endpoint 1. **Model** the request/response in `models/`. 2. **Repository** method(s) in `repositories/` — SQL only, take a `context.Context`, return `error`. 3. **Service** in `services/` — the rules, with sentinel errors (`ErrXxxBadRequest`, `ErrXxxNotFound`) the controller can map to statuses. 4. **Controller** in `controllers/` — `BodyParser`/`Query`, call the service, map sentinel errors to 400/404/503, everything else to 500. 5. **Routes** file in `routes/`, registered in `routes/routes.go`. 6. **Wire** it in `facade/container.go`. 7. **Test** the service with a fake repository (see `services/scan_test.go` for the pattern) — no database needed. `services/scanService.go` + `controllers/scanController.go` are a complete, current example of all seven. ## Features with their own docs | Feature | For | Doc | |---|---|---| | Environment & deployment | everyone | `docs/ENVIRONMENT.md` | | Scan-to-order (camera → product → nearest store with stock) | mobile app | `docs/SCAN_TO_ORDER.md` | | Catalogue import into a store | console | `docs/CATALOGUE_IMPORT_INTEGRATION.md` | | POS terminal ingest, login, API | POS / tills | `docs/POS_*.md` | | Access-control audit and what is still open | everyone | `docs/SECURITY_HANDOFF.md` | ## Things to know before you get surprised - **There is no auth layer.** `customerid` / `tenantid` in a request are trusted. `docs/SECURITY_HANDOFF.md` §1 is the standing issue. - **Login errors mean what they say.** `409 Invalid Email` = the query ran and matched nobody. `500 Login is temporarily unavailable` = the database could not answer (it used to be reported as Invalid Email; see `services/userService.go` `lookupLogin`). - **Stock is a ledger.** Live stock is always `SUM(in) − SUM(out)` of `productstocks` at an outlet, never a stored number. Filter on the same expression you display (`services/productVisibility.go` explains why). - **The catalogue is a different database** and must never be reached through the `nearledb` handle. Its per-brand tables are discovered from `information_schema`; brands appear and columns vary. - **Catalogue ids are not stable** across re-scrapes; `imageid` is the durable key (`models.Products.Imageid`). - **Migrations run on boot** and are guarded by `IF NOT EXISTS` / schema checks, not a version table. Read the comments in `main.go` before adding one — several have bitten before. - **One MQTT client id per replica.** A second connection with the same id evicts the first. Never run a local process with the production `MQTT_URL`. - **`scratch/` tools read production** when run with `.env.production`. They are read-only by construction; keep them that way. ## Operations cheat-sheet (Kubernetes, namespace `nearle`) ```sh kubectl get pods -n nearle # fiesta-0/1/2 (StatefulSet) kubectl logs -n nearle fiesta-0 | grep -E 'config:|scan:|pos:' kubectl exec -n nearle fiesta-0 -- env | grep EMBEDDING_ kubectl set env statefulset/fiesta -n nearle KEY=value # adds a var and rolls the pods kubectl rollout status statefulset/fiesta -n nearle ``` The embedding model behind scan-to-order is served by the cluster's Ollama (`ollama.krow.svc.cluster.local:11434`); `docs/SCAN_TO_ORDER.md` has the exact settings and why that model.