34 Commits

Author SHA1 Message Date
Tim Leikauf
1085a54761 fix: prompt_styles.themenfeld_id → kategorie_id mit FK auf categories
- Spalte umbenannt (idempotent ALTER TABLE)
- FK-Constraint zu categories hinzugefügt
- Seed befüllt kategorie_id per Kategoriename-Lookup (unabhängig von UUIDs)
- Route prompt-styles.js angepasst

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 21:15:28 +02:00
Tim Leikauf
1335136dcb feat: prompt_styles + picture_jobs Tabellen + Seed-Daten
Neue Tabellen via db-migrate.js:
- prompt_styles (fix/atmosphere/setting, themenfeld_id, text_en) inkl. 38 Seed-Einträge aus CSV
- picture_jobs (FKs auf categories, prompt_styles, pictures) + Status-Enum
- picture_job_words (M2M-Junction für word_ids)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 20:54:33 +02:00
455969bdec fix: unique index words.titel_en als partial index + robuster Upsert ohne ON CONFLICT
- Migration: partiell WHERE IS NOT NULL, dedup vorher, kein silent-catch
- Route: INSERT mit .catch(23505) → UPDATE statt ON CONFLICT (partial index inkompatibel)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:14:21 +02:00
7ba6b7120b feat: words-Tabelle – Brysbaert-Import + hierarchische Kategorien + Batch-Anreicherung
- categories: parent_id (self-referential) + 49 Unterkategorien geseedet
- words: neue Spalten conc_m, dom_pos, level, themenfeld_id + unique index titel_en
- enrich_batches + word_generative Tabellen
- src/lib/enrichWords.js: Batch-Anreicherung (DE/SV-Übersetzung, Wortart, CEFR, Themenfeld)
- src/routes/wordGenerative.js: CRUD für KI-Bild-Pipeline
- src/routes/words.js: Filter dom_pos/level/themenfeld_id/has_conc_m + picture_count
- scripts/import-brysbaert.js: CSV-Import-Skript (lokal gegen Prod-DB)
- POST /api/words/enrich-batch als manueller Trigger

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:41:52 +02:00
61b3bcb5ff feat: Erfolge (Achievements) – Unlock-Erkennung + Listing
- Tabelle user_achievements (Migration in db-migrate.js)
- src/lib/achievements.js: Definitionen + dedup-sichere Freischaltung
  (ON CONFLICT DO NOTHING … RETURNING → nur Neues), Listing mit Status
- /auth/progress liefert unlocked_achievements (defensiv gekapselt)
- neue Route GET /auth/achievements

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:53:49 +02:00
339a3ed27d fix: bessere Wort-Kategorisierung, weniger "Sonstiges"
- Taxonomie um "Eigenschaften" (Adjektive) und "Verben & Handlungen"
  ergänzt → Wortarten haben ein Zuhause statt Sonstiges.
- Klassifizierer geschärft: klare Wortart-/Themen-Regeln, "Sonstiges"
  nur als letzter Ausweg; Sofort-Pfad nutzt jetzt Beispielsätze und
  kleinere Batches (15) für deutlich genauere Treffer.
- ?reset=true: bestehende Zuordnungen verwerfen und neu klassifizieren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:39:28 +02:00
bd18a9c303 fix: greeting für en/sv zuverlässig setzen
Das ON-CONFLICT-Update griff bei bereits existierenden en/sv-Zeilen
nicht (Begrüßung blieb NULL). Stattdessen explizites, idempotentes
UPDATE für de/en/sv (Hallo/Hi/Hej).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:34:04 +02:00
d66cff3f61 feat: automatische Wort-Kategorisierung (Batches API + Sofort-Backfill)
Feste ~20er-Taxonomie geseedet (de/en/sv, published; bestehende
Kategorien werden wiederverwendet) + Tabelle category_batches.

src/lib/classifyWords.js: findet in Pairs verwendete Wörter ohne
Kategorie und klassifiziert sie per Haiku gegen die feste Liste.
- Stundenjob über die Message Batches API (asynchron, ~50% günstiger):
  submit/collect-Ticks, in index.js nach Boot + stündlich.
