Commit Graph

75 Commits

Author SHA1 Message Date
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