feat: add Hardcover import workflow

Add a preview-and-review workflow that matches Hardcover books by cached IDs, ISBNs, slugs, and title/author confidence before updating BookOrbit read status.

Import reading progress only when requested and safe, preserve existing local progress, and keep status imports resilient when progress writes fail.

Closes #394
This commit is contained in:
neon
2026-06-22 02:41:22 +00:00
parent ea6f10df53
commit 4197a8fc2e
18 changed files with 3712 additions and 4 deletions
+64
View File
@@ -41,3 +41,67 @@ export interface HardcoverActiveSyncStatus {
}
export type HardcoverPrivacySetting = 1 | 2 | 3;
export type HardcoverImportMatchMethod = "hardcover_id" | "isbn" | "title_author";
export type HardcoverImportPreviewOutcome = "will_update" | "needs_review" | "conflict" | "unmatched" | "skipped";
export type HardcoverImportProgressOutcome = "will_update" | "conflict" | "skipped";
export type HardcoverImportedReadStatus = Exclude<import("./book").ReadStatus, "rereading" | "skimmed" | "unread"> | "want_to_read";
export interface HardcoverImportPreviewRow {
hardcoverUserBookId: number;
hardcoverBookId: number;
hardcoverEditionId: number | null;
hardcoverReadId: number | null;
hardcoverTitle: string | null;
hardcoverAuthors: string[];
hardcoverStatusId: number;
hardcoverStatusLabel: string;
importedStatus: HardcoverImportedReadStatus | null;
importedStartedAt: string | null;
importedFinishedAt: string | null;
importedProgressPercent: number | null;
localBookId: number | null;
localPrimaryFileId: number | null;
localTitle: string | null;
localAuthors: string[];
localReadStatus: import("./book").ReadStatus | null;
localProgressPercent: number | null;
matchMethod: HardcoverImportMatchMethod | null;
confidence: number | null;
outcome: HardcoverImportPreviewOutcome;
reason: string;
progressOutcome: HardcoverImportProgressOutcome;
progressReason: string;
}
export interface HardcoverImportSummary {
totalHardcoverBooks: number;
matchedBooks: number;
willUpdate: number;
needsReview: number;
conflicts: number;
unmatched: number;
skipped: number;
progressWillUpdate: number;
progressConflicts: number;
progressSkipped: number;
}
export interface HardcoverImportPreview {
summary: HardcoverImportSummary;
rows: HardcoverImportPreviewRow[];
}
export interface ApplyHardcoverImportPayload {
hardcoverUserBookIds?: number[];
importProgress?: boolean;
}
export interface HardcoverImportApplyResult extends HardcoverImportSummary {
applied: number;
progressApplied: number;
failed: number;
}