Commit Graph

45 Commits

Author SHA1 Message Date
8f9a48fa5a feat: Pro-Pair-Übersetzung + Review-Kaskade auf Objekt/Bild
- Übersetzungs-Kern (Claude + Platzhalter-Schutz) nach src/lib/translate.js
  ausgelagert; claude.js importiert von dort (Endpoints unverändert).
- Neuer Endpoint POST /pairs/:id/translate: füllt fehlende Sprachen für
  Frage, Statements bzw. (bei word-Typ) verlinkte Wörter und liefert das
  3-sprachige Inhalts-Bündel fürs Review-Modal.
- POST /pairs/:id/review hebt verlinkte Objekte + Bilder zusätzlich auf
  'reviewed' (idempotent).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 14:27:52 +02:00
209a765154 fix: translation-coverage zählt nur übersetzbare Zeilen als fehlend
Leere Hüllen-Zeilen (in allen Sprachen leer, z.B. word-Typ-Statements ohne
Satztext) wurden als fehlend gezählt, obwohl translate-missing sie mangels
Quelltext nie anrührt. Dadurch zeigte die UI viele offene Übersetzungen, der
Button meldete aber 'Fertig: 0 übersetzt'. total/missing basieren jetzt auf
dem übersetzbaren Bestand (Text in mind. einer Sprache).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 13:49:02 +02:00
28435e89c3 docs: API_TOKENS env in .env.example (für ServerMonitor)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 07:59:17 +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
2f0e08e264 fix: instruct Claude to avoid pronouns/articles in word-type pairs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 21:54:10 +02:00
4fc7b42032 feat: add word type and difficulty to Claude pair generation prompt
Extends prompt to 40 pairs: adds 10 × word type with positive_words
and negative_words arrays. Difficulty easy/medium maps to level 1/2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 21:31:20 +02:00
24853f710f feat: add Claude proxy endpoint for auto pair generation
POST /api/claude/generate-pairs — proxies image + objects to Claude
Haiku and returns 30 structured pairs (text/yes_no/question, easy/medium)
as JSON. Keeps the Anthropic API key server-side via ANTHROPIC_API_KEY.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 21:00:29 +02:00
7b3ce50a17 feat: add set-password endpoint for admin user management
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 14:01:47 +02:00
556fdb1d29 feat: new placeholder format {{label.w:id}} / {{label.o:id}}
- Labels are now embedded in sentence text — no DB lookup needed
- Objects fetch selections directly from objects table
- Pictures resolved via object_pairs join instead of sentence UUID scan
- Simpler placeholderMap: only type + selections for objects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:11:03 +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
b57d69fa8e fix(feed): load pictures via object_pairs instead of sentence UUID heuristic
Pictures are now fetched through the canonical pair→object relationship
(object_pairs table) rather than by guessing objects from sentence
placeholder UUIDs. Removes the word→object indirect lookup hack added
previously. One query covers all pairs in the batch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 22:20:00 +02:00
fa446ab353 fix(feed): resolve word→object links for picture lookup on word-type cards
Word-type placeholders in sentences (type='word') were never matched
against object_pictures, so those cards always had no image.
Now queries object_words by word_id to find associated objects,
adds them to resolvedObjectIds, and pickPicture checks wordObjectMap
as a fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 22:16:40 +02:00
d243e6e286 Use object.selections polygon for chip highlight instead of bbox columns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:38: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
52dce342f4 docs: complete README rewrite — current schema, auth, all endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:40:43 +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
cea19083b4 Add ?search= server-side ILIKE filter to words, pictures, categories
- words: ILIKE across titel_de, titel_en, titel_sv
- pictures: ILIKE on design
- categories: ILIKE across name_de, name_en, name_sv

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:50:38 +02:00
6d1f610e3d Fix sub-route shadowing: move /:id after sub-routes, add missing GETs
- pictures.js: move GET/POST/DELETE /:id/words/* BEFORE GET /:id
  so /:id/words is not shadowed; add POST /:id/words/:wordId
- words.js: move GET /:id after sub-routes; add GET /:id/pictures
  and GET /:id/categories
- objects.js: move GET /:id after sub-routes (/:id/words, /:id/pairs,
  /:id/pictures); add GET /:id/pictures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:42:09 +02:00
15fa315b3f Add sentence_de filter to GET /api/questions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:09:08 +02:00
b4de0b98c3 Add missing relation endpoints for content_mentor migration
- GET /api/objects?picture_id=X filter via object_pictures join
- GET /api/objects/:id/words — full word details
- GET /api/objects/:id/pairs — pairs with nested question + statements
- GET /api/words?titel_de=X case-insensitive filter
- GET /api/pictures/:id/words — full word details
- DELETE /api/pictures/:id/words/:wordId — convenience unlink

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:07:18 +02:00
9b0603427e Add users management route (GET, PATCH role/is_active, DELETE)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:29:49 +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
b5f5745107 docs: add README with full API and database documentation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 13:47:31 +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
b82a468197 fix: add Docker native HEALTHCHECK, replaces broken Coolify HTTP check
Coolify's HTTP health check hits localhost:3000 from the host — but the
container port is only reachable inside the Docker network via Traefik.
Docker's own HEALTHCHECK runs inside the container where localhost works.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:38:55 +02:00
7921929f73 feat: add Bearer token authentication
All /api/* routes require Authorization: Bearer <token>.
Tokens are configured via API_TOKENS env var (comma-separated for multiple).
/health remains public for Coolify health checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:22:45 +02:00
fc35e265b2 fix: prevent container crash on DB error, fix health check
- Remove process.exit(-1) on pool error — a DB blip killed the whole container
- Health check now always returns 200 so Coolify doesn't mark it unhealthy
  when PostgreSQL is temporarily unreachable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 10:04:52 +02:00
69f7962518 add package-lock.json for npm ci in Dockerfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 10:00:31 +02:00
ab720b09d0 Initial setup: snakkimo API server with PostgreSQL connection
Node.js/Express API server that connects to PostgreSQL via environment variables.
Includes health check, table listing, row queries, and raw SQL endpoint.
Designed for deployment in Coolify alongside the snakkimo PostgreSQL container.

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