Files
bookorbit/server
neon 1e5fcedb2a fix(koreader): stop a rebuilt library silently dropping reading history
Deleting a library cascades away its books, files, page stats and reading sessions, and a rescan
mints new rows. The device's sync state is keyed by the file's partial md5, so an unchanged file
kept its cursors across that swap: the plugin asked only for events newer than a watermark earned
against rows the server no longer had, and nothing recorded before the swap was ever resent.
"Recheck all book matches" did not help, because it funnels back through setMatched.

Treat a changed book file id or book id as a fresh sync target and keep only the local file path.
Both ids matter: statistics hang off the file row while highlights, status, rating and review hang
off the book row, and an existing-folder merge re-parents a file to another book without changing
its id. Resending costs nothing because the page-stat dedup key makes a replay a server-side no-op.

Order both hash resolvers by book file id so content that exists as two files always resolves to
the same row. They previously took whichever row Postgres returned first, so they could disagree
with each other and, with the reset above, move a device's sync target under it.

Closes #1049
2026-08-21 16:38:51 +00:00
..

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:

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.