import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; // 도메인 일정 모듈(modules/schedule)과 이름이 겹쳐 크론 모듈을 별칭으로 부른다 import { ScheduleModule as CronScheduleModule } from '@nestjs/schedule'; import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; import { CacheModule } from '@nestjs/cache-manager'; import { APP_GUARD } from '@nestjs/core'; import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard'; import { RequestIdMiddleware } from '@/common/middleware/request-id.middleware'; import { AuthModule } from '@/modules/auth/auth.module'; import { DiaryModule } from '@/modules/diary/diary.module'; import { HealthModule } from '@/modules/health/health.module'; import { PetModule } from '@/modules/pet/pet.module'; import { PetCardModule } from '@/modules/pet-card/pet-card.module'; import { ScheduleModule } from '@/modules/schedule/schedule.module'; import { UploadModule } from '@/modules/upload/upload.module'; import { UserModule } from '@/modules/user/user.module'; import { parsePositiveInt } from '@/shared/utils/env'; import { buildDatabaseOptions } from '@/shared/config/database.config'; import { validateEnv } from '@/shared/config/env.validation'; import { ShutdownLogger } from '@/shared/logger/shutdown.logger'; // Rate Limiting 기본 정책 — 루트 .env 의 THROTTLE_* 로 덮어쓸 수 있다 (.claude/rules/env-structure.md) const THROTTLE_DEFAULT_TTL_MS = 60_000; // 집계 구간: 60초 const THROTTLE_DEFAULT_LIMIT = 100; // 구간당 IP별 최대 요청 수 @Module({ imports: [ CronScheduleModule.forRoot(), ConfigModule.forRoot({ isGlobal: true, envFilePath: `.env.${process.env.NODE_ENV || 'development'}`, // 설정이 잘못되면 조용히 기본값으로 굴러가지 않고 기동 시점에 실패시킨다 validate: validateEnv, }), // 전역 캐시 매니저 — Redis 도입 시 store 옵션을 redisStore 로 변경 CacheModule.register({ isGlobal: true, ttl: 60_000, // 60초 기본 TTL }), // 전역 Rate Limiter — API 남용/브루트포스 방지 (프로젝트의 유일한 rate limiting 수단) // 한도 초과 시 429 → 전역 필터가 REQ_001 로 매핑한다. // 라우트 단위 조정은 @Throttle({ default: { ttl, limit } }), 제외는 @SkipThrottle(). ThrottlerModule.forRoot([ { // compose 가 미정의 변수를 빈 문자열로 주입하므로 parsePositiveInt 로 방어한다 ttl: parsePositiveInt( process.env.THROTTLE_TTL_MS, THROTTLE_DEFAULT_TTL_MS, ), limit: parsePositiveInt( process.env.THROTTLE_LIMIT, THROTTLE_DEFAULT_LIMIT, ), }, ]), // DB 접속 설정은 database.config.ts 하나만 수정한다 (CLI 와 공유) TypeOrmModule.forRootAsync({ useFactory: () => ({ ...buildDatabaseOptions(), // Nest 모듈에 등록된 엔티티를 자동으로 수집한다 (CLI 는 glob 경로를 사용) autoLoadEntities: true, }), }), HealthModule, UploadModule, UserModule, AuthModule, PetModule, ScheduleModule, DiaryModule, PetCardModule, ], providers: [ ShutdownLogger, { provide: APP_GUARD, useClass: ThrottlerGuard, }, // 전역 인증 — 모든 라우트가 기본으로 로그인을 요구한다. 예외는 @Public() 으로 연다. // (rate limit 이 먼저 돌아 토큰 없는 대량 요청도 한도에 걸린다) { provide: APP_GUARD, useClass: JwtAuthGuard, }, ], }) export class AppModule implements NestModule { // 모든 요청에 추적 ID 를 부여한다 (로그·에러 응답 traceId 와 동일 값) configure(consumer: MiddlewareConsumer): void { consumer.apply(RequestIdMiddleware).forRoutes('*'); } }