reading: split the book tracker into books, papers and articles (kind column + migration classifying 20 papers), segmented tabs with counts, article cards that open the link, owner + add link with server-side unfurl, kind switch on detail pages, /trackers/reading alias
All checks were successful
Deploy production / deploy (push) Successful in 1m8s

This commit is contained in:
2026-10-25 13:42:51 +00:00
parent 7b5faf6e74
commit e31e1be73f
20 changed files with 27592 additions and 247 deletions

View File

@@ -34,10 +34,15 @@ export const bookReadingStatus = booksSchema.enum('reading_status', [
'abandoned'
]);
// What a work is: a book, a scientific paper, or a web article / link on the reading list.
// Articles have no files; they live at `url` and are opened externally.
export const bookKind = booksSchema.enum('kind', ['book', 'paper', 'article']);
export const bookWorks = booksSchema.table(
'works',
{
id: id(),
kind: bookKind('kind').notNull().default('book'),
title: text('title').notNull(),
subtitle: text('subtitle'),
authors: text('authors').array().notNull().default([]),
@@ -61,9 +66,18 @@ export const bookWorks = booksSchema.table(
metadataSource: text('metadata_source'), // 'openlibrary' | 'googlebooks' | 'libgen' | 'manual'
metadataFetchedAt: timestamptz('metadata_fetched_at'),
notes: text('notes'),
// Articles and papers on the web: canonical link, site name and publish date. The excerpt
// goes in `description` and the og:image in `cover_url`.
url: text('url'),
siteName: text('site_name'),
publishedAt: timestamptz('published_at'),
...timestamps
},
(t) => [
uniqueIndex('works_url_key')
.on(t.url)
.where(sql`${t.url} is not null`),
index('works_kind_idx').on(t.kind),
uniqueIndex('works_isbn13_key')
.on(t.isbn13)
.where(sql`${t.isbn13} is not null`),
@@ -206,4 +220,5 @@ export type BookShelf = typeof bookShelves.$inferSelect;
export type BookProgress = typeof bookProgress.$inferSelect;
export type BookNote = typeof bookNotes.$inferSelect;
export type BookSession = typeof bookSessions.$inferSelect;
export type BookKind = (typeof bookKind.enumValues)[number];
export type BookReadingStatus = (typeof bookReadingStatus.enumValues)[number];