# Directus Datenbankstruktur — Language App

**Domain:** https://db.hejyou.com
**Stand:** 2026-04-23
**Eigene Collections:** 10 (inkl. Junction `questions_words`)
**Frontend:** React 19 + Vite, kein Router-Package (State-basiertes Routing)

---

## App-Konzept (Kurzfassung)

Social-Media-Feed-Style-Sprachlern-App mit Fokus auf "Language Parenting" (Kontext & Immersion statt Grammatikdrill).

**Datenfluss:**
1. Ein Bild wird in `pictures` hochgeladen
2. KI-Analyse erkennt Objekte → werden in `objects` gespeichert (mit bbox, confidence, Label-Wort)
3. Zum Bild werden `questions` generiert (z. B. "Kannst du die Möwe fliegen sehen?")
4. Jedes Wort der Frage wird in `words` gespeichert und per M2M (`questions_words`) verknüpft

**Lern-Flow in der App:**
- User scrollt durch Feed-Kacheln
- Kacheln existieren **nicht als DB-Einträge**, sondern werden dynamisch aus `words` / `questions` generiert
- Kachel-Typen: `listen`, `speak`, `write`, `multiple_choice`, `image_match`, `sentence_fill`
- 1 EP pro gelöster Kachel
- Erst alle Wörter einer Frage auf Ziellevel → dann Satzfrage freischalten
- Danach gemischter Feed

---

## Sprachrichtung

- **Zielsprachen:** Alle drei gegenseitig (DE/EN/SE × DE/EN/SE)
- Gespeichert in `directus_users.language_native` + `language_target` (M2O → `language_options`)
- Zusätzlich `learning_pairs` pro aktive Sprachrichtung (mit eigenem Level + Punkten)

---

## App-Architektur (Frontend)

### Navigation
4 Seiten, über `BottomNav` (Tabs) gesteuerter `useState`-Router in `App.jsx`:

| Tab | Seite | Status |
|-----|-------|--------|
| Feed   | `pages/Feed.jsx`   | Implementiert, Directus-Daten |
| Game   | `pages/Game.jsx`   | Placeholder |
| Pro    | `pages/Pro.jsx`    | Placeholder |
| Profil | `pages/Profil.jsx` | Implementiert, Directus-Daten |

`App.jsx` zeigt `<AuthScreen />` wenn `!user.username || !user.language_native || !user.language_target`.

### Auth-Flow (2 Schritte)

```
RegisterStep1 (E-Mail + Passwort)
  → registerUser() + login() → Token in localStorage (hejyou_token)
  → onSuccess(userId, token) → RegisterStep2

RegisterStep2 (Username + Muttersprache + Zielsprache)
  → checkUsername() → createProfile()
    → POST /items/users_language
    → PATCH /users/:id  (username, language_native, language_target)
    → PATCH /items/users_language/:id  (user-Verknüpfung)
    → POST /items/learning_pairs
  → setUser() → AuthScreen zeigt Erfolgsscreen

AuthContext überwacht Token, ruft getMe() beim Start
```

Token-Key im localStorage: `hejyou_token`

### Frontend API-Funktionen (`src/api/directus.js`)

| Funktion | Endpoint | Zweck |
|----------|----------|-------|
| `login(email, pw)` | POST `/auth/login` | Token holen |
| `getMe(token)` | GET `/users/me` | Basisfelder für Auth-Check |
| `getProfilData(token)` | GET `/users/me` (erweitert) | Profil inkl. username-Objekt, points_total, streak_days |
| `registerUser(email, pw)` | POST `/users` | Neuen Directus-User anlegen |
| `checkUsername(name)` | GET `/items/users_language` (filter) | Verfügbarkeit prüfen |
| `createProfile(...)` | 4 PATCH/POST-Calls | Vollständiges Onboarding |
| `getActiveLearningPair(profileId)` | GET `/items/learning_pairs` (filter active) | Aktives Lernpaar des Nutzers |
| `getWords(limit)` | GET `/items/words` | Wörter für Feed-Karten |
| `langById(id)` | lokal | LANGUAGE_OPTIONS-Lookup per UUID |

### LANGUAGE_OPTIONS (Frontend-Konstante)

```js
// Felder pro Sprache:
{ id, label, flag, suffix, speech }
```

