Document the four features that landed on main in b44bc8c..85c790e: - AGENTS.md: post-MVP section now covers server-driven auth discovery (GET /api/config, AUTH_PASSWORD_ENABLED 403 enforcement, OIDC_GENERIC_DISPLAY_NAME), the settings screen with GET/PUT /api/me, and the critical Android sync/data-safety fixes (pull-all-lists, client HLC tick per server op, no destructive migration, ProGuard rules). Repo structure updated (httpapi config.go/me.go, ui/settings), roadmap entries added, open-points list extended with test backlog for the new endpoints. - API.md: new sections for GET /api/config (public) and GET/PUT /api/me; 403 responses documented for register/login when password auth is off. - SYNC.md: client pull loop (every tracked list, op_log pruning) and the client HLC discipline (tick per incoming server op) that keeps LWW correct across devices with skewed clocks. - README: highlights for auth discovery/OIDC-only mode and the settings screen.
324 lines
21 KiB
Markdown
324 lines
21 KiB
Markdown
# 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=`.
|
||
- **Post-MVP (umgesetzt):**
|
||
- **Geteilte Listen per Invite-Code:** `lists.invite_code` (8 Zeichen, UNIQUE,
|
||
Migration 000002). `POST /api/lists/{id}/invite` (Owner/Member lesen Code,
|
||
wird lazy generiert), `POST /api/lists/join` (beitreten als `member`).
|
||
`GET /api/lists` + `GET /api/lists/{id}` + Push/Pull-Ops prüfen Membership
|
||
(owner ODER `list_members`-Eintrag). Kein Leave/Revoke-Endpoint, kein
|
||
Rate-Limiting auf join (bekannt, bewusst offengehalten).
|
||
- **Account optional / Local-Only-Mode:** App startet ohne Login direkt in die
|
||
Listen (`local_user`-Fallback im `SessionManager`); Sync/Account ist optionaler
|
||
Einstieg. Teilen/Beitreten erfordert aktive Server-Verbindung (App-gated).
|
||
- **Self-Hosted Server-URL:** konfigurierbar in der App (Auth-Screen), umgesetzt
|
||
via `DynamicBaseUrlInterceptor` (schreibt scheme/host/port pro Request um).
|
||
- **Server-getriebene Auth-Discovery:** Öffentlicher Endpoint `GET /api/config`
|
||
verrät der App vor dem Login die verfügbaren Methoden (`password_enabled`,
|
||
OIDC-Provider mit `display_name`, z.B. „Authentik"). `AUTH_PASSWORD_ENABLED=false`
|
||
→ Register/Login per Passwort → 403 (OIDC-only-Enforcement). App-Login zeigt
|
||
nur noch die angebotenen Methoden (ConnectView → LoginView).
|
||
- **Profil + Settings-Screen:** `GET/PUT /api/me` (aktuell `display_name`),
|
||
`SettingsScreen` mit Account & Sync, Profil, Theme (System/Hell/Dunkel,
|
||
persistiert in SessionManager), „Lokale Daten zurücksetzen" (nur Room-Tabellen,
|
||
Serverdaten bleiben) und About.
|
||
- **Kritische Sync-/Datensicherheits-Fixes (Android):** SyncWorker pullt jetzt
|
||
**alle** getrackten Listen (nicht nur welche mit pending Outbox – vorher kamen
|
||
Remote-Änderungen stiller Listen nie an); lokale HLC tickt mit jedem eingehenden
|
||
Server-Op (`tick(op.hlcTs)` → LWW korrekt bei Uhr-Drift); `list_create` in
|
||
`applyServerOpProjection` ergänzt; Projektions-Write + op_log-Insert in einer
|
||
Room-Transaktion; `clientId`/`clientSeq` persistent in SessionManager;
|
||
**kein** `fallbackToDestructiveMigration` mehr (Source of Truth darf nie
|
||
gewispt werden); ProGuard-Keep-Rules für kotlinx.serialization/Retrofit
|
||
(R8-Releases crashten sonst); `network_security_config` (Cleartext nur für
|
||
localhost/10.0.2.2); WorkManager-Default-Initializer deaktiviert (Hilt).
|
||
- **Authentik-OIDC:** läuft über den bestehenden Generic-OIDC-Provider
|
||
(`provider: "generic"`); die App erlaubt manuelle id_token-Eingabe.
|
||
- **MVP-Scope:** 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
|
||
│ │ ├── auth/ # PHASE B – Password + Session + OIDC
|
||
│ │ │ ├── password.go # Argon2id im PHC-Format (HashPassword/VerifyPassword)
|
||
│ │ │ ├── password_test.go # PHC-Roundtrip-Tests
|
||
│ │ │ ├── user.go # UserStore: CreateUser/GetByEmail/GetByID/GetByOIDCSubject/CreateOIDCUser/UpdateDisplayName
|
||
│ │ │ ├── session.go # SessionStore: opaque Tokens, SHA-256-Hash, Create/Lookup/Revoke
|
||
│ │ │ ├── oidc.go # OIDCService: id_token-Verifikation (JWKS/iss/aud/exp), Provider-Caching
|
||
│ │ │ └── pgcode.go # isUniqueViolation (SQLSTATE 23505)
|
||
│ │ ├── sync/ # PHASE C – Hybrid Logical Clock
|
||
│ │ │ ├── hlc.go # HLC: Tick/Now/After, wall_ms<<16|counter, global mutex
|
||
│ │ │ └── hlc_test.go # HLC Unit-Tests (monoton, kausal, keine Duplikate)
|
||
│ │ ├── store/ # PHASE C – Store-Schicht
|
||
│ │ │ ├── db.go # pgxpool-Setup
|
||
│ │ │ ├── opstore.go # AppendOps (idempotent, LWW-Projektion), PullOps (cursor)
|
||
│ │ │ ├── liststore.go # CreateList/GetLists/GetList/GetInviteCode/JoinByInviteCode
|
||
│ │ │ ├── liststore_test.go # Invite/Join/Membership-Integrationstests (brauchen TEST_DATABASE_URL)
|
||
│ │ │ ├── itemstore.go # GetItems
|
||
│ │ │ └── suggeststore.go # Search (pg_trgm fuzzy)
|
||
│ │ └── httpapi/
|
||
│ │ ├── api.go # API-Objekt + Router (alle Routen aktiv)
|
||
│ │ ├── api_test.go # E2E-HTTP-Tests: Share/Join/Sync + 401-Gating (TEST_DATABASE_URL)
|
||
│ │ ├── render.go # JSON-Render + Problem + Fehler-Sentinale + decodeJSON
|
||
│ │ ├── middleware.go # requestID/logging/recover/cors + Chain
|
||
│ │ ├── health.go # /healthz + /readyz
|
||
│ │ ├── auth.go # Register/Login/Logout/OIDC-Handler + RequireAuth-Middleware (403 wenn Passwort-Login deaktiviert)
|
||
│ │ ├── config.go # GET /api/config (öffentlich): verfügbare Auth-Methoden für die App
|
||
│ │ ├── me.go # GET/PUT /api/me (eigenes Profil, display_name)
|
||
│ │ ├── lists.go # GET/POST /api/lists, GET /api/lists/{id}, POST /invite, POST /join
|
||
│ │ ├── ops.go # POST/GET /api/lists/{id}/ops (Push/Pull, Membership-Check)
|
||
│ │ └── suggest.go # GET /api/suggestions?q=
|
||
│ ├── 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
|
||
│ │ ├── 000002_add_invite_code.up.sql # lists.invite_code UNIQUE + Backfill owner→list_members
|
||
│ │ └── 000002_add_invite_code.down.sql
|
||
│ ├── Dockerfile # Multi-Stage, baut server + migrate
|
||
│ ├── .dockerignore
|
||
│ ├── go.mod / go.sum
|
||
├── deploy/
|
||
│ ├── docker-compose.yml # caddy + backend + migrate + db (CADDY_HTTP/HTTPS_PORT)
|
||
│ ├── Caddyfile # standalone: auto-HTTPS (Let's Encrypt)
|
||
│ ├── Caddyfile.behind-proxy # hinter externem Reverse Proxy: auto_https off, trusted_proxies
|
||
│ ├── .env.example # alle env-Vars dokumentiert inkl. Caddy-Ports
|
||
│ └── db/init/001_extensions.sql # CREATE EXTENSION pgcrypto, pg_trgm
|
||
├── docs/
|
||
│ ├── ARCHITECTURE.md
|
||
│ ├── SYNC.md # HLC/LWW/op_log
|
||
│ └── API.md # REST Specs (inkl. join/invite)
|
||
└── android/ # Single-Module :app (Compose M3, Room, Hilt)
|
||
└── app/src/main/java/com/example/mitbringsl/
|
||
├── data/auth/ # SessionManager (Local-User-Fallback, Server-URL, clientId/clientSeq, Theme-Pref)
|
||
├── data/local/ # Room DB (lists, items, op_log), DAOs (LWW upsert)
|
||
├── data/remote/ # Retrofit-API, DTOs, DynamicBaseUrlInterceptor, AuthInterceptor
|
||
├── data/repository/ # ShoppingRepository, AuthRepository
|
||
├── data/sync/ # HybridLogicalClock, SyncManager, SyncWorker (pullt ALLE Listen, HLC-Tick pro Server-Op)
|
||
├── di/ # Hilt Modules
|
||
├── ui/auth/ # AuthScreen (ConnectView: Server-URL + /api/config-Discovery, dann LoginView)
|
||
├── ui/lists/ # ListsScreen/ViewModel (Join-Dialog, Offline-Banner, Settings-Zahnrad)
|
||
├── ui/detail/ # ListDetailScreen/ViewModel (Share-Button, Autocomplete)
|
||
├── ui/settings/ # SettingsScreen/ViewModel (Account, Profil, Theme, Reset, About)
|
||
└── util/ # NetworkMonitor
|
||
```
|
||
|
||
---
|
||
|
||
## 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 (Password):** Argon2id im PHC-Format + `UserStore` (Create/GetByEmail/GetByID)
|
||
+ `SessionStore` (opaque Tokens, SHA-256-Hash, Create/Lookup/Revoke) + Handler
|
||
`Register`/`Login`/`Logout` + `RequireAuth`-Middleware.
|
||
**Verifiziert:** `go test ./internal/auth/...` grün, E2E-Smoke-Test gegen echtes
|
||
PostgreSQL via Docker (Register/Login/Logout/Duplicate/Short-PW/Wrong-PW alle korrekt).
|
||
- ✅ **Phase B – OIDC:** `OIDCService` (go-oidc v3, JWKS-Signatur, iss/aud/exp, Provider-Caching)
|
||
+ `POST /auth/oidc` (find-or-create User via `(oidc_issuer, oidc_subject)`, reuses `issueSession`)
|
||
+ Config-Validierung (enabled → client_id/issuer Pflicht).
|
||
**Verifiziert:** E2E-Flow gegen lokalen Mock-IdP (Discovery → JWKS → signiertes id_token
|
||
→ User angelegt → 2. Login findet gleichen User → tampered Token → 401). Alle Fehlerpfade
|
||
geprüft (disabled→400, unknown provider→400, missing fields→400, invalid→401).
|
||
- ✅ **Phase C – Sync-Kern:** HLC (`internal/sync/hlc.go`), `op_log`-Append idempotent mit
|
||
LWW-Projektion (items/lists) + Tombstones. `internal/store/opstore.go`.
|
||
**Verifiziert:** `go build/vet/test ./...` grün. HLC-Tests (monoton, kausal, keine Duplikate).
|
||
- ✅ **Phase C – Endpoints:** `GET/POST /api/lists`, `GET /api/lists/{id}`,
|
||
`POST/GET /api/lists/{id}/ops` (Push idempotent + Pull mit Cursor `?since=`).
|
||
- ✅ **Phase C – Suggestions:** `suggeststore.go` (pg_trgm fuzzy), `GET /api/suggestions?q=`,
|
||
`upsertItemName` in Projektion (innerhalb Push-Transaktion).
|
||
- ✅ **Phase C – Caddy:** `Caddyfile.behind-proxy` (auto_https off, trusted_proxies),
|
||
`CADDY_HTTP_PORT`/`CADDY_HTTPS_PORT` in docker-compose.
|
||
- ✅ **Phase D – Android-Fundament:** Gradle (Kotlin DSL, Version Catalog, Hilt/KSP, Compose BOM, Room, Retrofit, WorkManager), Theme, Nav.
|
||
**Verifiziert:** `./gradlew assembleDebug` und `./gradlew test` erfolgreich.
|
||
- ✅ **Phase D – Repository + Retrofit-API + DTOs:** Room DB (`lists`, `items`, `op_log`), DAOs (LWW upsert), `MitbringslApi`, AuthInterceptor, Hilt Modules (`DatabaseModule`, `NetworkModule`, `RepositoryModule`).
|
||
- ✅ **Phase D – Login-Screen:** `AuthScreen` (E-Mail/Passwort Login + Registrierung), `AuthViewModel`, `AuthRepository`, `SessionManager`.
|
||
- ✅ **Phase E – SyncEngine:** `HybridLogicalClock` (client-seitig), `SyncWorker` (`CoroutineWorker` Outbox Drain + Server Cursor Pull), `SyncManager` (15 min periodisch + Sofort-Sync), `ShoppingRepository` (local-first mutations via Room + `op_log`).
|
||
- ✅ **Phase E – Listen-Übersicht + Detail + AddItemBar:** `ListsScreen` & `ListsViewModel`, `ListDetailScreen` & `ListDetailViewModel` (Sectioning erledigt/offen, Autocomplete Suggestions dropdown), Compose Navigation 3.
|
||
- ✅ **Phase F – Polish:** `NetworkMonitor` (ConnectivityManager StateFlow), Offline-Banner in `ListsScreen`, Material 3 Empty States.
|
||
- ✅ **Phase F – README + docs:** `README.md` Quickstart, `docs/ARCHITECTURE.md`,
|
||
`docs/SYNC.md` (HLC/LWW/op_log), `docs/API.md` (REST Specs).
|
||
- ✅ **Post-MVP – Account optional:** App startet ohne Login direkt in die Listen
|
||
(Local-Only-Default), Sync/Account optional.
|
||
- ✅ **Post-MVP – Self-Hosted Server-URL + Authentik:** Server-URL in der App
|
||
konfigurierbar (`DynamicBaseUrlInterceptor`), Authentik über Generic-OIDC.
|
||
- ✅ **Post-MVP – Geteilte Listen:** Invite-Codes (Migration 000002), Join/Invite-
|
||
Endpoints, Membership-Checks in `GetLists`/`GetList`/Push/Pull.
|
||
- ✅ **Post-MVP – Tests für Share/Join:** `liststore_test.go` (Store-Level) +
|
||
`api_test.go` (E2E über HTTP): Invite/Join/Idempotenz/Rollen/Access-Control
|
||
(Fremde → 404 auf allen Listen-Endpoints), Cross-Member-Sync via op_log,
|
||
401-Gating aller geschützten Routen. Beide Dateien skippen ohne
|
||
`TEST_DATABASE_URL` (Docker-Rezept im Dateikopf).
|
||
- ✅ **Post-MVP – Kritische Android-Fixes (Sync/Build/Data-Safety):** SyncWorker
|
||
pullt alle getrackten Listen, HLC-Tick pro Server-Op, Room-Transaktionen um
|
||
Projektion+Outbox, persistente clientId/clientSeq, kein destructive Migration,
|
||
ProGuard-Rules, Network-Security-Config, Hilt-WorkManager-Init.
|
||
- ✅ **Post-MVP – Auth-Discovery + OIDC-only:** `GET /api/config` (öffentlich),
|
||
`AUTH_PASSWORD_ENABLED` (403-Enforcement), `OIDC_GENERIC_DISPLAY_NAME`,
|
||
App-Connect-Flow (Server-URL → Methoden-Discovery → LoginView).
|
||
- ✅ **Post-MVP – Settings-Screen + Profil:** `GET/PUT /api/me`
|
||
(`UserStore.UpdateDisplayName`), `SettingsScreen`/`SettingsViewModel`
|
||
(Account&Sync, Profil, Theme, Reset, About), Theme-Pref in SessionManager.
|
||
- ✅ **Post-MVP – Build hinter restriktiven Proxies:** foojay-Resolver &
|
||
jvmToolchain auskommentiert, Gradle-Wrapper 9.6.1, `.ssl-work/` ignoriert.
|
||
|
||
### Wo genau weitermachen?
|
||
**MVP + Post-MVP-Features sind abgeschlossen.** Das Backend ist feature-complete
|
||
für den aktuellen Scope. Offene Punkte, geordnet nach Nutzen:
|
||
|
||
1. **Join-Rate-Limiting:** `POST /api/lists/join` hat kein Rate-Limit; 8-Zeichen-
|
||
Code ist brute-force-bar (62⁸ ≈ 2×10¹⁴, aber trotzdem). Z.B. pro Session/IP
|
||
drosseln oder fehlerhafte Joins verzögern.
|
||
2. **Leave-List / Member-Removal / Code-Revocation:** Es gibt keinen Endpoint,
|
||
eine Liste zu verlassen, Mitglieder zu entfernen oder einen Invite-Code zu
|
||
rotieren. `list_members`-Rollen (`owner`/`member`) werden bisher kaum genutzt.
|
||
3. **Invite-Code-Optimierung:** Codes sind UUID-Präfixe (`uuid[:8]`), nicht
|
||
kollisionsresistent geprüft (UNIQUE-Constraint fängt es, aber CreateList kann
|
||
dann fehlschlagen). Besser: kryptografisches Alphabet ohne Verwechslungsbuchstaben.
|
||
4. **Tests für neuere Endpoints:** `GET /api/config`, `GET/PUT /api/me` und das
|
||
`AUTH_PASSWORD_ENABLED=false`-Verhalten (403) haben noch keine Testabdeckung –
|
||
das Test-Setup dafür steht in `api_test.go` bereit.
|
||
5. **Echtzeit-Push (langfristig):** Periodic-Pull 15 min ist MVP; SSE/WebSocket
|
||
für sofortige Updates wäre der nächste Schritt.
|
||
6. **Android-Tests:** UI/ViewModel-Tests fehlen fast komplett (nur 2 Stock-Tests).
|
||
|
||
---
|
||
|
||
## 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 ./...
|
||
|
||
# Unit-Tests (ohne DB)
|
||
cd backend && go test ./...
|
||
|
||
# Integrationstests (Store + E2E-HTTP) gegen Docker-PostgreSQL:
|
||
docker run -d --name mitbringsl-test-pg -e POSTGRES_USER=app \
|
||
-e POSTGRES_PASSWORD=testpw -e POSTGRES_DB=appdb -p 55432:5432 postgres:16-alpine
|
||
cd backend && TEST_DATABASE_URL="postgres://app:testpw@localhost:55432/appdb?sslmode=disable" \
|
||
go test ./...
|
||
docker rm -f mitbringsl-test-pg # danach aufräumen
|
||
|
||
# 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
|
||
|
||
# Android bauen
|
||
cd android && ./gradlew assembleDebug && ./gradlew test
|
||
```
|
||
|
||
## Git-Status
|
||
- Branch `main`, Remote `origin` konfiguriert (git.dietzlabs.net).
|
||
- MVP (Phase A–F) + Post-MVP-Features (optionale Accounts, Server-URL-Config,
|
||
geteilte Listen) committed und gepusht.
|