mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
338331d006
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.
3.8 KiB
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 coresrc/lib/services/shelf-sync-core.service.ts— Shared sync logic (Audible lookup, cover enrichment, request creation)src/lib/utils/shelf-helpers.ts— SharedprocessBooks()utility for cover URL parsingsrc/lib/hooks/createShelfHooks.ts— Generic hook factory for shelf CRUD operationssrc/app/api/user/goodreads-shelves/route.ts— GET (list) + POST (add) routessrc/app/api/user/goodreads-shelves/[id]/route.ts— DELETE + PATCH routessrc/app/api/user/shelves/route.ts— Combined GET for all providers (GenericShelf shape)src/lib/hooks/useGoodreadsShelves.ts— Frontend hooks (viacreateShelfHooksfactory)
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-parserextractsitementries withbook_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:
- BookMapping lookup — Check if book already resolved (
provider+externalBookId) - Audible search — Full query (
title author), fallback with cleaned title (strips parenthetical series info) - noMatch retry — Re-searches after
NO_MATCH_RETRY_DAYS(7 days) - Request creation — Calls
createRequestForUser()for matched ASINs - Cover enrichment — Queries
audibleCachefor cached covers, builds/api/cache/thumbnails/URLs - Shelf metadata update — Writes
lastSyncAt,bookCount, top 8 books as JSON tocoverUrls
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
- Create Prisma shelf model + migration (BookMapping table is already shared)
- Create API client service for the external data source
- Create thin sync service (~50-80 lines) that fetches books and calls
processShelfBooks() - Create API routes (or use a generic route handler)
- Create hook file (~40 lines) using
createShelfHooks(endpoint) - Add tab in
AddShelfModalwith provider-specific form fields