mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
feat(metadata): add full publication date support (#598)
- Add a persisted publication date while continuing to derive publication years. - Thread full dates through metadata import, editing, filters, sorting, write-back, and sync payloads. - Cover provider parsing, DTO validation, jump buckets, and date formatting with tests. Closes #596
This commit is contained in:
@@ -19,6 +19,11 @@ describe("jumpBucketKindForSort", () => {
|
||||
expect(jumpBucketKindForSort([{ field: "publishedYear", dir: "desc" }])).toBe("year");
|
||||
});
|
||||
|
||||
it("returns year for publishedDate sorts", () => {
|
||||
expect(jumpBucketKindForSort([{ field: "publishedDate", dir: "asc" }])).toBe("year");
|
||||
expect(jumpBucketKindForSort([{ field: "publishedDate", dir: "desc" }])).toBe("year");
|
||||
});
|
||||
|
||||
it("returns letter for an empty sort (defaults to title asc)", () => {
|
||||
expect(jumpBucketKindForSort([])).toBe("letter");
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface BookDockMetadata {
|
||||
narrators?: string[];
|
||||
description?: string;
|
||||
publisher?: string;
|
||||
publishedDate?: string;
|
||||
publishedYear?: number;
|
||||
language?: string;
|
||||
pageCount?: number;
|
||||
|
||||
@@ -54,6 +54,7 @@ export type BookCard = {
|
||||
seriesIndex: number | null;
|
||||
seriesMemberships?: BookSeriesMembership[];
|
||||
files: BookFileRef[];
|
||||
publishedDate: string | null;
|
||||
publishedYear: number | null;
|
||||
language: string | null;
|
||||
genres: string[];
|
||||
@@ -126,6 +127,7 @@ export type BookDetail = {
|
||||
isbn10: string | null;
|
||||
isbn13: string | null;
|
||||
publisher: string | null;
|
||||
publishedDate: string | null;
|
||||
publishedYear: number | null;
|
||||
language: string | null;
|
||||
pageCount: number | null;
|
||||
@@ -169,6 +171,7 @@ export type BookMetadataRefreshPreviewFields = {
|
||||
authors?: string[];
|
||||
genres?: string[];
|
||||
publisher?: string | null;
|
||||
publishedDate?: string | null;
|
||||
publishedYear?: number | null;
|
||||
language?: string | null;
|
||||
pageCount?: number | null;
|
||||
|
||||
@@ -3,6 +3,7 @@ export const CORE_BOOK_FILE_WRITE_FIELDS = [
|
||||
"subtitle",
|
||||
"description",
|
||||
"publisher",
|
||||
"publishedDate",
|
||||
"publishedYear",
|
||||
"language",
|
||||
"pageCount",
|
||||
@@ -60,6 +61,7 @@ export const BOOK_FILE_WRITE_FIELD_LABELS = {
|
||||
subtitle: "Subtitle",
|
||||
description: "Description",
|
||||
publisher: "Publisher",
|
||||
publishedDate: "Published date",
|
||||
publishedYear: "Published year",
|
||||
language: "Language",
|
||||
pageCount: "Page count",
|
||||
@@ -122,6 +124,7 @@ export const AUDIO_BOOK_FILE_WRITE_FIELDS = [
|
||||
"subtitle",
|
||||
"authors",
|
||||
"narrators",
|
||||
"publishedDate",
|
||||
"publishedYear",
|
||||
"publisher",
|
||||
"description",
|
||||
|
||||
@@ -22,6 +22,7 @@ export type JumpBucketsResponse = {
|
||||
const KIND_BY_PRIMARY_SORT_FIELD: Partial<Record<SortField, JumpBucketKind>> = {
|
||||
title: "letter",
|
||||
author: "letter",
|
||||
publishedDate: "year",
|
||||
publishedYear: "year",
|
||||
};
|
||||
|
||||
|
||||
@@ -228,6 +228,7 @@ export interface KoreaderCatalogBookDetail extends KoreaderCatalogBookListItem {
|
||||
subtitle: string | null;
|
||||
description: string | null;
|
||||
publisher: string | null;
|
||||
publishedDate: string | null;
|
||||
publishedYear: number | null;
|
||||
language: string | null;
|
||||
isbn10: string | null;
|
||||
|
||||
@@ -66,6 +66,7 @@ export interface MetadataCandidate {
|
||||
authors?: string[];
|
||||
description?: string;
|
||||
publisher?: string;
|
||||
publishedDate?: string;
|
||||
publishedYear?: number;
|
||||
language?: string;
|
||||
pageCount?: number;
|
||||
@@ -126,6 +127,7 @@ export interface MetadataSource {
|
||||
subtitle: string | null;
|
||||
description: string | null;
|
||||
publisher: string | null;
|
||||
publishedDate: string | null;
|
||||
publishedYear: number | null;
|
||||
language: string | null;
|
||||
pageCount: number | null;
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { CommunityRatingProviderKey } from "./metadata-fetch";
|
||||
* - `library` - resolved via `books.library_id` join to `libraries.name`
|
||||
* - `format` - resolved via `book_files.format` (primary file)
|
||||
* - `isbn` - matches both `isbn10` and `isbn13` in `book_metadata`
|
||||
* - `publishedDate` - uses full dates when available and falls back to published year
|
||||
* - `lockStatus` - derived from `book_metadata.locked_fields` (non-empty array = locked)
|
||||
* - `seriesStatus` - computed per-user: "up next in series" (next unstarted book whose earlier series entries are all finished)
|
||||
*/
|
||||
@@ -26,6 +27,7 @@ export type RuleField =
|
||||
| "language"
|
||||
| "series"
|
||||
| "seriesIndex"
|
||||
| "publishedDate"
|
||||
| "publishedYear"
|
||||
| "pageCount"
|
||||
| "author"
|
||||
@@ -89,6 +91,7 @@ export const FIELD_OPERATORS: Record<RuleField, RuleOperator[]> = {
|
||||
collection: ["includesAny", "excludesAll", "isEmpty", "isNotEmpty"],
|
||||
library: ["includesAny", "excludesAll"],
|
||||
format: ["includesAny", "excludesAll"],
|
||||
publishedDate: ["before", "after", "between", "withinLast", "isEmpty", "isNotEmpty"],
|
||||
publishedYear: ["eq", "notEq", "gt", "gte", "lt", "lte", "between", "isEmpty", "isNotEmpty"],
|
||||
seriesIndex: ["eq", "notEq", "gt", "gte", "lt", "lte", "between", "isEmpty", "isNotEmpty"],
|
||||
pageCount: ["gt", "gte", "lt", "lte", "between", "isEmpty", "isNotEmpty"],
|
||||
@@ -181,6 +184,7 @@ export type GroupRule = {
|
||||
* - `finishedAt` - from `user_book_status.finished_at` (per-user, correlated subquery)
|
||||
* - `rating` - from `user_book_ratings.rating` (per-user, correlated subquery)
|
||||
* - `format` - from `book_files.format` for the primary file (correlated subquery)
|
||||
* - `publishedDate` - uses full dates when available and falls back to published year
|
||||
* - `random` - day-seeded pseudorandom based on book id and user id
|
||||
*
|
||||
* Fields marked "per-user, correlated subquery" require an authenticated userId and
|
||||
@@ -193,6 +197,7 @@ export type SortField =
|
||||
| "seriesIndex"
|
||||
| "addedAt"
|
||||
| "updatedAt"
|
||||
| "publishedDate"
|
||||
| "publishedYear"
|
||||
| "pageCount"
|
||||
| "rating"
|
||||
@@ -215,6 +220,7 @@ export const SORT_FIELDS: SortField[] = [
|
||||
"seriesIndex",
|
||||
"addedAt",
|
||||
"updatedAt",
|
||||
"publishedDate",
|
||||
"publishedYear",
|
||||
"pageCount",
|
||||
"rating",
|
||||
|
||||
Reference in New Issue
Block a user