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.
This commit is contained in:
kikootwo
2026-03-04 10:11:19 -05:00
parent 6ca2e964e8
commit 338331d006
23 changed files with 1613 additions and 1391 deletions
+22 -31
View File
@@ -518,26 +518,34 @@ model GoodreadsShelf {
@@map("goodreads_shelves")
}
model GoodreadsBookMapping {
id String @id @default(uuid())
goodreadsBookId String @unique @map("goodreads_book_id")
title String
author String
audibleAsin String? @map("audible_asin")
coverUrl String? @map("cover_url") @db.Text
noMatch Boolean @default(false) @map("no_match")
lastSearchAt DateTime? @map("last_search_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// ============================================================================
// UNIFIED BOOK MAPPING TABLE
// Global book-to-ASIN mapping cache shared across all shelf providers.
// Uses provider + externalBookId composite key for cross-provider dedup.
// ============================================================================
@@index([goodreadsBookId])
model BookMapping {
id String @id @default(uuid())
provider String // "goodreads", "hardcover", etc.
externalBookId String @map("external_book_id")
title String
author String
audibleAsin String? @map("audible_asin")
coverUrl String? @map("cover_url") @db.Text
noMatch Boolean @default(false) @map("no_match")
lastSearchAt DateTime? @map("last_search_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([provider, externalBookId])
@@index([provider, externalBookId])
@@index([audibleAsin])
@@map("goodreads_book_mappings")
@@map("book_mappings")
}
// ============================================================================
// HARDCOVER SYNC TABLES
// Per-user Hardcover list subscriptions + global book-to-ASIN mapping cache
// Per-user Hardcover list subscriptions
// ============================================================================
model HardcoverShelf {
@@ -560,23 +568,6 @@ model HardcoverShelf {
@@map("hardcover_shelves")
}
model HardcoverBookMapping {
id String @id @default(uuid())
hardcoverBookId String @unique @map("hardcover_book_id") // Internal ID from Hardcover
title String
author String
audibleAsin String? @map("audible_asin")
coverUrl String? @map("cover_url") @db.Text
noMatch Boolean @default(false) @map("no_match")
lastSearchAt DateTime? @map("last_search_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([hardcoverBookId])
@@index([audibleAsin])
@@map("hardcover_book_mappings")
}
// ============================================================================
// WORKS TABLE
// Cross-ASIN audiobook identity mapping — links multiple Audible ASINs