Skip to content

Add public Haven catalog API for early-church collection - #9

Open
bitskc wants to merge 1 commit into
mainfrom
claude/strata-haven-integration-eciqiy
Open

Add public Haven catalog API for early-church collection#9
bitskc wants to merge 1 commit into
mainfrom
claude/strata-haven-integration-eciqiy

Conversation

@bitskc

@bitskc bitskc commented Jun 28, 2026

Copy link
Copy Markdown
Owner

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

  • Catalog service (src/lib/haven-catalog.ts): Core logic to shape books/episodes data into the Haven contract; merges live DB data with seed metadata; graceful fallback when DB unavailable.
  • Catalog endpoints:
    • GET /api/haven/catalog: Collection summary (all works, no episodes)
    • GET /api/haven/catalog/[slug]: Single work with episodes
    • Both public, CORS-enabled, CDN-cached
  • Early Church seed data (src/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.
  • Schema update: Added optional collection varchar(100) column to books table via migration 030_books_collection.sql.
  • Contract documentation (docs/haven-integration.md): Versioned schema and semantics (status field, episode structure, versioning strategy).

The Contract

GET /api/haven/catalog
{
  "version": "1",
  "collection": "early-church",
  "generatedAt": "<ISO8601>",
  "works": [{ "slug", "title", "author", "era", "description", "coverImageUrl", "totalEpisodes", "totalDurationSeconds" }]
}

GET /api/haven/catalog/didache
{ ...work detail... "episodes": [{ "slug", "episodeNumber", "title", "audioUrl", "durationSeconds", "transcript" }] }

Status is "ready" when audioUrl exists (audio has been produced), else "coming_soon" (Haven disables cards).

Why this works

  • Haven can build and demo offline against the bundled mock
  • When Strata audio pipeline produces real episodes, the endpoint returns them immediately
  • No Haven code change needed to go live — just flip the config URL
  • Contract is versioned so future Pro features (abridgments, commentary) extend additively

Testing

  • curl http://localhost:3000/api/haven/catalog returns valid summary
  • curl http://localhost:3000/api/haven/catalog/didache returns work with episodes
  • CORS header Access-Control-Allow-Origin: * present
  • Cache-Control headers set for CDN

Generated by Claude Code

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

bitskc commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

Recommend closing as superseded. A public Haven catalog API already shipped to main and is live: GET /api/haven/catalog, /api/haven/catalog/[slug], and /[slug]/text — public, CORS-open, versioned, with tier-checked audio URLs and stable chapter ids. The contract is documented in docs/haven-integration.md, and the remaining (Haven-side) integration work is tracked in #38.

This PR (opened 2026-06-28) predates that merge and its base is far behind current main, so merging it would conflict with and partly revert the shipped version. Nothing here appears to be un-captured by the merged API. Suggest closing unless there's seed data or a contract detail here you specifically want to port forward — happy to diff it against the live catalog if useful.


Generated by Claude Code

@bitskc

bitskc commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

⚠️ This PR has been inactive for 32 days. Consider closing or updating.

Comment thread src/lib/haven-catalog.ts

if (bookRows.length === 0) return result;

for (const book of bookRows) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/lib/haven-catalog.ts
title: episodes.title,
audioUrlFull: episodes.audioUrlFull,
durationSeconds: episodes.durationSeconds,
transcript: episodes.transcript,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/lib/haven-catalog.ts
const seed = EARLY_CHURCH_WORKS_BY_SLUG[slug];
if (!seed) return null;

const live = (await loadLiveCollection()).get(slug);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@bitskc

bitskc commented Aug 12, 2026

Copy link
Copy Markdown
Owner Author

Automated Code Review

Risk Level: Medium | Verdict: MINOR_CONCERNS
Recommendation: ⚠️ Merge with follow-up

Persona Severity Finding
Security Auditor P3 No rate limiting on public unauthenticated endpoints — consistent with existing podcast feed pattern; CDN caching (s-maxage=300) mitigates. No sensitive data exposed (transcripts are already public via podcast RSS feed). Observation, not blocking.
Regression Skeptic None. The collection column is additive (nullable, idempotent migration with IF NOT EXISTS). No existing code paths modified.
Performance Critic P2 N+1 query in loadLiveCollection (loops over books, queries episodes per book); buildWorkDetail over-fetches entire collection for a single-work request; buildCatalogSummary fetches transcript column it never uses. See issue #68.
Architecture Judge P3 Duplicated summary-field computation between buildCatalogSummary and buildWorkDetail (title/author/era/description/coverImageUrl/status/totalEpisodes/totalDurationSeconds computed identically). Could extract a shared helper.
QA Lead P2 No tests for buildCatalogSummary or buildWorkDetail — the seed/live merge logic, 404 path, and DB-unavailable fallback are all untested. Repo has an established vitest pattern. See issue #69.
Product Reliability P3 loadLiveCollection catches all DB errors and returns empty map with only console.warn — no metric/alert. Silent degradation to seed-only catalog if DB is down.

Opened Issues

  • #68 — N+1 query and over-fetching in haven-catalog loadLiveCollection
  • #69 — No tests for Haven catalog shaping logic

Summary

Introduces a public, CORS-enabled JSON catalog API (/api/haven/catalog and /api/haven/catalog/[slug]) serving early-church texts to the Haven app, plus an additive books.collection column and migration. The design is sound — seed metadata fallback, versioned contract, graceful DB-unavailable degradation. The main concerns are performance (N+1 queries, over-fetching the entire collection for single-work detail, fetching unused transcript data in the summary path) and missing test coverage for the core shaping logic. None are blocking; safe to merge with follow-up on the opened issues. Human reviewers should focus on the query patterns in loadLiveCollection and whether the collection is expected to grow significantly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants