40 Commits
Author SHA1 Message Date
patrick 55e00ba960 feat: import a Calibre library
Reads metadata.db and copies the books into a library — from a zip uploaded on
the library settings page, or from a path with `litestar calibre-import`. The
source is never touched, and re-running only picks up what is new.

Also names the formats mimetypes does not know: a Calibre library is full of
MOBI and AZW3, and a null content type used to fail the book endpoint.
2026-08-17 13:38:44 -04:00
patrick 22539644a9 feat: merge books into one record
Files move on disk before any row moves, then everything repoints in one
transaction and the folded records are deleted. Progress keeps whichever is
furthest, shelves and tags union, and identifiers move only under a name the
survivor lacks. Metadata is left alone unless the caller resolves it, since
choosing between two titles is a judgement this endpoint cannot make. Nothing
is removed from disk.
2026-08-16 20:21:10 -04:00
patrick a48be517e4 fix: type the ids filter from its configured id type
advanced_alchemy annotates the `ids` query parameter as list[str] whatever the
config says, so a bigint primary key was compared against strings and Postgres
refused. Nothing had called ?ids= until now.
2026-08-16 20:20:44 -04:00
patrick 2e0d556c33 fix: return the publisher an EPUB declares
The lookup discarded its own result and fell off the end of the function, so
no EPUB ever contributed a publisher.
2026-08-16 00:01:02 -04:00
patrick ff75f2c758 chore: clear the lint in the files this branch touches
Unused imports, duplicated import lines, and bare excepts that swallowed
KeyboardInterrupt along with everything else.
2026-08-15 22:01:04 -04:00
patrick e967019964 feat: split the edition out of a book's title
"Fluent Python, 2nd Edition" is one book with a field for the edition. Left in
the title it also splits the library, since the second edition never looks like
the first. Only a numbered statement is moved, so "Catch 22" keeps its number
and "Global Edition" — which has nowhere to go in an integer column — stays put.
2026-08-15 21:57:17 -04:00
patrick f6bb06ac6e fix: merge identifiers across a book's formats
Identifiers are a collection, but the whole dict was replaced per format, so
the last file to report won outright — an EPUB declaring an ASIN, a Google id
and a Calibre id kept none of them once a PDF contributed one ISBN. Accumulate
instead, letting a declared identifier outrank one scraped off a page.
2026-08-15 21:57:01 -04:00
patrick 8ee406533c feat: canonicalise author names
Extractors wrote whatever the file said, so one person held several rows:
"Sam Newman" beside "Newman, Sam;" beside "Sam Newman.epub", the last because
the upload path never stripped the file extension. Tidy on write, in the
validator and in the uniqueness lookup alike, and merge the rows that collide.
2026-08-15 21:56:35 -04:00
patrick 373c96d6d6 fix: match the compact edition markers a cover carries
"Building Microservices, 2E" never matched "Building Microservices". Key the
strip on the trailing "e" so 2E, 5e and 3 Ed are caught, while a bare number
leaves "Catch 22" and "Blade Runner 2049" alone. Stored keys are recomputed.
2026-08-15 21:55:45 -04:00
patrick 5047277845 feat: detect books that may already be in the library
Match on a shared identifier, or on a normalised title credited to a shared
author, and report candidates rather than refusing anything — a metadata match
is a guess, and a second edition is not a mistake. Adds a library-wide review
pass, dismissals, and renames the fingerprint pre-flight to duplicate-files.
2026-08-15 21:55:13 -04:00
patrick 6d1890ce04 feat: store matching keys and duplicate dismissals
Derive normalized_title, normalized_name and normalized_value with @validates
so no write can bypass them, and add the table recording pairs a reader has
said are not the same book.
2026-08-15 21:54:32 -04:00
patrick 523117ec28 fix: keep the identifiers an EPUB declares
DC:identifier was validated verbatim, so hyphenated and urn:isbn: forms never
reached the checksum and every non-ISBN identifier was discarded. Normalise
first, and name whatever survives.
2026-08-15 21:54:12 -04:00
patrick 86e1d096ef feat: add normalisation helpers for book matching
Reduce a title, an author and an identifier to a single comparison key, so
two copies of one book can be recognised by equality rather than by a
similarity score.
2026-08-15 21:44:50 -04:00
patrick d321315acf fix: ignore duplicate matches whose file is gone
The hash lives in the database and the file does not, so a file deleted behind
the app's back went on refusing its own replacement. Matches are now checked
against disk, and re-adding a book's own missing file writes it back into the
row that already describes it.
2026-08-14 14:18:08 -04:00
patrick d78b21c27f feat: check for duplicate files when importing books
Incoming files are matched against what is already stored, keyed on the
KOReader hash and the file size. Bulk uploads skip and report them, deliberate
creates are refused with a 409, the consume directory parks them aside, and
allow_duplicates overrides all three.

