diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index 6d3ee26..0000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,20 +0,0 @@ -FROM node:20-slim AS base - -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable - -FROM base AS prod - -COPY pnpm-lock.yaml /app -WORKDIR /app -RUN pnpm fetch --prod - -COPY . /app -RUN pnpm run build - -FROM base -COPY --from=prod /app/node_modules /app/node_modules -COPY --from=prod /app/dist /app/dist -EXPOSE 8000 -CMD [ "pnpm", "start" ] \ No newline at end of file diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index d76c80c..3299302 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,14 +1,39 @@ +x-defaults: &common-settings + image: dongho18/connect-gnu-node:latest + pull_policy: always + restart: always + networks: + - connect-gnu-network + environment: + TZ: 'Asia/Seoul' + SLACK_WEBHOOK: ${SLACK_WEBHOOK} + SENTRY_NODE_DSN: ${SENTRY_NODE_DSN} + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + DB_DATABASE: ${DB_DATABASE} + deploy: + resources: + limits: + memory: 256m + cpus: '0.25' + reservations: + memory: 128m + cpus: '0.1' services: - backend_node_server: - build: - context: . - dockerfile: Dockerfile.dev - container_name: backend_node_server - restart: always + connect-gnu-node-blue: + <<: *common-settings + container_name: connect-gnu-node-blue ports: - - '5000:5000' - volumes: - - .:/app - - /app/node_modules - environment: - TZ: 'Asia/Seoul' + - '5200:5200' + + connect-gnu-node-green: + <<: *common-settings + container_name: connect-gnu-node-green + ports: + - '5201:5200' + +networks: + connect-gnu-network: + external: true diff --git a/package.json b/package.json index 54db1df..01fb980 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@nestjs/platform-express": "^9.0.0", "@nestjs/schedule": "^5.0.1", "@nestjs/swagger": "^7.4.0", + "@nestjs/terminus": "^11.0.0", "@nestjs/typeorm": "^10.0.2", "@sentry/browser": "^8.54.0", "@sentry/minimal": "^6.19.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12f8cf6..403bb97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@nestjs/swagger': specifier: ^7.4.0 version: 7.4.0(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(reflect-metadata@0.1.14)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14) + '@nestjs/terminus': + specifier: ^11.0.0 + version: 11.0.0(uxrazl6lcueo6nkl7l5kxmqnye) '@nestjs/typeorm': specifier: ^10.0.2 version: 10.0.2(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(reflect-metadata@0.1.14)(rxjs@7.8.1))(reflect-metadata@0.1.14)(rxjs@7.8.1)(typeorm@0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@18.16.12)(typescript@5.5.4))) @@ -691,6 +694,54 @@ packages: class-validator: optional: true + '@nestjs/terminus@11.0.0': + resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0 + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/microservices': ^10.0.0 || ^11.0.0 + '@nestjs/mongoose': ^11.0.0 + '@nestjs/sequelize': ^10.0.0 || ^11.0.0 + '@nestjs/typeorm': ^10.0.0 || ^11.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + '@nestjs/testing@9.4.3': resolution: {integrity: sha512-LDT8Ai2eKnTzvnPaJwWOK03qTaFap5uHHsJCv6dL0uKWk6hyF9jms8DjyVaGsaujCaXDG8izl1mDEER0OmxaZA==} peerDependencies: @@ -1683,6 +1734,9 @@ packages: ajv@8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1833,6 +1887,10 @@ packages: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1927,6 +1985,10 @@ packages: charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -1963,6 +2025,10 @@ packages: class-validator@0.14.1: resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -4501,6 +4567,10 @@ packages: engines: {node: '>= 8'} hasBin: true + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + windows-release@4.0.0: resolution: {integrity: sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==} engines: {node: '>=10'} @@ -5401,6 +5471,19 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.1 + '@nestjs/terminus@11.0.0(uxrazl6lcueo6nkl7l5kxmqnye)': + dependencies: + '@nestjs/common': 9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 9.4.3(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(reflect-metadata@0.1.14)(rxjs@7.8.1) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.1.14 + rxjs: 7.8.1 + optionalDependencies: + '@nestjs/axios': 4.0.0(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(axios@0.21.4)(rxjs@7.8.1) + '@nestjs/typeorm': 10.0.2(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(reflect-metadata@0.1.14)(rxjs@7.8.1))(reflect-metadata@0.1.14)(rxjs@7.8.1)(typeorm@0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@18.16.12)(typescript@5.5.4))) + typeorm: 0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@18.16.12)(typescript@5.5.4)) + '@nestjs/testing@9.4.3(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3(@nestjs/common@9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@9.4.3))': dependencies: '@nestjs/common': 9.4.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) @@ -6772,6 +6855,10 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -6956,6 +7043,17 @@ snapshots: transitivePeerDependencies: - supports-color + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -7056,6 +7154,8 @@ snapshots: charenc@0.0.2: {} + check-disk-space@3.4.0: {} + chokidar@3.5.3: dependencies: anymatch: 3.1.3 @@ -7102,6 +7202,8 @@ snapshots: libphonenumber-js: 1.12.6 validator: 13.12.0 + cli-boxes@2.2.1: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -9810,6 +9912,10 @@ snapshots: dependencies: isexe: 2.0.0 + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + windows-release@4.0.0: dependencies: execa: 4.1.0 diff --git a/src/main.ts b/src/main.ts index faa1ac8..ebff2ec 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,9 +6,9 @@ import * as process from 'process'; import { NestFactory, HttpAdapterHost } from '@nestjs/core'; import { AppModule } from './modules/app/app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import { KakaoInterceptor } from './modules/interceptors/kakao.interceptor'; +import { KakaoInterceptor } from './modules/common/interceptors/kakao.interceptor'; import { ResponseDTO } from './modules/common/dtos/response.dto'; -import { SentryInterceptor } from './modules/interceptors/sentry.interceptor.js'; +import { SentryInterceptor } from './modules/common/interceptors/sentry.interceptor.js'; import { SentryFilter } from './modules/common/filters/sentry.filter'; import { initializeTransactionalContext } from 'typeorm-transactional'; import { ValidationPipe } from '@nestjs/common'; @@ -26,15 +26,18 @@ async function bootstrap() { } else { app.useGlobalFilters(new HttpExceptionFilter()); } - app.useGlobalInterceptors(new KakaoInterceptor(ResponseDTO)); - - app.useGlobalPipes(new ValidationPipe({ - whitelist: false, - forbidNonWhitelisted: false, - transform: true, - //transformOptions: { enableImplicitConversion: true } - })) + app.useGlobalInterceptors( + new KakaoInterceptor(ResponseDTO, ['/api/node/health']), + ); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: false, + forbidNonWhitelisted: false, + transform: true, + //transformOptions: { enableImplicitConversion: true } + }), + ); app.setGlobalPrefix('api/node'); const config = new DocumentBuilder() @@ -54,14 +57,12 @@ async function bootstrap() { .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('api/node/docs', app, document, - { - swaggerOptions: { - persistAuthorization: true, // 새로고침해도 인증 정보 유지 - defaultModelsExpandDepth: -1 - } - } - ); + SwaggerModule.setup('api/node/docs', app, document, { + swaggerOptions: { + persistAuthorization: true, // 새로고침해도 인증 정보 유지 + defaultModelsExpandDepth: -1, + }, + }); await app.listen(nodeEnv == 'production' ? 5200 : 5001); } diff --git a/src/modules/app/app.module.ts b/src/modules/app/app.module.ts index 868b19c..d6bf31d 100644 --- a/src/modules/app/app.module.ts +++ b/src/modules/app/app.module.ts @@ -10,25 +10,24 @@ import { validationSchema } from 'src/modules/common/utils/enviornment'; import { APP_FILTER } from '@nestjs/core'; import { CommonModule } from '../common/common.module'; import { ReadingRoomsModule } from '../reading-rooms/reading-rooms.module'; -import { ScheduleModule } from '@nestjs/schedule'; import { HttpModule } from '@nestjs/axios'; import { AuthModule } from 'src/modules/auth/auth.module'; -import { DiscoveryModule } from '@nestjs/core'; +import { HealthModule } from '../health/health.module'; @Module({ imports: [ SentryModule.forRoot(), - ScheduleModule.forRoot(), + HealthModule, HttpModule, SupabaseModule, UtilsModule, - DiscoveryModule, CommonModule, UsersModule, ReadingRoomsModule, AuthModule, ConfigModule.forRoot({ isGlobal: true, - envFilePath: process.env.NODE_ENV === 'production' ? '.env.prod' : '.env.dev', + envFilePath: + process.env.NODE_ENV === 'production' ? '.env.prod' : '.env.dev', validationSchema, }), ], diff --git a/src/modules/interceptors/kakao.interceptor.ts b/src/modules/common/interceptors/kakao.interceptor.ts similarity index 58% rename from src/modules/interceptors/kakao.interceptor.ts rename to src/modules/common/interceptors/kakao.interceptor.ts index dc01331..6aad42d 100644 --- a/src/modules/interceptors/kakao.interceptor.ts +++ b/src/modules/common/interceptors/kakao.interceptor.ts @@ -3,9 +3,17 @@ import { plainToInstance } from 'class-transformer'; import { Observable, map } from 'rxjs'; export class KakaoInterceptor implements NestInterceptor { - constructor(private dto: any) {} + constructor(private dto: any, private excludePaths: string[] = []) {} intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const path = request.url; + + // 제외 경로에 해당하는 경우 인터셉터를 건너뜁니다 + if (this.excludePaths.some((excludePath) => path.includes(excludePath))) { + return next.handle(); + } + return next.handle().pipe( map((data: any) => { return plainToInstance(this.dto, data, { diff --git a/src/modules/interceptors/sentry.interceptor.ts b/src/modules/common/interceptors/sentry.interceptor.ts similarity index 100% rename from src/modules/interceptors/sentry.interceptor.ts rename to src/modules/common/interceptors/sentry.interceptor.ts diff --git a/src/modules/health/health.controller.ts b/src/modules/health/health.controller.ts new file mode 100644 index 0000000..38cd537 --- /dev/null +++ b/src/modules/health/health.controller.ts @@ -0,0 +1,23 @@ +import { Controller, Get, UseInterceptors } from '@nestjs/common'; +import { + HealthCheckService, + HttpHealthIndicator, + HealthCheck, +} from '@nestjs/terminus'; + +@Controller('health') +@UseInterceptors() +export class HealthController { + constructor( + private health: HealthCheckService, + private http: HttpHealthIndicator, + ) {} + + @Get() + @HealthCheck() + check() { + return this.health.check([ + () => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'), + ]); + } +} diff --git a/src/modules/health/health.module.ts b/src/modules/health/health.module.ts new file mode 100644 index 0000000..0fe8e70 --- /dev/null +++ b/src/modules/health/health.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { HealthController } from './health.controller'; +import { HttpModule } from '@nestjs/axios'; +@Module({ + imports: [TerminusModule, HttpModule], + controllers: [HealthController], +}) +export class HealthModule {}