import { type MiddlewareConsumer, Module, type NestModule, RequestMethod } from '@nestjs/common'; import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { ScheduleModule } from '@nestjs/schedule'; import { ThrottlerModule } from '@nestjs/throttler'; import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; import { AdminModule } from '@modules/admin'; import { AgentsModule } from '@modules/agents'; import { AnalyticsModule } from '@modules/analytics'; import { AuthModule } from '@modules/auth'; import { HealthModule } from '@modules/health'; import { InquiriesModule } from '@modules/inquiries'; import { LeadsModule } from '@modules/leads'; import { ListingsModule } from '@modules/listings'; import { McpIntegrationModule } from '@modules/mcp'; import { HttpMetricsInterceptor, MetricsModule } from '@modules/metrics'; import { NotificationsModule } from '@modules/notifications'; import { PaymentsModule } from '@modules/payments'; import { ReviewsModule } from '@modules/reviews'; import { SearchModule } from '@modules/search'; import { SharedModule } from '@modules/shared'; import { ThrottlerBehindProxyGuard } from '@modules/shared/infrastructure/guards/throttler-behind-proxy.guard'; import { CsrfMiddleware } from '@modules/shared/infrastructure/middleware/csrf.middleware'; import { SanitizeInputMiddleware } from '@modules/shared/infrastructure/middleware/sanitize-input.middleware'; import { SubscriptionsModule } from '@modules/subscriptions'; import { AppController } from './app.controller'; @Module({ imports: [ SentryModule.forRoot(), CqrsModule.forRoot(), ScheduleModule.forRoot(), SharedModule, HealthModule, AuthModule, AgentsModule, InquiriesModule, LeadsModule, ListingsModule, ReviewsModule, SearchModule, NotificationsModule, PaymentsModule, SubscriptionsModule, AdminModule, AnalyticsModule, MetricsModule, McpIntegrationModule, // ── Rate Limiting ── // Default: 60 requests per 60 seconds per IP // Override per-route with @Throttle() decorator ThrottlerModule.forRoot({ throttlers: [ { name: 'default', ttl: 60_000, limit: process.env['NODE_ENV'] === 'test' ? 10_000 : 60, }, { name: 'auth', ttl: 60_000, limit: process.env['NODE_ENV'] === 'test' ? 10_000 : 10, }, { name: 'payment-callback', ttl: 60_000, limit: process.env['NODE_ENV'] === 'test' ? 10_000 : 20, }, ], }), ], controllers: [AppController], providers: [ { provide: APP_FILTER, useClass: SentryGlobalFilter, }, { provide: APP_GUARD, useClass: ThrottlerBehindProxyGuard, }, { provide: APP_INTERCEPTOR, useClass: HttpMetricsInterceptor, }, ], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer): void { // Sanitize all incoming request strings to prevent stored XSS consumer .apply(SanitizeInputMiddleware) .forRoutes('*'); // CSRF double-submit cookie (sets on GET, validates on state-changing methods) // Exclude health endpoints — they must remain accessible without cookies // Skip entirely in test mode so E2E / API tests can POST without a CSRF cookie if (process.env['NODE_ENV'] !== 'test') { consumer .apply(CsrfMiddleware) .exclude( { path: 'health', method: RequestMethod.GET }, { path: 'health/(.*)', method: RequestMethod.GET }, ) .forRoutes('*'); } } }