Every shopping item now gets a visual like in Bring: a product emoji on a softly tinted category tile. - ItemVisuals.kt: pure client-side classifier. German dictionary (~180 entries) maps item names to emoji + one of 13 categories (produce, drinks, bakery, dairy, meat&fish, frozen, pantry, sweets, household, drugstore, baby, pet, other), each with an accent color. Longest-keyword-first matching so compounds resolve to their most specific entry (Wassermelone is produce, not a drink); juice compounds (Apfelsaft, Orangensaft, ...) are listed explicitly because their fruit prefix is longer than 'saft'. Unknown items fall back to the generic cart on neutral grey. Works fully offline, nothing leaves the device. - ListDetailScreen: emoji tile in every item row, emoji in the autocomplete suggestions dropdown, and a live emoji preview as the text field's leading icon while typing. - ItemVisualsTest: longest-match priority, case/whitespace insensitivity, fallbacks, category coverage. Verified: assembleDebug + unit tests green (JDK 21).
331 lines
21 KiB
Markdown
331 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), ItemVisuals (Emoji-/Kategorie-Icons)
|
||
├── 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 – Produkt-Icons (Bring-Style):** `ui/detail/ItemVisuals.kt` –
|
||
rein clientseitiger Classifier: deutsches Wörterbuch (~180 Einträge) mappt
|
||
Item-Namen auf **Emoji + Kategorie** (13 Kategorien mit Akzentfarbe).
|
||
Longest-Keyword-Match („Apfelsaft" → Getränk, nicht Obst; Saft-Komposita
|
||
explizit gelistet), unbekannte Items → 🛒/Sonstiges. UI: Icon-Kachel in
|
||
ItemRow, Icons im Autocomplete-Dropdown, Live-Emoji im Eingabefeld
|
||
(`leadingIcon`). Unit-Tests in `ItemVisualsTest.kt`.
|
||
- ✅ **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.
|