The previous hardcoded AUTHOR_MAP was incomplete and caused missing authors
in the generated release notes. The config now attempts to fetch actual
GitHub usernames associated with commit SHAs using the GitHub API,
falling back to local git log author names if the API request fails.
Use a single semantic-release config to avoid diverging behavior, and update the release notes context logic to map authors with full commit hashes and stable issue URLs.
Add books.id ASC tiebreaker to all 7 OPDS_SORT_MAP sort orders so
pagination is deterministic even when books share identical addedAt
timestamps (e.g. after a bulk library scan). Without a tiebreaker,
PostgreSQL returns equal-valued rows in an undefined order, causing
books to appear on multiple pages or not at all.
Make catalog feed IDs context-specific: filtered views (by library,
collection, smartScope, author, series, or search query) now produce
distinct URNs instead of all sharing urn:bookorbit:catalog. This
aligns with the OPDS spec requirement that <id> uniquely identify
the logical feed.
Remove the dead idQuery.$dynamic() block from paginatedBookQuery
that was built and conditionally mutated but never awaited.
Closes#93
support toggling membership in the collection sheet so users can remove books without leaving the details and quick-view flow.
cover add/remove paths plus access edge cases with frontend and backend tests.
Closes#85
Run recipient fetch on initial open state so dialogs mounted with open=true load data immediately. Add dialog and recipient composable unit tests to lock in the regression behavior and error paths.
Closes#89
improve header discoverability with quick access to documentation and
community support actions. keep the github prompt in a dismissible
popover to avoid adding noise to the main navigation.
* feat(users): tag/genre content filters for per-user access control
Add granular per-user content filtering by tags and genres, allowing admins to set include/exclude filter rules that are enforced across all book-serving surfaces.
- Implement DB schema, repository, and SQL helpers for content filters
- Wire enforcement across core domains (Books, Collections, OPDS, Kobo sync)
- Add REST endpoints for managing filter rules
- Redesign UserFormDrawer to support configuring content restrictions
- Add read-only Restrictions tab in account settings and user list badges
- Add comprehensive unit tests and fix existing fixtures
Closes#68
* test(client): update account tab expectations for restrictions
Use each group's displayed tier description instead of the next tier description so titles and copy stay consistent across all tiered cards. Refresh achievement seed names for books, pages, hours, long-book, and library ladders, and adjust books_finished_3 to a 35-book threshold to match the new progression.
Enable author image upload and removal from the author detail edit panel.
Persist author photos with generated thumbnails and enforce server-side validation plus access-scoped API routes.
Cover the new controller and service behavior with author module tests.
Prevent enrichment from applying unrelated author records when provider search returns low-signal names.
Rank multiple candidates and require strong name compatibility before accepting a match.
Treat lookup failures as provider failures instead of silently falling back to raw search hits.
* 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.
Introduce status badges for library capabilities such as file write, file rename, and watching. Add cronstrue to format the auto-scan cron expression into human-readable text. Increase the layout max-width for the library settings page to accommodate the new cards.
Add file rename feature that renames physical book files on disk when
naming-relevant metadata fields are updated (title, authors, series,
seriesIndex, publishedYear).
Closes#69
Extract the achievements page out of /statistics?tab=achievements into its
own /achievements route. Add a page header with a Trophy icon and subtitle
to match the app visual style (consistent with BookDockView pattern).
Add a beforeEnter redirect on the statistics route so ?tab=achievements links
continue to work. Update AppHeader navigation and active state checks to use
the new route name. Remove the achievements tab conditional from StatisticsPage.
Add per-user startedAt and finishedAt filtering, sorting, and timezone-safe date normalization.
Surface compact read-date editing in Details and return canonical date-only status payloads.
Preserve manual dates across status updates and reject null status patches.
Closes#66
Unify cover rendering with a reusable surface wrapper so cover styling stays consistent across grid, list, table, and dashboard views.
Expose spine overlay and shadow strength controls in appearance settings and fit spine overlays to cropped cover frames to avoid visual bleed.
Add tests for the cover surface component and aspect-ratio fitting helpers.
closes#62
* feat(docker): expose Book Dock folder as a configurable drop zone
Add support for an external book drop folder that users can bind-mount
into the container via the BOOK_DROP_HOST_PATH env var. Files copied or
moved into this folder are automatically picked up and ingested by the
existing Book Dock file watcher.
- docker-compose.yml: add BOOK_DROP_HOST_PATH volume override so the
book-dock path is separately bind-mountable
- .env.example: document BOOK_DROP_HOST_PATH with an example NAS path
- AppInfoResponse: add bookDockPath so the UI can show the
container-internal path admins need to bind-mount
- AppInfoService: compute and return bookDockPath from storage config
- BookDockSettings: display drop folder path with BOOK_DROP_HOST_PATH hint
- useAppInfo: expose bookDockPath ref from the app-info API response
- README: document the optional drop folder in the quickstart section
When BOOK_DROP_HOST_PATH is unset, the volume defaults to
./data/app/book-dock, preserving full backward compatibility.
No watcher code changes required - the watcher already watches
the correct container-internal path.
Closes#58
* fix(book-dock): resolve symlink paths so rescan stays idempotent
BookDockWatcherService and BookDockIngestService were building
bookDockPath with path.join(), which preserves symlinks. `@parcel/watcher`
resolves symlinks internally when reporting events (using realpath), so the
absolutePath values stored by watcher events and the paths computed by
walkAndIngest during rescan never matched. This caused findByAbsolutePath
to always return undefined on rescan, creating duplicate DB records for
every file already tracked via a watcher event.
Fix: both services now call `realpath(bookDockPath)` in
onApplicationBootstrap (after mkdir), updating the stored path to the
canonical real path that the watcher also uses. Rescan is now idempotent
regardless of symlinks in the data directory.
Verified by E2E test: 3 consecutive rescans produce zero new records.
* fix(books): switch progress reset to per-file controls
Remove the book-level reset action and endpoint, and keep reset scoped to each file row to avoid accidental bulk clears.
Add tests for per-file reset behavior and progress formatting edge cases while keeping migration import filters aligned with the new progress expectations.
closes#57
* ci: increase commitlint body line limit to 250
Allow longer explanatory commit bodies so CI accepts existing project commit patterns without forcing manual hard wraps at 100 characters.
Reduce the settings navigation bar from 17 flat tabs to 10 by grouping
related settings into subtab containers, following the existing ?tab=
query-param pattern used by Reader and Metadata.
New groups:
- Integrations (/settings/integrations): Kobo, KOReader, Hardcover
- Admin (/settings/admin): Users, OIDC/SSO (permission-filtered)
- System (/settings/system): File Naming, Book Dock, Maintenance, Audit Log (permission-filtered)
- Account (/settings/account): Profile, Notifications (merged from standalone tabs)
All old routes redirect to their new grouped equivalents for backward
compatibility. Admin and System containers filter available tabs based on
user permissions. Demo-restricted accounts hide the Notifications tab.
New files:
- IntegrationsAllSettings.vue, AdminAllSettings.vue, SystemAllSettings.vue, AccountAllSettings.vue
- lib/integrations-tabs.ts, lib/admin-tabs.ts, lib/system-tabs.ts, lib/account-tabs.ts
- Unit tests for all new containers and tab-lib files (SettingsHeader included)
Closes#55
Block Google Books from being enabled without credentials because unauthenticated API calls now fail quota checks. Normalize legacy enabled configs without keys to disabled.
Use BigInt stat reads for upload and file-event paths to preserve 64-bit inode values. Clamp inodes that exceed PostgreSQL bigint range to 0 before persistence and inode lookups, and add regression coverage for insert, upsert, restore, and move-detection paths.
Fixes#48
- Add tap-to-cycle footer modes (pages, time/session, chapter) along with elapsed reading time and chapter progress on the slider
- Prevent screen dimming during reading using the Wake Lock API
- Add keyboard shortcuts (t/s/b/f/Esc/?/Home/End) with an integrated help modal
- Add go-to-location support (page number or percentage) and a resume-reading toast on book open
* fix(scanner): clamp oversized MergerFS inodes before storage
MergerFS generates synthetic unsigned 64-bit inodes by hashing file paths.
Values above 2^63-1 overflow PostgreSQL's signed bigint column, crashing
the scanner with 'value out of range for type bigint'.
Switch all file stat() calls to stat(path, { bigint: true }) to read
inodes as precise BigInt values instead of lossy JS numbers. Introduce
clampIno(ino: bigint): number which returns 0 for any inode exceeding
PostgreSQL's bigint max - 0 is the existing sentinel meaning 'rename
detection disabled', so no other code needs to change.
The fix covers all four code paths that write ino to a FileStat:
statFilesIntoAcc and buildSingleBookCandidate in walk.ts, plus the two
targeted-scan paths in scanner.service.ts.
Fixes#19
* ci: add maintainer-gated PR GHCR image publish
---------
Signed-off-by: neon <18559658+neonsolstice@users.noreply.github.com>
archiver v8 switched to pure ESM with no default export and a
completely different API, breaking every zip export request with
TypeError: (0 , _archiver.default) is not a function. The NestJS
SWC build produces CJS output, which cannot import the new ESM-only
package at runtime.
Downgrade to ~7.0.1 (the last CJS release) and add a Dependabot
ignore rule for archiver major bumps so this cannot recur silently.
Fixes#31
Close the quick-view sheet before dispatching add or delete actions.
Ensure the collection sheet opens reliably from the side panel.
Add regression tests for quick-view and table-shell action flow.
Expand collection controller and service edge-case coverage for this path.
Fixes#36
Series position badge (#N) rendered in the top-right corner of cover
cards, sharing a flex-column container with the existing lock-status
badge. Shown only when seriesIndex is non-null. Whole numbers strip the
decimal (3.0 -> #3); fractional values display as-is (1.5 -> #1.5).
Hovering the badge shows a tooltip with the full series name and position.
Enabled by default in Appearance -> Card Overlays.
Closes#32
Node.js inside Docker validates the full TLS certificate chain against its bundled
CA store. When the chain cannot be verified (e.g. outdated CA bundle, self-signed
cert, or Let's Encrypt chain not trusted), nodemailer reports a misleading error
like "certificate has expired". Add a per-provider tlsRejectUnauthorized flag
(default: true) that passes tls.rejectUnauthorized=false to nodemailer when
disabled, letting self-hosted SMTP servers opt out of cert verification. A visible
warning is shown in the provider form whenever the flag is turned off.
The delete provider API already returned a clear 400 message explaining that the
system designation must be removed first, but the frontend threw a hardcoded
"Failed to delete provider" string and never read the response body. Fix the
composable to parse and rethrow the actual API error message. Also disable the
delete button for system providers with a tooltip stating the constraint, so users
cannot trigger the error in the first place.
Adds tlsRejectUnauthorized boolean column (NOT NULL DEFAULT true) to
email_providers via Drizzle migration 0004.
Closes#26
Validate BO-<issue>-<slug> branch names and lint PR titles and commit headers in CI and release workflows.
Require GitHub closing-keyword issue references and verify referenced issues exist. Align contributing, release, commit, testing, and policy docs with the enforced behavior and tone.
- add achievement schema, migrations, evaluators, APIs, and backfill support on the server
- add achievements UI, composables, toast utilities, and tests on the client
- add shared achievement/auth/notification type updates