Commit Graph

42 Commits

Author SHA1 Message Date
neon fc6e6f03d5 feat(book-requests): add automated request fulfillment
Let users request books, route approvals, search configured sources, and import fulfilled releases through the Book Dock. Include scoped permissions, automation, download-client integrations, plugin support, requester visibility, and one consolidated schema migration.

Closes #248
Ref #1115
2026-08-31 16:52:43 +00:00
neon 3ea6063f31 fix(users): add safe superuser management
Expose promotion and demotion to existing superusers while keeping shared and self targets blocked. Serialize role lifecycle changes, preserve one active superuser, and invalidate stale credentials.

Closes #1208
2026-08-30 05:44:37 +00:00
neon 5ec34aef23 feat(settings): redesign users, field rules, file naming and libraries
Rebuilds the users roster, metadata field rules, file naming and metadata
score panels, and replaces the libraries list with an expandable ledger.
Libraries now batch their counts and last scan into one request instead of a
per-library fan-out, and surface scan history from scan_jobs on demand.
2026-08-23 23:57:17 +00:00
neon 1199e3cdd4 fix(metadata): stop one book record being assembled from several books
Field rules resolve each field from whichever provider ranks first for it, and nothing ever
compared candidates to one another. A provider that matched the wrong book still won every field
it led on, so a single record could carry a title from one book, a cover from a second and a
subtitle from a third. The stock defaults make that the ordinary case rather than an unlucky one,
since cover leads with Amazon while title and subtitle lead with Goodreads.

Relevance scoring is what let those wrong matches in. A candidate survived on any score above
zero, and token overlap counted stopwords, so "The Girl on the Train" scored against a query for
"The Hobbit" on the strength of a shared "the". Substring matching ignored word boundaries as
well, which is how "Ubik" matched "Rubik Cube Solutions". Drop stopwords from the query side,
require a majority of the words that remain, match whole words only, and give the surviving floor
a name.

Then hold every candidate to an anchor before field rules ever see it. The anchor is the edition
confirmed by the book's own ISBN when there is one, otherwise the candidate the query supports
most strongly, with the title rule breaking ties because that is where the reader declares which
provider decides identity. Agreement admits subtitle extensions, reordered words and translated
editions, and rejects on a contradicting author, which is the only signal that separates a book
from a companion volume that extends its title. Reject solely on positive evidence: a candidate
with no title, or with no author to compare against, keeps the benefit of the doubt, and a lone
provider is never second-guessed. Rejected candidates no longer contribute their provider ids
either, which would otherwise pin the wrong book for every later refresh.

Normalizing titles to [a-z0-9] emptied every title written in Japanese, Chinese, Russian or
Greek. That scored two providers reporting an identical title as a disagreement, which under the
new anchor would drop all but one of them in those libraries, and it had already denied the
relevance gate any way to score them at all. Decompose instead, strip nonspacing marks so Soseki
matches Sōseki, and keep letters of any script.

Two sets of fixtures described different books per provider while testing something else
entirely, field-rule precedence and metadata locking, and now describe one book so that they
still test what they were written for.

Closes #1023
2026-08-18 19:30:13 +00:00
neon 42f175f1ef feat(authors): add Goodreads as an author metadata source
Audnexus was the only author provider, and it returns just an ASIN, name,
description and image. Goodreads carries a far richer author record, so add it
as a second provider and let each field choose where it comes from.

The Goodreads API was retired in 2020, so the provider scrapes: a name resolves
to an author id through the WAF-free /book/auto_complete endpoint, falling back
to the author search page for names that only differ by a middle initial, and
the author page yields the biography, photo, birth and death dates, website,
genres and influences. Two rendering quirks are worth knowing about, since both
silently return the wrong value: free-text fields appear twice, truncated and
whole, so the parser takes whichever copy carries the most content; and the
birthplace sits in a bare text node while only the date is wrapped in a
dataItem, so authors with a place but no date correctly store nothing.