| Sprache | suffix | speech |
|---------|--------|--------|
| Deutsch | `de` | `de-DE` |
| Englisch | `en` | `en-US` |
| Schwedisch | `se` | `sv-SE` |

`suffix` wird genutzt um DB-Felder dynamisch zu bauen: `title_${suffix}` (z.B. `title_de`).
`speech` wird an die Web Speech API (SpeechRecognition) übergeben.

### Feed-Karten-Generierung

`buildCards(words, fromLang, toLang)` erzeugt pro Wort bis zu 3 Kacheln:

| Kachel-Typ | Komponente | Bedingung |
|------------|-----------|-----------|
| `text`   | `NewWordTextCard`  | immer |
| `voice`  | `NewWordVoiceCard` | immer |
| `letter` | `LetterOrderCard`  | `word.length >= 4` |

Felder aus DB: `word = words.title_${to.suffix}`, `translation = words.title_${from.suffix}`

Zusätzlich werden Demo-Karten (`DEMO_EXTRA`) an den echten Feed angehängt, solange Bilder/Audio noch nicht in der DB vorhanden sind (ImagePickCard, AudioQuizCard, ImageQuizCard).

> **Noch nicht implementiert:** Nach Kartenabschluss wird aktuell **kein** `user_progress`-Eintrag angelegt und `learning_pairs.points` wird nicht aktualisiert. Das ist der nächste Schritt.

---

## Collections im Überblick

| Collection | Zweck |
|---|---|
| `categories` | Kategorien für Wörter/Objekte (Farben, Zuhause, Tiere, …) |
| `language_options` | Verfügbare Sprachen (DE/EN/SE) |
| `learning_pairs` | Aktive Sprachrichtungen pro Nutzer, mit Level & Punkten |
| `objects` | Von KI erkannte Objekte in Bildern (bbox, confidence) |
| `pictures` | Bilder, Ausgangspunkt für Fragen |
| `questions` | Fragen/Sätze für den Lernfeed |
| `questions_words` | Junction: M2M Fragen ↔ Wörter |
| `user_progress` | Lernfortschritt pro Nutzer × Kachel |
| `users_language` | Öffentliches Profil (Username, Avatar) |
| `words` | Vokabeln in DE/EN/SE mit Level & Kategorie |

---

## Collection-Details

### `categories`
Kategorien für Wörter und Objekte.

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `status` | string | draft/published/archived |
| `sort` | integer | Sortierreihenfolge |
| `Icon` | string | Material-Icon-Name |
| `type` | string | Dropdown, Werte undefiniert |
| `title_en/de/se` | string | Titel in 3 Sprachen |

---

### `language_options`
Verfügbare Sprachen.

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `status` | string | |
| `short` | string | Kurzcode: `de`, `en`, `se` |
| `title_en/de/se` | string | |

> Frontend-Mapping: `LANGUAGE_OPTIONS` in `directus.js` ergänzt die DB-Werte um `suffix` (= `short`), `speech` (Web Speech API Code), `flag` (Emoji) und `label` (Anzeigename auf Deutsch).

**Aktuelle Einträge:**
- DE: `88053026-3d7e-4799-b10d-67187f7c1709`
- EN: `99fbaa9d-3cac-48cb-a5e2-dcb320e913e4`
- SE: `25350b32-e9ab-4fec-946e-c0f11eff70dd`

---

### `learning_pairs`
Welche Sprachrichtungen ein Nutzer aktiv lernt.

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `user` | uuid M2O → `users_language` | |
| `language_from` | uuid M2O → `language_options` | Ausgangssprache |
| `language_to` | uuid M2O → `language_options` | Zielsprache |
| `active` | boolean | Aktuell aktiv? |
| `current_level` | integer (1–10) | Aktuelles Level in dieser Richtung |
| `points` | integer | Punkte in dieser Richtung |

---

### `words`
Vokabeln in 3 Sprachen.

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `status` | string | |
| `title_de/en/se` | string | Wort in 3 Sprachen |
| `categories` | uuid M2O → `categories` | Ein Wort gehört zu einer Kategorie |
| `image` | uuid file → `directus_files` | Bild zum Wort (optional) |
| `audio_de/en/se` | uuid file → `directus_files` | Aussprache pro Sprache |
| `level` | integer (1–10) | Schwierigkeitslevel |
| `times_learned` | integer | Global, wie oft insgesamt gelernt |
| `related_questions` | alias M2M → `questions` | Alle Fragen, in denen das Wort vorkommt |

