* fix(metadata): extract and write back comicvine id for comics ComicVine references were never read from a comic's embedded ComicInfo.xml on import, even though every other provider id (Goodreads, Amazon, Hardcover, Google Books, OpenLibrary, Kobo) already round-trips through the same <Web>/Notes fields. This affected Book Dock import, direct upload, and library scanning alike, since they share the same extractor. Write-back had the same gap: a comicvineId resolved via search/auto-fill was never persisted back into the file. Closes #840 * fix(metadata): keep comicvine id off the contested Web slot and source-check notes ComicInfo has a single Web element and buildComicInfoXml writes the first provider id in COMIC_INFO_PROVIDER_ID_KEYS order into it. The other six providers are also in COMIC_INFO_MANAGED_NOTES_KEYS, so losing that slot costs them nothing; comicvineId had no second channel, so a write-back for a book that also carried a goodreads/amazon/hardcover/google/openlibrary/kobo id dropped the id from the file entirely and clobbered any ComicVine URL that was already there. Add comicvineId to the managed notes keys, which also makes the existing managed-note read precedence reachable from our own writes. ComicTagger emits "[Issue ID N]" for whichever talker produced the tag, naming the source separately as "using info from <source>". Matching the bare marker attributed Metron and other non-ComicVine issue ids to ComicVine and wrote them back out as fabricated comicvine.gamespot.com URLs. Require the Comic Vine source in the same sentence. Also anchor the Web URL scan to a path segment so a segment ending in 4000-, such as "14000-777", is no longer read as an issue id. --------- Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
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.