All checks were successful
Deploy to NAS / deploy (push) Successful in 2m20s
- Migrate static JS to TypeScript (static-ts/ → compiled to internal/api/static/) - Add image resizing on upload: JPEG/PNG/WebP scaled to max 1920px at quality 80 - Extract shared upload logic into upload.go (saveUpload, saveResizedImage, saveResizedWebP) - Add POST /media endpoint for drag-drop/paste media uploads with markdown ref return - Add background music player with video/audio coordination (autoplay.ts) - Add global nav, public feed, hashtags, visibility, Markdown rendering for entries - Add Dockerfile stage for TypeScript compilation (static-ts-builder) - Add goldmark, disintegration/imaging, golang.org/x/image dependencies Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
257 lines
7.4 KiB
Markdown
257 lines
7.4 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
**Pamietnik** 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://pamietnik:pamietnik@localhost:5432/pamietnik?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 POST /entries, GET/POST /entries/{id}, GET /entries/{id}/edit
|
|
media.go POST /media — single-file upload, returns markdown reference
|
|
response.go shared response helpers
|
|
api/static/
|
|
style.css global styles (Pico CSS overrides)
|
|
day.js GPS button, time auto-fill
|
|
editor.js textarea drag-drop/paste upload → markdown ref insert
|
|
autoplay.js IntersectionObserver: videos autoplay when visible
|
|
api/templates/
|
|
base.html layout + global nav (LoggedIn, IsAdmin injected by render())
|
|
days.html day list + date picker
|
|
day.html day detail: new entry form, entries, stops, trackpoints
|
|
edit_entry.html edit existing entry
|
|
public.html public feed (infinite scroll)
|
|
login.html login form
|
|
register.html self-registration
|
|
admin/ admin layout + entries/users pages
|
|
```
|
|
|
|
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.pamietnik/
|
|
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:**
|
|
```bash
|
|
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:**
|
|
```bash
|
|
cd webapp
|
|
npm run build # statische Dateien in webapp/dist/
|
|
```
|
|
|
|
---
|
|
|
|
## Build & Deployment
|
|
|
|
### Gesamte Applikation (Docker)
|
|
|
|
```bash
|
|
# 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):
|
|
```bash
|
|
# Lokal (docker-compose):
|
|
docker-compose exec api /createuser <username> <password>
|
|
|
|
# Produktion (NAS, Container läuft via Gitea Actions):
|
|
docker exec -it pamietnik-api-1 /createuser <username> <password>
|
|
# Container-Name prüfen: docker ps | grep pamietnik
|
|
|
|
# Lokal ohne Docker:
|
|
cd backend && go run ./cmd/createuser <username> <password>
|
|
```
|
|
|
|
**Nur neu bauen ohne Cache:**
|
|
```bash
|
|
docker-compose build --no-cache
|
|
docker-compose up
|
|
```
|
|
|
|
**Logs:**
|
|
```bash
|
|
docker-compose logs -f api
|
|
docker-compose logs -f webapp
|
|
```
|
|
|
|
### Einzelne Komponenten bauen
|
|
|
|
**Backend (Go-Binary):**
|
|
```bash
|
|
cd backend
|
|
go build -o ./bin/server ./cmd/server
|
|
go build -o ./bin/migrate ./cmd/migrate
|
|
```
|
|
|
|
**Backend (Docker-Image):**
|
|
```bash
|
|
docker build -t pamietnik-backend ./backend
|
|
```
|
|
|
|
**Webapp (statische Dateien):**
|
|
```bash
|
|
cd webapp
|
|
npm run build # Ausgabe: webapp/dist/
|
|
```
|
|
|
|
**Webapp (Docker-Image mit nginx):**
|
|
```bash
|
|
docker build -t pamietnik-webapp ./webapp
|
|
```
|
|
|
|
### Android APK
|
|
|
|
```bash
|
|
cd app
|
|
./gradlew assembleRelease # Release-APK (erfordert Signing-Konfiguration)
|
|
./gradlew assembleDebug # Debug-APK
|
|
./gradlew installDebug # Direkt auf verbundenem Gerät/Emulator installieren
|
|
```
|
|
|
|
### Dienste stoppen
|
|
|
|
```bash
|
|
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
|