- 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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
- 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>
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>