> `getWords()` fetcht aktuell nur `id, title_de, title_en, title_se, level`. `image` und `audio_de/en/se` werden noch nicht geladen → ImagePickCard und AudioQuizCard laufen noch mit Demo-Daten.
> **Hinweis (2026-04-23):** Felder `categories`, `image`, `audio_de`, `audio_en`, `audio_se` wurden mit korrekten FK-Relationen und `special`-Markierungen angelegt.

---

### `questions`
Fragen/Sätze, die im Feed erscheinen.

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `status` | string | |
| `question_de/en/se` | string | Frage in 3 Sprachen |
| `answer_de/en/se` | string | Korrekte Antwort |
| `level` | integer (1–10) | Schwierigkeitslevel |
| `related_words` | alias M2M → `words` | Alle Wörter der Frage |

> Hinweis: `questions.type` (Kachel-Typen) wurde entfernt – Kacheln werden dynamisch in der App generiert, nicht pro Frage gespeichert.

---

### `questions_words`  (Junction-Collection für M2M)
Verknüpft Fragen und Wörter (M:N).

| Feld | Typ |
|---|---|
| `id` | integer (PK, auto) |
| `questions_id` | uuid → `questions` (CASCADE) |
| `words_id` | uuid → `words` (CASCADE) |

---

### `pictures`
Bilder als Ausgangspunkt für Fragen.

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `media` | uuid → directus_files | Das eigentliche Bild |
| `type` | string | `simple_object` / `multi_object` |
| `season` | string | `spring`/`summer`/`fall`/`winter` |
| `objects` | uuid M2O → `objects` | Legacy-Feld (zeigt auf ein einzelnes Objekt, nicht mehr nutzen) |
| `objects` (O2M alias) | alias O2M ← `objects.picture` | Alle Objekte dieses Bildes — über `objects.picture` Relation |

> **Hinweis:** Die korrekte Beziehung läuft über `objects.picture` (M2O von objects zu pictures). Das alte `pictures.objects`-Feld ist ein Legacy-FK und wird nicht mehr befüllt.

---

### `objects`
Von der KI erkannte Objekte innerhalb eines Bildes.

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `picture` | uuid M2O → `pictures` | Bild, zu dem dieses Objekt gehört (O2M-Seite: `pictures.objects`) |
| `media` | uuid → directus_files | Optionaler Zuschnitt des Objekts im Bild |
| `categories` | uuid M2O → `categories` | |
| `label` | uuid M2O → `words` | Name des Objekts als Wort |
| `color` | uuid M2O → `words` | Farbe des Objekts (optional) |
| `action` | uuid M2O → `words` | Aktion des Objekts (z. B. "fliegen") |
| `questions` | uuid M2O → `questions` | Generierte Frage zum Objekt |
| `resolution` | string | z. B. `1920x1080` |
| `confidence` | float | KI-Konfidenz 0–1 |
| `bbox` | json | `{x, y, w, h}` |
| `polygon` | json | Polygon-Koordinaten |
| `parent` | uuid M2O → `objects` | Hierarchie (Szene → Teilobjekte) |

> **Hinweis (2026-04-23):** `objects.picture` wurde ergänzt, um die O2M-Beziehung zu `pictures` herzustellen. Ein Bild kann nun mehrere Objekte haben. Bestehende Testdaten (Möwen-Objekt) wurden verknüpft.

---

### `users_language`
Öffentliches Nutzerprofil (gekoppelt mit `directus_users`).

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `username_public` | string | Angezeigter Name |
| `username_lowercases` | string | Für Suche/Eindeutigkeit |
| `user` | uuid M2O → `directus_users` | Verknüpfung zum System-User |

---

### `directus_users` (System-Collection, custom fields)