Also: books whose metadata generates a path another book already owns are moved
aside, so a forced copy cannot overwrite the original's files.
2026-08-13 17:23:48 -04:00
patrick 510306f24d fix: keep embedded metadata and group formats on directory upload
The filepath extractor was merged on the right, so a parent folder's name
overrode the title inside the file. Grouping also split a book's formats when
its own folder was the upload root.
2026-08-12 15:17:48 -04:00
patrick bd8d68b9ba fix: resolve file paths and stream the multi-book zip download
file.path is relative to book.path, so the archive matched nothing on disk and
came out empty. Also namespace entries per book to stop filename collisions, and
stream the zip instead of buffering it whole.
2026-08-12 15:17:48 -04:00
patrick 540522e828 fix: reuse existing link rows when updating book relationships
Assigning through the association proxy recreated links for targets the book
already had, colliding with the link tables' unique constraints. Reconcile the
collections in place instead, identifiers included.
2026-08-12 15:17:48 -04:00
patrick a1f39a8dc8 fix: scope book progress and shelves to the requesting user 2026-08-11 20:08:26 -04:00
patrick 3a3957d432 fix: resolve BookProgressRead reference in BookRead 2026-08-11 00:27:56 -04:00
patrick 23a12f7970 docs: add AGENTS.md for repo, backend and frontend 2026-08-10 23:58:42 -04:00
patrick 3091cc879d fix: skip unreadable PDFs during metadata extraction 2026-08-10 23:58:42 -04:00
patrick cc39e79cd7 chore: regenerate initial migrations 2026-08-10 23:58:09 -04:00
patrick c5b703b75a refactor: use unique constraints instead of composite keys on link tables 2026-08-10 23:58:09 -04:00
patrick 9fe69641c5 feat: add library icons 2026-08-10 23:58:02 -04:00
patrick 0963ee85c2 chore: update nix dev shells and pnpm build settings 2026-08-10 23:58:02 -04:00
patrick 448e0e0090 chore: ignore local library files 2026-08-10 23:57:46 -04:00
patrick 9711c68fbb chore: add migration for initial db tables 2026-03-09 14:28:35 -04:00
patrick 20df5ea140 remove python313Full package as it is no longer available in nixos 25.11 2026-03-09 14:26:33 -04:00
patrick 2e1d16ef9e refactor: move db and file watcher setup to use async context manager 2026-03-09 14:25:24 -04:00
patrick 2de0aac23a chore: update dependencies and ignore new warning in tests
ignore JWT related warning that was introduced when updating
dependencies related to JWT secret size (test value of "secret" is too
short).
2026-03-09 14:21:37 -04:00
patrick 51c1900d8c feat: add KOSync server
- Add KOSync device management
- Add API key auth middleware for devices to authenticate
- Add KOSync-compatible progress sync endpoints
- Add basic tests for KOSync compatible hashes
2026-03-09 14:11:21 -04:00
patrick 20a69de968 feat: add KOReader compatible hash to file metadata
Implement KOReader's partial MD5 algorithm for document identification. This hash allows KOReader devices to match local files with server records for reading progress synchronization (KOSync).
2026-03-09 13:38:07 -04:00
patrick c67ca0e1df Handle optional book.path to support books without files
Books may not have a path (e.g., physical books, metadata-only entries).
Updated path-dependent operations to handle None gracefully:

- get_file: raise ValueError if book has no path
- update_book: skip path relocation if no path exists
- remove_files: skip filesystem cleanup if no path exists

Also fixed _save_book_files return type and removed unused imports.
2026-03-07 12:38:04 -05:00
patrick 3a5ea1d158 fix typecheck errors in test_book_service.py 2026-03-07 12:17:29 -05:00
patrick 8117f0dbfe rename BookService CRUD overrides to domain-specific methods
The create, update, and delete methods had incompatible signatures
resulting in typecheck errors. Renamed to create_book, update_book, and
delete_books.
2026-03-07 11:58:24 -05:00
patrick 67fab3f9c6 fix update progress test to match new progress schema
"progress" field was renamed to "percentage"
2026-03-07 11:44:08 -05:00
patrick a19c944b6e Fix hardcoded absolute paths in book upload tests
The Darwin epub test cases used absolute paths. I hadn't notices until switching machines, whcih caused errors in the test harness. Changed to relative paths to be consistent with other test cases.
2026-03-07 11:35:30 -05:00
hiperman 766fca2c39 Update BookProgress model and schemas
Updated book progress model in anticipation of the KOReader sync
feature.

Renamed properties:
 - renamed "progress" to "percentage"
 - renamed "pdf_loc" to "pdf_page"
 - renamed "epub_loc" to "epub_cfi"

New properties:
 - "epub_xpointer", marks the location of an epub in KOReader
 - "device", the device type that updated the progress
 - "device_id", the id of the device that updated the progress
2025-12-12 14:19:16 -05:00
hiperman 7ca0a21283 Initial commit 2025-12-04 00:33:37 -05:00