File size: 2,902 Bytes
57a889c
 
46a0a8a
57a889c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46a0a8a
 
 
 
 
 
 
 
 
57a889c
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import type { INestApplication } from '@nestjs/common';
import { AppModule } from './nest/app.module';
import { applyGlobalMiddleware } from './middleware/globalMiddleware';
import { applyPlatformUploads, applyPlatformTransport, applyPlatformStatic } from './nest/platform/platform.routes';

/**
 * Builds the unified TREK NestJS application that serves the ENTIRE surface β€” the
 * former Express app is gone. One builder is shared by the production bootstrap
 * (index.ts) and the integration-test harness so the two can never drift.
 *
 * Composition order is load-bearing. Everything except the SPA index.html fallback
 * is registered on the underlying Express instance BEFORE `app.init()`, because
 * Nest's router terminates an unmatched request by throwing NotFoundException β€” it
 * does NOT fall through to a route registered after init, so a post-init Express
 * route is unreachable. The platform routes are all specific paths (/uploads/*,
 * /api/health, /mcp, /.well-known/*, /oauth/{authorize,register,consent}) so they
 * match their own requests and `next()` everything else through to the Nest
 * controllers registered during init.
 *
 *   1. applyGlobalMiddleware β€” helmet/CSP, CORS, HSTS, forced-HTTPS, the global MFA
 *      policy, request logging + cookie-parser. `bodyParser: false` so Nest does its
 *      own parsing and the raw /mcp body reaches the MCP handler unparsed.
 *   2. applyPlatformUploads β€” the static + guarded /uploads/* routes.
 *   3. applyPlatformTransport β€” /api/health, the OAuth/MCP SDK + /.well-known
 *      metadata, the /mcp routes, the /oauth/consent COOP header.
 *   4. applyPlatformStatic β€” the production built-client static assets (so a real
 *      asset request returns the file before the Nest router 404s it).
 *   5. app.init() β€” registers every migrated /api domain (the Nest controllers).
 *
 * The SPA index.html fallback (unmatched GET β†’ index.html in production) is the
 * SpaFallbackFilter (APP_FILTER in AppModule); the global error envelope is the
 * TrekExceptionFilter (also APP_FILTER).
 */
export async function buildApp(): Promise<INestApplication> {
  const app = await NestFactory.create(AppModule, new ExpressAdapter());
  const instance = app.getHttpAdapter().getInstance();
  applyGlobalMiddleware(instance, { bodyParser: false });
  applyPlatformUploads(instance);
  applyPlatformTransport(instance);
  applyPlatformStatic(instance);

  const config = new DocumentBuilder()
    .setTitle('TREK API')
    .setDescription('The TREK API documentation')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api-docs', app, document);

  await app.init();
  return app;
}