- Sofortiger synchroner One-Shot-Backfill (classifyWordsSync) für
  Live-Test ohne 24h-Verzug.
Beides materialisiert pair_categories via derivePairCategories.

POST /api/categories/auto-assign (admin): ?sync=true = Sofort-Backfill,
sonst ein Batch-Tick. Entkoppelt von generate-words und Publish.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:27:09 +02:00
9738d3e35a feat: Profil-Kategorien + Begrüßung in Zielsprache
languages.greeting (de/en/sv geseedet), neue pair_categories-Tabelle
(abgeleitet aus statement- und objektverknüpften Wörtern via
word_categories) inkl. Backfill für bereits veröffentlichte Pairs.
derivePairCategories() wird beim Publish (pairs + pipeline) aufgerufen.
/auth/me liefert language_target_greeting, /auth/stats liefert
categories[] mit Punkten je Kategorie fürs Profil.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 12:55:57 +02:00
f0f768ff2c feat: Fortschritts-Tracking – user_daily_activity, Tagesziel & GET /auth/stats
- Neue Tabelle user_daily_activity (Tagesverlauf) + Spalte daily_goal_ep
- POST /auth/progress schreibt Tagesaktivität mit
- GET /auth/me liefert daily_goal_ep
- Neuer GET /auth/stats: Tagesverlauf, Tagesziel, Totals, echte Skills je answer_type
- Neuer PUT /auth/goal zum Setzen des Tagesziels

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 16:40:57 +02:00
96ae76f295 fix: gültige Default-Voice für Schwedisch (voice_not_found behoben)
Die geseedete sv-Voice 'XXCqsM8I9KhqA7jLGj1U' existiert bei ElevenLabs
nicht — jede schwedische Audio-Generierung schlug mit voice_not_found
fehl (de/en haben eigene, gültige Account-Voices).

- Seed + Migration: sv → Premade 'Charlotte' (XB0fDUnXU5powFXDhCwa,
  schwedischer Akzent, in jedem Account verfügbar); Bestandsdaten mit
  der defekten ID werden beim Boot automatisch korrigiert
- voices.js: Fallback auf Premade 'Sarah' statt der toten ID

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:04:10 +02:00
6af2428df5 feat: automatische Content-Pipeline (release → pairs → übersetzen → audio → ready)
- pictures.pipeline_* Spalten + app_settings Tabelle (Migration)
- lib/placeholders.js: Placeholder-Auflösung; TTS spricht keine UUIDs mehr
- lib/pairContent.js: geteilte Pair-Logik (Readiness mit Skip-Optionen)
- lib/generatePairs.js: Claude-Generierung (konfigurierbare Anzahl, nur
  Nomen/Adjektive bei word-Pairs) + serverseitige Persistenz inkl. object_pairs
- lib/pipeline.js: In-Process-Runner, idempotente Schritte, Boot-Resume
- routes/pipeline.js: release/retry/overview/bundle/settings + Bild-Publish
  (kaskadiert Fragen/Statements/Pairs/Wörter/Objekte/Bild)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:52:11 +02:00
a3ff787259 feat: reviewed-Status für Bilder, Auto-Trigger, Übersetzungen, Vertonbarkeits-Regel
- pictures: reviewed-Status (Constraint + ALLOWED_STATUSES + Auto-Trigger beim Object-Linking)
- objects: STATUSES um reviewed erweitert; Auto-Trigger draft→reviewed wenn Pair verlinkt
- pairs/statements/questions: STATUSES um reviewed (Phase-1-Lücke)
- pairs: POST /:id/review kaskadiert Pair+Frage+Statements (verlangt alle 3 Sprachen)
- words: Auto requested→translated wenn alle titel_* gefüllt (POST+PATCH)
- audios computeUnits: nur vertonbar wenn ALLE 3 Sprachen pro Feld gefüllt
- claude: translate-text/translate-row/translate-missing mit Placeholder-Schutz
  (⟦PHn:label⟧-Tokenisierung, Label übersetzt, UUID erhalten); translation-coverage

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 07:35:37 +02:00
6c74aabc3f feat: TTS-Settings je Sprache, Audio-Coverage entkoppelt, Veröffentlichen-Workflow
- tts_settings (voice/model/speed/... pro Sprache) + Seed de/en/sv; Route /api/tts-settings
- audios: Stimme/Parameter aus tts_settings; Coverage zählt jetzt auch draft/translated
- pairs: GET /publishability (Readiness, sortierbar nach 'am wenigsten fehlt'),
  POST /:id/publish (kaskadiert question/statements→published, validiert Bild+Audio je Sprache)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:02:07 +02:00
