Files
pamietnik/CLAUDE.md
2026-04-05 20:30:17 +02:00

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 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:

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_URLpostgres://ralph:ralph@localhost:5432/ralph?sslmode=disable
  • LISTEN_ADDR:8080
  • UPLOAD_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
  • 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

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)

  • 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