augmenttoolkit / server /src /nest /README.md
Leon4gr45's picture
Upload folder using huggingface_hub (part 5)
57a889c verified
|
Raw
History Blame Contribute Delete
4.49 kB
# NestJS migration layer β€” module & test guide
This folder holds the co-hosted NestJS app that incrementally strangles the legacy
Express API (see the "Brownfield Rewrite" board). Until a prefix is migrated, the
top-level dispatcher in `src/index.ts` routes it to the legacy app; migrated
prefixes go to Nest. **Weather (`weather/`) is the reference implementation** β€” copy
its shape when migrating a new domain.
## Module layout (per domain)
```
shared/src/<domain>/<domain>.schema.ts(.spec.ts) # Zod contract β€” single source of truth
server/src/nest/<domain>/<domain>.service.ts # business logic (ported 1:1 from the Express service)
server/src/nest/<domain>/<domain>.controller.ts # same routes/verbs/params/status codes as Express
server/src/nest/<domain>/<domain>.module.ts # registered in app.module.ts
```
Add the prefix to `DEFAULT_NEST_PREFIXES` in `strangler.ts` to route it to Nest
(operators can override at runtime via the `NEST_PREFIXES` env var β€” instant
rollback, no redeploy). Trip-scoped mounts use a pattern prefix with a `:param`
segment (e.g. `/api/trips/:tripId/packing`); the matcher routes only that nested
mount to Nest and leaves the sibling trip routes (days, places, ...) on Express.
## Migrated so far
- **Phase 1 (leaf):** weather, airports, config (public), system-notices, maps,
categories, tags, notifications, atlas.
- **Phase 2 (trip sub-domains):** vacay (addon), packing, todo.
## Cross-cutting Foundation pieces
- `common/idempotency.interceptor.ts` β€” global `APP_INTERCEPTOR` replaying the
client's `X-Idempotency-Key` on mutations, mirroring the legacy
`applyIdempotency` middleware so retried writes don't double-apply.
- `strangler.ts` β€” supports both static prefixes and `:param` pattern prefixes.
## Parity gotchas worth remembering
- A POST that answers with `res.json` in Express stays **200**; add `@HttpCode(200)`
(Nest defaults POST to 201). Creates that Express sends as 201 need nothing.
- Static sub-routes that collide with a `:id` param (e.g. `/in-app/all` vs
`/in-app/:id`, `/reorder` vs `/:id`) must be declared **before** the param route.
- Reproduce bespoke admin/error wording exactly β€” e.g. notifications' `test-smtp`
returns `{ error: 'Admin only' }`, not the AdminGuard's `Admin access required`.
- Trip-scoped routes verify trip access (404) and the relevant permission (403)
per handler and forward `X-Socket-Id` to the WebSocket broadcast.
## Parity is law
A migrated route must be **byte-identical** for the client: same URL, method,
query/body, HTTP status, `Set-Cookie`, and JSON body β€” including bespoke error
strings. Where the legacy route returns a hand-written error (e.g. weather's
`{ error: 'Latitude and longitude are required' }`), reproduce that exact body in
the controller rather than relying on the generic `ZodValidationPipe` envelope.
## How to write the tests
Every module ships three kinds of tests; the coverage gate (`vitest.config.ts`,
scoped to `src/nest/**`) requires β‰₯80%.
1. **Service / controller unit spec** β€” `tests/unit/nest/<domain>.controller.test.ts`.
Instantiate the controller with a mocked service; assert status codes, the exact
`{ error }` bodies, and that inputs are forwarded correctly (defaults, coercion).
See `weather.controller.test.ts`.
2. **Parity test** β€” `tests/parity/<domain>.parity.test.ts`. Mock the shared service
identically for both apps, then fire the same request at the Express route and the
Nest controller with the `expectParity()` harness (`tests/parity/parity.ts`) and
assert identical status + body. This is the gate before flipping the toggle.
See `weather.parity.test.ts`.
3. **e2e** β€” `tests/e2e/<domain>.e2e.test.ts`. Boot the Nest module against a temp
in-memory SQLite db via the shared harness (`tests/e2e/harness.ts`:
`createTempDb`/`seedUser`/`sessionCookie`), exercising the **real** `JwtAuthGuard`
end-to-end (401 without cookie, 200 with a signed session). Mock external I/O
(HTTP/etc.). See `weather.e2e.test.ts`.
## Definition of Done (per module)
Contract in `@trek/shared` β†’ service ported 1:1 β†’ controller with identical routes β†’
validation/error parity β†’ unit + parity + e2e tests over the gate β†’ prefix toggled to
Nest β†’ parity verified on the demo DB β†’ **then** decommission the old Express
route/service (separate step, after the toggle is confirmed in prod) β†’ frontend points
at the typed contract (Frontend Track).