# AGENTS.md – Kontext & Fortschritt für KI-Agenten > Diese Datei hält den Projekt-Stand und die Architekturentscheidungen fest, damit > ein Agent (z.B. zuhause) sofort weiterarbeiten kann. Sie ist Teil des Repos. ## Projekt: Mitbringsl Local-First Einkaufslisten-App (Bring-Alternative, werbefrei). **Backend:** Go (net/http, pgx, golang-migrate, go-oidc) als Docker-Container. **App:** Kotlin + Jetpack Compose, Room, Hilt, WorkManager, offline-first. **Deployment:** docker-compose (Caddy + backend + migrate + postgres:16), auto-HTTPS. Vollständiger Plan liegt als genehmigtem Plan zugrunde (siehe Abschnitt "Roadmap"). --- ## Getroffene Architekturentscheidungen (verbindlich) - **Backend-Sprache:** Go (nicht Rust). - **DB:** PostgreSQL (`postgres:16-alpine`), Extensions `pgcrypto` + `pg_trgm`. - **Sync:** Local-First, **keine Datenverluste** – Append-only `op_log` (SOURCE OF TRUTH), `items`/`lists` sind Projektionen; Konfliktlösung per LWW-Register `(hlc_ts, client_id)` + Tombstones (`deleted_at`). HLC = Hybrid Logical Clock. - Push: `POST /api/lists/{id}/ops` (idempotent via `UNIQUE(client_id, client_seq)`). - Pull: `GET /api/lists/{id}/ops?since={seq}`. - **Auth:** - Eigene User: E-Mail/Passwort, **Argon2id** (PHC-Format). - OIDC: **Google** + **Generic OIDC**. Die **App macht den Code+PKCE-Flow selbst** und schickt nur das `id_token` ans Backend (`POST /auth/oidc`). Backend verifiziert via `github.com/coreos/go-oidc/v3` (Signatur gegen JWKS, iss/aud/exp) und stellt **eigene opaque Session-Tokens** aus (`sessions`-Tabelle, SHA-256-Hash gespeichert). - **AppAuth-Android bewusst NICHT verwendet** (seit 2021 verwaist). Stattdessen: Google via **Credential Manager**, Generic OIDC via **Custom Tabs + eigenes PKCE**. - **Vorschläge:** aggregiert aus Item-Namen aller User (`item_names`-Tabelle, pg_trgm fuzzy search). Endpoint `GET /api/suggestions?q=`. - **MVP-Scope:** Single-Owner-Listen (`list_members` existiert, wird in Phase 2 für geteilte Listen genutzt); keine Echtzeit-Push (nur Periodic-Pull 15 min + Pull-on-Online); keine Web-UI. --- ## Technologie-Stacks (final) ### Backend | Bereich | Wahl | |---|---| | Go-Version | **1.26** (`go.mod` hat `go 1.26`; Docker-Image `golang:1.26-alpine`) | | HTTP | stdlib `net/http` (Go 1.22+ Routing mit Methoden + Path-Vars) | | DB-Driver | `github.com/jackc/pgx/v5` (pgxpool) | | Migrationen | `github.com/golang-migrate/migrate/v4` + `source/iofs` (eingebettet via `embed.FS`) | | OIDC | `github.com/coreos/go-oidc/v3` + `golang.org/x/oauth2` | | Passwörter | Argon2id (`golang.org/x/crypto/argon2`) | | Sessions | opaque Tokens (32 B base64url), `sessions`-Tabelle, SHA-256-Hash | | Config | `github.com/caarlos0/env/v11` (struct-tag env) | | Logging | `log/slog` mit `NewJSONHandler` → stdout | | Docker | Multi-Stage, `CGO_ENABLED=0`, `gcr.io/distroless/static-debian12:nonroot` | | Queries | **direkt mit pgx** (sqlc wurde aus Skalierbarkeit bewusst auf später verschoben) | ### Android (noch nicht begonnen) | Bereich | Wahl | |---|---| | Build | Kotlin DSL + `libs.versions.toml`, Single-Module `:app` | | Kotlin/AGP/Gradle | Kotlin 2.x (K2), AGP 8.9+, Gradle 8.11+, JDK 17 | | UI | Compose BOM (2025.x) + Material 3 | | Arch | MVVM + `ViewModel` + `StateFlow`, UDF | | DB | Room (KSP), Source of Truth via `Flow` | | Netzwerk | Retrofit + OkHttp + kotlinx.serialization | | Sync | WorkManager `CoroutineWorker` (Outbox-Drain + Cursor-Pull) | | DI | Hilt (KSP) + `hilt-navigation-compose` + `hilt-work` | | IDs | Client-seitige UUIDs | | Auth | Google: Credential Manager; Generic OIDC: Custom Tabs + PKCE selbst | | Suche | `OutlinedTextField` + `DropdownMenu`, Room-FTS, Server-Fallback | | SDK | minSdk 26, compile/target 36 | --- ## Repository-Struktur ``` mitbringsl/ ├── AGENTS.md ← DIES DATEI ├── README.md ├── .gitignore ├── backend/ │ ├── cmd/ │ │ ├── server/main.go # HTTP-Server-Einstieg (verdrahtet cfg/logger/pool/api) │ │ └── migrate/main.go # Migrations-Runner (iofs-embedded) │ ├── internal/ │ │ ├── config/config.go # Config (caarlos0/env) │ │ ├── logging/logging.go # slog JSON-Setup │ │ ├── store/db.go # pgxpool-Setup │ │ └── httpapi/ │ │ ├── api.go # API-Objekt + Router (Health aktiv, Rest noch auskommentiert) │ │ ├── render.go # JSON-Render + Problem + Fehler-Sentinale + decodeJSON │ │ ├── middleware.go # requestID/logging/recover/cors + Chain │ │ └── health.go # /healthz + /readyz │ ├── migrations/ │ │ ├── embed.go # //go:embed *.sql │ │ ├── 000001_init_schema.up.sql # users/sessions/lists/list_members/items/op_log/item_names │ │ └── 000001_init_schema.down.sql │ ├── Dockerfile # Multi-Stage, baut server + migrate │ ├── .dockerignore │ ├── go.mod / go.sum ├── deploy/ │ ├── docker-compose.yml # caddy + backend + migrate + db (mit YAML-anchors) │ ├── Caddyfile # auto-HTTPS │ ├── .env.example # alle env-Vars dokumentiert │ └── db/init/001_extensions.sql # CREATE EXTENSION pgcrypto, pg_trgm ├── docs/ # NOCH LEER (folgt Phase F) └── android/ # NOCH LEER (folgt Phase D) ``` --- ## Roadmap / Fortschritt Legende: ✅ erledigt · 🚧 in Arbeit · ⬜ offen - ✅ Repo-Struktur + `.gitignore` + `README.md` + `git init` (Branch `main`) - ✅ **Phase A – Backend-Fundament:** Config, slog, pgxpool, `/healthz`+`/readyz`, Server-Main. - ✅ **Phase A – Migrationen:** vollständiges Init-Schema (up+down) + `migrate`-Binary (iofs). - ✅ **Phase A – Docker:** Dockerfile (Go 1.26 → distroless nonroot), docker-compose (caddy/backend/migrate/db), Caddyfile, `.env.example`. **Verifiziert:** Image baut, beide Binaries laufen im Container (Smoke-Test OK). - ⬜ **Phase B – Auth:** Argon2id + eigene User (register/login) + Session-Middleware. - ⬜ **Phase B – OIDC:** go-oidc-Verifikation (Google + Generic) + `POST /auth/oidc`. - ⬜ **Phase C – Sync-Kern:** `op_log`-Append (idempotent), HLC, Projektion op→items/lists (LWW+Tombstones). - ⬜ **Phase C – Endpoints:** `/api/lists`, `/api/lists/{id}/ops` (push+pull). - ⬜ **Phase C – Suggestions:** `item_names`-Trigger + `/api/suggestions`. - ⬜ **Phase D – Android-Fundament:** Gradle (Kotlin DSL, Version Catalog, Hilt/KSP, Compose BOM), Theme, Nav, Room. - ⬜ **Phase D – Repository + Retrofit-API + DTOs.** - ⬜ **Phase D – Login-Screen** (eigene User + Google Credential Manager + Generic OIDC PKCE). - ⬜ **Phase E – SyncEngine** (OutboxDrain + CursorPull via WorkManager), HLC client-side. - ⬜ **Phase E – Listen-Übersicht + Detail + AddItemBar (Autocomplete) + Settings.** - ⬜ **Phase F – Polish** (Fehlerbehandlung, Offline-Indikator, Empty States, Tests). - ⬜ **Phase F – README + docs** (ARCHITECTURE/SYNC/API). ### Wo genau weitermachen? **Nächster Schritt = Phase B (Auth):** 1. `internal/auth/password.go` – Argon2id (PHC-Format) Hashen/Verifizieren. 2. `internal/auth/session.go` – Token generieren (crypto/rand, base64url), SHA-256-Hash, in `sessions` einfügen, Middleware `requireAuth` (lädt `user_id` in Context). 3. `internal/httpapi/auth.go` – Handler `Register`/`Login`/`Logout`. 4. In `api.go` die `/auth/*`-Routen einkommentieren + verdrahten. 5. Dann Phase B Teil 2: OIDC (`internal/auth/oidc.go` + `POST /auth/oidc`). --- ## Wichtige technische Notizen / Fallstricke (bereits gelöst) - **golang-migrate `iofs`-Pfad:** Der Import ist `github.com/golang-migrate/migrate/v4/source/iofs` (MIT `/source/`). Die Pfade ohne `/source/` gibt es nicht mehr → Build-Fehler. - **Go-Version:** `pgx/v5 v5.10.0` braucht Go ≥ 1.25, deshalb Go 1.26 (lokal + Docker-Image `golang:1.26-alpine`). `go.mod` hat `go 1.26`. - **Docker-Build auf Windows/Docker Desktop:** `--network host` im Build-Container funktioniert NICHT (WSL2-NAT). Für Tests: echtes `docker network create` + Container-Namen nutzen. - **Compose braucht zwingend `POSTGRES_PASSWORD`** (`.env` oder Env), da `db.environment` mit `${POSTGRES_PASSWORD:?...}`-Assertion vor dem Build validiert wird. - **Healthcheck im `backend`-Service wurde entfernt**, weil distroless/static kein `wget`/`curl` enthält. Später: eigenes Binary, das `/healthz` per Go-HTTP prüft, oder `healthcheck` über Caddy/extern. - **SQL-Queries werden direkt mit pgx geschrieben** (kein sqlc im MVP), da sqlc lokal nicht installiert ist und ein extra Code-Gen-Schritt nötig wäre. Bei Bedarf später problemlos nachrüstbar. --- ## Build- & Test-Befehle ```bash # Backend lokal bauen cd backend && go build ./... && go vet ./... # Backend-Image bauen cd backend && docker build -t mitbringsl-backend:test . # Komplettes Stack starten (braucht deploy/.env) cd deploy && cp .env.example .env && docker compose up -d --build # Migrationen manuell gegen bestehende DB anwenden docker run --rm --network \ -e DATABASE_URL="postgres://app:PW@:5432/appdb?sslmode=disable" \ mitbringsl-backend:test /app/migrate up ``` ## Git-Status - Repo initialisiert, Branch `main`. **Remote ist noch NICHT konfiguriert.** - Es wurde **noch nicht committet** (Stand beim Schreiben dieser Datei).