6.4 KiB
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 UIREADME.md— single source of truth for architecture, requirements, and backlog
Backend (backend/)
Stack: Go, chi router, pgx/v5 (PostgreSQL), golang-migrate, Argon2id
Run locally:
cd backend
docker-compose up -d # starts PostgreSQL + API on :8080
./start.sh # alternative start script
Run only PostgreSQL and server manually:
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=disableLISTEN_ADDR—:8080UPLOAD_DIR—./uploads
Tests:
go test ./... # all tests
go test ./internal/api/... # handler tests only
Create a user:
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:
cd app
./gradlew assembleDebug
./gradlew installDebug # install on connected device/emulator
Tests:
./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 sourcefield distinguishes"gps"vs"manual"trackpoints- Upload worker uses
NetworkConnectedconstraint + retry/backoff - No DB or network access directly in Composables — always via ViewModel
Webapp (webapp/)
Stack: TypeScript, Vite, Vanilla Web Components, MapLibre GL JS, nginx (Produktion)
Entwicklung:
cd webapp
npm install
npm run dev # Dev-Server :5173, proxied /v1 + /login + /logout → :8080
npm run typecheck # TypeScript-Prüfung ohne Build
Produktions-Build:
cd webapp
npm run build # statische Dateien in webapp/dist/
Build & Deployment
Gesamte Applikation (Docker)
# Im Root-Verzeichnis — baut und startet alle Services:
docker-compose up --build
| Service | Port | Beschreibung |
|---|---|---|
postgres |
intern | PostgreSQL 16 |
migrate |
— | Führt DB-Migrationen aus (einmalig, dann beendet) |
api |
8080 | Go REST API |
webapp |
9050 | SPA via nginx (proxied API-Calls zum api-Service) |
Webapp erreichbar unter http://localhost:9050.
Erster Benutzer anlegen (nach dem ersten Start):
docker-compose exec api /createuser
# oder lokal:
cd backend && go run ./cmd/createuser
Nur neu bauen ohne Cache:
docker-compose build --no-cache
docker-compose up
Logs:
docker-compose logs -f api
docker-compose logs -f webapp
Einzelne Komponenten bauen
Backend (Go-Binary):
cd backend
go build -o ./bin/server ./cmd/server
go build -o ./bin/migrate ./cmd/migrate
Backend (Docker-Image):
docker build -t pamietnik-backend ./backend
Webapp (statische Dateien):
cd webapp
npm run build # Ausgabe: webapp/dist/
Webapp (Docker-Image mit nginx):
docker build -t pamietnik-webapp ./webapp
Android APK
cd app
./gradlew assembleRelease # Release-APK (erfordert Signing-Konfiguration)
./gradlew assembleDebug # Debug-APK
./gradlew installDebug # Direkt auf verbundenem Gerät/Emulator installieren
Dienste stoppen
docker-compose down # Container stoppen
docker-compose down -v # + PostgreSQL-Volume löschen (Daten verloren!)
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)
timestampformat:epochMillisvs 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