import { Global, type MiddlewareConsumer, Module, type NestModule, RequestMethod } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { PrometheusModule, makeCounterProvider } from '@willsoto/nestjs-prometheus'; import { CacheService, CACHE_HIT_TOTAL, CACHE_MISS_TOTAL, CACHE_DEGRADATION_TOTAL, } from './infrastructure/cache.service'; // RFC-004 Phase 0 (GOO-172) — durable async messaging backbone is parked // while the `@goodgo/contracts-events` workspace package is wired up. // `event-bus/` and `outbox/` subdirectories are excluded from typecheck via // apps/api/tsconfig.json. Re-enable the imports + providers below once the // missing types land. // import { EVENT_BUS, RedisStreamsEventBus } from './infrastructure/event-bus'; import { EventBusService } from './infrastructure/event-bus.service'; import { FieldEncryptionService } from './infrastructure/field-encryption.service'; import { GlobalExceptionFilter } from './infrastructure/filters/global-exception.filter'; import { GeoLookupService } from './infrastructure/geo-lookup.service'; import { DeprecationInterceptor, VersionInterceptor } from './infrastructure/interceptors'; import { LoggerService } from './infrastructure/logger.service'; import { CorrelationIdMiddleware } from './infrastructure/middleware/correlation-id.middleware'; import { CsrfMiddleware } from './infrastructure/middleware/csrf.middleware'; import { RequestLoggingMiddleware } from './infrastructure/middleware/request-logging.middleware'; import { SanitizeInputMiddleware } from './infrastructure/middleware/sanitize-input.middleware'; // import { OutboxRelay, OutboxService } from './infrastructure/outbox'; import { PrismaService } from './infrastructure/prisma.service'; import { RedisService } from './infrastructure/redis.service'; import { TypesenseClientService } from './infrastructure/typesense-client.service'; @Global() @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), EventEmitterModule.forRoot(), PrometheusModule.register({ path: '/metrics', defaultMetrics: { enabled: true } }), ], providers: [ LoggerService, FieldEncryptionService, PrismaService, RedisService, CacheService, EventBusService, GeoLookupService, // RFC-004 Phase 0 (GOO-172) — see import comment above. // { provide: EVENT_BUS, useClass: RedisStreamsEventBus }, // OutboxService, // OutboxRelay, TypesenseClientService, makeCounterProvider({ name: CACHE_HIT_TOTAL, help: 'Total number of cache hits', labelNames: ['resource'], }), makeCounterProvider({ name: CACHE_MISS_TOTAL, help: 'Total number of cache misses', labelNames: ['resource'], }), makeCounterProvider({ name: CACHE_DEGRADATION_TOTAL, help: 'Total number of cache degradation events', labelNames: ['resource', 'operation'], }), { provide: APP_FILTER, useClass: GlobalExceptionFilter, }, // RFC-001 Phase 1 (GOO-170) — order matters: VersionInterceptor first // populates req.apiVersion; DeprecationInterceptor reads from it. { provide: APP_INTERCEPTOR, useClass: VersionInterceptor, }, { provide: APP_INTERCEPTOR, useClass: DeprecationInterceptor, }, ], exports: [ PrismaService, RedisService, CacheService, LoggerService, EventBusService, FieldEncryptionService, GeoLookupService, TypesenseClientService, PrometheusModule, ], }) export class SharedModule implements NestModule { configure(consumer: MiddlewareConsumer): void { consumer .apply(CorrelationIdMiddleware, SanitizeInputMiddleware, RequestLoggingMiddleware) .forRoutes('*'); if (process.env['NODE_ENV'] !== 'test') { consumer .apply(CsrfMiddleware) .exclude( // NOTE: Nest 11 + path-to-regexp v8 matches `forRoutes('*')` // middleware exclude paths against the FULL request URL — i.e. // including the global prefix `api/v1`. Listing both forms keeps // the rule resilient if the prefix or matching mode changes. { path: 'api/v1/payments/callback/*path', method: RequestMethod.POST }, { path: 'api/v1/auth/login', method: RequestMethod.POST }, { path: 'api/v1/auth/register', method: RequestMethod.POST }, { path: 'api/v1/auth/refresh', method: RequestMethod.POST }, { path: 'api/v1/auth/exchange-token', method: RequestMethod.POST }, { path: 'api/v1/auth/logout', method: RequestMethod.POST }, { path: 'api/v1/auth/forgot-password', method: RequestMethod.POST }, { path: 'api/v1/auth/reset-password', method: RequestMethod.POST }, { path: 'api/v1/web-vitals', method: RequestMethod.POST }, // sendBeacon cannot send CSRF headers // Legacy controller-relative forms (kept for older path-matching modes). { path: 'auth/login', method: RequestMethod.POST }, { path: 'auth/register', method: RequestMethod.POST }, { path: 'auth/refresh', method: RequestMethod.POST }, { path: 'auth/exchange-token', method: RequestMethod.POST }, { path: 'auth/logout', method: RequestMethod.POST }, { path: 'auth/forgot-password', method: RequestMethod.POST }, { path: 'auth/reset-password', method: RequestMethod.POST }, { path: 'web-vitals', method: RequestMethod.POST }, { path: 'payments/callback/*path', method: RequestMethod.POST }, ) .forRoutes('*'); } } }