Commit Graph

43 Commits

Author SHA1 Message Date
neon b1ba3a03e7 fix(koreader): stop a stale device position undoing a progress reset
Clearing a book's progress deleted the shared row and left the KOReader device row behind, so
the next pull served the deleted position straight back. Clearing that row too is not enough
either: an empty response reads as "this server knows nothing about your book", which is the
one reply that leaves a device certain its own sidecar is the only position there is, and it
pushes that back on the next sync.

Record the reset as a durable fact and answer the pull with a real start position attributed to
BookOrbit. Route the position by format, because KOReader's own kosync plugin feeds it straight
to GotoPage or GotoXPointer with no percentage fallback and an empty value leaves the reader
where it was. Stamp each delivery as current, because both clients compare that against their
last page turn and default backward syncs to disabled, so the original reset time would be
dropped on any pull deferred past a page turn without ever prompting.

Serving a reset proves nothing, since either client can classify it as a backward sync and drop
it silently, so the marker survives delivery and a device is held until its own push reports the
start. Retire it once a device that took the reset reads on: the pull carries no device
identity, so a marker kept alive past its purpose is answered to every device and would send
that one back to the start on every sync. The bound this leaves is deliberate. A second device
that never took the reset is protected only until the first reads on, and closing that needs
device_id on the pull, which is a plugin change.

Judge convergence on the reported position rather than the percentage, so page 1 of a hundred
page comic is not mistaken for a position no short book could ever reach. Wind Kobo bookmarks
back through the same paths, since a Kobo device otherwise resumes from its own bookmark and
reports it back the same way.

Closes #1032
2026-08-18 21:29:36 +00:00
neon 7b43756258 fix(stats): track last read separately from progress row updates
Sorting by "Last Read" ordered on reading_progress.updated_at, but the KOReader sync path
deliberately freezes that column so it stays a reliable "last local write" marker when resolving
conflicts against device progress. Readers who sync from KOReader therefore kept the timestamp of
their first ever sync of each book forever, so the smart scope sort, the jump-bucket rails and the
dashboard Continue Reading list all looked unordered.

Add reading_progress.last_read_at, which advances on every write unless a path opts out, and move
every "last read" reader onto it. Leave updated_at to the conflict-resolution and progress-resume
paths that genuinely mean "last local write". Kobo bookmark-location stamping opts out because
recording which Location a bookmark maps to is bookkeeping rather than reading, while the Kobo
progress write and the migration importer pass their source timestamps through so imported history
is not stamped as read today.

Move the ebook-versus-audiobook recency tiebreaks onto the new column as well. They compared a
frozen timestamp against a live one, which selected the wrong percentage for the same reason.

Swap the (user_id, updated_at) index for (user_id, last_read_at), since the ordering consumer that
justified it moved. Backfill last_read_at from updated_at in the migration, as the generated
DEFAULT now() would otherwise make every existing book look as though it had just been read.

Fixes #1013
2026-08-16 01:04:34 +00:00
neon 5d55b8aa0d fix(stats): evaluate achievements for updated KOReader sessions
KOReader page-stat uploads can grow an existing derived session as more page events arrive, but
only newly inserted sessions reached achievement evaluation. A session that crossed a threshold by
being updated showed progress past that threshold while the badge stayed locked.

Carry the updated sessions out of the repository instead of a bare count and emit for them too.
The oversized-upload branch emitted a backfill event that nothing listened for, so subscribe
achievement evaluation to it as well; those runs are serialized per user and a burst collapses into
one trailing re-run, since each backfill re-evaluates the whole catalogue. Awards from that branch
stay silent: an upload that large is a device catching up, and notifying per badge would burst a
toast for every historical unlock.

Fixes #991
2026-08-14 06:09:03 +00:00
neon 0480242a98 feat(koreader): retire devices without deleting their synced data
Removing a KOReader device was the only way to take a decommissioned reader off the list, and it
permanently deleted that device's page-by-page reading events, per-device progress, sweep record,
naming pattern and sole-source unmatched books. Derived reading sessions survived, but the raw
events did not, and the plugin keeps its upload watermark on the device, so nothing ever
re-uploaded them.

