From fc6e6f03d5c82d32e013247c0906605ce42cbd83 Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:33:40 +0000 Subject: [PATCH] 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 --- .github/workflows/ci.yml | 2 +- README.md | 2 + client/src/assets/main.css | 30 + client/src/assets/theme/bridge.css | 2 + client/src/assets/theme/tokens.css | 93 + client/src/components/AppLayout.vue | 8 + client/src/components/AppSidebar.vue | 30 +- .../src/components/sidebar/SidebarBadge.vue | 20 +- .../sidebar/SidebarSectionHeader.vue | 5 +- .../sidebar/SidebarSectionPopover.vue | 2 +- .../sidebar/__tests__/SidebarBadge.spec.ts | 12 + client/src/components/ui/ChipInput.vue | 84 +- client/src/components/ui/TokenSelect.vue | 206 + .../components/ui/__tests__/ChipInput.spec.ts | 84 + .../ui/__tests__/TokenSelect.spec.ts | 117 + .../composables/__tests__/useRouteTab.spec.ts | 105 + .../__tests__/useSidebarNav.spec.ts | 97 +- .../settings => }/composables/useRouteTab.ts | 5 +- client/src/composables/useSidebarNav.ts | 56 +- client/src/features/admin/UserFormDrawer.vue | 981 +- client/src/features/admin/UsersPage.vue | 16 +- .../admin/__tests__/UserFormDrawer.spec.ts | 301 + .../admin/__tests__/UsersPage.spec.ts | 14 +- .../components/UserPermissionsSection.vue | 239 + .../components/UserRestrictionsSection.vue | 150 + .../features/admin/composables/useUserForm.ts | 370 + .../features/admin/composables/useUsers.ts | 2 +- .../lib/__tests__/permission-presets.spec.ts | 65 +- .../features/admin/lib/permission-presets.ts | 110 +- client/src/features/audit/audit-resources.ts | 3 + .../src/features/auth/composables/useAuth.ts | 2 + .../book-dock/components/BookDockFileRow.vue | 48 +- .../__tests__/BookDockFileRow.spec.ts | 108 +- .../__tests__/BookDockFileSheet.spec.ts | 1 + .../book-requests/BookRequestsPage.vue | 798 + .../ReleaseFileInspectionPanel.spec.ts | 227 + .../__tests__/ReleasePickerPanel.spec.ts | 764 + .../__tests__/ReleaseUnitChooser.spec.ts | 74 + .../__tests__/RequestAttemptsPanel.spec.ts | 107 + .../__tests__/RequestCover.spec.ts | 91 + .../__tests__/RequestDownloadProgress.spec.ts | 178 + .../RequestPipeline.component.spec.ts | 62 + .../__tests__/RequestReviewPanel.spec.ts | 120 + .../__tests__/RequestSearchPanel.spec.ts | 778 + .../__tests__/RequestSeedPanel.spec.ts | 205 + .../__tests__/releaseFacets.spec.ts | 96 + .../__tests__/requestActions.spec.ts | 121 + .../__tests__/requestOutcome.spec.ts | 96 + .../__tests__/requestPipelineState.spec.ts | 99 + .../__tests__/sourceColors.spec.ts | 36 + .../__tests__/useBookRequestDetail.spec.ts | 106 + .../__tests__/useBookRequestProgress.spec.ts | 212 + .../__tests__/useBookRequestSummary.spec.ts | 65 + .../__tests__/useBookRequests.spec.ts | 232 + .../__tests__/useCandidateGroups.spec.ts | 259 + .../__tests__/useCoalescedRefresh.spec.ts | 87 + .../__tests__/useFulfilmentPicker.spec.ts | 98 + .../__tests__/useReleaseFilters.spec.ts | 145 + .../__tests__/useReleaseGrab.spec.ts | 157 + .../__tests__/useReleasePicker.spec.ts | 359 + .../useRequestDestinationDefault.spec.ts | 229 + .../__tests__/useRequestSubmission.spec.ts | 191 + .../components/GrabReleaseDialog.vue | 171 + .../components/NoRequestSourcesNotice.vue | 50 + .../components/ReleaseFileInspectionPanel.vue | 166 + .../components/ReleasePickerPanel.vue | 1284 ++ .../components/ReleaseUnitChooser.vue | 68 + .../components/RequestAttemptsPanel.vue | 105 + .../book-requests/components/RequestCover.vue | 74 + .../components/RequestDetailPanel.vue | 976 + .../components/RequestDownloadProgress.vue | 121 + .../components/RequestDrawer.vue | 66 + .../components/RequestDrawerToolbar.vue | 90 + .../components/RequestEmptyState.vue | 24 + .../components/RequestListSkeleton.vue | 31 + .../components/RequestListTable.vue | 575 + .../components/RequestPipeline.vue | 106 + .../components/RequestReviewPanel.vue | 145 + .../components/RequestRowActions.vue | 109 + .../components/RequestSearchPanel.vue | 1037 + .../components/RequestSeedPanel.vue | 201 + .../components/RequestStatusBadge.vue | 53 + .../composables/useBookRequestDetail.ts | 66 + .../composables/useBookRequestProgress.ts | 158 + .../composables/useBookRequestSummary.ts | 60 + .../composables/useBookRequests.ts | 368 + .../composables/useCandidateGroups.ts | 200 + .../composables/useCoalescedRefresh.ts | 32 + .../composables/useDownloadClients.ts | 237 + .../composables/useFulfilmentPicker.ts | 142 + .../book-requests/composables/useIndexers.ts | 203 + .../composables/useReleaseFacetGroups.ts | 160 + .../composables/useReleaseFilters.ts | 298 + .../composables/useReleaseGrab.ts | 150 + .../composables/useReleasePicker.ts | 234 + .../composables/useReleaseSearchEditor.ts | 248 + .../composables/useRequestAttempts.ts | 54 + .../composables/useRequestAutomation.ts | 64 + .../useRequestDestinationDefault.ts | 205 + .../composables/useRequestListDensity.ts | 38 + .../composables/useRequestQueue.ts | 27 + .../composables/useRequestReview.ts | 55 + .../composables/useRequestSeedStatus.ts | 49 + .../composables/useRequestSourceStatus.ts | 48 + .../composables/useRequestSubmission.ts | 379 + .../features/book-requests/fulfilmentBase.ts | 21 + .../features/book-requests/releaseFacets.ts | 50 + .../requestActionFailure.test.ts | 30 + .../book-requests/requestActionFailure.ts | 24 + .../features/book-requests/requestActions.ts | 63 + .../book-requests/requestDrawerContext.ts | 19 + .../features/book-requests/requestOutcome.ts | 77 + .../features/book-requests/requestPipeline.ts | 128 + .../features/book-requests/sourceColors.ts | 61 + .../__tests__/useMetadataSearch.spec.ts | 28 +- .../book/composables/useMetadataSearch.ts | 21 + .../email/components/EmailSettings.vue | 2 +- .../components/NotificationItem.spec.ts | 105 +- .../components/NotificationItem.vue | 94 +- .../lib/notification-category-groups.ts | 5 +- .../features/settings/ConnectionHealth.vue | 56 + .../settings/DownloadClientsPanel.vue | 854 + .../settings/RequestAutomationPanel.vue | 548 + .../settings/RequestIndexersPanel.vue | 927 + .../features/settings/RequestsSettings.vue | 77 + client/src/features/settings/SearchHealth.vue | 54 + .../__tests__/ConnectionHealth.spec.ts | 47 + .../__tests__/DownloadClientsPanel.spec.ts | 282 + .../__tests__/RequestAutomationPanel.spec.ts | 452 + .../__tests__/RequestIndexersPanel.spec.ts | 1098 + .../__tests__/RequestsSettings.spec.ts | 75 + .../settings/__tests__/SearchHealth.spec.ts | 40 + .../__tests__/SettingsEditorSheet.spec.ts | 159 + .../settings/__tests__/SettingsNav.spec.ts | 158 +- .../__tests__/SettingsSidebar.spec.ts | 3 +- .../settings/__tests__/settings-nav.spec.ts | 22 +- .../settings/__tests__/system-tabs.spec.ts | 46 +- .../__tests__/useSettingsDraft.spec.ts | 72 + .../settings/components/AdapterTypePicker.vue | 56 + .../settings/components/FieldHint.vue | 15 + .../components/PluginDirectoryLink.vue | 27 + .../components/PluginVersionBadge.vue | 20 + .../components/ReleaseProfileEditor.vue | 410 + .../components/RequestDestinationRow.vue | 105 + .../settings/components/RequestSourceRow.vue | 204 + .../components/RequestSourcesEmpty.vue | 79 + .../components/SettingsEditorSheet.vue | 229 + .../components/SettingsEmptyPanel.vue | 50 + .../settings/components/SettingsField.vue | 50 + .../settings/components/SettingsNav.vue | 83 +- .../settings/components/SettingsRow.vue | 21 + .../settings/components/SettingsSection.vue | 16 + .../settings/components/SettingsSlider.vue | 33 + .../components/SettingsToggleField.vue | 39 + .../settings/components/SourceColorPicker.vue | 68 + .../__tests__/useSettingsNavStatus.spec.ts | 125 + .../settings/composables/useIndexerDraft.ts | 451 + .../settings/composables/useIndexerPlugins.ts | 328 + .../settings/composables/useSettingsDraft.ts | 59 + .../composables/useSettingsNavStatus.ts | 29 + .../features/settings/lib/editor-actions.ts | 17 + .../src/features/settings/lib/settings-nav.ts | 253 +- .../src/features/settings/lib/system-tabs.ts | 23 +- client/src/locales/en.json | 1278 +- client/src/router/index.ts | 30 + docs/AUDIOBOOKSHELF_MIGRATION.md | 218 - docs/BOOK_REQUESTS.md | 169 + docs/CALIBRE_WEB_AUTOMATED_MIGRATION.md | 290 - packages/plugin-api/package.json | 14 + packages/plugin-api/src/index.ts | 224 + packages/plugin-api/tsconfig.json | 15 + packages/types/src/audit.ts | 24 + packages/types/src/book-dock.ts | 25 +- packages/types/src/book-request-automation.ts | 192 + .../types/src/book-request-destination.ts | 47 + .../types/src/book-request-preferences.ts | 32 + packages/types/src/book-request-profile.ts | 198 + packages/types/src/book-request-review.ts | 61 + packages/types/src/book-request.ts | 573 + packages/types/src/book.ts | 16 +- packages/types/src/content-filter.ts | 8 + packages/types/src/download-client.ts | 370 + packages/types/src/index.ts | 11 + packages/types/src/indexer.ts | 599 + packages/types/src/language.ts | 123 + packages/types/src/metadata-fetch.ts | 2 + packages/types/src/network-profile.ts | 26 + packages/types/src/notification.ts | 15 + packages/types/src/permissions.ts | 39 + packages/types/src/request-credential.ts | 8 + pnpm-lock.yaml | 12 + scripts/e2e/suite-registry.mjs | 25 + server/.env.example | 19 + server/package.json | 12 +- server/src/app.module.ts | 16 +- .../archive/release-archive-sevenzip.test.ts | 59 + .../common/archive/release-archive.test.ts | 433 + server/src/common/archive/release-archive.ts | 393 + .../decorators/common-decorators.test.ts | 8 + .../require-any-permission.decorator.ts | 11 + .../filters/http-exception.filter.test.ts | 41 + .../common/filters/http-exception.filter.ts | 17 + .../common/guards/permission.guard.test.ts | 54 + server/src/common/guards/permission.guard.ts | 7 + server/src/common/sevenzip.ts | 20 +- .../src/common/text-match/isbn-normalize.ts | 13 + .../text-match}/title-match.test.ts | 0 .../text-match}/title-match.ts | 38 + .../src/common/utils/bounded-response.test.ts | 105 + server/src/common/utils/bounded-response.ts | 89 + .../utils/content-filter-sql.utils.test.ts | 11 + .../common/utils/content-filter-sql.utils.ts | 48 +- .../src/common/utils/db-error.utils.test.ts | 27 + server/src/common/utils/db-error.utils.ts | 20 + server/src/common/utils/safe-fetch.test.ts | 159 + server/src/common/utils/safe-fetch.ts | 138 + server/src/common/utils/ssrf.utils.ts | 3 +- .../common/utils/with-deadline.utils.test.ts | 44 + .../src/common/utils/with-deadline.utils.ts | 33 + server/src/common/utils/ws-cors.utils.test.ts | 37 + server/src/common/utils/ws-cors.utils.ts | 23 + server/src/config/config.ts | 9 + server/src/config/env.validation.ts | 1 + .../db/migrations/0083_add_book_requests.sql | 214 + .../src/db/migrations/meta/0083_snapshot.json | 18335 ++++++++++++++++ server/src/db/migrations/meta/_journal.json | 7 + server/src/db/schema/auth.ts | 6 + server/src/db/schema/book-dock.ts | 52 +- .../db/schema/book-requests.schema.test.ts | 55 + server/src/db/schema/book-requests.ts | 217 + .../db/schema/download-clients.schema.test.ts | 52 + server/src/db/schema/download-clients.ts | 182 + server/src/db/schema/index.ts | 3 + .../db/schema/request-indexers.schema.test.ts | 46 + server/src/db/schema/request-indexers.ts | 97 + .../app-settings/app-settings.service.ts | 24 + server/src/modules/auth/auth.service.test.ts | 54 + server/src/modules/auth/auth.service.ts | 30 +- .../book-dock-finalize.service.test.ts | 491 +- .../book-dock/book-dock-finalize.service.ts | 365 +- .../book-dock-ingest.service.test.ts | 80 + .../book-dock/book-dock-ingest.service.ts | 74 +- .../book-dock-unit-files.sql.test.ts | 28 + .../book-dock-watcher.service.test.ts | 77 +- .../book-dock/book-dock-watcher.service.ts | 85 +- .../modules/book-dock/book-dock.gateway.ts | 3 +- .../src/modules/book-dock/book-dock.module.ts | 2 +- .../modules/book-dock/book-dock.repository.ts | 71 +- .../book-dock/book-dock.service.test.ts | 3 + .../modules/book-dock/book-dock.service.ts | 29 +- .../book-metadata-fetch.gateway.ts | 3 +- .../book-request-admin.controller.ts | 303 + .../book-request-attribution.service.test.ts | 130 + .../book-request-attribution.service.ts | 82 + .../book-request-automation.controller.ts | 36 + .../book-request-dedupe.service.test.ts | 452 + .../book-request-dedupe.service.ts | 179 + .../book-request-events.service.ts | 15 + .../book-request-notifier.service.test.ts | 129 + .../book-request-notifier.service.ts | 98 + .../book-request-self-fulfil.controller.ts | 166 + .../book-request/book-request.controller.ts | 150 + .../book-request/book-request.gateway.test.ts | 79 + .../book-request/book-request.gateway.ts | 93 + .../book-request/book-request.mapper.ts | 103 + .../book-request/book-request.module.ts | 148 + .../book-request.repository.test.ts | 391 + .../book-request/book-request.repository.ts | 823 + .../book-request/book-request.service.test.ts | 2096 ++ .../book-request/book-request.service.ts | 1141 + .../adapters/client-http.utils.test.ts | 167 + .../adapters/client-http.utils.ts | 90 + .../adapters/deluge.adapter.test.ts | 469 + .../adapters/deluge.adapter.ts | 443 + .../adapters/qbittorrent.adapter.test.ts | 401 + .../adapters/qbittorrent.adapter.ts | 341 + .../adapters/transmission.adapter.test.ts | 403 + .../adapters/transmission.adapter.ts | 348 + .../download-client-adapter.ts | 108 + .../download-client-config.service.test.ts | 361 + .../download-client-config.service.ts | 341 + ...load-client-reconciliation.service.test.ts | 164 + .../download-client-reconciliation.service.ts | 175 + .../download-client-registry.ts | 24 + .../download-client.controller.ts | 111 + .../download-client.repository.test.ts | 118 + .../download-client.repository.ts | 141 + .../dto/download-client.dto.test.ts | 25 + .../dto/download-client.dto.ts | 172 + .../dto/reconcile-download-client.dto.ts | 13 + .../path-mapping.service.test.ts | 177 + .../download-clients/path-mapping.service.ts | 209 + .../dto/automation-settings.dto.ts | 244 + .../dto/bulk-book-requests.dto.ts | 28 + .../dto/check-availability.dto.ts | 42 + .../dto/create-book-request.dto.test.ts | 27 + .../dto/create-book-request.dto.ts | 159 + .../dto/decide-book-request.dto.ts | 21 + .../dto/fulfill-book-request.dto.ts | 22 + .../book-request/dto/grab-book-request.dto.ts | 49 + .../dto/inspect-book-request-release.dto.ts | 14 + .../dto/list-book-requests.dto.test.ts | 49 + .../dto/list-book-requests.dto.ts | 71 + .../dto/list-requester-options.dto.ts | 14 + .../book-request/dto/release-guid.dto.test.ts | 17 + .../book-request/dto/remove-download.dto.ts | 11 + .../dto/search-book-request-releases.dto.ts | 33 + .../dto/select-release-unit.dto.ts | 10 + .../dto/update-book-request-language.dto.ts | 15 + .../book-request-download.repository.test.ts | 47 + .../book-request-download.repository.ts | 409 + .../direct-download.service.test.ts | 649 + .../fulfillment/direct-download.service.ts | 611 + .../download-monitor.service.test.ts | 546 + .../fulfillment/download-monitor.service.ts | 350 + .../download-removal.service.test.ts | 142 + .../fulfillment/download-removal.service.ts | 152 + ...st-automation-search-delay.service.test.ts | 28 + ...request-automation-search-delay.service.ts | 12 + ...equest-automation-settings.service.test.ts | 287 + .../request-automation-settings.service.ts | 278 + .../request-automation.service.test.ts | 893 + .../fulfillment/request-automation.service.ts | 643 + .../request-fulfillment.service.test.ts | 903 + .../request-fulfillment.service.ts | 1034 + .../fulfillment/request-import-queue.test.ts | 103 + .../fulfillment/request-import-queue.ts | 94 + .../request-import.service.test.ts | 840 + .../fulfillment/request-import.service.ts | 721 + .../fulfillment/request-seed.service.test.ts | 191 + .../fulfillment/request-seed.service.ts | 87 + .../request-user-cleanup.service.test.ts | 67 + .../request-user-cleanup.service.ts | 67 + .../request-verification.service.test.ts | 562 + .../request-verification.service.ts | 542 + .../request-watchdog.service.test.ts | 238 + .../fulfillment/request-watchdog.service.ts | 246 + .../fulfillment/torrent.utils.test.ts | 184 + .../book-request/fulfillment/torrent.utils.ts | 261 + .../indexers/adapters/torznab.adapter.test.ts | 471 + .../indexers/adapters/torznab.adapter.ts | 363 + .../book-request/indexers/dto/indexer.dto.ts | 220 + .../book-request/indexers/indexer-adapter.ts | 191 + .../indexers/indexer-config.service.test.ts | 373 + .../indexers/indexer-config.service.ts | 396 + .../indexers/indexer-credential-store.test.ts | 130 + .../indexers/indexer-credential-store.ts | 81 + .../indexer-keepalive.service.test.ts | 105 + .../indexers/indexer-keepalive.service.ts | 94 + .../indexers/indexer-operation-lock.ts | 22 + .../indexers/indexer-registry.test.ts | 30 + .../book-request/indexers/indexer-registry.ts | 164 + .../indexers/indexer-search.service.test.ts | 698 + .../indexers/indexer-search.service.ts | 423 + .../indexers/indexer.controller.test.ts | 140 + .../indexers/indexer.controller.ts | 178 + .../indexers/indexer.repository.test.ts | 87 + .../indexers/indexer.repository.ts | 138 + .../indexers/plugins/plugin-host.test.ts | 537 + .../indexers/plugins/plugin-host.ts | 397 + .../plugins/plugin-install.service.test.ts | 375 + .../plugins/plugin-install.service.ts | 292 + .../plugins/plugin-loader.service.test.ts | 227 + .../indexers/plugins/plugin-loader.service.ts | 184 + .../indexers/plugins/plugin-shape.ts | 144 + .../indexers/release-profile.test.ts | 194 + .../indexers/release-scoring.test.ts | 346 + .../book-request/indexers/release-scoring.ts | 408 + .../book-request/indexers/search-text.test.ts | 30 + .../book-request/indexers/search-text.ts | 26 + .../request-credential.service.test.ts | 53 + .../request-credential.service.ts | 83 + .../formats/cbx/cb7-format-writer.test.ts | 1 + .../src/modules/library/library.repository.ts | 20 +- server/src/modules/library/library.service.ts | 11 + .../metadata-fetch/candidate-agreement.ts | 2 +- .../candidate-relevance.test.ts | 8 + .../metadata-fetch/candidate-relevance.ts | 8 +- .../dto/metadata-search.dto.test.ts | 14 +- .../metadata-fetch/dto/metadata-search.dto.ts | 16 +- .../src/modules/metadata-fetch/isbn-match.ts | 6 +- .../metadata-fetch.controller.test.ts | 86 +- .../metadata-fetch.controller.ts | 40 +- .../metadata-fetch.service.test.ts | 33 + .../metadata-fetch/metadata-fetch.service.ts | 8 +- .../metadata-fetch/provider-registry.test.ts | 39 +- .../metadata-fetch/provider-registry.ts | 13 +- .../provider-throttle.error.test.ts | 13 + .../metadata-fetch/provider-throttle.error.ts | 12 +- .../providers/amazon/amazon.provider.ts | 16 +- .../providers/audible/audible.provider.ts | 1 + .../providers/audnexus/audnexus.provider.ts | 1 + .../providers/comicvine/comicvine.provider.ts | 1 + .../goodreads/goodreads.provider.test.ts | 54 + .../providers/goodreads/goodreads.provider.ts | 30 +- .../google/google-cover-validator.test.ts | 85 + .../google/google-cover-validator.ts | 129 + .../providers/google/google.provider.test.ts | 56 + .../providers/google/google.provider.ts | 9 +- .../providers/kobo/kobo.provider.ts | 20 +- .../providers/librofm/librofm.provider.ts | 15 +- .../lubimyczytac/lubimyczytac.provider.ts | 18 +- .../providers/metadata-provider.ts | 7 +- .../providers/metadata-search-params.ts | 3 + .../providers/provider-utils.test.ts | 64 +- .../providers/provider-utils.ts | 15 + .../providers/ranobedb/ranobedb.provider.ts | 15 +- .../modules/metadata/lib/cover-cb7.test.ts | 2 +- .../src/modules/metadata/lib/isbn-detect.ts | 6 +- .../modules/metadata/lib/opf-parser.test.ts | 21 + server/src/modules/metadata/lib/opf-parser.ts | 23 + .../src/modules/metadata/lib/sevenzip-vfs.ts | 7 +- .../modules/reader/cbz/cbz.service.test.ts | 2 +- .../modules/scanner/lib/release-plan.test.ts | 334 + .../src/modules/scanner/lib/release-plan.ts | 410 + server/src/modules/scanner/lib/stability.ts | 83 +- server/src/modules/scanner/lib/walk.ts | 4 +- .../upload/upload-processor.service.test.ts | 77 + .../upload/upload-processor.service.ts | 214 +- .../user-preferences.controller.test.ts | 18 + .../user-preferences.controller.ts | 12 + .../user-preferences.service.test.ts | 77 +- .../user-preferences.service.ts | 55 + .../user/content-filter.repository.test.ts | 10 + .../modules/user/content-filter.repository.ts | 21 +- .../user/dto/set-content-filters.dto.ts | 7 +- .../src/modules/user/user-events.service.ts | 25 + server/src/modules/user/user.module.test.ts | 3 + server/src/modules/user/user.module.ts | 21 +- .../src/modules/user/user.repository.test.ts | 93 + server/src/modules/user/user.repository.ts | 21 +- server/src/modules/user/user.service.test.ts | 62 +- server/src/modules/user/user.service.ts | 38 +- server/src/scripts/migrate.ts | 5 +- .../migration-ledger-compatibility.test.ts | 64 + .../scripts/migration-ledger-compatibility.ts | 69 + server/test/authorization-matrix.e2e-spec.ts | 77 +- server/test/book-requests.e2e-spec.ts | 679 + .../route-inventory/manifest.json | 23 +- .../route-inventory/routes-05.json | 2 +- .../route-inventory/routes-06.json | 24 + .../route-inventory/routes-08.json | 865 + .../route-inventory/uncovered-routes.json | 300 + 443 files changed, 87915 insertions(+), 1730 deletions(-) create mode 100644 client/src/components/ui/TokenSelect.vue create mode 100644 client/src/components/ui/__tests__/ChipInput.spec.ts create mode 100644 client/src/components/ui/__tests__/TokenSelect.spec.ts create mode 100644 client/src/composables/__tests__/useRouteTab.spec.ts rename client/src/{features/settings => }/composables/useRouteTab.ts (90%) create mode 100644 client/src/features/admin/__tests__/UserFormDrawer.spec.ts create mode 100644 client/src/features/admin/components/UserPermissionsSection.vue create mode 100644 client/src/features/admin/components/UserRestrictionsSection.vue create mode 100644 client/src/features/admin/composables/useUserForm.ts create mode 100644 client/src/features/book-requests/BookRequestsPage.vue create mode 100644 client/src/features/book-requests/__tests__/ReleaseFileInspectionPanel.spec.ts create mode 100644 client/src/features/book-requests/__tests__/ReleasePickerPanel.spec.ts create mode 100644 client/src/features/book-requests/__tests__/ReleaseUnitChooser.spec.ts create mode 100644 client/src/features/book-requests/__tests__/RequestAttemptsPanel.spec.ts create mode 100644 client/src/features/book-requests/__tests__/RequestCover.spec.ts create mode 100644 client/src/features/book-requests/__tests__/RequestDownloadProgress.spec.ts create mode 100644 client/src/features/book-requests/__tests__/RequestPipeline.component.spec.ts create mode 100644 client/src/features/book-requests/__tests__/RequestReviewPanel.spec.ts create mode 100644 client/src/features/book-requests/__tests__/RequestSearchPanel.spec.ts create mode 100644 client/src/features/book-requests/__tests__/RequestSeedPanel.spec.ts create mode 100644 client/src/features/book-requests/__tests__/releaseFacets.spec.ts create mode 100644 client/src/features/book-requests/__tests__/requestActions.spec.ts create mode 100644 client/src/features/book-requests/__tests__/requestOutcome.spec.ts create mode 100644 client/src/features/book-requests/__tests__/requestPipelineState.spec.ts create mode 100644 client/src/features/book-requests/__tests__/sourceColors.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useBookRequestDetail.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useBookRequestProgress.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useBookRequestSummary.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useBookRequests.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useCandidateGroups.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useCoalescedRefresh.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useFulfilmentPicker.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useReleaseFilters.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useReleaseGrab.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useReleasePicker.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useRequestDestinationDefault.spec.ts create mode 100644 client/src/features/book-requests/__tests__/useRequestSubmission.spec.ts create mode 100644 client/src/features/book-requests/components/GrabReleaseDialog.vue create mode 100644 client/src/features/book-requests/components/NoRequestSourcesNotice.vue create mode 100644 client/src/features/book-requests/components/ReleaseFileInspectionPanel.vue create mode 100644 client/src/features/book-requests/components/ReleasePickerPanel.vue create mode 100644 client/src/features/book-requests/components/ReleaseUnitChooser.vue create mode 100644 client/src/features/book-requests/components/RequestAttemptsPanel.vue create mode 100644 client/src/features/book-requests/components/RequestCover.vue create mode 100644 client/src/features/book-requests/components/RequestDetailPanel.vue create mode 100644 client/src/features/book-requests/components/RequestDownloadProgress.vue create mode 100644 client/src/features/book-requests/components/RequestDrawer.vue create mode 100644 client/src/features/book-requests/components/RequestDrawerToolbar.vue create mode 100644 client/src/features/book-requests/components/RequestEmptyState.vue create mode 100644 client/src/features/book-requests/components/RequestListSkeleton.vue create mode 100644 client/src/features/book-requests/components/RequestListTable.vue create mode 100644 client/src/features/book-requests/components/RequestPipeline.vue create mode 100644 client/src/features/book-requests/components/RequestReviewPanel.vue create mode 100644 client/src/features/book-requests/components/RequestRowActions.vue create mode 100644 client/src/features/book-requests/components/RequestSearchPanel.vue create mode 100644 client/src/features/book-requests/components/RequestSeedPanel.vue create mode 100644 client/src/features/book-requests/components/RequestStatusBadge.vue create mode 100644 client/src/features/book-requests/composables/useBookRequestDetail.ts create mode 100644 client/src/features/book-requests/composables/useBookRequestProgress.ts create mode 100644 client/src/features/book-requests/composables/useBookRequestSummary.ts create mode 100644 client/src/features/book-requests/composables/useBookRequests.ts create mode 100644 client/src/features/book-requests/composables/useCandidateGroups.ts create mode 100644 client/src/features/book-requests/composables/useCoalescedRefresh.ts create mode 100644 client/src/features/book-requests/composables/useDownloadClients.ts create mode 100644 client/src/features/book-requests/composables/useFulfilmentPicker.ts create mode 100644 client/src/features/book-requests/composables/useIndexers.ts create mode 100644 client/src/features/book-requests/composables/useReleaseFacetGroups.ts create mode 100644 client/src/features/book-requests/composables/useReleaseFilters.ts create mode 100644 client/src/features/book-requests/composables/useReleaseGrab.ts create mode 100644 client/src/features/book-requests/composables/useReleasePicker.ts create mode 100644 client/src/features/book-requests/composables/useReleaseSearchEditor.ts create mode 100644 client/src/features/book-requests/composables/useRequestAttempts.ts create mode 100644 client/src/features/book-requests/composables/useRequestAutomation.ts create mode 100644 client/src/features/book-requests/composables/useRequestDestinationDefault.ts create mode 100644 client/src/features/book-requests/composables/useRequestListDensity.ts create mode 100644 client/src/features/book-requests/composables/useRequestQueue.ts create mode 100644 client/src/features/book-requests/composables/useRequestReview.ts create mode 100644 client/src/features/book-requests/composables/useRequestSeedStatus.ts create mode 100644 client/src/features/book-requests/composables/useRequestSourceStatus.ts create mode 100644 client/src/features/book-requests/composables/useRequestSubmission.ts create mode 100644 client/src/features/book-requests/fulfilmentBase.ts create mode 100644 client/src/features/book-requests/releaseFacets.ts create mode 100644 client/src/features/book-requests/requestActionFailure.test.ts create mode 100644 client/src/features/book-requests/requestActionFailure.ts create mode 100644 client/src/features/book-requests/requestActions.ts create mode 100644 client/src/features/book-requests/requestDrawerContext.ts create mode 100644 client/src/features/book-requests/requestOutcome.ts create mode 100644 client/src/features/book-requests/requestPipeline.ts create mode 100644 client/src/features/book-requests/sourceColors.ts create mode 100644 client/src/features/settings/ConnectionHealth.vue create mode 100644 client/src/features/settings/DownloadClientsPanel.vue create mode 100644 client/src/features/settings/RequestAutomationPanel.vue create mode 100644 client/src/features/settings/RequestIndexersPanel.vue create mode 100644 client/src/features/settings/RequestsSettings.vue create mode 100644 client/src/features/settings/SearchHealth.vue create mode 100644 client/src/features/settings/__tests__/ConnectionHealth.spec.ts create mode 100644 client/src/features/settings/__tests__/DownloadClientsPanel.spec.ts create mode 100644 client/src/features/settings/__tests__/RequestAutomationPanel.spec.ts create mode 100644 client/src/features/settings/__tests__/RequestIndexersPanel.spec.ts create mode 100644 client/src/features/settings/__tests__/RequestsSettings.spec.ts create mode 100644 client/src/features/settings/__tests__/SearchHealth.spec.ts create mode 100644 client/src/features/settings/__tests__/SettingsEditorSheet.spec.ts create mode 100644 client/src/features/settings/__tests__/useSettingsDraft.spec.ts create mode 100644 client/src/features/settings/components/AdapterTypePicker.vue create mode 100644 client/src/features/settings/components/FieldHint.vue create mode 100644 client/src/features/settings/components/PluginDirectoryLink.vue create mode 100644 client/src/features/settings/components/PluginVersionBadge.vue create mode 100644 client/src/features/settings/components/ReleaseProfileEditor.vue create mode 100644 client/src/features/settings/components/RequestDestinationRow.vue create mode 100644 client/src/features/settings/components/RequestSourceRow.vue create mode 100644 client/src/features/settings/components/RequestSourcesEmpty.vue create mode 100644 client/src/features/settings/components/SettingsEditorSheet.vue create mode 100644 client/src/features/settings/components/SettingsEmptyPanel.vue create mode 100644 client/src/features/settings/components/SettingsField.vue create mode 100644 client/src/features/settings/components/SettingsRow.vue create mode 100644 client/src/features/settings/components/SettingsSection.vue create mode 100644 client/src/features/settings/components/SettingsSlider.vue create mode 100644 client/src/features/settings/components/SettingsToggleField.vue create mode 100644 client/src/features/settings/components/SourceColorPicker.vue create mode 100644 client/src/features/settings/composables/__tests__/useSettingsNavStatus.spec.ts create mode 100644 client/src/features/settings/composables/useIndexerDraft.ts create mode 100644 client/src/features/settings/composables/useIndexerPlugins.ts create mode 100644 client/src/features/settings/composables/useSettingsDraft.ts create mode 100644 client/src/features/settings/composables/useSettingsNavStatus.ts create mode 100644 client/src/features/settings/lib/editor-actions.ts delete mode 100644 docs/AUDIOBOOKSHELF_MIGRATION.md create mode 100644 docs/BOOK_REQUESTS.md delete mode 100644 docs/CALIBRE_WEB_AUTOMATED_MIGRATION.md create mode 100644 packages/plugin-api/package.json create mode 100644 packages/plugin-api/src/index.ts create mode 100644 packages/plugin-api/tsconfig.json create mode 100644 packages/types/src/book-request-automation.ts create mode 100644 packages/types/src/book-request-destination.ts create mode 100644 packages/types/src/book-request-preferences.ts create mode 100644 packages/types/src/book-request-profile.ts create mode 100644 packages/types/src/book-request-review.ts create mode 100644 packages/types/src/book-request.ts create mode 100644 packages/types/src/download-client.ts create mode 100644 packages/types/src/indexer.ts create mode 100644 packages/types/src/language.ts create mode 100644 packages/types/src/network-profile.ts create mode 100644 packages/types/src/request-credential.ts create mode 100644 server/src/common/archive/release-archive-sevenzip.test.ts create mode 100644 server/src/common/archive/release-archive.test.ts create mode 100644 server/src/common/archive/release-archive.ts create mode 100644 server/src/common/decorators/require-any-permission.decorator.ts create mode 100644 server/src/common/text-match/isbn-normalize.ts rename server/src/{modules/metadata-fetch => common/text-match}/title-match.test.ts (100%) rename server/src/{modules/metadata-fetch => common/text-match}/title-match.ts (75%) create mode 100644 server/src/common/utils/bounded-response.test.ts create mode 100644 server/src/common/utils/bounded-response.ts create mode 100644 server/src/common/utils/db-error.utils.test.ts create mode 100644 server/src/common/utils/db-error.utils.ts create mode 100644 server/src/common/utils/safe-fetch.test.ts create mode 100644 server/src/common/utils/safe-fetch.ts create mode 100644 server/src/common/utils/with-deadline.utils.test.ts create mode 100644 server/src/common/utils/with-deadline.utils.ts create mode 100644 server/src/common/utils/ws-cors.utils.test.ts create mode 100644 server/src/common/utils/ws-cors.utils.ts create mode 100644 server/src/db/migrations/0083_add_book_requests.sql create mode 100644 server/src/db/migrations/meta/0083_snapshot.json create mode 100644 server/src/db/schema/book-requests.schema.test.ts create mode 100644 server/src/db/schema/book-requests.ts create mode 100644 server/src/db/schema/download-clients.schema.test.ts create mode 100644 server/src/db/schema/download-clients.ts create mode 100644 server/src/db/schema/request-indexers.schema.test.ts create mode 100644 server/src/db/schema/request-indexers.ts create mode 100644 server/src/modules/book-dock/book-dock-unit-files.sql.test.ts create mode 100644 server/src/modules/book-request/book-request-admin.controller.ts create mode 100644 server/src/modules/book-request/book-request-attribution.service.test.ts create mode 100644 server/src/modules/book-request/book-request-attribution.service.ts create mode 100644 server/src/modules/book-request/book-request-automation.controller.ts create mode 100644 server/src/modules/book-request/book-request-dedupe.service.test.ts create mode 100644 server/src/modules/book-request/book-request-dedupe.service.ts create mode 100644 server/src/modules/book-request/book-request-events.service.ts create mode 100644 server/src/modules/book-request/book-request-notifier.service.test.ts create mode 100644 server/src/modules/book-request/book-request-notifier.service.ts create mode 100644 server/src/modules/book-request/book-request-self-fulfil.controller.ts create mode 100644 server/src/modules/book-request/book-request.controller.ts create mode 100644 server/src/modules/book-request/book-request.gateway.test.ts create mode 100644 server/src/modules/book-request/book-request.gateway.ts create mode 100644 server/src/modules/book-request/book-request.mapper.ts create mode 100644 server/src/modules/book-request/book-request.module.ts create mode 100644 server/src/modules/book-request/book-request.repository.test.ts create mode 100644 server/src/modules/book-request/book-request.repository.ts create mode 100644 server/src/modules/book-request/book-request.service.test.ts create mode 100644 server/src/modules/book-request/book-request.service.ts create mode 100644 server/src/modules/book-request/download-clients/adapters/client-http.utils.test.ts create mode 100644 server/src/modules/book-request/download-clients/adapters/client-http.utils.ts create mode 100644 server/src/modules/book-request/download-clients/adapters/deluge.adapter.test.ts create mode 100644 server/src/modules/book-request/download-clients/adapters/deluge.adapter.ts create mode 100644 server/src/modules/book-request/download-clients/adapters/qbittorrent.adapter.test.ts create mode 100644 server/src/modules/book-request/download-clients/adapters/qbittorrent.adapter.ts create mode 100644 server/src/modules/book-request/download-clients/adapters/transmission.adapter.test.ts create mode 100644 server/src/modules/book-request/download-clients/adapters/transmission.adapter.ts create mode 100644 server/src/modules/book-request/download-clients/download-client-adapter.ts create mode 100644 server/src/modules/book-request/download-clients/download-client-config.service.test.ts create mode 100644 server/src/modules/book-request/download-clients/download-client-config.service.ts create mode 100644 server/src/modules/book-request/download-clients/download-client-reconciliation.service.test.ts create mode 100644 server/src/modules/book-request/download-clients/download-client-reconciliation.service.ts create mode 100644 server/src/modules/book-request/download-clients/download-client-registry.ts create mode 100644 server/src/modules/book-request/download-clients/download-client.controller.ts create mode 100644 server/src/modules/book-request/download-clients/download-client.repository.test.ts create mode 100644 server/src/modules/book-request/download-clients/download-client.repository.ts create mode 100644 server/src/modules/book-request/download-clients/dto/download-client.dto.test.ts create mode 100644 server/src/modules/book-request/download-clients/dto/download-client.dto.ts create mode 100644 server/src/modules/book-request/download-clients/dto/reconcile-download-client.dto.ts create mode 100644 server/src/modules/book-request/download-clients/path-mapping.service.test.ts create mode 100644 server/src/modules/book-request/download-clients/path-mapping.service.ts create mode 100644 server/src/modules/book-request/dto/automation-settings.dto.ts create mode 100644 server/src/modules/book-request/dto/bulk-book-requests.dto.ts create mode 100644 server/src/modules/book-request/dto/check-availability.dto.ts create mode 100644 server/src/modules/book-request/dto/create-book-request.dto.test.ts create mode 100644 server/src/modules/book-request/dto/create-book-request.dto.ts create mode 100644 server/src/modules/book-request/dto/decide-book-request.dto.ts create mode 100644 server/src/modules/book-request/dto/fulfill-book-request.dto.ts create mode 100644 server/src/modules/book-request/dto/grab-book-request.dto.ts create mode 100644 server/src/modules/book-request/dto/inspect-book-request-release.dto.ts create mode 100644 server/src/modules/book-request/dto/list-book-requests.dto.test.ts create mode 100644 server/src/modules/book-request/dto/list-book-requests.dto.ts create mode 100644 server/src/modules/book-request/dto/list-requester-options.dto.ts create mode 100644 server/src/modules/book-request/dto/release-guid.dto.test.ts create mode 100644 server/src/modules/book-request/dto/remove-download.dto.ts create mode 100644 server/src/modules/book-request/dto/search-book-request-releases.dto.ts create mode 100644 server/src/modules/book-request/dto/select-release-unit.dto.ts create mode 100644 server/src/modules/book-request/dto/update-book-request-language.dto.ts create mode 100644 server/src/modules/book-request/fulfillment/book-request-download.repository.test.ts create mode 100644 server/src/modules/book-request/fulfillment/book-request-download.repository.ts create mode 100644 server/src/modules/book-request/fulfillment/direct-download.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/direct-download.service.ts create mode 100644 server/src/modules/book-request/fulfillment/download-monitor.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/download-monitor.service.ts create mode 100644 server/src/modules/book-request/fulfillment/download-removal.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/download-removal.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-automation-search-delay.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-automation-search-delay.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-automation-settings.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-automation-settings.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-automation.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-automation.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-fulfillment.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-fulfillment.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-import-queue.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-import-queue.ts create mode 100644 server/src/modules/book-request/fulfillment/request-import.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-import.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-seed.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-seed.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-user-cleanup.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-user-cleanup.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-verification.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-verification.service.ts create mode 100644 server/src/modules/book-request/fulfillment/request-watchdog.service.test.ts create mode 100644 server/src/modules/book-request/fulfillment/request-watchdog.service.ts create mode 100644 server/src/modules/book-request/fulfillment/torrent.utils.test.ts create mode 100644 server/src/modules/book-request/fulfillment/torrent.utils.ts create mode 100644 server/src/modules/book-request/indexers/adapters/torznab.adapter.test.ts create mode 100644 server/src/modules/book-request/indexers/adapters/torznab.adapter.ts create mode 100644 server/src/modules/book-request/indexers/dto/indexer.dto.ts create mode 100644 server/src/modules/book-request/indexers/indexer-adapter.ts create mode 100644 server/src/modules/book-request/indexers/indexer-config.service.test.ts create mode 100644 server/src/modules/book-request/indexers/indexer-config.service.ts create mode 100644 server/src/modules/book-request/indexers/indexer-credential-store.test.ts create mode 100644 server/src/modules/book-request/indexers/indexer-credential-store.ts create mode 100644 server/src/modules/book-request/indexers/indexer-keepalive.service.test.ts create mode 100644 server/src/modules/book-request/indexers/indexer-keepalive.service.ts create mode 100644 server/src/modules/book-request/indexers/indexer-operation-lock.ts create mode 100644 server/src/modules/book-request/indexers/indexer-registry.test.ts create mode 100644 server/src/modules/book-request/indexers/indexer-registry.ts create mode 100644 server/src/modules/book-request/indexers/indexer-search.service.test.ts create mode 100644 server/src/modules/book-request/indexers/indexer-search.service.ts create mode 100644 server/src/modules/book-request/indexers/indexer.controller.test.ts create mode 100644 server/src/modules/book-request/indexers/indexer.controller.ts create mode 100644 server/src/modules/book-request/indexers/indexer.repository.test.ts create mode 100644 server/src/modules/book-request/indexers/indexer.repository.ts create mode 100644 server/src/modules/book-request/indexers/plugins/plugin-host.test.ts create mode 100644 server/src/modules/book-request/indexers/plugins/plugin-host.ts create mode 100644 server/src/modules/book-request/indexers/plugins/plugin-install.service.test.ts create mode 100644 server/src/modules/book-request/indexers/plugins/plugin-install.service.ts create mode 100644 server/src/modules/book-request/indexers/plugins/plugin-loader.service.test.ts create mode 100644 server/src/modules/book-request/indexers/plugins/plugin-loader.service.ts create mode 100644 server/src/modules/book-request/indexers/plugins/plugin-shape.ts create mode 100644 server/src/modules/book-request/indexers/release-profile.test.ts create mode 100644 server/src/modules/book-request/indexers/release-scoring.test.ts create mode 100644 server/src/modules/book-request/indexers/release-scoring.ts create mode 100644 server/src/modules/book-request/indexers/search-text.test.ts create mode 100644 server/src/modules/book-request/indexers/search-text.ts create mode 100644 server/src/modules/book-request/request-credential.service.test.ts create mode 100644 server/src/modules/book-request/request-credential.service.ts create mode 100644 server/src/modules/metadata-fetch/providers/google/google-cover-validator.test.ts create mode 100644 server/src/modules/metadata-fetch/providers/google/google-cover-validator.ts create mode 100644 server/src/modules/scanner/lib/release-plan.test.ts create mode 100644 server/src/modules/scanner/lib/release-plan.ts create mode 100644 server/src/modules/user/user-events.service.ts create mode 100644 server/src/scripts/migration-ledger-compatibility.test.ts create mode 100644 server/src/scripts/migration-ledger-compatibility.ts create mode 100644 server/test/book-requests.e2e-spec.ts create mode 100644 server/test/e2e/authorization-matrix/route-inventory/uncovered-routes.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6308d5317..0af74de2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -732,7 +732,7 @@ jobs: file: ./Dockerfile push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.pr-meta.outputs.publish_tag }} - platforms: linux/amd64 + platforms: linux/amd64,linux/arm64 cache-from: type=gha,scope=ci-yml-pr-image cache-to: type=gha,mode=max,scope=ci-yml-pr-image diff --git a/README.md b/README.md index c84a3feaf..b823fed1a 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ The download is pre-configured with your server URL and credentials, so there is Full documentation is at **[bookorbit.app](https://bookorbit.app/what-is-bookorbit)**, covering libraries, metadata, readers, Kobo sync, OPDS, users and permissions, OIDC setup, and more. +For setting up book requests, see the [book requests guide](docs/BOOK_REQUESTS.md): indexers, +download clients, path mappings, automation, and the encryption key they all need. For a one-time import from Audiobookshelf, see the [Audiobookshelf migration guide](docs/AUDIOBOOKSHELF_MIGRATION.md). For a stopped-snapshot import from Calibre-Web Automated, see the [Calibre-Web Automated migration guide](docs/CALIBRE_WEB_AUTOMATED_MIGRATION.md). diff --git a/client/src/assets/main.css b/client/src/assets/main.css index 8df227e6b..e8538a074 100644 --- a/client/src/assets/main.css +++ b/client/src/assets/main.css @@ -404,12 +404,42 @@ outline-offset: -1px; } + .settings-input { + @apply mt-1 h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none disabled:opacity-50; + } + /* `.settings-input` with its own top margin dropped, for a field whose wrapper already owns the + spacing between label, control and hint. */ + .settings-control { + @apply h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none disabled:opacity-50; + } + .settings-control[aria-invalid='true'] { + @apply border-destructive focus-visible:border-destructive focus-visible:ring-destructive/40; + } .settings-card { @apply overflow-hidden rounded-lg border border-border bg-card divide-y divide-border shadow-xs; } .settings-row { @apply flex flex-col gap-3 bg-card px-4 py-3.5 md:flex-row md:items-center md:justify-between md:px-5 md:py-4; } + .settings-card-header { + @apply flex items-center justify-between gap-4 bg-card px-4 py-2.5 md:px-5; + } + .settings-card-title { + @apply text-xs font-semibold uppercase tracking-wider text-foreground; + } + .settings-state-pill { + @apply shrink-0 rounded-full bg-primary/15 px-2 py-0.5 text-xs font-medium text-primary; + } + /* Hints and summaries are 12px. Past roughly 62 characters a line that small stops being + scannable, and a settings card is read in glances rather than in paragraphs. */ + .settings-prose { + @apply max-w-[62ch]; + } + /* Settings that only exist while their parent switch is on. The rail says so at a glance, + which a `v-if` block alone never does. */ + .settings-subgroup { + @apply divide-y divide-border border-l-2 border-primary/30 bg-card; + } .settings-loading-state { @apply flex items-center justify-center rounded-lg border border-border bg-card px-5 py-8 text-center text-sm text-muted-foreground shadow-xs; } diff --git a/client/src/assets/theme/bridge.css b/client/src/assets/theme/bridge.css index 43e3523f4..be1aa60b9 100644 --- a/client/src/assets/theme/bridge.css +++ b/client/src/assets/theme/bridge.css @@ -33,6 +33,7 @@ --color-success-foreground: var(--success-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); + --color-info: var(--info); --color-star-highlight: var(--star-highlight); --color-volume-read: var(--volume-read); --color-volume-reading: var(--volume-reading); @@ -43,6 +44,7 @@ --color-ring: var(--ring); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-count-foreground: var(--sidebar-count-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-accent: var(--sidebar-accent); diff --git a/client/src/assets/theme/tokens.css b/client/src/assets/theme/tokens.css index de7b3ea47..d8ded0820 100644 --- a/client/src/assets/theme/tokens.css +++ b/client/src/assets/theme/tokens.css @@ -48,6 +48,9 @@ --warning: oklch(0.52 0.13 68); --warning-foreground: oklch(0.985 0 0); + /* In flight: neither finished nor broken. Tuned per theme for the same reason. */ + --info: oklch(0.55 0.12 255); + --star-highlight: oklch(0.76 0.18 90); /* Series volume track. Kept apart from --success / --warning / --destructive because those are @@ -74,6 +77,9 @@ --sidebar: oklch(0.965 var(--tint-c-surface) var(--tint-h)); --sidebar-foreground: oklch(0.145 var(--tint-c-surface) var(--tint-h)); + /* Count chips sit one step above --muted-foreground: they are secondary, but they carry a + number the eye scans, so they are tuned per theme rather than faded from the row label. */ + --sidebar-count-foreground: oklch(0.42 0.001 var(--tint-h)); --sidebar-primary: oklch(0.21 var(--tint-c-surface) var(--tint-h)); --sidebar-primary-foreground: oklch(0.985 0.004 var(--tint-h)); --sidebar-accent: oklch(0.955 var(--tint-c-surface) var(--tint-h)); @@ -156,6 +162,52 @@ --pattern-modifier: oklch(0.47 0.17 305); --pattern-optional: oklch(0.46 0.1 195); --pattern-fallback: oklch(0.48 0.13 68); + /* Release chips. Protocol is a fact about the release and keeps two fixed hues; the source hues + below are the operator's to assign. */ + --pill-torrent: oklch(0.52 0.2 295); + --pill-direct: oklch(0.5 0.11 195); + + --pill-source-blue: oklch(0.55 0.16 245); + --pill-source-indigo: oklch(0.55 0.18 275); + --pill-source-purple: oklch(0.55 0.19 330); + --pill-source-pink: oklch(0.55 0.17 355); + --pill-source-red: oklch(0.55 0.17 25); + --pill-source-orange: oklch(0.58 0.15 60); + --pill-source-yellow: oklch(0.6 0.13 95); + --pill-source-lime: oklch(0.56 0.15 135); + --pill-source-green: oklch(0.55 0.14 160); + --pill-source-teal: oklch(0.53 0.11 180); + + /* Medium, read as an icon alone in the request list. Three hues far enough apart to separate at + 16px, and the icon shape still carries the meaning where colour does not land. */ + --pill-media-ebook: oklch(0.55 0.16 245); + --pill-media-audiobook: oklch(0.55 0.2 300); + --pill-media-comic: oklch(0.55 0.18 25); + + /* File format, worn as a chip whose own text carries the colour. Tuned per theme for the same + reason every other text colour is: the raw palette these replaced was one set of hex values + for both themes, and the brightest of them (yellow on white, around 1.9:1) was unreadable in + light mode while the darkest disappeared in dark. Lightness matches the source pills above. */ + --pill-format-default: oklch(0.55 0.02 260); + --pill-format-epub: oklch(0.55 0.14 150); + --pill-format-kepub: oklch(0.55 0.12 178); + --pill-format-mobi: oklch(0.55 0.18 275); + --pill-format-azw3: oklch(0.53 0.11 185); + --pill-format-azw: oklch(0.55 0.14 235); + --pill-format-fb2: oklch(0.55 0.17 355); + --pill-format-djvu: oklch(0.55 0.04 255); + --pill-format-txt: oklch(0.55 0.03 60); + --pill-format-pdf: oklch(0.55 0.17 25); + --pill-format-cbz: oklch(0.55 0.16 260); + --pill-format-cbr: oklch(0.58 0.15 55); + --pill-format-cb7: oklch(0.55 0.19 300); + --pill-format-cbx: oklch(0.55 0.17 325); + --pill-format-m4b: oklch(0.58 0.13 75); + --pill-format-m4a: oklch(0.58 0.13 95); + --pill-format-mp3: oklch(0.56 0.15 145); + --pill-format-opus: oklch(0.55 0.11 205); + --pill-format-ogg: oklch(0.56 0.15 130); + --pill-format-flac: oklch(0.55 0.13 165); /* Rename diff. A bulk rename is a small edit inside a long path, so the ink that marks removed and added characters carries the whole feature. Tuned per theme like every other @@ -251,6 +303,8 @@ --warning: oklch(0.8 0.13 75); --warning-foreground: oklch(0.145 0 0); + --info: oklch(0.72 0.12 255); + --star-highlight: oklch(0.84 0.17 95); --volume-read: oklch(0.7 0.16 148); @@ -271,6 +325,7 @@ --sidebar: oklch(calc(0.18 + var(--bg-lift)) var(--tint-c-surface) var(--tint-h)); --sidebar-foreground: oklch(0.985 0.001 var(--tint-h)); + --sidebar-count-foreground: oklch(0.8 0.001 var(--tint-h)); --sidebar-primary: oklch(0.91 var(--tint-c-surface) var(--tint-h)); --sidebar-primary-foreground: oklch(0.18 var(--tint-c-surface) var(--tint-h)); --sidebar-accent: oklch(calc(0.245 + var(--bg-lift)) var(--tint-c-surface) var(--tint-h)); @@ -326,6 +381,44 @@ --pattern-modifier: oklch(0.8 0.11 305); --pattern-optional: oklch(0.82 0.09 195); --pattern-fallback: oklch(0.82 0.12 75); + --pill-torrent: oklch(0.74 0.17 295); + --pill-direct: oklch(0.78 0.11 195); + + --pill-source-blue: oklch(0.72 0.14 245); + --pill-source-indigo: oklch(0.72 0.16 275); + --pill-source-purple: oklch(0.72 0.17 330); + --pill-source-pink: oklch(0.73 0.15 355); + --pill-source-red: oklch(0.72 0.15 25); + --pill-source-orange: oklch(0.76 0.13 60); + --pill-source-yellow: oklch(0.8 0.13 95); + --pill-source-lime: oklch(0.78 0.15 135); + --pill-source-green: oklch(0.75 0.13 160); + --pill-source-teal: oklch(0.76 0.11 180); + + --pill-media-ebook: oklch(0.72 0.14 245); + --pill-media-audiobook: oklch(0.72 0.17 300); + --pill-media-comic: oklch(0.72 0.16 25); + + --pill-format-default: oklch(0.75 0.02 260); + --pill-format-epub: oklch(0.75 0.13 150); + --pill-format-kepub: oklch(0.75 0.11 178); + --pill-format-mobi: oklch(0.72 0.16 275); + --pill-format-azw3: oklch(0.76 0.11 185); + --pill-format-azw: oklch(0.72 0.13 235); + --pill-format-fb2: oklch(0.73 0.15 355); + --pill-format-djvu: oklch(0.75 0.04 255); + --pill-format-txt: oklch(0.76 0.03 60); + --pill-format-pdf: oklch(0.72 0.15 25); + --pill-format-cbz: oklch(0.72 0.14 260); + --pill-format-cbr: oklch(0.76 0.13 55); + --pill-format-cb7: oklch(0.72 0.17 300); + --pill-format-cbx: oklch(0.73 0.15 325); + --pill-format-m4b: oklch(0.79 0.13 75); + --pill-format-m4a: oklch(0.8 0.13 95); + --pill-format-mp3: oklch(0.78 0.15 145); + --pill-format-opus: oklch(0.76 0.11 205); + --pill-format-ogg: oklch(0.78 0.15 130); + --pill-format-flac: oklch(0.75 0.13 165); --diff-del: oklch(0.79 0.16 25); --diff-del-bg: oklch(0.79 0.16 25 / 0.16); diff --git a/client/src/components/AppLayout.vue b/client/src/components/AppLayout.vue index 48924207d..1ecc49ca2 100644 --- a/client/src/components/AppLayout.vue +++ b/client/src/components/AppLayout.vue @@ -14,6 +14,13 @@ const backgroundClass = computed(() => BACKGROUND_OPTIONS.find((b) => b.id === t const BOOK_ROUTE_NAMES = new Set(['book-detail']) +/** + * The request drawer is a child route of the list, so the list has to stay mounted while the URL + * moves under it. Keying on the path would remount it, re-fetch the rows and run the page + * transition every time a drawer opened. + */ +const REQUEST_ROUTE_NAMES = new Set(['book-requests', 'book-request-detail', 'book-request-releases']) + // Grid views are kept alive so scroll position and virtual list state survive // round-trips to the book detail / metadata editor page. const GRID_VIEW_NAMES = [ @@ -32,6 +39,7 @@ const GRID_VIEW_NAMES = [ const viewKey = computed(() => { const name = String(route.name) if (BOOK_ROUTE_NAMES.has(name)) return name + if (REQUEST_ROUTE_NAMES.has(name)) return 'requests' if (name.startsWith('settings-')) return 'settings' if (name.startsWith('tools-')) return 'tools' return route.path diff --git a/client/src/components/AppSidebar.vue b/client/src/components/AppSidebar.vue index 0be5f8376..75a832b94 100644 --- a/client/src/components/AppSidebar.vue +++ b/client/src/components/AppSidebar.vue @@ -1,8 +1,9 @@