Add public Haven catalog API for early-church collection - #9
Conversation
Exposes Strata's early-church public-domain audiobooks to the Haven app via a versioned, public, CORS-enabled JSON contract — letting both teams build in parallel before Strata is deployed. - books.collection column (migration 030) to group titles for cross-app surfacing - src/lib/seed/early-church.ts: curated Apostolic Fathers works + source texts - src/lib/haven-catalog.ts: contract types + DB/seed-merged shaping (falls back to seed metadata as "coming_soon" until the pipeline produces real audio) - GET /api/haven/catalog and /api/haven/catalog/[slug] (public, no auth) - docs/haven-integration.md: shared contract Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kimn4e79f6EcbjDjP2NhdW
|
Recommend closing as superseded. A public Haven catalog API already shipped to This PR (opened 2026-06-28) predates that merge and its base is far behind current Generated by Claude Code |
|
|
|
|
||
| if (bookRows.length === 0) return result; | ||
|
|
||
| for (const book of bookRows) { |
There was a problem hiding this comment.
🔍 Performance Critic [P2]: N+1 query — loops over each book and issues a separate episode query per book. Should use a single Drizzle relational query (db.query.books.findMany with with: { episodes: ... }). See issue #68.
| title: episodes.title, | ||
| audioUrlFull: episodes.audioUrlFull, | ||
| durationSeconds: episodes.durationSeconds, | ||
| transcript: episodes.transcript, |
There was a problem hiding this comment.
🔍 Performance Critic [P2]: The transcript column is fetched for every ready episode but is never used by buildCatalogSummary (the summary has no episodes). Full transcript text is pulled from DB into memory and discarded. The summary path should select only count/duration aggregates. See issue #68.
| const seed = EARLY_CHURCH_WORKS_BY_SLUG[slug]; | ||
| if (!seed) return null; | ||
|
|
||
| const live = (await loadLiveCollection()).get(slug); |
There was a problem hiding this comment.
🔍 Performance Critic [P2]: buildWorkDetail calls loadLiveCollection() which loads ALL collection books + ALL their episodes (with transcripts), then picks one work via .get(slug). The detail endpoint should query only the requested book + its episodes. See issue #68.
Automated Code ReviewRisk Level: Medium | Verdict: MINOR_CONCERNS
Opened Issues
SummaryIntroduces a public, CORS-enabled JSON catalog API ( |
Summary
Introduces a public, CORS-enabled JSON catalog API that serves early-church texts to the Haven app. Strata produces audio via its existing pipeline; Haven consumes and displays it.
Changes
src/lib/haven-catalog.ts): Core logic to shapebooks/episodesdata into the Haven contract; merges live DB data with seed metadata; graceful fallback when DB unavailable.GET /api/haven/catalog: Collection summary (all works, no episodes)GET /api/haven/catalog/[slug]: Single work with episodessrc/lib/seed/early-church.ts): Curated collection of 8 Apostolic Fathers works (Didache, 1/2 Clement, Ignatius, Polycarp, Barnabas, Shepherd of Hermas) with titles, authors, eras, descriptions, and Project Gutenberg source URLs.collection varchar(100)column tobookstable via migration030_books_collection.sql.docs/haven-integration.md): Versioned schema and semantics (status field, episode structure, versioning strategy).The Contract
Status is
"ready"whenaudioUrlexists (audio has been produced), else"coming_soon"(Haven disables cards).Why this works
Testing
curl http://localhost:3000/api/haven/catalogreturns valid summarycurl http://localhost:3000/api/haven/catalog/didachereturns work with episodesAccess-Control-Allow-Origin: *presentGenerated by Claude Code