9bfd5e8dba feat: Status-Pipeline (reviewed), Audio-Verknüpfung+Coverage, EP-Fortschritt, Wort-Generierung
- reviewed-Status für objects/questions/statements/pairs (Constraints)
- feed: nur fertige Inhalte (published + Bild + Audio-Gate), audio_url
- pairs: Publish-Gating (draft→published = 409)
- audios: source_table/source_id/source_field/language + Unique-Index;
  generate-for, generate-batch, GET /coverage; voices.js (Voice je Sprache)
- auth: POST /auth/progress, /auth/me mit total_ep/streak/level;
  users_public EP-Spalten + user_pair_progress.earned_points
- claude: POST /generate-words; words POST akzeptiert status

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 21:29:48 +02:00
75f05f45f2 feat: add audios table and ElevenLabs TTS endpoint
- New audios table with voice params, S3 link, alignment JSON
- POST /api/audios/generate calls ElevenLabs with-timestamps, uploads to S3
- GET/PATCH/DELETE /api/audios endpoints
- Requires ELEVENLABS_API_KEY env var

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 13:05:34 +02:00
3147191f55 migrate: backfill old {{uuid}} placeholders to new {{label.w/o:uuid}} format
Runs at startup (idempotent) — only touches rows that still contain bare
{{uuid}} placeholders. Looks up each UUID in words first, then objects,
and rewrites to {{label.w:uuid}} or {{label.o:uuid}} accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:00:04 +02:00
b8802baf36 Add bbox PATCH endpoint + seed watermelon test bbox
- PATCH /api/objects/:id/pictures/:pictureId sets bounding box values
- Migration seeds bbox for watermelon test object (x=0.08, y=0.10, w=0.78, h=0.76)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:29:55 +02:00
9f738312e7 Add bbox coordinates to object_pictures for chip highlight feature
- Add bbox_x/y/w/h FLOAT columns to object_pictures (0–1 percentage range)
- Include type ('word'|'object') and bbox in feed placeholder response
- Fix picture query to use DISTINCT ON instead of LIMIT 1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:24:27 +02:00
6d13000248 feat: add /auth/feed endpoint for hydrated learning pairs
- GET /auth/feed?lang=sv&limit=20 (JWT, end-user allowed)
- Resolves {{uuid}} placeholders to word labels in all languages
- Includes picture URLs, pos/neg words per statement
- Fix migration seed: use full unique index (non-partial) for ON CONFLICT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 18:37:06 +02:00
2f4285dbe9 feat: add user profile endpoints + language seed for LanguParent app
- users_public gets user_id FK (1:1 link to auth user)
- Seed languages: en, sv alongside existing de
- POST /auth/register + /auth/login now include needsProfile flag
- New JWT-authed endpoints (end-user allowed):
    GET  /auth/languages   public language list
    GET  /auth/check-username
    GET  /auth/me          full profile join
    POST /auth/profile     one-time profile creation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:58:01 +02:00
b0a67df328 refactor: answer_type single TEXT + new 'question' type
- Convert TEXT[] back to TEXT (take first element of existing arrays)
- Valid values: yes_no, text, question, word
- API validation updated for single string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 16:11:15 +02:00
7c8d5bfaaf feat: pairs answer_type TEXT[], statements.answer bool
- pairs: answer_type changed from VARCHAR(20) to TEXT[] (multi-value)
  POST/PATCH now accept arrays like ['text','yes_no','word']
