Projects BibleWeb Personal Translation Builder Word selections API (CRUD)
Done

Word selections API (CRUD)

Area: Personal Translation Builder Milestone: v3

Context

Problem: Word gloss selections need server-side storage for authenticated users, enabling cross-device sync and future export functionality. A RESTful API is needed for create, read, update, and delete operations.

Solution: A per-verse API endpoint that handles GET (load selections), POST (save/update a selection), and DELETE (remove a selection) for word gloss choices.

Not included: Batch operations across multiple verses, or a "get all selections for user" endpoint (needed for export — see Export Assembled Translation feature).

Functional

The API allows the interlinear popup to persist word gloss selections to the server for authenticated users.

Operations:

  • GET /api/word-selections/{bookId}/{chapter}/{verse} — returns all saved selections for that verse
  • POST /api/word-selections/{bookId}/{chapter}/{verse} — saves or updates a selection (upsert by word position)
  • DELETE /api/word-selections/{bookId}/{chapter}/{verse} — removes a selection for a specific word position

Edge cases:

  • GET works for anonymous users (uses cookie-based ID)
  • POST and DELETE require authenticated session
  • POST uses upsert — if a selection exists for that word position, it updates; otherwise inserts

UX & Design

No UI — this is a backend API consumed by the interlinear popup.

Technical

Endpoint: apps/web/src/routes/api/word-selections/[bookId]/[chapter]/[verse]/+server.ts

GET handler:

  • Extracts userId from session or cookie fallback
  • Calls getWordSelections(userId, bookId, chapter, verse)
  • Returns array of {wordPosition, selectedGloss}

POST handler:

  • Requires authenticated session
  • Body: {wordPosition: number, selectedGloss: string}
  • Calls saveWordSelection() with upsert (onConflictDoUpdate on unique index)

DELETE handler:

  • Requires authenticated session
  • Body: {wordPosition: number}
  • Deletes matching row from word_selections

DB table: word_selections — unique constraint on (userId, bookId, chapter, verse, wordPosition)

Query layer: apps/web/src/lib/server/queries/word-selections.ts

Files:

  • apps/web/src/routes/api/word-selections/[bookId]/[chapter]/[verse]/+server.ts
  • apps/web/src/lib/server/queries/word-selections.ts
  • packages/db/src/schema/bible.tswordSelections table definition

Status

Current: IN_PROGRESS Milestone: v3 Priority: Medium — core API works, but missing bulk operations

What's done:

  • Per-verse GET/POST/DELETE endpoints
  • Upsert logic with conflict handling
  • Cookie-based anonymous user ID fallback for GET
  • Integration with InterlinearPopup

What remains:

  • Bulk GET endpoint (all selections for a user) — needed for export feature
  • Rate limiting on write endpoints
  • Validation of wordPosition against actual word count in verse

Dependencies:

  • Requires: word_selections DB table (DONE), auth system (DONE)
  • Used by: word selection persistence (DONE), export assembled translation (IN_PROGRESS)