Files
bookorbit/server
neon 3a0c61ffbc fix(server): type the email module and fix narration audio serving
Three defects, found while planning the Android client:

- The email module's providers, preferences, templates, recipient
  groups, tests and send requests had no types in packages/types, so
  every client mirrored them from server code. They are typed now, from
  the JSON the routes return and accept, and the services and DTOs
  declare them. No response changes.
- Narration audio paths were decoded twice: the router decodes the
  wildcard, and the route decoded it again, so a name holding a literal
  % resolved to the wrong entry or none. A path is now decoded once and
  matched to the playlist's hrefs exactly, with an exact entry name
  preferred. Each request also read the whole audio entry into memory;
  it is now streamed, read from its offset when stored and inflated and
  skipped when deflated, with the same headers and Range handling, and
  an open range at or past the end is a 416. Both narration audio
  routes skip response compression, which dropped Content-Length.
- A playlist's section starts left out any clip of unknown length.
  They now use the media:duration each overlay declares when a clip's
  own length is unknown. A seq that interleaves par and seq children
  was reordered by the XML parser's grouping; SMIL is now read in
  document order.
2026-10-15 05:43:23 +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.