Files
kikootwo 338331d006 Add Hardcover shelf sync & unify book mappings
Introduce Hardcover provider support and consolidate per-provider book mapping tables into a unified BookMapping model. Adds two Prisma migrations (add_hardcover_shelves, unify_book_mappings), new backend services (hardcover-api, shelf-sync-core), and provider-specific sync logic and API routes for hardcover shelves with token/list validation. Frontend: new HardcoverForm component, refactor AddShelfModal to support Hardcover, hook updates, and small UI/accessibility tweaks. Also add documentation for Goodreads and Hardcover sync flows and update tests to cover scheduler/prisma helpers.
2026-03-04 10:11:19 -05:00

3.8 KiB

Goodreads & Shelf Sync

Status: Implemented | RSS feed parsing, shared sync core, extensible provider architecture

Overview

Syncs user-subscribed Goodreads shelves via RSS feeds, resolves books to Audible ASINs, and creates requests. Also documents the shared shelf sync core used by all providers.

Architecture

Files

  • src/lib/services/goodreads-sync.service.ts — RSS fetch/parse, delegates to shared core
  • src/lib/services/shelf-sync-core.service.ts — Shared sync logic (Audible lookup, cover enrichment, request creation)
  • src/lib/utils/shelf-helpers.ts — Shared processBooks() utility for cover URL parsing
  • src/lib/hooks/createShelfHooks.ts — Generic hook factory for shelf CRUD operations
  • src/app/api/user/goodreads-shelves/route.ts — GET (list) + POST (add) routes
  • src/app/api/user/goodreads-shelves/[id]/route.ts — DELETE + PATCH routes
  • src/app/api/user/shelves/route.ts — Combined GET for all providers (GenericShelf shape)
  • src/lib/hooks/useGoodreadsShelves.ts — Frontend hooks (via createShelfHooks factory)

Database Models

  • GoodreadsShelf — Per-user shelf subscription (userId, rssUrl, name, lastSyncAt, bookCount, coverUrls)
  • BookMapping — Shared table for all providers. Keyed by provider + externalBookId. Caches Audible ASIN lookups.

Goodreads RSS Feed

  • Format: https://www.goodreads.com/review/list_rss/{userId}?shelf={shelfName}
  • Auth: None required (public RSS)
  • Parsing: fast-xml-parser extracts item entries with book_id, title, author_name, book_image_url

Shared Sync Core

shelf-sync-core.service.ts contains all provider-agnostic sync logic:

Interface: ShelfBook

{ bookId: string; title: string; author: string; coverUrl?: string }

Function: processShelfBooks()

Accepts provider-agnostic book list + context, performs:

  1. BookMapping lookup — Check if book already resolved (provider + externalBookId)
  2. Audible search — Full query (title author), fallback with cleaned title (strips parenthetical series info)
  3. noMatch retry — Re-searches after NO_MATCH_RETRY_DAYS (7 days)
  4. Request creation — Calls createRequestForUser() for matched ASINs
  5. Cover enrichment — Queries audibleCache for cached covers, builds /api/cache/thumbnails/ URLs
  6. Shelf metadata update — Writes lastSyncAt, bookCount, top 8 books as JSON to coverUrls

Constants

  • DEFAULT_MAX_LOOKUPS_PER_SHELF = 10 (per scheduled cycle; 0 = unlimited for manual triggers)
  • NO_MATCH_RETRY_DAYS = 7

Hook Factory: createShelfHooks(endpoint)

Returns { useList, useAdd, useDelete, useUpdate } — all with SWR caching, optimistic updates, and automatic revalidation of the combined /api/user/shelves endpoint.

API Endpoints

Method Path Purpose
GET /api/user/goodreads-shelves List user's Goodreads shelves
POST /api/user/goodreads-shelves Add shelf (validates RSS feed, triggers sync)
DELETE /api/user/goodreads-shelves/[id] Remove shelf (ownership check)
PATCH /api/user/goodreads-shelves/[id] Update RSS URL (triggers re-sync)
GET /api/user/shelves Combined endpoint — merges all providers into GenericShelf

Adding a New Provider

  1. Create Prisma shelf model + migration (BookMapping table is already shared)
  2. Create API client service for the external data source
  3. Create thin sync service (~50-80 lines) that fetches books and calls processShelfBooks()
  4. Create API routes (or use a generic route handler)
  5. Create hook file (~40 lines) using createShelfHooks(endpoint)
  6. Add tab in AddShelfModal with provider-specific form fields