generated from ttipo/project-base
02c7ec0079
브랜치 리뷰(main..14e2bdd)에서 나온 서버 쪽 문제를 고친다. - 일정 연동 코드는 한 번 연동에 쓰이면(코드 주인), 나가면(나간 사람) 바꾼다 — 예전에 받은 코드로 다시 들어올 수 없다 - 코드 조회·참여 7개 엔드포인트가 회원 단위 한 바구니(분당 30회)를 같이 쓴다 (@InviteCodeLookup, 인증 뒤에 세는 가드) - 펫 명함 상세히 공유는 마지막으로 공유한 뒤 7일만 — 그 뒤에는 간략히로 보여 주고 저장한다 - 개발용 로그인·개발용 서명 키는 NODE_ENV 가 development·test 일 때만 (그 밖은 JWT_SECRET 필수, 기동 차단) - 다이어리 6권·명함 10장·받은 명함 한도를 회원별 advisory lock 으로 세어 동시 요청에 뚫리지 않게 한다 - 받은 명함: 같은 명함 중복(CARD_004)·보관함 100장(CARD_005), 사본 복사 실패 시 만든 사본 정리 - 탈퇴하면 내 다이어리의 다른 멤버 사진도 정리하고, 24시간 넘게 쓰이지 않는 파일을 매시간 지운다 - 대상 id 소문자 저장, GET /calendar-sharing 은 쓰지 않음(코드는 가입 때 만들고 기존 회원은 채움), 파일·작성자 FK 인덱스 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
4.6 KiB
TypeScript
113 lines
4.6 KiB
TypeScript
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 { ThrottlerModule } from '@nestjs/throttler';
|
|
import { CacheModule } from '@nestjs/cache-manager';
|
|
import { APP_GUARD } from '@nestjs/core';
|
|
|
|
import {
|
|
INVITE_THROTTLE,
|
|
INVITE_THROTTLER,
|
|
} from '@/common/decorators/invite-code-lookup.decorator';
|
|
import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard';
|
|
import {
|
|
InviteThrottlerGuard,
|
|
IpThrottlerGuard,
|
|
} from '@/common/guards/throttler.guards';
|
|
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().
|
|
// 'invite' 는 코드로 찾는 엔드포인트(@InviteCodeLookup)만 회원별로 한 통에서 센다 (common/guards/throttler.guards.ts)
|
|
ThrottlerModule.forRoot([
|
|
{
|
|
name: 'default',
|
|
// compose 가 미정의 변수를 빈 문자열로 주입하므로 parsePositiveInt 로 방어한다
|
|
ttl: parsePositiveInt(
|
|
process.env.THROTTLE_TTL_MS,
|
|
THROTTLE_DEFAULT_TTL_MS,
|
|
),
|
|
limit: parsePositiveInt(
|
|
process.env.THROTTLE_LIMIT,
|
|
THROTTLE_DEFAULT_LIMIT,
|
|
),
|
|
},
|
|
{ name: INVITE_THROTTLER, ...INVITE_THROTTLE },
|
|
]),
|
|
// DB 접속 설정은 database.config.ts 하나만 수정한다 (CLI 와 공유)
|
|
TypeOrmModule.forRootAsync({
|
|
useFactory: () => ({
|
|
...buildDatabaseOptions(),
|
|
// Nest 모듈에 등록된 엔티티를 자동으로 수집한다 (CLI 는 glob 경로를 사용)
|
|
autoLoadEntities: true,
|
|
}),
|
|
}),
|
|
HealthModule,
|
|
UploadModule,
|
|
UserModule,
|
|
AuthModule,
|
|
PetModule,
|
|
ScheduleModule,
|
|
DiaryModule,
|
|
PetCardModule,
|
|
],
|
|
providers: [
|
|
ShutdownLogger,
|
|
// 가드는 등록한 순서대로 돈다 — IP 한도 → 인증 → 코드 조회 한도(회원별)
|
|
{
|
|
provide: APP_GUARD,
|
|
useClass: IpThrottlerGuard,
|
|
},
|
|
// 전역 인증 — 모든 라우트가 기본으로 로그인을 요구한다. 예외는 @Public() 으로 연다.
|
|
// (rate limit 이 먼저 돌아 토큰 없는 대량 요청도 한도에 걸린다)
|
|
{
|
|
provide: APP_GUARD,
|
|
useClass: JwtAuthGuard,
|
|
},
|
|
// 코드 조회 한도는 회원을 알아야 하므로 인증 다음에 둔다
|
|
{
|
|
provide: APP_GUARD,
|
|
useClass: InviteThrottlerGuard,
|
|
},
|
|
],
|
|
})
|
|
export class AppModule implements NestModule {
|
|
// 모든 요청에 추적 ID 를 부여한다 (로그·에러 응답 traceId 와 동일 값)
|
|
configure(consumer: MiddlewareConsumer): void {
|
|
consumer.apply(RequestIdMiddleware).forRoutes('*');
|
|
}
|
|
}
|