# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **Pamietnik** (Codename: RALPH) is a life/travel journal consisting of three components: - `app/` — Android app (Kotlin + Jetpack Compose) - `backend/` — Go REST API + server-side rendered Web UI - `README.md` — single source of truth for architecture, requirements, and backlog ## Backend (`backend/`) **Stack:** Go, chi router, pgx/v5 (PostgreSQL), golang-migrate, Argon2id **Run locally:** ```bash cd backend docker-compose up -d # starts PostgreSQL + API on :8080 ./start.sh # alternative start script ``` **Run only PostgreSQL and server manually:** ```bash docker-compose up -d postgres go run ./cmd/migrate # run DB migrations go run ./cmd/server # starts API on :8080 (default) ``` **Env vars** (with defaults): - `DATABASE_URL` — `postgres://ralph:ralph@localhost:5432/ralph?sslmode=disable` - `LISTEN_ADDR` — `:8080` - `UPLOAD_DIR` — `./uploads` **Tests:** ```bash go test ./... # all tests go test ./internal/api/... # handler tests only ``` **Create a user:** ```bash go run ./cmd/createuser ``` **DB migrations** live in `migrations/` and are applied via `cmd/migrate`. ### Backend Architecture ``` cmd/ server/ entry point: wires stores → router → http.Server migrate/ runs golang-migrate against DATABASE_URL createuser/ CLI to create users with Argon2id-hashed passwords internal/ domain/ shared model types (Trackpoint, Stop, Suggestion, …) db/ pgx store implementations (trackpoints, stops, suggestions, journal) auth/ session store (Postgres-backed), Argon2id hashing api/ router.go chi router wiring + middleware middleware.go session auth middleware ingest.go POST /v1/trackpoints, POST /v1/trackpoints:batch query.go GET /v1/days, /v1/trackpoints, /v1/stops, /v1/suggestions webui.go server-side rendered web UI (login, /days, /days/{date}) journal.go journal entry endpoints response.go shared response helpers ``` Key invariants: - Idempotency via unique `(device_id, event_id)` — duplicate inserts return 200 OK - Geocoding is event-driven (per Stop only), never bulk/periodic - Sessions are stored in PostgreSQL (invalidatable on logout) - OpenAPI spec must be kept current (`openapi.yaml`); changes only via PR + CI validation ## Android App (`app/`) **Stack:** Kotlin, Jetpack Compose, Room (SQLite), WorkManager, MapLibre **Build:** ```bash cd app ./gradlew assembleDebug ./gradlew installDebug # install on connected device/emulator ``` **Tests:** ```bash ./gradlew test # JVM unit tests ./gradlew connectedAndroidTest # instrumentation tests (device required) ./gradlew :app:testDebugUnitTest # single module unit tests ``` ### Android Architecture ``` de.jacek.reisejournal/ domain/ Trackpoint domain model data/ Room entities, DAOs, local DB service/ Background location foreground service worker/ WorkManager upload worker ui/ home/ HomeScreen (Compose) + HomeViewModel navigation/ NavGraph theme/ Compose theme ``` Key invariants: - Offline-first: every point persisted to Room before any upload attempt - All points carry a client-generated `event_id` (UUID) for server-side idempotency - `source` field distinguishes `"gps"` vs `"manual"` trackpoints - Upload worker uses `NetworkConnected` constraint + retry/backoff - No DB or network access directly in Composables — always via ViewModel ## Project Decisions (from README) | Decision | Choice | |----------|--------| | Android UI | Jetpack Compose | | Local DB | Room / SQLite | | Backend DB | PostgreSQL | | Maps | MapLibre + OpenStreetMap tiles (configurable source) | | Geocoding | Nominatim (OSM), cached, provider-swappable via config | | Auth (Web UI) | Session Cookie (Postgres-backed) | | Password hashing | Argon2id | | API spec | OpenAPI 3.1 (`openapi.yaml`) | ## Open Decisions (TBD) - `timestamp` format: `epochMillis` vs RFC3339 - Android upload auth: X-API-Key vs Bearer/JWT - HTTP payload: JSON vs Protobuf - Batch limits (max items / max bytes) - Retention policy for trackpoints - Stop detection parameters (min duration, radius) - Geocoding provider: Nominatim public vs self-hosted vs alternative