Files
bookorbit/server
neon 54cb7dbda0 fix(auth): recover live sessions when an access token expires
A tab that slept past the 15 minute access-token lifetime woke up holding a token every consumer
then handed to the server independently. socket.io reconnected all seven namespaces with it, each
gateway rejected the handshake and closed the socket, and because a server-initiated close reports
`io server disconnect` the client never reconnects from it, so live updates stayed dead until the
page was reloaded. The dashboard widgets fetch once on mount with no retry, so whatever failed in
that window sat on "Failed to load" for the life of the page.

Make a valid token the thing consumers ask for rather than something they hope is still good.
Decode `exp` on the client and renew inside a 30 second skew before the token is used, so websocket
handshakes, uploads and `api()` requests no longer discover the expiry one 401 at a time. Route
every authenticated namespace through a shared factory that resolves the token per handshake and,
on an auth rejection, refreshes and reconnects with bounded exponential backoff.

Let the server say which rejections are worth retrying. Gateways now answer an authentication
failure with an `unauthorized` event before closing, and stay silent on a permission denial, which
a fresh token would not fix. Classifying that needed the gateways to stop throwing bare `Error` for
both cases, so the handshake paths now throw `UnauthorizedException` or `ForbiddenException` as the
project's conventions already require, and jsonwebtoken's own `TokenExpiredError` counts as an
authentication failure since it is the single most common way a slept-through handshake fails.

Collapse the twelve identical widget composables onto one loader that reloads a failed widget once
a rejected request is followed by a successful refresh.

Closes #942
2026-08-09 03:24:24 +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.