* feat(settings): add SABnzbd download client Support API-key authentication, NZB uploads, queue monitoring, reconciliation, and completed-download imports through SABnzbd. Expose the client in request settings and validate its required category and credentials. Ref #1312 * feat(settings): add Prowlarr indexer management Manage synchronized torrent and Usenet sources through a shared Prowlarr connection. Ref #1312 * fix(settings): correct SABnzbd adoption and managed search retries Address four defects found in review of the Prowlarr and SABnzbd work. SABnzbd keeps a failed job in its history until somebody clears it, and BookOrbit removes a client download only on an explicit action. Because the client key is a stable digest, re-grabbing the same release adopted that dead history entry, never uploaded the NZB, and failed instantly and forever. Adopt only an entry that is not already failed; a queue entry still wins, so a fresh upload reports correctly. The category-expansion retry reused the title-and-author query, making it strictly narrower on text than the title-only attempt that had just returned nothing. A release matching only a title-only search with expanded categories was therefore unreachable. Drop the author there so each rung of the ladder is genuinely broader than the last. Changing the download client type through the create form's select bypassed the picker's handler, so a username typed for qBittorrent was still sent when SABnzbd was chosen. The server stores it, the API-key form hides it, and it can no longer be edited away. Watch the credential kind instead, so both controls clear it. Finally, a swallowed managed-release refresh failure left nothing behind at all; give it its own fail log while still reporting the original refusal to the caller. * fix(requests): cast managed search health flags to boolean The bulk search-health update bound each case branch as an untyped parameter, so Postgres could not resolve it against the boolean column. Cast every branch explicitly and assert the cast in the repository test. * fix(requests): close review gaps in Prowlarr and SABnzbd support Inventory the eight indexer-manager routes so the authorization matrix probes them, resolve a managed indexer without reading its manager's whole source list, keep an unreadable credential out of the test-failed mapping, translate manager error codes, and show per-source search health. Documents Prowlarr connections in docs/BOOK_REQUESTS.md. * i18n(client): keep the retired sources empty-state key Dropping bodyOneWay from en.json forced pruning it from five Crowdin target catalogs, which the localization policy check rejects outright. Leave the now-unused key in place; nothing validates unreferenced English source keys, so Crowdin can retire it on its own schedule. * db: regenerate the SABnzbd and Prowlarr migration onto main Rebasing collided with main's own 0091 and 0092, which left this branch's two migrations on disk with no journal entry referencing them. Drop both and regenerate a single migration on main's lineage; the three affected tables are schema-identical to the pre-rebase snapshot.
BookOrbit - Server
NestJS 11 backend running on Fastify.
Running
From the repo root: pnpm dev (starts both server and client).
Server only: pnpm start:dev (from this directory).
The server loads environment variables from .env in this directory. See .env.example for all available options.
API docs
OpenAPI docs are disabled by default. Enable them with SWAGGER_ENABLED=true:
SWAGGER_ENABLED=true pnpm start:dev
Then open:
- Swagger UI: http://localhost:3000/api/docs
- OpenAPI JSON: http://localhost:3000/api/docs-json
Dev watch uses a Nest config without the Swagger metadata generator so the
server does not restart immediately after startup. Full build output still uses
the standard Nest config and generates src/metadata.ts for enriched docs.
Module structure
Each feature is a self-contained NestJS module under src/modules/:
src/modules/
├── auth/ Authentication (JWT, OIDC, password reset)
├── book/ Book CRUD, cover images, file serving
├── library/ Library management
├── scanner/ File system scanning, metadata extraction
├── collection/ User book collections
├── smart-scope/ Saved search/filter views
├── user/ User management
├── role/ Roles and permissions (RBAC)
├── metadata/ Book metadata providers
├── kobo/ Kobo device sync
├── annotation/ Highlights and annotations
├── bookmark/ Reading position bookmarks
├── reader-preferences/ Per-user reader settings
├── cbz/ CBZ archive handling
├── path/ File path utilities
├── seed/ Database seeding
└── app-settings/ Global application settings
Database
Schema files live in src/db/schema/, one per domain. Migrations are generated by Drizzle Kit.
pnpm db:generate add_some_column # generate migration from schema diff
pnpm db:migrate # apply pending migrations
pnpm db:studio # open Drizzle Studio (DB browser)
Testing
pnpm test # unit tests
pnpm test:watch # unit tests in watch mode
pnpm e2e:run # app-smoke end-to-end test from server package
pnpm test:cov # unit tests with coverage
For suite-based e2e runs, run from the repository root:
pnpm run e2e:list
pnpm run e2e:all
pnpm run e2e:run -- scanner-scenarios
Quality
pnpm type-check # TypeScript checks (no emit)
pnpm lint:check # Non-mutating lint checks
pnpm lint:fix # Auto-fix lint issues
pnpm format:check # Prettier check
pnpm format # Prettier write
Key conventions
- Constructor injection with
@Inject(DB)for the Drizzle instance. - Config via
@nestjs/confignamed configs (appConfig,dbConfig,authConfig,storageConfig,mailerConfig). Never readprocess.envdirectly in services. - DTOs use
class-validatordecorators. GlobalValidationPiperejects unknown fields. - Throw NestJS
HttpExceptionsubclasses (NotFoundException,ForbiddenException, etc.) - never rawError. - All user-owned data is scoped by
userId. Service methods check ownership.