Preferences are now per field, mirroring the book-side FieldPreference model,
with an ordered provider list and a merge strategy each. They are global,
because an author row is shared across every library and cannot hold a
different biography per library. Providers declare which fields they can return
and both the resolver and the settings UI filter on that, so Audnexus can never
be assigned to a field it has no way to fill. The single global write mode is
migrated into each field's merge strategy on first read, keeping an existing
install's overwrite behaviour instead of silently adopting the default.

The executor now queries every listed provider and resolves each field
independently, first non-empty value winning, and records which provider
supplied the record so a re-run can pin the match instead of searching by name
again. overwriteIfProvided skips empty values, which is what lets a matched
author with an empty Audnexus biography fall through to Goodreads.
2026-08-18 03:48:20 +00:00
neon ec4072e4be feat(server): add Audiobookshelf and Calibre-Web Automated migration sources
Extend the migration module beyond Booklore and Grimmory so an existing Audiobookshelf or
Calibre-Web Automated library can be moved into BookOrbit without rebuilding user state by
hand. Audiobookshelf supports both a live API connection and an offline backup file, while
Calibre-Web Automated reads a stopped snapshot of the app and metadata databases.

Match source books by ASIN in addition to ISBN, and batch the ISBN, ASIN, file hash, file
path, and title/author lookups so planning a large library issues a bounded number of
queries rather than one per book. Route the batched author comparison through the shared
accent-insensitive predicate, which now accepts a SQL fragment so a per-row pattern column
is matched the same way a bound literal is.

Extract the shared source connection form into MigrationSourceFields so the modal and the
settings page stay in sync as source types are added, and allow a mapping to skip a source
user instead of forcing every account to be imported.

Closes #986
2026-08-16 05:15:34 +00:00
neon 26b7c602f8 security(settings): gate Book Dock settings behind manage_book_dock
Require manage_book_dock for the settings UI and the scoped API so regular Book Dock users cannot
view or modify administrative configuration.

Reject every Book Dock key on the generic PATCH /app-settings/:key route as well, including
book_dock_paused. That route accepted the same keys with any string value, so an operator with
manage_app_settings could store an out-of-range threshold or an unknown metadata mode and bypass
the validation the scoped endpoints apply.

Closes #1003
2026-08-14 03:15:30 +00:00
neon ca3c637271 fix(auth): split Book Dock access from Book Dock administration
Book Dock was a single permission that the standard preset handed to every user, so an ordinary
account could pause processing, trigger a rescan, and finalize straight into the library. Keep
book_dock_access for personal upload and review, and move global control behind a new
manage_book_dock permission in the administration group.

Finalization now also requires library_upload, since it writes into a library. Per-file reads and
mutations are scoped to the uploader unless the caller can manage the dock, which closes the gap
that let any Book Dock user read, edit, or discard another user's entry by id. The summary
websocket now announces a change instead of broadcasting global counts, so each client refetches
its own scoped totals.

Closes #961
2026-08-09 21:27:33 +00:00
neon 6769bf4b0f fix(auth): make account lockout recoverable and visible
The lockout was checked before the password was verified, so a correct password on a locked
account returned the same generic invalid-credentials error as a wrong one. Users had no way to
tell the two apart, and the natural response was to keep retrying a password that was already
right.

Completing a password reset did not clear the lock either, so the one recovery path the UI
advertises appeared to fail for up to fifteen minutes after a successful reset.

Verify the password first and report the lockout, with the remaining wait, only when the supplied
password was correct. Someone guessing wrong passwords still gets the generic failure, so account
existence is not leaked. Clear the lock whenever a password is reset or changed, and give admins
an explicit unlock action for the case where waiting is not acceptable.
2026-08-09 05:12:57 +00:00
neon 25bc7a21d4 perf(dashboard): optimize random book lookups and scroller batching
Add index on books table for random dashboard scroller queries, batch dashboard scroller requests, instrument pg pool logging, and cover dashboard scroller batching in e2e tests.
2026-08-05 21:48:23 +00:00
neon f4cfc3ca5d feat(books): rework cross-library moves with preview and progress
Replaces the implementation from #818 with a job-backed flow. Moves are
planned up front and surfaced as a preview the user confirms, then run
through a planner/executor split against a book_move_jobs table, so
progress is trackable and partial failures roll back.

