133 lines
5.6 KiB
TypeScript
133 lines
5.6 KiB
TypeScript
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('*');
|
|
}
|
|
}
|
|
}
|