From 3ce9cb86734c50c654aa70798afadbfe102635bb Mon Sep 17 00:00:00 2001 From: Merve Doker Date: Thu, 17 Sep 2026 14:07:46 +0300 Subject: [PATCH] docs(readme): running notes, handled edge cases and production checklist Co-Authored-By: Claude Fable 5.1 --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/README.md b/README.md index 0434444..82606a1 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,92 @@ docker-compose.yml * **CD** runs on pushes to `main` and `v*` tags and publishes `ghcr.io/dkrmerve/cpp-taskboard-backend` and `ghcr.io/dkrmerve/cpp-taskboard-frontend`. +## Running notes + +### Ports and environment + +| Variable | Default | Used by | Meaning | +|--------------|-----------|----------|------------------------------------------------------| +| `PORT` | `8080` | backend | HTTP listen port | +| `HOST` | `0.0.0.0` | backend | Bind address (`127.0.0.1` to keep it local-only) | +| `STATIC_DIR` | *(unset)* | backend | If set, serves that folder at `/` (frontend without nginx) | + +| Host port | Service | Notes | +|-----------|----------|-----------------------------------------| +| `3000` | frontend | nginx; `/api/*` is proxied to backend | +| `8080` | backend | Direct API access, handy for curl/Postman | + +### Useful commands + +```bash +docker compose up --build -d # start in background +docker compose logs -f backend # request log: "POST /api/tasks -> 201" +docker compose ps # both services should say (healthy) +docker compose down # stop; add -v to drop volumes (none today) +``` + +### Troubleshooting + +* **Frontend loads but shows a red dot** – backend is down or not yet healthy. + `docker compose logs backend` and `curl localhost:8080/api/health`. +* **Port already in use** – change the host side of the mapping in + `docker-compose.yml` (`"3001:80"`), the container side stays as is. +* **Container reports unhealthy on Alpine/nginx** – healthchecks must use + `127.0.0.1`, not `localhost` (resolves to IPv6 first, nginx listens on IPv4). +* **`docker build` slow the first time** – FetchContent downloads cpp-httplib, + nlohmann/json and GoogleTest; later builds hit the layer cache. +* **Data disappears on restart** – expected, the store is in-memory (see below). + +## Edge cases handled + +| Case | Behaviour | Covered by test | +|---------------------------------------------|---------------------------------------------|-----------------| +| Empty / whitespace-only title | 400 `title is required`, nothing stored | `CreateTrimsTitleAndRejectsEmpty`, `CreateValidatesInput` | +| Title padded with spaces | Trimmed before storing | `CreateTrimsTitleAndRejectsEmpty` | +| Update that would blank the title | 400, original task untouched | `UpdateRejectsEmptyTitleAndKeepsOriginal` | +| Empty body / malformed JSON / wrong field type | 400 with a specific error message | `CreateValidatesInput`, `ParsePatch.RejectsWrongTypes` | +| Unknown status value (`"DONE"`, `"in-progress"`) | 400, only `todo`, `in_progress`, `done` accepted | `Status.RejectsUnknownStrings` | +| Unknown `?status=` filter | 400 instead of silently returning nothing | `ListFilterAndStats` | +| Non-existent id on GET/PUT/DELETE | 404 | `GetByIdAnd404`, `UpdateChangesStatus`, `DeleteRemovesTask` | +| Non-numeric id (`/api/tasks/abc`) | Route does not match → 404 | `GetByIdAnd404` | +| Deleting the same task twice | Second call returns 404 | `DeleteRemovesTask` | +| Concurrent creates from many threads | Mutex-guarded store, ids stay unique and ordered | `ConcurrentCreatesProduceUniqueIds` | +| Partial update (`{"status": "done"}` only) | Other fields keep their values | `UpdateAppliesPartialPatch` | +| Browser on a different origin | CORS headers + `OPTIONS` preflight → 204 | `CorsHeadersArePresent` | + +Known limits, by design for this version: ids are 64-bit and never reused; +there is no maximum title length on the API side (the UI caps at 120/500 chars); +`created_at` is second-precision UTC. + +## Before going to production + +The stack is a complete, tested demo. Before exposing it to real users: + +- [ ] **Persistence** – the store is in-memory, so a restart wipes every task. Swap + `TaskStore` for SQLite/PostgreSQL behind the same interface (the API and + tests are already decoupled from the storage). +- [ ] **Authentication / authorisation** – every endpoint is public. Put the API + behind a reverse proxy with auth, or add token checks in `register_routes`. +- [ ] **Lock down CORS** – `Access-Control-Allow-Origin: *` is for development. + Set it to the real frontend origin. +- [ ] **TLS** – terminate HTTPS at nginx or a load balancer; the backend speaks + plain HTTP. +- [ ] **Request limits** – set `server.set_payload_max_length(...)`, read/write + timeouts and a title length cap to avoid abuse. +- [ ] **Graceful shutdown** – handle `SIGTERM` and call `server.stop()` so + rolling deploys do not cut requests mid-flight. +- [ ] **Observability** – switch the stdout logger to structured JSON logs, add a + `/metrics` endpoint (Prometheus) and a request-id header. +- [ ] **Resource limits** – add `deploy.resources.limits` (or Kubernetes + requests/limits) and a non-`unless-stopped` restart policy suited to the + orchestrator. +- [ ] **Image hygiene** – pin base images by digest, run Trivy/Grype in CI, and + tag releases (`v1.0.0`) so CD publishes immutable versions. +- [ ] **Secrets / config** – keep environment in a secrets manager, never in the + compose file committed to git. +- [ ] **Backups & migrations** – once persistence exists, script schema + migrations and automated backups before the first real deploy. + ## Branching `main` (production) ← `develop` (integration) ← `feature/*`, `fix/*`.