The dialog-based flow from #818 is superseded: its module, service, and
repository are removed here in favour of the job-based equivalents. Its
e2e coverage is replaced by a book-move suite that exercises preview
classification, the collision policies, file relocation, preserved user
state, and the books-before-book_files update ordering that the
composite folder foreign key constrains.

Also adds dashboard shelf layout configuration.

Ref #257
2026-08-02 17:24:18 +00:00
neon d0047bbf18 feat(library): add FB2 file metadata writing and library settings
Add server support for writing metadata, embedded covers, and FictionBook XML structures directly into FB2 format files. Introduce migration 0058 to persist FB2 file write preferences per library, and update library creator UI and locale catalogs.
2026-07-28 06:50:37 +00:00
neon fb93f6ec20 fix(kobo): support GET request on analytics gettests endpoint
Kobo devices send GET requests to /v1/analytics/gettests during sync checks. Previously only POST was handled, causing 404 errors during subsequent syncs.

Closes #697
2026-07-22 17:39:45 +00:00
Chris Mansell a7c281955a feat(stats): expose privacy-aware reading insights (#662)
* build(deps): synchronize i18n lockfile

Record the locale dependencies so frozen installs can resolve the client workspace.

* feat(stats): expose privacy-aware reading insights

- Give administrators permission-gated access to bounded account activity.
- Let readers opt into auditable shared reading insights.

Ref #501
2026-07-12 23:11:35 -06:00
neon 39d9be30f9 feat(library): support LIBRARY_BROWSE_ROOT to restrict file path access
Introduce a LIBRARY_BROWSE_ROOT environment variable (defaulting to the
current directory) to restrict directories users can browse and select when
setting up library paths. Add a PathPolicyService on the backend to enforce
path containment checks securely, and update the frontend FolderPickerModal
to prevent navigating above the configured browse root.

Closes #567
2026-07-07 17:02:46 +00:00
neon 14b262fa35 feat(server): import Booklore reading state
Import Booklore and Grimmory reading sessions, personal ratings, and progress state through the migration pipeline.

Preserve book-level progress as primary-file progress when source file progress rows are absent.

Closes #482

Closes #483
2026-06-30 18:54:06 +00:00
neon 920aa3b477 build(deps): refresh client and server dependencies
Keep the dependency baseline current after the release window, and adapt the icon and zip integrations to current package APIs.
2026-06-21 06:00:55 +00:00
neon 57e2f39ec5 feat(opds): expose all content files for multi-format books
Select all acquisition content files from the database for each book
instead of only the primary file. Return multiple acquisition links
in the OPDS catalog feed for books available in multiple formats,
and update unit and E2E tests to verify catalog output and downloads.

Closes #224
2026-06-03 20:21:01 +00:00
Chris Mansell e659111e4e feat(library): add bulk rename tool and e2e coverage (#124)
* feat(library): add bulk rename based on file naming settings

Provides a way to rename all books in a library according to the user's
configured file naming template without requiring manual metadata edits.
Includes a streaming endpoint to report progress back to the frontend.

Closes #121

* test(library): add e2e tests for bulk file rename

Introduce a comprehensive E2E test suite for the bulk rename functionality to verify behavior across different library modes (book per file, book per folder) and handle edge cases like naming collisions or special characters.

Additionally fix a TypeScript compilation error in BulkRenameView.vue by replacing `replaceAll` with a regex-based `replace` for broader target compatibility.

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-05-28 09:27:03 -06:00
neon 53736a95b5 fix(server): deduplicate migration user-state upsert batches (#81)
* fix(server): deduplicate migration user-state upsert batches

Prevent migration failures when source data contains duplicate user-state rows for the same target keys. Keep the latest row per upsert key and prefer richer reading progress signals on timestamp ties, then cover the behavior with focused unit and e2e tests.

Closes #71

* ci: raise commitlint body line limit to 500 chars

Allow longer commit body lines so convention checks do not fail on detailed but still concise commit explanations in this repository.
2026-05-21 22:30:13 -06:00
neon db2b3f9696 ci: run E2E only on nightly schedule, skip E2E on PR/push/manual CI, keep manual e2e workflow for on-demand runs, and align matrix/docs accordingly 2026-05-14 16:37:18 +00:00
neon cfc3672a6c refactor: rename book-bucket to book-dock and lens to smart-scope 2026-04-20 22:57:27 -06:00
neon ce6a439062 feat(server): add unified entity manager with duplicate detection
- Remove author insights and duplicate suggestions panels, composables, DTOs, and all backend support
- Add genre and tag duplicate detection, merge, and dismiss with server-side paginated scan results
- Consolidate per-entity-type tools into a single entity-manager module backed by the Strategy pattern
- Support browse, merge, rename, delete, split, and dismiss across all entity types (author, genre, tag, narrator, publisher, language, series)
- Replace old tools module with unified entity-manager controller, service, and repository; add 85+ unit tests
2026-04-20 19:10:47 -06:00
neon 67ec10f703 feat: implement security audit fixes SEC-001 through SEC-031 2026-04-12 16:15:03 -06:00
neon 698951a245 refactor(metadata): improve parsing, cover storage, and content type 2026-04-10 00:26:45 -06:00
neon 26d1e6ebc8 feat: improve header UI and optimize docker storage configuration 2026-04-08 20:34:38 -06:00
neon 54f2894762 feat(scanner): enhance upload durability and add E2E test suites 2026-04-07 12:44:12 -06:00
neon cfc424ea37 feat: move file write settings from global to per-library 2026-04-06 14:54:50 -06:00
neon decd0f1a2e fix(server): fix migration executor bugs and schema corrections 2026-04-05 20:24:08 -06:00
neon 9fd4e162aa feat(server): harden booklore migration with adapters and E2E tests 2026-04-05 16:01:04 -06:00
neon 578cfd584a test(server): harden E2E suites across scanner, auth, and email modules 2026-04-04 23:32:25 -06:00
neon bab3813ff8 feat(metadata): add per-field locking to protect fields from overwrites 2026-04-03 21:31:02 -06:00
neon 0a58a87239 refactor(scanner): rename staging module to book bucket 2026-04-03 20:50:11 -06:00
neon 00ecdc34cd test(users): add lifecycle E2E suite and harden admin contracts 2026-04-02 04:05:00 -06:00
neon 6115656153 test(email): add email lifecycle E2E suite and harden send behavior 2026-04-02 02:45:00 -06:00
neon 835bc91a09 test(reader): add state isolation E2E suite and fix isolation bugs 2026-04-02 01:25:00 -06:00
neon 64abfa8c41 test(opds): add auth catalog end-to-end tests 2026-04-02 00:05:00 -06:00
neon 914f8a3440 test(auth): add authorization-matrix end-to-end suite 2026-04-01 15:49:30 -06:00
neon a6d84ae20c test(metadata): add initial E2E coverage for metadata write flow 2026-04-01 08:05:00 -06:00
neon 0c195165e6 test(scanner): add staging ingest and finalize E2E test suite 2026-03-30 23:17:27 -06:00
neon b5cc1ef370 test(scanner): refactor E2E scaffolding and extract shared test harness 2026-03-30 02:11:51 -06:00
neon db30a45782 test(scanner): bootstrap scanner E2E test infrastructure 2026-03-30 01:18:32 -06:00