| Feld | Typ | Notiz |
|---|---|---|
| `app_type` | string | `socialmedia` / `language` |
| `language_native` | uuid M2O → `language_options` | Muttersprache |
| `language_target` | uuid M2O → `language_options` | Zielsprache |
| `username` | uuid M2O → `users_language` | Verknüpfung zum Profil |
| `points_total` | integer | Gesamtpunkte über alle Richtungen |
| `streak_days` | integer | Aktuelle Lernserie (Tage) |
| `streak_last_activity` | date | Für Streak-Berechnung |

---

### `user_progress`
Trackt jede gelöste/übersprungene Kachel pro Nutzer.

> **Status:** Schema definiert, aber noch **nicht in der App implementiert**. Karten-Ergebnisse werden aktuell nur lokal angezeigt, nicht in die DB geschrieben. Nächster Schritt: `saveProgress()`-Funktion in `directus.js` + Aufruf nach Kartenabschluss.

| Feld | Typ | Notiz |
|---|---|---|
| `id` | uuid (PK) | |
| `user` | uuid M2O → `users_language` | Nutzerprofil |
| `question` | uuid M2O → `questions` | Wenn Kachel zu einer Frage gehört |
| `word` | uuid M2O → `words` | Wenn Kachel zu einem Einzelwort gehört |
| `card_type` | string | `listen` / `speak` / `write` / `multiple_choice` / `image_match` / `sentence_fill` |
| `result` | string | `correct` / `wrong` / `skipped` |
| `points_earned` | integer | Verdiente EP (1 pro gelöster Kachel) |
| `language_from` | uuid M2O → `language_options` | Ausgangssprache |
| `language_to` | uuid M2O → `language_options` | Zielsprache |

> Entweder `word` ODER `question` ist gesetzt, je nachdem ob die Kachel ein Einzelwort oder eine Satzfrage trainiert.

---

## Beziehungs-Diagramm

```
users_language ──┬── learning_pairs (pro Sprachrichtung)
                 ├── user_progress (pro Kachel)
                 └── directus_users (System-User)

pictures ──── objects (O2M via objects.picture) ──── questions
   │              │                                       │
   └── media      ├── picture → pictures (M2O)            ├── related_words (M2M via questions_words)
                  ├── label → words                       └── answer (de/en/se)
                  ├── color → words
                  └── action → words

words ──── categories (M2O)
      ├── image → directus_files
      └── audio_de/en/se → directus_files
```

---

## Testdaten (Tim, Stand 2026-04-22)

- **User (users_language):** `24f9a499-e36d-4df8-91f7-6a7abc6f42ce` (Tim1505)
- **3 Kategorien:** Farben, Zuhause, Tiere
- **12 Wörter** (Rot, Blau, Grün, Tisch, Stuhl, Fenster, Hund, Katze, Vogel, Möwe, fliegen, sehen)
- **8 Fragen** (7 Single-Word + 1 komplexe Möwen-Frage mit 3 verknüpften Wörtern)
- **1 Bild** im Herbst (`multi_object`) mit erkanntem Möwen-Objekt (94% confidence)
- **2 learning_pairs:** DE→SE (aktiv, 30P), DE→EN (inaktiv, 10P)
- **6 user_progress-Einträge** mit verschiedenen card_types (listen/speak/write/multiple_choice/image_match)

---

## Offene Punkte / Nächste Schritte

| Priorität | Aufgabe |
|-----------|---------|
| 🔴 | `user_progress` schreiben nach Kartenabschluss |
| 🔴 | `learning_pairs.points` + `directus_users.points_total` updaten |
| 🟠 | `words.image` + `words.audio_*` in `getWords()` laden → echte ImagePickCard / AudioQuizCard |
| 🟠 | `directus_users.streak_days` + `streak_last_activity` serverseitig aktualisieren |
| 🟡 | `questions` Collection in Feed integrieren (Satzlevel-Kacheln) |
| 🟡 | Game und Pro Pages implementieren |

---

## Kachel-Logik (für die App)

Die App generiert Kacheln dynamisch aus den vorhandenen Daten eines Wortes:

- `audio_de/en/se` vorhanden → `listen`, `speak` möglich
- `image` vorhanden → `image_match` möglich
- Mindestens 3 Wörter in gleicher Kategorie → `multiple_choice` möglich
- Zielsprache setzen via `learning_pairs.language_to`

Pro Abschluss: +1 EP in `user_progress.points_earned` + Update in `learning_pairs.points`.
