v2.1.2026 — PostgreSQL, Auth, Household, Shopping Smart-Add, Docker

Backend:
- SQLite → PostgreSQL (pg_trgm search, async services)
- All services rewritten to async with pg Pool
- Data imported (50 recipes, 8 categories)
- better-sqlite3 removed

Frontend:
- ProfilePage complete (edit profile, change password, no more stubs)
- HouseholdCard (create, join via code, manage members, leave)
- Shopping scope toggle (personal/household)
- IngredientPickerModal (smart add with basics filter)
- Auth token auto-attached to all API calls (token.ts)
- Removed PlaceholderPage

Infrastructure:
- Docker Compose (backend + frontend + postgres)
- Dockerfile for backend (node:22-alpine + tsx)
- Dockerfile for frontend (vite build + nginx)
- nginx.conf with API proxy + SPA fallback
- .env.example for production secrets

Spec:
- AUTH-V2-SPEC updated: household join flow, manual shopping items
This commit is contained in:
clawd
2026-02-18 17:26:24 +00:00
parent 30e44370a1
commit 301e42b1dc
49 changed files with 2167 additions and 1474 deletions

View File

@@ -342,13 +342,18 @@ ALTER TABLE recipes ADD COLUMN created_by UUID REFERENCES users(id);
### 3.2 Profile Pages
#### /profile
- **Layout:** Tabbed Interface (Profil, Haushalt)
- **Profil Tab:**
- **Layout:** Card-basiert, vertikal gestapelt (kein Tab-Interface)
- **Profil-Karte:**
- Avatar (Upload oder URL)
- Display Name (inline editierbar)
- E-Mail (nicht editierbar, mit "Ändern" Link für v2.1)
- "Passwort ändern" Button
- **Mobile:** Große Avatar-Anzeige, Touch-freundliche Edit-Buttons
- **Haushalt-Karte:** (immer sichtbar, direkt unter Profil)
- **Kein Haushalt:** Card mit 🏠 Icon, "Haushalt erstellen" + "Beitreten" Buttons
- **In Haushalt:** Haushalt-Name, Mitglieder-Avatare, "Verwalten" Button → `/profile/household`
- Visuell prominent — nicht versteckt in einem Tab!
- **Quick-Actions:** Logout-Button unten
- **Mobile:** Große Avatar-Anzeige, Touch-freundliche Edit-Buttons, 44px Targets
#### /profile/edit
- **Modal oder Fullscreen (Mobile):** Profil bearbeiten
@@ -475,10 +480,36 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
### 5.2 Haushalt beitreten
1. User erhält Einladungscode (per Link, QR-Code oder Text)
2. User klickt "Haushalt beitreten" und gibt Code ein
3. Backend validiert Code und fügt User als Member hinzu
4. Einkaufsliste wird automatisch geteilt
#### Flow
1. User erhält Einladungscode (per Link, QR-Code oder manuell)
2. In `/profile/household` → Button "Haushalt beitreten"
3. **Eingabe:** 6-stelliger Code (uppercase, alphanumerisch, z.B. `COOK2A`)
4. Backend validiert Code → fügt User als `member` hinzu
5. Erfolg: Konfetti 🎉 + Toast "Willkommen bei [Haushalt-Name]!"
6. Einkaufsliste zeigt ab sofort Haushalt-Items
#### Deep Link Support
- URL-Format: `https://luna.supertoll.xyz/join/{invite_code}`
- Wenn eingeloggt → direkt beitreten (mit Bestätigungs-Dialog)
- Wenn nicht eingeloggt → Redirect zu `/login?redirect=/join/{code}`
- Nach Login → automatisch Join-Flow fortsetzen
#### QR-Code
- Owner kann QR-Code generieren (enthält Deep Link)
- Share-Button: QR als Bild teilen oder Code kopieren
- Library: `qrcode.react` (lightweight)
#### Fehlerbehandlung
- `INVALID_INVITE_CODE` → "Code ungültig oder abgelaufen"
- `ALREADY_IN_HOUSEHOLD` → "Du bist bereits in einem Haushalt. Zuerst verlassen?"
- `HOUSEHOLD_FULL` → "Haushalt hat maximale Mitgliederzahl erreicht" (Limit: 10)
- Netzwerkfehler → Retry-Button
#### Haushalt verlassen
- Member können jederzeit verlassen (Bestätigungs-Dialog)
- Owner kann nur verlassen wenn ein anderer Member zum Owner gemacht wird
- Letzter Member → Haushalt wird gelöscht
- Nach Verlassen: eigene Shopping-Items bleiben privat erhalten
### 5.3 Gemeinsame Einkaufsliste
@@ -493,6 +524,109 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
- **Filter:** "Alle", "Haushalt", "Privat" Tabs in Shopping Liste
- **Mobile:** Swipe-Actions: "Als privat markieren" / "Mit Haushalt teilen"
### 5.4 Smarte Mengenabfrage beim Rezept-Einkauf
Aktuell werden beim "Zur Einkaufsliste hinzufügen" alle Zutaten 1:1 übernommen. Aber oft hat man schon Mehl, Eier, Zucker etc. daheim. Statt blind alles draufzupacken → User fragen was noch fehlt.
#### Flow: "Was brauchst du noch?"
1. User klickt "Zutaten zur Einkaufsliste" auf der Rezeptseite
2. **Modal/Sheet öffnet sich** mit allen Zutaten als Checkliste
3. Jede Zutat hat:
- ☑️ Checkbox (Standard: alle an)
- Name + Menge aus Rezept
- Optional: Menge anpassen (z.B. "hab noch 200g, brauch nur 300g")
4. User deaktiviert was schon da ist
5. Button: "X Artikel hinzufügen" (zeigt Anzahl der aktiven)
6. Nur ausgewählte Zutaten landen auf der Liste
#### Quick-Actions im Modal
- **"Alles"** — alle Checkboxen an (default)
- **"Basics abwählen"** — typische Vorrats-Zutaten automatisch deaktivieren (Salz, Pfeffer, Öl, Wasser)
- **"Nichts"** — alle aus, manuell auswählen
#### Basics-Liste (konfigurierbar pro User/Haushalt)
Standard-Basics die man meistens daheim hat:
- Salz, Pfeffer, Zucker, Mehl, Öl, Wasser, Butter, Eier
- User kann in Settings eigene Basics definieren
- Passt zu Luna: Mehl ✅ Zucker ✅ Eier ✅ Backpulver ✅ (Butter ❌ muss immer drauf 😄)
#### Späterer Ausbau (v2+: Vorratskammer/Pantry)
- Automatischer Abgleich mit Vorratskammer
- "Hab ich schon" wird automatisch vorausgewählt
- Fehlmengen werden berechnet (Rezept braucht 500g, hast 200g → 300g auf Liste)
### 5.5 Manuelle Artikel zur Einkaufsliste hinzufügen
Aktuell kommen Shopping-Items nur aus Rezepten. User sollen auch eigene Artikel hinzufügen können (z.B. "Klopapier", "Spülmittel", Zutaten ohne Rezept).
#### API
##### POST /api/shopping/manual
```json
// Request
{
"name": "Klopapier",
"amount": "1",
"unit": "Packung",
"private": false
}
// Response (201 Created)
{
"success": true,
"item": {
"id": "uuid",
"name": "Klopapier",
"amount": "1",
"unit": "Packung",
"checked": false,
"recipe_id": null,
"user_id": "user-uuid",
"household_id": "household-uuid",
"created_at": "2026-02-18T16:00:00Z"
}
}
```
#### DB-Erweiterung
```sql
-- recipe_id wird NULLABLE (ist es vermutlich schon)
-- Manuelle Items haben recipe_id = NULL
-- Optional: source-Feld um Herkunft zu unterscheiden
ALTER TABLE shopping_items ADD COLUMN source TEXT DEFAULT 'recipe'
CHECK (source IN ('recipe', 'manual'));
```
#### Frontend UI
##### Eingabefeld (oben in der Einkaufsliste)
- **Sticky Input-Bar** oben auf der Shopping-Page
- Textfeld mit Placeholder "Artikel hinzufügen..." + Button
- **Smart Parsing:** "2 Liter Milch" → amount: "2", unit: "Liter", name: "Milch"
- **Autocomplete:** Vorschläge aus bisherigen Items (häufig gekaufte)
- Enter oder fügt hinzu, Feld wird geleert
- Standard: Haushalt-Item (wenn in Haushalt), sonst privat
##### Smart Parsing Regeln
```
"Milch" → name: "Milch", amount: null, unit: null
"2 Milch" → name: "Milch", amount: "2", unit: null
"2 Liter Milch" → name: "Milch", amount: "2", unit: "Liter"
"500g Mehl" → name: "Mehl", amount: "500", unit: "g"
"1 Packung Butter" → name: "Butter", amount: "1", unit: "Packung"
```
##### Visuelle Unterscheidung
- Rezept-Items: normaler Style + Rezept-Name als Subtitle
- Manuelle Items: leicht anderer Style (z.B. 📝 Icon oder kursiver Subtitle "Manuell hinzugefügt")
- Haushalt-Items: 🏠 Badge
- Private Items: 🔒 Badge
##### Quick-Add Vorschläge
- Unter dem Input: Chips mit häufig hinzugefügten Artikeln
- Max 5 Vorschläge, basierend auf History
- Tap = sofort hinzufügen
### 5.4 Persönliche Favoriten
- Favoriten sind immer pro User (`user_favorites` Tabelle)