Files
snakkimo-API/CLAUDE.md

43 lines
5.2 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
REST-API für das snakkimo-Projekt. Node/Express + PostgreSQL (`pg`, kein ORM), Bild-Assets auf Hetzner Object Storage (S3-kompatibel). Ausführliche API-Doku in [README.md](README.md).
## Befehle
- `npm run dev` — lokaler Server mit nodemon (Hot-Reload)
- `npm start` — Produktion (`node src/index.js`)
- Keine Tests / kein Linter konfiguriert.
## Architektur
- Einstieg: [src/index.js](src/index.js) — registriert alle Routen, jede `/api/*`-Route ist mit der `auth`-Middleware geschützt.
- **Migrationen laufen automatisch beim Boot** ([src/db-migrate.js](src/db-migrate.js)), bevor der Server lauscht. Idempotent halten: `CREATE TABLE IF NOT EXISTS`, Spalten-Renames mit `.catch(() => {})`. Es gibt **kein** separates Migrations-Tool — Schema-Änderungen hier eintragen.
- `src/db.js` exportiert `query(text, params)` und `pool`. Immer parametrisierte Queries (`$1, $2 …`), nie String-Interpolation von User-Input.
- `src/routes/` — eine Datei pro Entität. `src/lib/`, `src/middleware/`, `src/s3.js`, `src/voices.js` für geteilte Logik.
- **Hintergrund-Job (Auto-Kategorisierung):** [src/index.js](src/index.js) startet ~30 s nach dem Boot und stündlich `runCategorizationTick()` ([src/lib/classifyWords.js](src/lib/classifyWords.js)). Er klassifiziert in Pairs verwendete Wörter ohne Kategorie per **Anthropic Message Batches API** (Haiku, asynchron, ~50 % günstiger) gegen die feste Taxonomie und materialisiert `pair_categories`. ⚠️ Braucht `ANTHROPIC_API_KEY` und verursacht echte LLM-Kosten — **auch lokal bei `npm run dev`**. Manuell anstoßen: `POST /api/categories/auto-assign` (`?sync=true` = sofort/synchron statt Batch, `&reset=true` = bestehende Zuordnungen verwerfen und neu klassifizieren).
- **Kategorie-Datenfluss:** Kategorien hängen an Wörtern (`word_categories`, feste Taxonomie wird in [src/db-migrate.js](src/db-migrate.js) geseedet). `pair_categories` wird daraus abgeleitet ([src/lib/pairCategories.js](src/lib/pairCategories.js) `derivePairCategories`) — beim Pair-Publish (`routes/pairs.js`, `routes/pipeline.js`) und im Job. `GET /auth/stats` liefert daraus die Punkte je Kategorie fürs Profil; `GET /auth/me` liefert `language_target_greeting` (Spalte `languages.greeting`, de/en/sv geseedet). Async-Batch-Status liegt in `category_batches`.
## Fortschritt / Gamification ([src/routes/auth.js](src/routes/auth.js))
- **Level-Kurve = Single Source of Truth:** [src/lib/leveling.js](src/lib/leveling.js) (`levelForEp`/`levelInfo`, progressive Kurve — kumulativ `5·n·(n+3)` EP, Level 1 bei 20 EP). Wird in `GET /auth/me` (liefert `level` + `ep_into_level` + `ep_to_next_level`) **und** `POST /auth/progress` genutzt. Das Frontend spiegelt dieselbe Kurve nur als Fallback — Kurvenänderungen hier vornehmen.
- **`POST /auth/progress`** bucht EP/Streak/Pair-Statistik und liefert den **Milestone-Vertrag**: `{ total_ep, level, prev_level, streak_days, streak_increased, daily_ep, daily_goal_ep, goal_just_reached, unlocked_achievements }`. Ein CTE fängt die **Pre-Update-Werte** mit, damit Level-Up/Streak-Up atomar erkennbar sind. `daily_goal_ep` via `PUT /auth/goal` (geklemmt 5500).
- **Erfolge (Achievements):** [src/lib/achievements.js](src/lib/achievements.js) definiert die Erfolge und schaltet sie **dedup-sicher** frei (`INSERT … ON CONFLICT DO NOTHING RETURNING` → nur Neues). Persistenz in Tabelle `user_achievements` (Migration in `db-migrate.js`). `/auth/progress` ruft `evaluateAchievements` (defensiv gekapselt, darf die Buchung nicht kippen); `GET /auth/achievements` listet alle mit Status fürs Profil.
## Konventionen
- **Code-Kommentare auf Deutsch**, Code/Bezeichner auf Englisch (dem Bestand folgen).
- Route-Handler-Muster: `async (req, res, next) => { try { … } catch (err) { next(err); } }`. Fehler an den zentralen Error-Handler in `index.js` durchreichen, nicht selbst 500en.
- Listen-Endpoints: `limit`/`offset` aus Query, `limit` hart begrenzen (z. B. `Math.min(parseInt(limit), 500)`).
- Status-Felder gegen eine `STATUSES`-Whitelist prüfen → bei Verstoß `400`.
- **Sprachen-Suffixe: `_de`, `_en`, `_sv`.** `_se` ist veraltet (falscher ISO-639-1-Code) und wird beim Boot zu `_sv` umbenannt — niemals neue `_se`-Spalten anlegen.
## Bild-Generierungs-Pipeline
- **`prompt_styles`** — Seed-Tabelle mit fertigen Prompt-Bausteinen (Typen: `fix` / `atmosphere` / `setting`). `themenfeld_id` gruppiert Settings nach Thema (plain UUID, kein FK). 38 Einträge werden beim Boot geseeded. Route: `/api/prompt-styles`.
- **`picture_jobs`** — Job-Queue für die Bildgenerierung. Referenziert eine Kategorie, bis zu drei Prompt-Styles und nach Generierung ein Bild. M2M-Words über `picture_job_words`. Status-Flow: `pending → generating → done | failed`. Route: `/api/picture-jobs` (inkl. `/words`-Subroute für M2M).
## Auth (zwei Pfade, siehe [src/middleware/auth.js](src/middleware/auth.js))
1. Statische Tokens aus `API_TOKENS` (komma-separiert) → Server-zu-Server / Admin, keine Rollenprüfung.
2. JWT aus `/auth/login` · `/auth/register`. Rolle `end-user` bekommt auf allen `/api/*` bewusst **403** (App-Gating).
Öffentlich (ohne Auth): `GET /health`, `/auth/*`.
Konfig über `.env` (siehe [.env.example](.env.example)). Deployment via Coolify/Docker.