mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-11-15 11:38:36 +00:00
e575ff0fcb586bc0aa0600a2ea198d5b8c05dd5d
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e575ff0fcb |
feat(activity): clearer cards, a Rejected view and Reopen for failed deliveries (#1618)
A batch of activity list changes from running it as an admin on my own
instance. They are separate commits that build on each other, so they
can be reviewed in order.
The cards read better. Every card gets a format pill (violet for audio,
orange for ebooks), the requester is named once, and finished cards say
how long a download took and how long ago it ended ("took 5m · 35m
ago"). A download that picked up work already under way shows a
Reattached pill.
Admins can tell what is automatic and what needs a person. A declined
request shows who declined it and when, anything with an `[auto]` note
reads "Declined automatically", and "Needs Review" becomes "No Match
Yet" with a line saying the search repeats on every sync.
There is a Rejected view next to History for admins, with Reopen on each
card and a way to hide the dead ones.
The part to read most carefully is the backend in `requests_service.py`
and `user_db.py`. An admin can now reopen a fulfilled request whose
download failed or was cancelled, and the reopen a failed download
triggers remembers which release failed, so the next search doesn't pick
it again.
I ran it on a scratch instance (when pulling them together to move from
my fork to upstream) as an admin with seeded requests and looked at the
pills, the durations, the wording and the Rejected view in a browser.
Reopening a fulfilled request I only exercised in the tests. The
frontend and Python suites, type check, ruff and basedpyright pass.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->
## Summary by CodeRabbit
* **New Features**
* Admins can review rejected requests in a dedicated view, hide or
restore cards, and reopen requests.
* Fulfilled requests with failed or cancelled deliveries can be
reopened; the failed release is skipped in the next automatic search.
* Activity cards now show format badges, download timing, and when a
download resumes existing work.
* **Improvements**
* Activity views clarify automatic retries and actions, and show
reviewer names for admins.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
8c06862ec7 |
feat(requests): let a book be requested alongside downloads (#1579)
In Download mode there is no way to ask for a book that has nothing to download yet. The release modal has an "Add to requests" chip for admins, which sends a book-level request, and when the default mode is Download the server answers it with "Download policy requires a concrete release". That is #995, where @expostarv2 used to bookmark unreleased books this way. #1019 is the other side of it: @punkincognito wants a less technical user to be able to download from one source and, when that fails, ask for the book without choosing a release. A book-level request in Download mode now becomes a normal pending book request, stored with the mode `request_book`, when the actor is an admin or when a new setting allows it: **Allow book requests alongside downloads** (`REQUESTS_ALLOW_BOOK_REQUEST_WITH_DOWNLOAD`, off by default, per-user overridable). A request that names a release still goes straight to the download queue as before, and with the setting off a non-admin gets the same error as today, so nothing changes for an existing install unless it turns the setting on. The policy endpoint reports the setting, and the release modal shows "Add to requests" to a non-admin when requests are on and the setting allows it (admins keep it as before, and it stays off while fulfilling a request). The small `canRequestBookFromReleaseModal` helper holds that rule so it can be tested. I chose a setting over making this the default for everyone because it changes what Download mode means, which is yours to decide. The other way to read #1019 is a per-source rule, "Request Book" for Prowlarr and Download for the direct sources. That is not here: the rules matrix excludes Request Book today, the release modal shows such a source as unavailable, and it needs more UI work than I can check without trying it in a browser. This PR covers the half that works with the existing flow, and the rule version can follow if you want it. Tests: four route tests (a user with the setting on, an admin with it off, a release request that must still queue a download, and the policy endpoint reporting the setting), and a frontend test for the helper. The route tests that depend on the change fail on current main. Full backend and frontend suites pass, with ruff, oxlint and tsc clean. I have not tried it in a browser, so this is a draft until someone has looked at the modal. Closes #995. #1019 stays open for the per-source rule. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Added an off-by-default, user-overridable setting that lets users with download access request a book without selecting a release. These requests remain pending for admin action, and admins can always make book-level requests. * Eligible users can make book-level requests under the download policy when the setting is enabled. This option is unavailable in browse-fulfil mode. * Requests that include release details continue to queue downloads rather than create book-level requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
c38f4c6522 |
feat(activity): Clear Completed also clears finished requests with no download (#1650)
A request that auto-download closes because the book is already in the library never gets a download, so Clear Completed skipped it and those requests piled up under Complete. The button also disappeared once the last finished download was cleared, leaving no way to clear them. ActivitySidebar.tsx: hasTerminalDownloadItems is dropped; the button shows whenever getClearCompletedTargets returns anything, and on the Requests tab as well as Downloads and All. clearCompleted.ts: a request card with no download of its own is a target when its request is fulfilled with no active delivery_state (queued, resolving, locating, downloading), or cancelled. Pending and rejected requests are left alone. The server already treats any fulfilled request as terminal in _request_terminal_status, so the client rule is the stricter of the two. docs/users-and-requests.md: a line under Clearing the activity list saying what the button clears. Tests: two new cases in clearCompleted.test.ts, one for a closed request and a cancelled one with no download, one that pending, rejected and still-downloading requests are not touched. Builds on #1438. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * **Clear Completed** can appear on the Requests tab, as well as Downloads and All, when there are items to clear. * Clearing completed items removes finished downloads and their requests, fulfilled requests that are no longer in progress, and cancelled requests without downloads. Pending and rejected requests remain. * **Documentation** * Updated the help documentation to explain what **Clear Completed** removes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
20848a8774 |
docs: small fixes from a check of the docs against the code (#1567)
These are the leftovers from the docs check, the fixes that did not belong in the other three. Claude did the comparison and wrote the changes here too, so I would still like a human look before it merges. The one new piece of prose worth a read is `troubleshooting.md`. It was a TODO page that two other pages link to, so I filled it with only what I could confirm in the code: where the logs go, `LOG_LEVEL`, the `DEBUG=true` bundle from the bug report template, and reporting an issue. Happy to drop it back to a stub if you would rather write that one yourself. - `configuration.md`, `custom-scripts.md`, `users-and-requests.md`: Booklore is Grimmory in the UI now. The stored mode value is still `booklore`, and the custom script doc says so. The hardlink heading now matches the "Hardlink Book Torrents" and "Hardlink Audiobook Torrents" checkboxes. - `custom-scripts.md`: the email output runs scripts too (`phase=post_email`, `output.mode=email`, details under `output.details.email`). It was missing from the phase list. - `users-and-requests.md`: a delivery that fails with no retry available goes back to pending on its own, and the admin reopen action is for rejected requests. The page said an admin reopens a failed delivery. Also noted that rules cannot use Request Book. - `hardcover-sync.md`: added the missing `AUTO_DOWNLOAD_AUDIOBOOK_MIN_SIZE_PERCENT` row and a sentence in the ranking paragraph. - `api-access.md`: an admin session also reads `/api/stats`, and what the 403 says. - `installation.md`: points at the Lite, Tor and WireGuard variants. Docs only, no code touched. |
||
|
|
a11a63a29e |
feat(notifications): add {User}/{Email} placeholders to per-user routes (#1499)
Closes #1130. Per-user notification route URLs can now use `{Email}` and `{User}`, filled in from each user's account at send time, the same way `{User}` already works in destination paths. One default route like `mailtos://user:pass@smtp.example.com?to={Email}` now reaches every user at their own address, with no per-user setup. - Values are URL-encoded (`alice+books@example.com` keeps its `+`); placeholders are case-insensitive. - A route that needs a value the user doesn't have (e.g. no email on file) is skipped for that user; other routes still send. - The user lookup only runs when a route contains a placeholder. - The personal "Test notification" button expands the placeholders too, so testing a `{Email}` route works. - Added to the routes setting help, `docs/users-and-requests.md`, and tests. @calibrain ready for review. --------- Co-authored-by: CaliBrain <calibrain@l4n.xyz> |
||
|
|
a1ac017487 |
feat(requests): let an admin reopen a rejected request (#1491)
Closes #1445, where you said you were open to it. A rejected request was final. `update_request` won't move a terminal status and the admin routes only fulfil or reject, so an admin who changed their mind had to ask the user to request the book again, and the user kept seeing the old rejected one. `POST /api/admin/requests/<id>/reopen` moves a rejected request back to pending and clears the review fields. What the user asked for (request level, release, note) is kept. It refuses anything that isn't rejected, and refuses when that user already has a pending request for the same book. Admins get a Reopen action on rejected cards in the activity sidebar and in History. A rejected request has usually been cleared from the list by the time anyone wants it back, so reopening also clears the dismissal, the way the failed-download reopen does. Without that the pending request stays hidden. Tests cover the service (state change, what is kept, the refusals, a non-string admin note) and the route, and the card model for the new action. Ruff, tsc, oxlint, vitest and the backend suite pass. It has been running on my instance since Oct 8. |
||
|
|
ece6b8f341 |
fix(auth): fail closed when auth prerequisites are missing (#1387) (#1397)
Auth mode resolution fell back to "none" (anonymous full admin) whenever the configured method's prerequisites were missing: no local password admin for builtin/OIDC, no Calibre-Web database, a blank proxy header, an unrecognized AUTH_METHOD (including "OIDC" in uppercase), or any error while reading the config. Deleting or demoting the last local admin was allowed on purpose because of that fallback, which exposed OIDC instances publicly. - Only an explicit AUTH_METHOD=none disables authentication. A configured method stays active when its prerequisites are missing, so sign-in fails instead of opening up. - An unrecognized or unreadable AUTH_METHOD resolves to "unavailable", which still requires a session and accepts no login. Values are normalized, so AUTH_METHOD=OIDC works. - Restore the guard against deleting or demoting the last local password admin while builtin/OIDC is active (unless DISABLE_LOCAL_AUTH is set). - Require a local admin before enabling Local auth, as OIDC already did. - Log a recovery hint at startup when builtin/OIDC runs without a local admin, and document recovery via AUTH_METHOD=none. - Drop the "will fall back to No Authentication" UI toasts and hints. Fixes https://github.com/calibrain/shelfmark/issues/1387 |
||
|
|
463ef49ac3 |
feat(search): let each user pick their own default book languages (#1255)
## Why `BOOK_LANGUAGE` is a per-reader property, not a per-instance one. On a shared install one household member searches in German while another wants English and German — today whoever changes the setting changes it for everyone, and the only escape is re-picking languages in the filter on every single search. The per-user override machinery already carries `SEARCH_MODE`, the metadata providers and the default release sources, so the language default mostly had to opt into it. ## What changed **The field.** `BOOK_LANGUAGE` becomes `user_overridable` and moves from the **General** tab to **Search Mode**, next to the other user-overridable search defaults (per [review](https://github.com/calibrain/shelfmark/pull/1255#issuecomment-5391189094) — the first version had the Search section span two tabs, this one doesn't). Admins set it per user in the user editor, users set it in **My Account → Search Preferences**, and the Search Mode tab carries the usual "N users override this" summary. **No migration for the move.** `general` and `search_mode` both persist into `settings.json`, and a field's value is resolved through `load_config_file(tab)` for the tab it's declared on — so an install that already stores `BOOK_LANGUAGE` keeps its value. Checked against a `settings.json` written while the field still lived on General: the stored value resolves unchanged, a fresh install still gets `["en"]`, and `BOOK_LANGUAGE` in the environment still overrides both. **The two places the default is read.** - `/api/config` seeds the frontend's language filter, so it now resolves `BOOK_LANGUAGE` for the session user. - `build_release_search_plan` falls back to the default whenever a request carries no language filter — which is exactly what the filter's "Default" option sends. It takes an optional `user_id`, passed by `/api/releases` from the session and by the Prowlarr retry path from `task.user_id`, so a retry re-searches in the languages of whoever queued the download. **Validation.** Overrides go through `normalize_language()`, so `"German"`, `"ger"` and `"de"` all store as `de`, and an unknown language is rejected with a message naming it instead of being silently searched for. An empty list stays an empty list (a deliberate "no default filter"), `null` clears the override as everywhere else, and ENV still wins: with `BOOK_LANGUAGE` set in the environment the field reports `fromEnv` and overrides are ignored. **Scope.** Only the language default becomes overridable. The two format lists left behind under "Default Search Filters" stay admin-only — they describe what the library and its post-processing accept, not what a reader wants to read. There's a test pinning that. ## Verification - 2681 unit tests pass (2670 before, 11 added) - `ruff check`, `ruff format`, `basedpyright` over backend and tests, and `vulture` all clean; frontend lint, format, typecheck and 126 unit tests clean - `docs/environment-variables.md` regenerated via `scripts/generate_env_docs.py` (the `BOOK_LANGUAGE` row follows the field into the Search Mode section) - Manually against a two-user instance with builtin auth (first round, before the tab move): with user A on German and user B on English+German, `/api/config` returns each reader their own `default_language` and an unfiltered `/api/releases` plans the matching languages; an admin can set and read the same override for another user; clearing it falls back to the global value; a stray `"klingon"` is rejected; and `BOOK_LANGUAGE` in the environment overrides both users with the field marked `fromEnv` - After the tab move I re-ran the suites above plus the stored-value/fresh-install/ENV check described under "No migration for the move"; the behaviour it exercises is what the move could have broken Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: CaliBrain <calibrain@l4n.xyz> |
||
|
|
fdd46852f2 | Add new docs (#633) |