Add retirement as a marker table rather than a device registry. Device identity is implicit,
spread across five device-keyed tables and surfaced by a DISTINCT ON over device progress, so
there is no row to carry a flag and nothing to backfill. Retiring records only (user, device);
every byte the device synced stays where it is.

Clear the marker whenever a retired device syncs again, on single-book progress, bulk sweeps,
page-stat uploads and sweep completion, so reading never accrues against a device the user cannot
see. Keep deleting synced data as a separate, explicit action on the retired row, and clear the
marker it would otherwise leave behind.

Move the confirm copy to new message keys instead of reusing the ones whose meaning changed, so
the sparse target catalogs cannot ship a stale translation of a destructive dialog.

Closes #996
2026-08-14 05:34:06 +00:00
Moritz ed0964a50c feat(library): add {library} placeholder to file naming patterns (#976)
Resolve {library} to the name of the library a book belongs to, so a
single KOReader device pattern can keep each library in its own subtree
on the device instead of merging every library into one tree.

The token is populated in every server-side pattern surface: KOReader
manifest and book detail device paths, upload, automatic and bulk
rename, book move (target library), download filenames, and BookDock
finalize.
2026-08-11 09:57:58 -06:00
Jadehawk 80223b5e67 fix(koreader): accept optional KOSync document metadata (#966)
* fix(koreader): accept optional KOSync document metadata

* fix(koreader): reject null progress metadata
2026-08-10 17:42:59 -06:00
neon 68fae9a121 fix(reader): route KOReader progress by format so paged books resume
KOReader reports a paged document's position as a page number and a reflowable one's as an
xpointer, but applySharedProgress treated every incoming position as an xpointer and
upsertReadingProgress hardcoded pageNumber to null on every write. A PDF synced from a device
therefore stored a correct percentage with no page, and the web reader reopened it at page 1.
The same write also destroyed a page the web reader had saved earlier, including during an
unattended bulk sweep, so this lost positions that were never synced from a device at all.

Carry the book file's format through hash resolution and pick the column from it. CBZ/CBR were
affected identically at the data level but masked in the UI, because the comic reader estimates
a page from the percentage when none is stored.

Closes #954
2026-08-09 17:29:19 +00:00
neon 9b274cbae0 fix(kobo): stop offering plugin updates to devices that crash applyin… (#912)
* fix(kobo): stop offering plugin updates to devices that crash applying them

Plugin 1.3.x calls Device:unpackArchive, which KOReader removed in
koreader/koreader@751b4978 (July 2026). The call is unguarded and the whole apply path runs
without a pcall inside a ConfirmBox callback, so the missing method raises on UIManager's event
loop and takes KOReader down. Plugin 1.4.0 fixed the extraction, but the broken updater is the
code that would have to install that fix, so it can never self-deliver: every device still on
1.3.x crashes on every update attempt, forever.

Gate the offer server side instead. GET /koreader/plugin/version now resolves the calling user
and withholds pluginVersion when any of their devices last reported a plugin older than 1.4.0.
Those clients already treat "unknown" as "the server could not tell me" and show a message
rather than the update prompt, so the crashing path becomes unreachable without touching the
plugin on the device.

Two limits are inherent to a server-only mitigation and are not bugs. The endpoint carries no
device id, only the user, so one stale device parks self-update for all of that user's devices
until it is updated by hand or removed. A device that has never completed a sweep has no
recorded version, so it is not gated. Neither is closable until the plugin reports its own
version on the version check.

Surface requiresManualUpdate per device in the KOReader settings so the affected devices say
what to do, since the 1.3.x dialog string is baked into the installed plugin and cannot be
changed from here.

Closes #905

* fix(kobo): keep blank plugin versions inside the self-update gate

A device reporting an empty pluginVersion produced a falsy blocker, so the truthiness check
opened the gate and handed out the real version. The DTO allows an empty string, so this was
reachable. Also move the settings spec fixtures above the manual-update cutoff, since a
sub-1.4.0 version with requiresManualUpdate false describes a state that cannot occur.
2026-08-06 16:18:05 -06:00
neon 57e2616aec feat(koreader): add not-on-device tile, bulk folder picker, browse counts
Add a "Not on device" Browse tile listing recently added books not yet
downloaded here. On-device state is local to KOReader and has no
server-side filter, so the list is assembled by a bounded walk of the
newest books, capped by both a page budget and the server's ids limit.
It takes the slot of the Search tile, which duplicated the title bar
search button present on every non-detail page.

Add a folder picker to the bulk download settings, sharing one chooser
with the single-book dialog so both write the same download_dir.

Badge every Browse tile the server can count. Authors and series reuse
the cached totals the web sidebar already requests; libraries,
collections, smart scopes and in-progress get dedicated count queries
rather than the list methods behind the section endpoints, which
aggregate per-entity book counts a badge never reads. Badges are
right-aligned and abbreviated past 999 so the column stays fixed.

Replace the shared count path that paged and hydrated a book to read a
single integer, removing five queries per count on the dashboard, the
series read-through summary and the section endpoints.

Closes #869
2026-08-02 22:36:44 +00:00
neon 49e0b12e97 fix(koreader): allow plugin API to reset read status to unread
The catalog read-status enum omitted unread, so a device write could only
be undone from the web dashboard. Add it to the settable enum and the
plugin status dialog, and mark Unread as current when a book has no
status row.

Closes #853
2026-08-02 17:38:45 +00:00
neon 1215a45f93 feat(client): add nine locales with English fallback catalogs
Register Czech, Danish, Finnish, Hungarian, Korean, Russian, Simplified Chinese, Swedish, and
Ukrainian so the language picker, browser detection, and server preference validation accept them.
Catalogs hold English fallback values; Crowdin supplies translations through the normal export flow.

Give each catalog exactly the CLDR plural categories its locale defines rather than copying the
English branches. Czech, Russian, and Ukrainian gain `few` and `many` seeded from `other` because
the source has no matching text. Korean and Chinese drop `one`, which they have no rule for and
Crowdin cannot store. Those synthesized branches all start from the same English string, so machine
translation returns identical forms where the grammar differs and a human has to resolve them.

Ship Chinese as Simplified only under the bare `zh` locale so every zh-* browser resolves to it.
Traditional Chinese, also requested in #86, would arrive later as a separate zh-Hant catalog.

Use `sv-SE` for Swedish in the Crowdin config, because Crowdin has no bare `sv` language and would
have skipped the export. Tolerate a wrapped SUPPORTED_LOCALES array in validate-locales, which
prettier now produces since the list outgrew the print width, and extend the exhaustive ICU test to
reach Polish `other`, a category its counts never selected.

Add the repository activity embed to the README alongside the existing translation progress chart.

Repair the KOReader plugin source assertions that 40e366a4 left stale after reshaping the
dashboard streak block and the downloaded badge icon lookup, and watch `koreader-plugin/**` in
the server CI path filter so plugin-only changes run the server suite that reads those files.

Closes #86
Closes #363
Closes #576
2026-07-31 19:33:33 +00:00
neon 40e366a463 feat(koreader): add dashboard reading statistics and recency metadata
Add lastReadAt timestamp to KOReader catalog book items and introduce reading streak and goal statistics widgets to the KOReader plugin dashboard layout.
2026-07-31 00:25:09 +00:00
Jadehawk 1f4420b454 feat(koreader): support customizable dashboard sections (#820)
* feat(koreader): add dashboard row configuration model

* feat(koreader): reuse catalog for dashboard source selection

* feat(koreader): configure dashboard rows on 1.4.0

* chore(koreader): preserve dashboard source line endings

* fix(koreader): keep BookOrbit tools menu loadable

* fix(koreader): keep dashboard requests out of rendering

* revert(koreader): restore stock 1.4.0 dashboard renderer

* revert(koreader): use stock single dashboard row

* fix(koreader): apply selected dashboard row source

* feat(koreader): add second dashboard row setting

* feat(koreader): render second dashboard row

* fix(koreader): fetch configured dashboard rows directly

* fix(koreader): honor configured dashboard row sources

* feat(koreader): support four configurable dashboard sections

* fix(koreader): use real catalog dashboard sources

* fix(koreader): select dashboard shelves from catalog

* fix(koreader): reuse catalog for dashboard source selection

* fix(koreader): close dashboard menu before catalog selection

* test(koreader): split dashboard configuration coverage

* chore(koreader): bump version to 1.5.0

* fix(koreader): address dashboard review feedback

* fix(koreader): address dashboard section review feedback

* test(koreader): cover dashboard behavior at runtime

* test(koreader): use Nest setup for dashboard DTO

* chore(ci): trigger pipeline

* fix(koreader): migrate untagged dashboard section settings

Plugin 1.4.x stored the configurable dashboard rows as a bare ordered
list with no schema marker, so gating the migration on schemaVersion == 1
never matched anything a device had actually written. Every upgrading
user who had ever picked a row was read positionally instead: the Stats
strip disappeared and Discover ended up in two slots, with a stored
SmartScope dropped entirely.

Treat an untagged stored list as the legacy shape and migrate it into the
slots those rows used to occupy. Cover it with the settings shapes 1.4.x
really wrote, rather than a marker nothing ever produced.

Also drop store() and primary(), which no caller uses now that slots are
written through storeAt(), along with the dead grow loop inside storeAt()
that normalize() already makes unreachable.

* fix(koreader): keep dashboard shelves in aspect, inside the page, and deduped

The four-slot renderer handed each shelf a height derived from the
leftover vertical budget while its cover cards kept the width computed
from the natural row, so any page with more than one shelf drew covers
out of aspect. It also had no answer for a page too small for the
configured slots: shelves clamped to a 48px floor and the layout simply
overflowed the available height.

Size shelves from their cards instead. A shelf that does not fit gets
more, narrower cards per row, so its height is always the height those
cards need. When even the narrowest row does not fit, drop slots the way
the pre-four-slot renderer did: the stats strip first, then shelves from
the bottom up.

Fetch each distinct shelf source once per dashboard load. Two slots
showing Discover previously rendered the same list twice, and two slots
on the same catalog filter each paid for their own request; a source that
fails is now also not retried by the next slot that wants it.

Drop what the four-slot rewrite left behind: the unused capabilities
require, the tall-layout helper and its constants, and the section
accessors and request helper that no longer have callers. Name the
per-shelf page size and the minimum cover width instead of repeating the
literals. Add setDashboardSections() so a whole configuration can be
applied in one refresh.

Add a layout spec that drives updateDashboardItems and asserts covers
stay in aspect, the drop order holds, and nothing is laid out past the
available height.

* fix(koreader): close the dashboard slot menu through its own API

Reset to Default wrote section_stale as a boolean where the renderer
reads a per-slot table, so the reset never actually marked anything
pending and stale books stayed on screen under the new headers until the
refresh landed. It also dereferenced current_context without a guard and
wrote the setting behind the catalog's back. Route it through the new
setDashboardSections() so the slots and the cache signature move
together in one refresh.

Close the invoking touch menu with closeMenu() rather than handing the
widget to UIManager:close(), which skips the close_callback that owns its
container.

Drop the section parameter from catalogDashboard() and the unused
catalogDashboardSection() helper: the plugin builds its shelves from the
ordinary catalog endpoints now and sends neither. The server keeps both
routes for devices still on 1.4.x, so its DTO test stays; rename it to
match the file it covers and drop the empty Nest module compile.

* revert(koreader): keep the plugin at 1.4.0

1.4.0 has not shipped yet: 1.3.0 is the last released plugin version, so
the dashboard slot work can land inside 1.4.0 rather than claiming a
version of its own. Devices in the field are on 1.3.0 and see a single
1.3.0 -> 1.4.0 step either way.

* fix(koreader): stop rotating upright read-status badges

buildReadStatusBadge turned every status icon a quarter turn, but only
two of the seven are corner folds. The dogear assets are drawn with their
right angle at the top left and the badge sits in the cover's top-right
corner, so those do need the turn. Bookmark, check and reload are upright
glyphs and were being drawn on their side: a want-to-read cover showed
its bookmark ribbon lying sideways like a chevron.

Rotate only the dogear icons.

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-07-30 13:37:26 -06:00
neon dbb10b5c2f feat(koreader): add bookmark sync and exchange support
Add two-way bookmark sync support between KOReader plugin and server. Include migration 0056 for bookmark sync columns, server endpoints and services for bookmark exchange, and KOReader plugin bookmark management with comprehensive unit and E2E tests.
2026-07-28 04:34:46 +00:00
neon e0ca660f2e feat(koreader): support configurable dashboard sections in KOReader catalog
Add server endpoints and KOReader plugin settings to render configurable dashboard section rows, such as Smart Scopes or custom scrollers, replacing the hardcoded Discover section. Update the plugin capability checks to fall back gracefully on older server versions.
2026-07-28 02:52:07 +00:00
neon edc2a2ccfc feat(reader): make KOReader plugin sync non-blocking and resumable
Lifecycle handlers previously blocked on network work, so closing or suspending a book stalled the
device on slow or offline links. Persist snapshots first and drain them through a lifecycle outbox
with connected-state gates and phase-aware retries, so the UI stays responsive and pending work
survives restarts.

Move persistence behind a state manager with atomic flushes, and chunk sweeps, bulk downloads, and
annotation exchange through a background queue with checkpoints and transfer policies, so large
libraries make progress incrementally instead of restarting from scratch after an interruption.

Server side, add bulk manifest and page stats endpoints with batched queries and catalog query
filtering so the plugin can reconcile many books per round trip rather than one request per book.
2026-07-28 02:06:50 +00:00
neon 3fabadcf0d fix(kobo): use server reading streak on plugin dashboard
Prefer the server account reading streak when provided by the dashboard endpoint, falling back to local KOReader statistics database data when offline or absent. This aligns the KOReader plugin day streak display with the web app.

Closes #711
2026-07-26 22:27:26 +00:00
Jadehawk ec84703cef feat(koreader): enhance settings and plugin behavior (#669)
* feat(koreader): add device file naming and improve plugin downloads (BO-658)

* fix(koreader): secure download paths and test collisions

* fix(koreader): preserve bulk collision identities

* fix(koreader): improve device file naming validation and path generation

* fix(koreader): remove duplicate settings lookup and audit file naming changes

* fix(koreader): resolve remaining CodeRabbit review issues

* fix(koreader): validate book detail query and finalize file naming release

validate book detail deviceId via DTO and ValidationPipe

localize new KOReader file naming settings (de, nl, sl)

bump KOReader plugin version to 1.3.0

* chore: rerun CI

* chore: normalized Closes #658 to not have embedded URL to issue. This is failing PR Conventions test

* test(koreader): align source and device removal assertions

* test(koreader): cover file naming settings flows

* test(koreader): cover file naming sync requests

* test(koreader): close remaining patch coverage gaps

* test(koreader): cover file naming server paths

* docs: add fixed2 modifier to file naming reference

* feat(koreader): support custom file naming patterns for KOReader sync

Add support for configurable KOReader file naming patterns, allowing users to define dynamic path templates for sync targets. Include UI draft settings, preview updates, and server path resolution.

Closes #658

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Co-authored-by: jadehawk <jadehawk@hotmail.com>
2026-07-14 11:26:27 -06:00
neon f68a3ca98e feat(stats): add reading attempt history
Track repeat reads as distinct attempts while preserving existing status flows, device integrations, goals, sessions, and legacy data.

Closes #652
2026-07-12 17:41:35 +00:00
neon 568bf9dd6f fix(kobo): strip leading v from server version in koreader plugin
The KOReader plugin's update check response provides the server version,
which already contains a leading "v". The update and plugin popups in
the KOReader plugin format the version with an explicit "v" prefix,
resulting in a "vv" typo (e.g. "vv1.2.0").

Strip the leading "v" from the server version in the updater script
if present to ensure it is displayed with a single "v" prefix.

Closes #644
2026-07-11 17:02:32 +00:00
Chris Mansell 729b221427 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
2026-07-09 08:17:53 -06:00
neon 36bfc6e83d feat(reader): serialize and coordinate sync jobs in KOReader plugin
Introduce a sync coordinator to serialize and prioritize KOReader sync jobs (such as progress sync, highlight sync, library sweep, and update check). Coalesce pending jobs by family to prevent race conditions and redundant network requests. Use a dedicated scheduler for open annotations to handle highlight sync and matching asynchronously on book open. Add a diagnostics menu in the settings to track detailed sync status and provide clear error messages for debugging.
2026-07-08 17:55:40 +00:00
Bernard Potocki d82a018d1e feat(kobo): sync smart scopes to kobo (#570)
Allow smart scopes to opt into Kobo sync while preserving collection sync behavior. Generate the current Drizzle migration and keep smart-scope matching aligned with the normal book query path.

Fixes #202

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Co-authored-by: Jane Doe <janedoe@users.noreply.github.com>
2026-07-07 15:55:08 -06:00
neon 2b8b8962fa feat(reader): adapt KOReader catalog layouts for tall screens
Use measured screen surplus to show additional dashboard and related book rows on tall devices.

Preserve compact one-row layouts when space is tight.

Closes #526
2026-07-07 17:51:31 +00:00
neon bf4102e8df feat(books): support reviews, rating removal, and plugin enhancements
Add support for user-scoped personal notes (reviews) and allow clearing book ratings to null,
providing full sync capabilities for these actions in the KOReader plugin. Modularize the
KOReader plugin codebase into dedicated mixin files to improve maintainability and expand
the catalog detail view with related sections (series, author, similar books).

Closes #552
2026-07-07 04:31:53 +00:00
neon db5f17f8de feat(opds): honor configured download file pattern for downloads
Update KOReader and OPDS catalog download endpoints to resolve filenames using the settings-configured download file pattern via BookService. This ensures consistent filenames across all download paths.

Closes #569
2026-07-06 23:03:39 +00:00
neon e97d54953b fix(reader): reconcile remote progress before manual KOReader book sync
When manually syncing a book, the plugin uploaded progress without checking if another device had written a newer progress state to the server first, causing reading progress to be overwritten and lost.

Fetch the remote progress from the server before starting a manual sync. If a newer progress state is found on the server, prompt the user to apply the remote progress locally first. If the prompt is rejected, proceed with the sync of annotations and reading history while bypassing the progress upload.

Closes #461
2026-07-03 05:52:22 +00:00
neon fb35e232ed feat(reader): add KOReader device removal and unmatched book tracking
Introduces the koreader_unmatched_book_devices schema to track unmatched
books per device. Adds API endpoints and UI in settings for dismissing
unmatched books and completely removing a KOReader device.

Closes #504
2026-07-02 17:36:55 +00:00
neon a0dad23087 feat(kobo): add manual KOReader book hash linking and unmatched tracking
When KOReader syncs progress or annotations, it identifies books by a document
hash. BookOrbit normally resolves this automatically, but some books cannot be
matched. This adds a tracking table for unmatched hashes and a UI to manually
link them to existing library books.

Closes #458
2026-07-02 00:34:16 +00:00
neon 68a395095a feat(kobo): add catalog bulk download and selection to KOReader plugin
Add support for selecting multiple books and downloading entire lists, pages, or selections in bulk. This includes long-press actions, a dedicated bulk download queue with retry and cancel support, and format preset configurations.

The commit also simplifies the OPDS random book query using ORDER BY random() and adjusts the Nest dev CLI config to disable the Swagger plugin for faster restarts.

Closes #478
2026-07-01 00:21:40 +00:00
neon db3d1a07af fix(series): resolve missing series in koreader catalog by id
The KOReader plugin previously matched series by name, which caused
issues for series with identical names or special characters. This
updates the plugin and backend to use the exact series ID for
navigation and filtering, ensuring all series load reliably. Also
bumps the KOReader plugin version to 1.0.1.

Closes #466
2026-06-29 18:08:58 +00:00
neon 2065af41f9 feat(server): add swagger setup for public api documentation
Introduces Swagger and OpenAPI documentation generation for the
backend API. Configures bearer authentication globally while
allowing explicit bypasses for public endpoints. Exports nested
DTO classes across several modules so the NestJS Swagger plugin
can correctly reflect their schema definitions.

Closes #399
2026-06-29 01:52:30 +00:00
neon 6956fa95a3 fix(server): encode non-ASCII filenames in download headers
Extract content disposition header logic into a shared utility and use it consistently across book and koreader downloads, fixing failures when filenames contain non-ASCII characters.

Closes #448
2026-06-28 05:30:32 +00:00
neon a72ca1155a feat(kobo): add KOReader catalog plugin integration
Add the KOReader catalog surfaces, plugin package metadata, and device progress handling needed for the plugin workflow. Keep progress status updates non-blocking so secondary status failures do not reject successful sync writes.
2026-06-27 14:28:36 +00:00
neon c28ea29671 feat(sync): three way (kobo, koreader and bookorbit) progress and annotation sync (#338)
Introduces a custom KOReader plugin to synchronize reading progress, stats, and annotations. Adds backend controllers, services, and database schema updates to ingest plugin data. Updates the frontend to display KOReader annotations alongside built-in reader highlights.

Closes #337
2026-06-25 20:13:04 -06:00
neon ceb2bb72d6 chore: add tests for BE strategies, repositories, and FE composables
Expand coverage across previously untested modules to close gaps identified
in the coverage audit.

BE: entity-manager strategies (inline, junction, author), koreader chapter
extractor, series and achievement repositories.

FE: reader settings/progress composables, notifications, live-scan books,
entity manager, and API wrapper modules (author, statistics, entity-manager).
2026-06-17 21:41:16 +00:00
neon feec3c794a fix(koreader): trigger hardcover auto-sync on progress update
KOReader progress updates were updating the database but failing to
emit the progress changed event, causing Hardcover auto-sync to be
skipped. This change adds the missing event emission to the KOReader
service so that external syncs trigger correctly, and patches a query
filter issue in the Hardcover repository.

Closes #258
2026-06-06 18:16:48 +00:00
neon 913dab0ac5 feat(kobo/koreader): two way page level sync between kobo/koreader and bookorbit (#250)
* feat(kobo): support two-way progress sync

Implement two-way page-level reading progress synchronization between Kobo devices, KOReader, and BookOrbit. This ensures readers can switch between physical devices and the web app without losing their exact page-level reading position.

Closes #249

* fix(settings): restore thumbnail click preference

Restore the main branch thumbnail-click setting after the Kobo branch accidentally dropped it during the rebase. Keep the Kobo architecture allowlist aligned with the new identity service.

* fix(kobo): harden two-way progress sync fallbacks

Preserve Kobo bookmark metadata and tolerate null auth payloads/settings failures.

Cover review-reported regressions with focused tests.
2026-06-05 09:30:25 -06:00
neon c481304e0d fix(settings): clarify koreader sync accuracy expectations
document cross-reader sync limitations in the KOReader settings panel.
set clear expectations for direction-specific accuracy so users understand why resume location can differ between KOReader and the web reader.
2026-05-25 04:23:53 +00:00
Chris Mansell 956a7a5e9a feat(users): add per-user tag/genre content restrictions (#83)
* 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
2026-05-22 11:01:11 -06:00
neon e3d82b53e5 fix(security): resolve 100 CodeQL alerts - sanitizeLogValue helper, kobo-proxy SSRF guard, duckduckgo URL parsing, dismiss false positives 2026-05-12 21:46:55 +00:00
neon 6cc9867a63 fix: remove KoreaderBookSyncCard, fix missing initial library load, add mediaSrc CSP, return null from getBookProgress instead of 404 2026-05-12 05:56:38 +00:00
neon 6a78df6597 feat(koreader): add KOReader sync with kosync protocol, device progress tracking, and reading position bridge 2026-05-12 03:36:00 +00:00