Skip to main content
Version: 1.1.0

feat-api-docs-swagger-scalar

✨ New Features

API documentation (Scalar + Swagger UI)

Goal: Expose the neo-backend HTTP API as browsable, interactive documentation so developers (and AI agents) can discover routes, read schemas, paste a Bearer token and fire real requests.

Solution: Code-first OpenAPI via @nestjs/swagger (CLI plugin enabled, so DTO schemas and route descriptions are derived from the code/comments). The generated document is served through two UIs over the same spec: Scalar on /docs and Swagger UI on /swagger, with the raw spec on /openapi.json and /openapi.yaml. Bearer auth is declared in the spec and both UIs allow authorizing and calling endpoints.

Features:

  • Scalar API reference on /docs
  • Swagger UI on /swagger (with persisted authorization)
  • Raw OpenAPI spec on /openapi.json (JSON) and /openapi.yaml (YAML, e.g. for Retool/Postman import)
  • Bearer token authentication preselected in both UIs

Implementation Details:

  • @nestjs/swagger CLI plugin (introspectComments) enabled in nest-cli.json.
  • Doc routes excluded from the auth middleware so the documentation is publicly reachable.
  • Worked around a @nestjs/swagger + bson@6 issue: the plugin emits require("bson/bson") for ObjectId-typed properties, a subpath bson@6 no longer exports. A module-load redirect (bson/bson -> bson) in main.ts fixes the crash; the affected DTO properties also get explicit @ApiProperty({ type: String }) for a clean schema.

Files Modified:

  • package.json / package-lock.json - Add @nestjs/swagger and @scalar/nestjs-api-reference
  • nest-cli.json - Enable the @nestjs/swagger CLI plugin (introspectComments)
  • src/main.ts - Build the OpenAPI document, serve Scalar (/docs) and Swagger UI (/swagger), add the bson/bson module-load redirect
  • src/app.module.ts - Exclude doc routes from the auth middleware
  • src/domains/company/attributes.dto.ts - @ApiProperty({ type: String }) on ObjectId fields
  • src/domains/document/document.type.ts - @ApiProperty({ type: String }) on ObjectId field
  • .env.example - Document the PROOF_WATCHER_ENABLED env var

⚡ Improvements

API documentation accuracy & usability

Goal: Make the generated OpenAPI faithfully reflect the real API so the docs are trustworthy and the "Try it out" calls actually work.

Solution: Fix the spots where the @nestjs/swagger plugin silently diverged from the code (file-suffix scanning, TS-only required/optional detection, untyped params) and declare the auth requirement globally so both UIs send the token.

Features:

  • Endpoints with inline DTOs now show their query params / request bodies (were "No parameters").
  • Optional fields are no longer wrongly marked as required.
  • Stats endpoints (/stats/*) now document their level, startDate, endDate, companyId params.
  • Both UIs send the Bearer token on every request (real calls succeed instead of 401).

Implementation Details:

  • Extend the plugin dtoFileNameSuffix to .controller.ts / .type.ts so DTOs declared inline are annotated.
  • Add a global Bearer security requirement (addSecurityRequirements) — the scheme was declared but never applied, so the UIs sent no Authorization header.
  • Mark @IsOptional() fields optional in TS (?) or via @ApiPropertyOptional() (the plugin derives required from TS optionality and ignores class-validator).
  • Type previously untyped @Query('x') / @Param('x') params (the plugin skips implicit any).
  • Normalize the shared ObjectId schema to a 24-hex string instead of an empty object.
  • Drop the hardcoded addServer('/api/v2') that double-prefixed requests (/api/v2/api/v2/...).
  • Add npm run openapi:generate (preview mode, DB-less) to dump the spec for inspection / static export.

Files Modified:

  • nest-cli.json - Extend dtoFileNameSuffix to scan controllers/types
  • src/main.ts - Global Bearer requirement, normalizeObjectIdSchema, remove addServer
  • src/generate-openapi.ts - Offline OpenAPI generator (preview mode)
  • package.json - openapi:generate script
  • .gitignore - Ignore openapi.generated.json
  • src/domains/document/document.controller.ts - Optional DocumentsSearchDto fields, typed badge param
  • src/domains/stat/stat.controller.ts - Typed @Query/@Param (level: StatLevel, dates/companyId)
  • src/domains/company/attributes.dto.ts - Optional fields (? / @ApiPropertyOptional), lov: string[]
  • README.md - Document the docs setup, annotation gotchas and the SCIM exclusion TODO

Documented response models & error envelope

Goal: Turn the spec into a real contract — every operation exposes its error shape, and the flagship document endpoints expose typed 2xx responses (were an empty 200).

Solution: A shared ErrorResponse DTO and an @ApiStandardErrors() decorator that mirror GlobalExceptionFilter, applied across the documented controllers, plus decorated response classes for the documents domain.

Features:

  • Every documented route now declares 400/401/403/404/500 with the real ErrorResponse envelope (statusCode, message, error, correlationId?, timestamp, path).
  • POST /documents, GET /documents, /documents/filters and /documents/from-id expose typed response schemas instead of an empty body.
  • GET /documents query params folder, attributes and autoAttributes are now typed in the spec.
  • Object-array DTO fields (targetAutoAttributes) now expose {code, value} item schemas instead of being wrongly inferred as string[] — strict clients (Scalar MCP) accept the correct payload.

Files Modified:

  • src/shared/openapi/error-response.dto.ts - Shared ErrorResponse schema (new)
  • src/shared/openapi/api-standard-errors.decorator.ts - @ApiStandardErrors() decorator (new)
  • src/domains/document/document.responses.ts - Decorated response models for the documents domain (new)
  • src/domains/document/document.controller.ts - @ApiOkResponse/@ApiCreatedResponse + typed folder/attributes/autoAttributes query params
  • src/domains/{company,stat,export,client-error}/...controller.ts and src/domains/platform/*/*.controller.ts - @ApiStandardErrors() on the documented surface

Absolute OpenAPI server for off-origin consumers (Scalar MCP)

Goal: Let off-origin tools (Scalar's hosted MCP) actually call the API. They fail with "Server base URL is not configured" because the spec exposes no servers and the global prefix (/api/v2) is not part of the paths — a browser infers the base from the page origin, but an off-origin MCP cannot.

Solution: When API_PUBLIC_URL is set, expose it as the absolute servers entry (the bare origin — runtime paths already carry the /api/v2 global prefix, so appending it would double it). Applied both at runtime and in the offline generator (which now mirrors the global prefix). Left empty locally; the doc UIs keep working unchanged.

Files Modified:

  • src/core/config/app.config.ts - Add optional publicUrl (API_PUBLIC_URL)
  • src/main.ts - addServer(<API_PUBLIC_URL>/api/v2) when configured
  • src/generate-openapi.ts - Same server entry in the offline spec
  • .env.example - Document API_PUBLIC_URL

🔒 Security

Fix dependency vulnerabilities

Goal: Clear all high- and medium-severity Snyk findings so the security scan passes in CI.

Solution: Upgrade direct dependencies and pin patched transitive versions via overrides.

Files Modified:

  • package.json / package-lock.json - Upgrade axios (SSRF / prototype pollution / sensitive data), json-2-csv (CSV injection), joi (uncaught exception), qs (null pointer dereference); override multer@2.2.0 (uncontrolled recursion), ws@8.21.0 (resource amplification), brace-expansion@5.0.6 under minimatch@9/@10 (ReDoS), form-data@4.0.6 (CRLF injection) and js-yaml@4.2.0 (algorithmic complexity)