Files
bookorbit/server
neon d2aacfae04 fix(server): keep book requests from closing early and refusing input
Three book request defects, found while planning the Android client:

- The sweep that cancels a self-serve request nobody picked a release
  for timed it from createdAt. A self-fulfiller who took on an older
  request somebody else made inherited that clock, so a request taken
  on more than six hours after it was made was cancelled at the next
  sweep and dismissed for the person who asked for it. It is now timed
  from when the request became self-serve, the decidedAt that creating
  it self-serve and taking it on both set.
- The grab routes kept Fastify's 1 MiB body limit while their DTO takes
  a base64 .torrent of up to 4 Mi characters and the service 2 MiB
  decoded, so a .torrent over about 768 KiB was refused with a 413
  before the DTO was read. Both grab routes now declare a body limit
  that fits the DTO.
- Approving with a new library and no folder kept the request's stored
  folder, which belongs to its old library, and was refused with
  SUBMIT_FOLDER_NOT_IN_LIBRARY; nothing the approver could send cleared
  it. Rerouting without a folder now leaves the old one behind.
2026-10-15 04:32:03 +00:00
..

BookOrbit - Server

NestJS 12 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:

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/config named configs (appConfig, dbConfig, authConfig, storageConfig, mailerConfig). Never read process.env directly in services.
  • DTOs use class-validator decorators. Global ValidationPipe rejects unknown fields.
  • Throw NestJS HttpException subclasses (NotFoundException, ForbiddenException, etc.) - never raw Error.
  • All user-owned data is scoped by userId. Service methods check ownership.