Finish the NestJS migration — drop the legacy Express app

NestJS now serves the whole surface: every /api domain plus the platform
routes (uploads, /mcp, the OAuth/MCP SDK + /.well-known metadata and the
production SPA fallback). Removed server/src/app.ts, all of
server/src/routes/* and the strangler dispatcher; index.ts and the
integration suite share a single buildApp() bootstrap so prod and tests
can't drift.

- Platform/transport routes extracted to nest/platform/platform.routes.ts
  and mounted before app.init() — Nest's router answers an unmatched
  request with a 404, so a route registered after init is never reached.
  The SPA fallback is a NotFoundException filter and the catch-all uses a
  RegExp (Express 5's path-to-regexp rejects a bare '*').
- New modules: memories (/api/integrations/memories — the Journey
  gallery's Immich/Synology proxy), addons (GET /api/addons) and the
  cross-trip GET /api/reservations/upcoming.
- TrekExceptionFilter reproduces the old multer / err.statusCode handling
  so upload rejections keep their 400/413 { error } body and non-ASCII
  filenames survive (defParamCharset).
- addTripToJourney and the MCP get_journey_share_link tool gained the
  trip-access check they were missing.
- Re-pointed the 34 integration tests + the websocket test onto the Nest
  app; removed the now-meaningless Express-vs-Nest parity tests and a few
  orphaned client components.
This commit is contained in:
Maurice
2026-05-31 13:29:22 +02:00
parent fc7d8b5d12
commit bfe52579df
138 changed files with 2289 additions and 12666 deletions
+19
View File
@@ -17,8 +17,11 @@
*/
import Database from 'better-sqlite3';
import type { INestApplication } from '@nestjs/common';
import { createTables } from '../../src/db/schema';
import { runMigrations } from '../../src/db/migrations';
import { AuthPublicController } from '../../src/nest/auth/auth-public.controller';
import type { RateLimitService } from '../../src/nest/auth/rate-limit.service';
// Tables to clear on reset, child-before-parent to be safe (FK checks are OFF during reset).
// Keep in sync with schema.ts + migrations.ts. Intentionally excluded: categories, addons,
@@ -238,6 +241,22 @@ export function buildDbMock(testDb: Database.Database) {
};
}
/**
* Resets the Nest per-IP rate-limit buckets between tests — the buildApp() drop-in
* for the legacy `loginAttempts.clear(); mfaAttempts.clear()`.
*
* The Nest auth path keeps its rate-limit state in a RateLimitService instance that
* lives inside the AuthModule injector (shared by AuthPublicController/AuthController
* for the login/mfa/forgot buckets). The same class is ALSO provided separately in
* OauthModule (its own instance, distinct oauth_* buckets), so a plain
* app.get(RateLimitService) is ambiguous and may hand back the wrong instance — we
* resolve the auth controller and clear the limiter it actually uses.
*/
export function resetRateLimits(app: INestApplication): void {
const ctrl = app.get(AuthPublicController, { strict: false }) as unknown as { rl: RateLimitService };
ctrl.rl.reset();
}
/** Fixed config mock — use with vi.mock('../../src/config', () => TEST_CONFIG) */
export const TEST_CONFIG = {
JWT_SECRET: 'test-jwt-secret-for-trek-testing-only',