-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Siwonpada/issue143 #147
Siwonpada/issue143 #147
Conversation
📝 Walkthrough개요Walkthrough이 풀 리퀘스트는 프로젝트의 구조를 재구성하고 여러 애플리케이션을 지원하는 모노레포 설정을 도입했습니다. 주요 변경 사항은 Changes
Poem
Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Nitpick comments (14)
apps/api/src/image/image.service.ts (1)
Line range hint
45-47
: 이미지 형식 검증 로직 추가를 제안드립니다.
uploadImage
메서드에서 파일 확장자 검증 로직을 추가하면 좋을 것 같습니다. 허용된 이미지 형식만 처리하도록 하여 보안성을 향상시킬 수 있습니다.private async uploadImage(file: Express.Multer.File): Promise<string> { + const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']; + const fileExtension = path.extname(file.originalname).toLowerCase(); + if (!allowedExtensions.includes(fileExtension)) { + throw new BadRequestException('지원하지 않는 이미지 형식입니다.'); + } const key = `${new Date().toISOString()}-${Math.random().toString(36).substring(2)}.${path.extname(file.originalname)}`; return this.fileService.uploadFile(await this.convertToWebp(file), key); }apps/crawler/src/user/user.repository.ts (1)
10-14
: 데이터베이스 성능 최적화 필요name 필드에 대한 조회가 자주 발생할 것으로 예상되므로, 데이터베이스 인덱스 추가를 고려해주세요.
schema.prisma 파일에 다음과 같이 인덱스를 추가하는 것을 추천드립니다:
model User { // ... 기존 필드들 @@index([name]) }apps/api/src/notice/notice.repository.ts (2)
64-65
: 검색 기능이 대소문자를 구분하지 않도록 개선되었습니다.제목과 본문 검색에 대소문자 구분 없이 검색할 수 있도록 개선된 것이 좋습니다.
데이터베이스 성능 최적화를 위해 title과 body 컬럼에 case-insensitive 인덱스 추가를 고려해보세요:
CREATE INDEX idx_crawls_title_body ON crawls USING gin (lower(title) gin_trgm_ops, lower(body) gin_trgm_ops); CREATE INDEX idx_contents_title_body ON contents USING gin (lower(title) gin_trgm_ops, lower(body) gin_trgm_ops);Also applies to: 74-75
143-144
: getNoticeList 메서드에도 동일한 대소문자 구분 없는 검색이 적용되었습니다.getTotalCount와 getNoticeList 메서드 간에 검색 로직이 일관되게 구현되어 있습니다.
검색 조건 로직이 두 메서드에서 중복되므로, 공통 메서드로 분리하는 것을 고려해보세요:
+private getSearchCondition(search: string) { + return search + ? { + OR: [ + { + crawls: { + some: { + OR: [ + { title: { contains: search, mode: 'insensitive' } }, + { body: { contains: search, mode: 'insensitive' } }, + ], + }, + }, + }, + { + contents: { + some: { + OR: [ + { title: { contains: search, mode: 'insensitive' } }, + { body: { contains: search, mode: 'insensitive' } }, + ], + }, + }, + }, + { + tags: { + some: { name: { contains: search, mode: 'insensitive' } }, + }, + }, + ], + } + : {}; +}Also applies to: 153-154, 159-163
apps/crawler/src/main.ts (3)
6-6
: 불필요한identity
임포트 제거
identity
함수는 이 코드에서 사용되지 않으므로 임포트를 제거하여 코드를 정리할 수 있습니다.import { concatMap, firstValueFrom, - identity, map, take, timeout, toArray, } from 'rxjs';
23-38
: 오류 처리를 추가하여 안정성 향상
getNoticeList()
와 그 후속 스트림에서 발생할 수 있는 오류를 캐치하지 않고 있습니다. 스트림에catchError
연산자를 추가하여 오류 발생 시 적절한 처리를 할 수 있습니다.toArray(), + ), + catchError((error) => { + // 오류 로깅 또는 처리 로직 추가 + console.error('오류 발생:', error); + return []; ), ),
46-60
:newNotices
처리에서 예외 상황에 대한 오류 처리 필요
newNotices
처리 중createCrawl
실행 시 발생할 수 있는 예외 상황에 대비하여 오류 처리가 필요합니다. 각 비동기 처리 내에try-catch
블록을 추가하여 오류를 캐치하고 로깅할 수 있습니다.await Promise.all( newNotices.map(async ({ notice }) => { + try { await app.get(CrawlerService).createCrawl( { title: notice.meta.title, body: notice.notice.content ?? '', type: 'ACADEMIC', url: notice.meta.link, crawledAt: new Date(), }, new Date(notice.meta.createdAt), notice.meta.author, ); + } catch (error) { + // 오류 로깅 또는 처리 로직 추가 + console.error('createCrawl 오류 발생:', error); + } }), );apps/crawler/src/crawler.service.ts (2)
55-74
: HTTP 요청 실패 시 적절한 오류 처리 필요
getNoticeList()
메서드에서 HTTP 요청 실패나 파싱 오류가 발생할 경우를 대비하여 더 자세한 오류 처리가 필요합니다.catchError
연산자를 활용하여 사용자 정의 오류 메시지를 제공하거나 재시도 로직을 추가할 수 있습니다.return this.httpService.get(this.targetUrl).pipe( timeout(10 * 1000), - catchError(throwError), + catchError((error) => { + // 오류 로깅 또는 사용자 정의 오류 반환 + console.error('공지 사항 리스트 가져오기 실패:', error); + return throwError(() => new Error('공지 사항 리스트를 가져오는 중 오류가 발생했습니다.')); + }), map((res) => load(res.data)), map(($) => $('table > tbody > tr')), concatMap(($) => $.toArray().map((value: any) => load(value))), map(($) => { return { title: $('td').eq(2).text().trim(), link: `${this.targetUrl}${$('td').eq(2).find('a').attr('href')}`, author: $('td').eq(3).text().trim(), category: $('td').eq(1).text().trim(), createdAt: $('td').eq(5).text().trim(), }; }), map((meta) => ({ id: Number.parseInt(meta.link.split('no=')[1].split('&')[0]), ...meta, })), );
85-106
:getNoticeDetail
에서 파일 링크 생성 시 URL 구성 오류 가능성 확인 필요파일의
href
를 생성할 때${this.targetUrl}${$(value).attr('href')}
로 구성하고 있습니다.$(value).attr('href')
가 절대 경로인 경우 URL이 잘못 형성될 수 있으므로new URL($(value).attr('href'), this.targetUrl).href
를 사용하는 것이 안전합니다.href: `${this.targetUrl}${$(value).attr('href')}`, + // 또는 + href: new URL($(value).attr('href'), this.targetUrl).href,apps/crawler/src/user/user.module.ts (1)
1-2
: 상대 경로 대신 절대 경로 사용을 권장
@lib/prisma
및@nestjs/common
모듈을 임포트할 때 상대 경로 대신 절대 경로 또는 별칭을 사용하면 코드의 가독성과 유지보수성이 향상됩니다.import { PrismaModule } from '@lib/prisma'; import { Module } from '@nestjs/common';위 코드에서는 이미 절대 경로를 사용하고 있으므로 추가 조치가 필요하지 않습니다. 이 점은 잘 처리된 부분입니다.
apps/crawler/test/crawler.service.spec.ts (3)
32-33
: 디버깅용 console.log 문을 제거해주세요.테스트 코드에 디버깅용 console.log가 남아있습니다. 프로덕션 테스트 코드에서는 제거하는 것이 좋습니다.
- console.log(noticeList); - console.log('noticeList.length:', noticeList.length);
26-35
: 테스트 검증을 더 구체적으로 보완해주세요.현재는 단순히 리스트의 길이만 확인하고 있습니다. 반환된 데이터의 구조와 필수 필드들도 검증하면 좋을 것 같습니다.
expect(noticeList.length).toBeGreaterThan(0); + expect(noticeList[0]).toHaveProperty('title'); + expect(noticeList[0]).toHaveProperty('link'); + expect(noticeList[0]).toHaveProperty('date');
46-46
: 디버깅용 console.log 문을 제거해주세요.테스트 코드에 디버깅용 console.log가 남아있습니다.
- console.log(noticeDetail);
apps/crawler/src/crawler.repository.ts (1)
17-49
: 입력값 검증을 추가해주세요.createCrawl 메소드에 전달되는 매개변수들에 대한 유효성 검사가 없습니다. 특히 필수 필드들에 대한 검증이 필요합니다.
if (!title || !body || !type || !crawledAt || !url || !user) { throw new BadRequestException('필수 필드가 누락되었습니다'); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.json
is excluded by!**/package-lock.json
📒 Files selected for processing (46)
.github/workflows/production.yml
(1 hunks).github/workflows/staging.yml
(2 hunks).vscode/settings.json
(1 hunks)apps/api/src/ai/ai.controller.ts
(1 hunks)apps/api/src/ai/ai.module.ts
(1 hunks)apps/api/src/api.controller.ts
(1 hunks)apps/api/src/api.module.ts
(2 hunks)apps/api/src/crawl/crawl.module.ts
(1 hunks)apps/api/src/crawl/crawl.service.ts
(1 hunks)apps/api/src/document/document.module.ts
(1 hunks)apps/api/src/document/document.service.ts
(1 hunks)apps/api/src/group/group.controller.ts
(1 hunks)apps/api/src/group/group.module.ts
(1 hunks)apps/api/src/image/image.module.ts
(1 hunks)apps/api/src/image/image.service.ts
(1 hunks)apps/api/src/main.ts
(1 hunks)apps/api/src/notice/dto/req/additionalNotice.dto.ts
(1 hunks)apps/api/src/notice/dto/req/createNotice.dto.ts
(1 hunks)apps/api/src/notice/dto/req/foreignContent.dto.ts
(1 hunks)apps/api/src/notice/dto/req/getAllNotice.dto.ts
(1 hunks)apps/api/src/notice/dto/req/getNotice.dto.ts
(1 hunks)apps/api/src/notice/dto/req/reaction.dto.ts
(1 hunks)apps/api/src/notice/dto/req/updateNotice.dto.ts
(1 hunks)apps/api/src/notice/notice.controller.ts
(1 hunks)apps/api/src/notice/notice.module.ts
(1 hunks)apps/api/src/notice/notice.repository.ts
(4 hunks)apps/api/src/notice/notice.service.ts
(1 hunks)apps/api/src/tag/tag.controller.ts
(1 hunks)apps/api/src/tag/tag.module.ts
(1 hunks)apps/api/src/user/decorator/get-idp-user.decorator.ts
(1 hunks)apps/api/src/user/decorator/get-user.decorator.ts
(1 hunks)apps/api/src/user/dto/req/login.dto.ts
(1 hunks)apps/api/tsconfig.app.json
(1 hunks)apps/crawler/Dockerfile
(1 hunks)apps/crawler/src/crawler.module.ts
(1 hunks)apps/crawler/src/crawler.repository.ts
(1 hunks)apps/crawler/src/crawler.service.ts
(1 hunks)apps/crawler/src/main.ts
(1 hunks)apps/crawler/src/user/user.module.ts
(1 hunks)apps/crawler/src/user/user.repository.ts
(1 hunks)apps/crawler/src/user/user.service.ts
(1 hunks)apps/crawler/test/crawler.service.spec.ts
(1 hunks)apps/crawler/test/jest.json
(1 hunks)apps/crawler/tsconfig.app.json
(1 hunks)nest-cli.json
(2 hunks)package.json
(4 hunks)
✅ Files skipped from review due to trivial changes (25)
- apps/api/src/user/decorator/get-idp-user.decorator.ts
- apps/api/src/notice/dto/req/reaction.dto.ts
- apps/api/src/user/decorator/get-user.decorator.ts
- apps/api/src/notice/dto/req/getNotice.dto.ts
- apps/api/src/api.controller.ts
- apps/api/src/crawl/crawl.service.ts
- apps/crawler/tsconfig.app.json
- apps/api/src/notice/dto/req/createNotice.dto.ts
- apps/api/src/notice/dto/req/additionalNotice.dto.ts
- apps/api/src/document/document.service.ts
- apps/api/src/notice/dto/req/foreignContent.dto.ts
- apps/api/src/notice/dto/req/updateNotice.dto.ts
- apps/api/src/image/image.module.ts
- apps/api/src/group/group.controller.ts
- apps/api/src/notice/notice.controller.ts
- apps/api/src/notice/dto/req/getAllNotice.dto.ts
- apps/api/tsconfig.app.json
- apps/api/src/document/document.module.ts
- apps/api/src/notice/notice.service.ts
- apps/api/src/tag/tag.module.ts
- apps/api/src/ai/ai.controller.ts
- apps/api/src/user/dto/req/login.dto.ts
- apps/crawler/test/jest.json
- .vscode/settings.json
- apps/api/src/notice/notice.module.ts
🔇 Additional comments (19)
apps/api/src/image/image.service.ts (2)
9-9
: 상대 경로로의 변경이 적절합니다.절대 경로에서 상대 경로로의 변경은 모듈의 이식성과 유지보수성을 향상시킵니다.
12-14
: 로깅 방식의 개선이 확인되었습니다.
Logger
인스턴스를 제거하고@Loggable()
데코레이터를 사용하는 방식으로 전환한 것이 확인됩니다. 이는 일관된 로깅 처리를 가능하게 합니다.apps/api/src/tag/tag.controller.ts (1)
24-24
: 상대 경로로의 변경이 적절해 보입니다.프로젝트 구조 개선을 위한 import 경로 표준화가 잘 이루어졌습니다. 다른 컨트롤러들의 변경사항과도 일관성이 있습니다.
아래 스크립트를 실행하여 import 경로가 올바른지 확인하겠습니다:
✅ Verification successful
상대 경로 변경이 정상적으로 이루어졌습니다.
모든 컨트롤러에서 동일한 상대 경로 패턴을 사용하고 있으며, 파일 위치도 정확합니다. 경로 표준화가 성공적으로 완료되었습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the import path exists and is consistent # Check if the guard file exists at the specified relative path fd "idp.guard.ts" "apps/api/src/user/guard" # Verify consistent usage of relative paths for IdPGuard across controllers rg "import.*IdPGuard.*from.*user/guard/idp.guard" "apps/api/src"Length of output: 517
apps/api/src/ai/ai.module.ts (1)
6-6
: 모듈 임포트 경로 변경이 적절합니다!상대 경로로의 변경은 모노레포 구조에 더 적합하며, 프로젝트의 모듈성을 향상시킵니다.
apps/api/src/group/group.module.ts (1)
8-8
: 일관된 모듈 경로 변경이 확인되었습니다!다른 모듈들과 동일한 패턴으로 상대 경로가 적용되어 있어 일관성이 잘 유지되었습니다.
apps/api/src/crawl/crawl.module.ts (1)
8-10
: 모듈 임포트 경로가 올바르게 변경되었습니다!여러 모듈의 임포트 경로가 일관되게 상대 경로로 변경되었습니다. 다만, 변경된 경로가 올바르게 동작하는지 확인이 필요합니다.
✅ Verification successful
모듈 임포트 경로가 정상적으로 확인되었습니다!
모든 모듈 파일들이 예상된 위치에서 발견되었으며, 상대 경로를 통한 임포트가 정확히 구성되어 있습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # 변경된 모듈 경로의 유효성 검증 for module in "../user/user.module" "../ai/ai.module" "../fcm/fcm.module"; do module_path="apps/api/src/crawl/$module" if ! [ -f "$module_path.ts" ]; then echo "경고: $module_path.ts 파일을 찾을 수 없습니다." fi doneLength of output: 539
Script:
#!/bin/bash # Search for module files echo "Searching for user.module.ts:" fd "user.module.ts" apps/api/src echo -e "\nSearching for ai.module.ts:" fd "ai.module.ts" apps/api/src echo -e "\nSearching for fcm.module.ts:" fd "fcm.module.ts" apps/api/srcLength of output: 401
apps/crawler/src/user/user.module.ts (1)
6-11
: 모듈 구성 완벽
UserModule
의 구성과 프로바이더 설정이 적절하게 이루어져 있습니다. 필요한 서비스와 리포지토리를 제공하고, 외부로UserService
를 내보내어 다른 모듈에서 사용할 수 있게 한 점이 좋습니다.apps/crawler/src/crawler.module.ts (1)
1-13
: 모듈 구조가 잘 설계되었습니다!NestJS의 모듈 아키텍처 패턴을 잘 따르고 있으며, 필요한 의존성들이 적절하게 주입되어 있습니다.
apps/api/src/api.module.ts (1)
Line range hint
16-56
: 모듈 구조 개선이 잘 이루어졌습니다!AppModule에서 ApiModule로의 이름 변경과 컨트롤러 업데이트가 프로젝트의 모노레포 구조에 잘 부합합니다. 각 기능별 모듈도 깔끔하게 구성되어 있습니다.
apps/api/src/main.ts (1)
7-7
: 모듈 변경이 적절히 이루어졌습니다.모노레포 구조 변경에 따라
AppModule
에서ApiModule
로의 전환이 올바르게 이루어졌습니다. 기존 부트스트랩 설정은 그대로 유지되어 있어 안정적입니다.Also applies to: 10-10
apps/crawler/Dockerfile (1)
2-2
: 🛠️ Refactor suggestionNode.js 버전 및 패키지 설치 방식 검토 필요
node:21-alpine3.18
은 매우 최신 버전으로, 프로덕션 환경에서는 안정성이 검증된 LTS 버전(예: node:20-alpine)을 사용하는 것이 좋습니다.npm install --force
사용은 패키지 충돌을 야기할 수 있으므로 권장되지 않습니다.-FROM node:21-alpine3.18 AS builder +FROM node:20-alpine3.18 AS builder RUN apk update && \ apk add build-base libheif vips-dev vips -q -RUN npm install && npm install --force @img/sharp-linuxmusl-arm64 +RUN npm installAlso applies to: 10-10
✅ Verification successful
Node.js 20 LTS로의 다운그레이드가 안전함을 확인했습니다
- 프로젝트의 의존성들이 모두 Node.js 20과 호환됩니다.
- Sharp v0.33.2는 Node.js 20에서 정상적으로 동작하며, 아키텍처별 강제 설치가 필요하지 않습니다.
다음과 같이 수정하시기를 권장드립니다:
-FROM node:21-alpine3.18 AS builder +FROM node:20-alpine3.18 AS builder RUN apk update && \ apk add build-base libheif vips-dev vips -q -RUN npm install && npm install --force @img/sharp-linuxmusl-arm64 +RUN npm install🏁 Scripts executed
The following scripts were executed for the analysis:
Error: No shell scripts found
Script:
#!/bin/bash # Check package.json for engine requirements and dependencies echo "=== Checking package.json files ===" fd package.json --exec cat {} # Search for potential Node.js 21 specific features echo -e "\n=== Checking for potential Node.js 21 features ===" rg -l "node:" . # Check sharp related configurations echo -e "\n=== Checking sharp related configurations ===" rg -l "sharp" .Length of output: 4332
nest-cli.json (1)
4-4
: 모노레포 구성이 적절히 설정되었습니다.프로젝트 구조가 NestJS 모노레포 베스트 프랙티스를 잘 따르고 있습니다. API와 크롤러 애플리케이션이 독립적으로 구성되어 있어 확장성이 좋습니다.
Also applies to: 8-8, 55-76
.github/workflows/production.yml (1)
52-52
: 워크플로우가 새로운 프로젝트 구조에 맞게 수정되었습니다.Dockerfile 경로가 새로운 모노레포 구조에 맞게 올바르게 지정되었습니다.
.github/workflows/staging.yml (2)
6-6
: 워크플로우 트리거 경로가 새로운 프로젝트 구조와 일치하도록 업데이트되었습니다.
apps/api/**
로의 경로 변경은 모노레포 구조에 맞게 적절히 수정되었습니다. API 애플리케이션의 변경사항만 워크플로우를 트리거하도록 하여 불필요한 빌드를 방지합니다.
55-55
: Dockerfile 경로가 새로운 프로젝트 구조를 반영하도록 업데이트되었습니다.
./apps/api/Dockerfile
로의 경로 변경은 모노레포 구조에 맞게 적절히 수정되었습니다.다음 스크립트를 실행하여 Dockerfile이 올바른 위치에 존재하는지 확인하겠습니다:
✅ Verification successful
Dockerfile 경로 검증이 완료되었습니다
./apps/api/Dockerfile
경로에 파일이 정상적으로 존재함을 확인했습니다. 모노레포 구조에 맞게 올바르게 위치해 있습니다.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Dockerfile 존재 여부 확인 if [ -f "./apps/api/Dockerfile" ]; then echo "Dockerfile exists at the correct location" else echo "ERROR: Dockerfile not found at ./apps/api/Dockerfile" exit 1 fiLength of output: 127
package.json (4)
10-17
: 스크립트 경로가 새로운 프로젝트 구조를 반영하도록 업데이트되었습니다.모노레포 구조로의 전환을 위해 다음과 같이 적절히 수정되었습니다:
- format 스크립트가 apps와 libs 디렉토리를 대상으로 하도록 변경
- start 관련 스크립트들이 새로운 디렉토리 구조를 반영
- 크롤러 서비스를 위한 새로운 스크립트 추가
115-116
: Jest 설정이 새로운 프로젝트 구조를 반영하도록 업데이트되었습니다.테스트 루트 디렉토리가 새로운 모노레포 구조에 맞게 적절히 수정되었습니다.
22-23
: 테스트 설정이 새로운 프로젝트 구조를 반영하도록 업데이트되었습니다.API와 크롤러 서비스에 대한 개별 테스트 설정이 적절히 추가되었습니다.
다음 스크립트를 실행하여 테스트 설정 파일들이 올바른 위치에 존재하는지 확인하겠습니다:
✅ Verification successful
테스트 설정 파일 검증이 완료되었습니다
API와 크롤러 서비스의 테스트 설정 파일이 모두 올바른 위치에 존재함을 확인했습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # 테스트 설정 파일 존재 여부 확인 for config in "./apps/api/test/jest-e2e.json" "./apps/crawler/test/jest.json"; do if [ -f "$config" ]; then echo "Test config exists at: $config" else echo "ERROR: Test config not found at: $config" exit 1 fi doneLength of output: 475
39-39
: 크롤러 서비스를 위한 새로운 의존성이 추가되었습니다.cheerio 라이브러리와 해당 타입 정의가 추가되었습니다. 웹 스크래핑을 위한 적절한 선택입니다.
다음 스크립트를 실행하여 크롤러 서비스의 cheerio 사용을 확인하겠습니다:
Also applies to: 61-61
✅ Verification successful
cheerio 라이브러리가 크롤러 서비스에서 적절하게 사용되고 있음을 확인했습니다.
크롤러 서비스에서 cheerio의
load
함수를 정상적으로 임포트하여 사용하고 있습니다. 웹 스크래핑을 위한 의존성이 올바르게 구현되었습니다.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # 크롤러 서비스에서 cheerio 사용 확인 rg -l "cheerio" apps/crawler/Length of output: 65
Script:
#!/bin/bash # Check cheerio implementation in crawler service rg "cheerio" apps/crawler/src/crawler.service.ts -B 2 -A 2Length of output: 198
Pull request
개요
PR Checklist
Summary by CodeRabbit
여기 주요 릴리즈 노트입니다:
새로운 기능
개선 사항
기술적 변경
버그 수정