- statements: add answer BOOLEAN (nullable) for yes/no correct answer
- migration: ALTER TABLE pairs + ADD COLUMN statements.answer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:48:29 +02:00
5411e478cb feat: objects_created flag on pictures, native_lang on users
- pictures: add objects_created (bool) + objects_created_at (auto timestamp)
  GET /pictures supports ?objects_created=true/false filter
  PATCH /pictures/:id allows setting objects_created
- db-migrate: seed German language, link to all existing users
- auth/login: include native_lang (from languages table) in response + JWT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 19:20:43 +02:00
10570786e9 Add languages, user_names, users_public tables and routes; fix _se→_sv rename
- Fix broken rename migration array (sed had corrupted from values to _sv)
- Add languages table with status lifecycle and trilingual titles
- Add user_names table with unique lowercase index
- Add users_public table linking to user_names and languages (native/target)
- Wire all three new routes under /api/languages, /api/user-names, /api/users-public

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 13:47:52 +02:00
217aab7dcd feat: registration and login with JWT auth
- users table: email, password_hash (bcrypt), role, is_active
- POST /auth/register — checks blocklist, hashes password, returns JWT
- POST /auth/login — verifies password, returns JWT
- Auth middleware: accepts env tokens (dev) OR valid JWTs
- end-user role → 403 Insufficient permissions on all /api/* routes
- JWT_SECRET + JWT_EXPIRES_IN env vars

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 13:04:17 +02:00
5f79e76b67 feat: blocklist table with registration check endpoint
- is_blocked BOOLEAN (default true), INET type for IP validation
- Indexes on email/username/phone/ip for fast registration checks
- POST /api/blocklist/check — checks all fields in one request, returns 403 if blocked
- Auto-timestamps on block/unblock, email stored lowercase

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 10:29:53 +02:00
9eac7b47fc feat: statements table with positive/negative words M2M
- Trilingual positive + negative sentences, status, auto-timestamps
- statement_positive_words + statement_negative_words junction tables
- /api/statements CRUD + link/unlink for both word relations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 10:16:56 +02:00
227247d51c feat: questions table with trilingual sentences
- status enum (draft/blocked/published), auto-timestamps
- sentence_de/en/se, blocked_topic
- Safe ALTER TABLE migration for existing placeholder
- /api/questions CRUD route

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 10:08:22 +02:00
30d180a3de feat: pairs table with questions/statements placeholders
- pairs: status, answer_type enum (yes_no/text/word), difficulty_level,
  FK to questions + 2x statements (positive/negative), auto-timestamps
- questions + statements placeholder tables for future use
- Safe ALTER TABLE migration for existing pairs placeholder
- /api/pairs CRUD route, answer_type required on create

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 10:00:35 +02:00
dac991c861 feat: objects table with M2M words/pictures/pairs
- objects: status enum, JSONB selections, notes, blocked_topic, auto-timestamps
- pairs placeholder table for future use
- Junction tables: object_words, object_pictures, object_pairs
- Full CRUD + link/unlink endpoints for all three relations
- README updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:51:34 +02:00
8bd4240ea9 feat: categories table with full CRUD
- Fills out placeholder categories table with all fields
- Trilingual titles, status enum, difficulty level, auto-timestamps
- ALTER TABLE IF NOT EXISTS for safe migration on existing table
- /api/categories CRUD route, word_ids included in responses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:10:22 +02:00
8751d7ceae feat: words table, M2M with pictures and categories
- words table with trilingual titles, status enum, difficulty level, timestamps
- word_pictures junction table (M2M words <-> pictures)
- categories placeholder table
- word_categories junction table (M2M words <-> categories)
- Auto-timestamps on status change (requested/published/blocked)
- Full CRUD + link/unlink endpoints for pictures and categories
- README updated with schema and endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:05:28 +02:00
0f35459b86 feat: pictures table, Hetzner S3 upload/delete, auto-migration
- pictures table with UUID, status enum, timestamps, blurhash, design
- Auto-trigger updates updated_at on every row change
- POST /api/pictures/:id/upload  → upload file to Hetzner snakkimo bucket
- DELETE /api/pictures/:id       → removes DB row + Hetzner file
- PATCH /api/pictures/:id        → auto-sets published/blocked timestamps
- Migration runs on every server start (idempotent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 13:39:16 +02:00