-
Notifications
You must be signed in to change notification settings - Fork 0
[25.12.17 / TASK-257] Feature - SVG Badge API 추가 #47
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
base: main
Are you sure you want to change the base?
Conversation
feat: SVG Badge API 구현 - Controller, Service, Repository 구현
fix: 쿼리 파라미터 오류 수정 - userStatsQuery에 불필요한 ateRange 파라미터 제거
refactor: SVG Badge API 구조 개선 및 응답 Json으로 변경 - assets, withrank 쿼리 파라미터 삭제 - API 응답 Svg에서 Json으로 변경
WalkthroughSVG 배지 기능 추가: 요청/응답 DTO, 서비스, 컨트롤러, 라우터가 도입되고 리더보드 저장소에 Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant SvgRouter
participant SvgController
participant SvgService
participant LeaderboardRepo
participant Database
Client->>SvgRouter: GET /:username/badge?type={type}
SvgRouter->>SvgRouter: validateRequestDto(query)
SvgRouter->>SvgController: getSvgBadge(username, type)
SvgController->>SvgService: getBadgeData(username, type, dateRange)
SvgService->>LeaderboardRepo: getUserStats(username, dateRange)
LeaderboardRepo->>Database: Execute stats query (CTE)
Database-->>LeaderboardRepo: stats row / not found
alt stats found
LeaderboardRepo-->>SvgService: user stats
opt type == "default"
SvgService->>LeaderboardRepo: getRecentPosts(username, dateRange, limit=3)
LeaderboardRepo->>Database: Execute recent posts query (CTE)
Database-->>LeaderboardRepo: recent posts rows
LeaderboardRepo-->>SvgService: recent posts
end
SvgService-->>SvgController: BadgeData
SvgController-->>Client: 200 { BadgeDataResponseDto }
else NotFound
LeaderboardRepo-->>SvgService: NotFoundError
SvgService-->>SvgController: throw NotFoundError
SvgController-->>Client: 404
end
alt DB error
LeaderboardRepo-->>SvgService: DBError
SvgService-->>SvgController: throw DBError
SvgController-->>Client: 500
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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: 2
🧹 Nitpick comments (6)
src/types/dto/requests/svgRequest.type.ts (1)
1-1: 사용하지 않는 import를 제거하세요.
IsString이 import되었지만 파일에서 사용되지 않습니다.-import { IsEnum, IsOptional, IsString } from 'class-validator'; +import { IsEnum, IsOptional } from 'class-validator';src/repositories/__test__/leaderboard.repo.test.ts (2)
232-237: 에러 테스트에서 불필요한 이중 호출을 피하세요.에러 타입과 메시지를 검증하기 위해
getUserStats를 두 번 호출하고 있습니다. 이는 불필요한 중복 실행입니다.it('쿼리 에러 발생 시 DBError를 던져야 한다', async () => { mockPool.query.mockRejectedValue(new Error('DB connection failed')); - await expect(repo.getUserStats('test-user', 30)).rejects.toThrow(DBError); - await expect(repo.getUserStats('test-user', 30)).rejects.toThrow('사용자 통계 조회 중 문제가 발생했습니다.'); + await expect(repo.getUserStats('test-user', 30)).rejects.toThrow( + expect.objectContaining({ + name: 'DBError', + message: '사용자 통계 조회 중 문제가 발생했습니다.', + }), + ); });
294-299: 에러 테스트에서 불필요한 이중 호출을 피하세요.위의
getUserStats테스트와 동일하게,getRecentPosts도 두 번 호출되고 있습니다.it('쿼리 에러 발생 시 DBError를 던져야 한다', async () => { mockPool.query.mockRejectedValue(new Error('DB connection failed')); - await expect(repo.getRecentPosts('test-user', 30, 3)).rejects.toThrow(DBError); - await expect(repo.getRecentPosts('test-user', 30, 3)).rejects.toThrow('최근 게시글 조회 중 문제가 발생했습니다.'); + await expect(repo.getRecentPosts('test-user', 30, 3)).rejects.toThrow( + expect.objectContaining({ + name: 'DBError', + message: '최근 게시글 조회 중 문제가 발생했습니다.', + }), + ); });src/routes/svg.router.ts (1)
62-66: Swagger 문서에서 recentPosts 항목의 스키마가 불완전합니다.
recentPosts배열 항목의 프로퍼티를 명시하면 API 문서가 더 유용해집니다.recentPosts: type: array items: type: object + properties: + title: + type: string + releasedAt: + type: string + viewCount: + type: number + likeCount: + type: number + viewDiff: + type: numbersrc/services/__test__/svg.service.test.ts (1)
1-1: 사용하지 않는 Pool 관련 코드를 제거하세요.
Pool이 import되고mockPool이 생성되지만,LeaderboardRepository전체가 mock되어 있어 실제로 사용되지 않습니다.-import { Pool } from 'pg'; import { DBError, NotFoundError } from '@/exception'; import { LeaderboardRepository } from '@/repositories/leaderboard.repository';beforeEach(() => { - const mockPoolObj = {}; - mockPool = mockPoolObj as jest.Mocked<Pool>; - - const repoInstance = new LeaderboardRepository(mockPool); + const repoInstance = new LeaderboardRepository({} as any); mockRepo = repoInstance as jest.Mocked<LeaderboardRepository>; service = new SvgService(mockRepo); });Also applies to: 18-22
src/repositories/leaderboard.repository.ts (1)
85-151: 선택적 개선: 공통 쿼리 로직 추출 고려
getUserStats와 기존getUserLeaderboard가 유사한 SELECT/집계 로직을 공유합니다. 향후 유지보수를 위해 공통 부분을 private 헬퍼 메서드로 추출하는 것을 고려해볼 수 있습니다. 현재 상태도 동작에 문제는 없습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/controllers/svg.controller.ts(1 hunks)src/repositories/__test__/leaderboard.repo.test.ts(2 hunks)src/repositories/leaderboard.repository.ts(2 hunks)src/routes/index.ts(2 hunks)src/routes/svg.router.ts(1 hunks)src/services/__test__/svg.service.test.ts(1 hunks)src/services/svg.service.ts(1 hunks)src/types/dto/requests/svgRequest.type.ts(1 hunks)src/types/dto/responses/svgResponse.type.ts(1 hunks)src/types/index.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-12-04T13:26:58.075Z
Learnt from: HA0N1
Repo: Check-Data-Out/velog-dashboard-v2-api PR: 5
File: src/middlewares/auth.middleware.ts:116-117
Timestamp: 2024-12-04T13:26:58.075Z
Learning: 'velog-dashboard-v2-api' 코드베이스에서는 `src/types/express.d.ts` 파일에서 Express의 `Request` 인터페이스를 확장하여 `user`와 `tokens` 속성을 추가하였습니다.
Applied to files:
src/types/index.ts
📚 Learning: 2024-12-09T11:15:44.429Z
Learnt from: HA0N1
Repo: Check-Data-Out/velog-dashboard-v2-api PR: 6
File: src/types/dto/velog-user.dto.ts:23-30
Timestamp: 2024-12-09T11:15:44.429Z
Learning: `src/types/dto/velog-user.dto.ts` 파일에서 `ProfileDTO` 클래스는 외부에서 import될 필요가 없으므로 export하지 않아도 된다.
Applied to files:
src/types/index.ts
📚 Learning: 2024-12-04T13:28:34.692Z
Learnt from: HA0N1
Repo: Check-Data-Out/velog-dashboard-v2-api PR: 5
File: src/types/dto/user-with-token.dto.ts:3-26
Timestamp: 2024-12-04T13:28:34.692Z
Learning: In `src/types/dto/user-with-token.dto.ts`, since the project is using Express instead of NestJS, `nestjs/swagger` cannot be used. Documentation will be implemented later using an Express-specific Swagger library.
Applied to files:
src/routes/svg.router.ts
📚 Learning: 2024-12-05T17:21:07.909Z
Learnt from: HA0N1
Repo: Check-Data-Out/velog-dashboard-v2-api PR: 6
File: src/reposities/user.repository.ts:7-15
Timestamp: 2024-12-05T17:21:07.909Z
Learning: Function `findByUserEmail` in `src/reposities/user.repository.ts` is scheduled to be deleted and should be ignored in future code reviews.
Applied to files:
src/repositories/leaderboard.repository.ts
🧬 Code graph analysis (6)
src/services/svg.service.ts (2)
src/types/dto/requests/svgRequest.type.ts (1)
SvgBadgeType(3-3)src/types/dto/responses/svgResponse.type.ts (1)
BadgeDataResponseDto(19-27)
src/controllers/svg.controller.ts (2)
src/services/svg.service.ts (1)
SvgService(5-42)src/types/dto/requests/svgRequest.type.ts (2)
GetSvgBadgeParams(5-7)GetSvgBadgeQuery(9-11)
src/types/dto/requests/svgRequest.type.ts (1)
src/types/index.ts (4)
SvgBadgeType(42-42)GetSvgBadgeParams(42-42)GetSvgBadgeQuery(42-42)GetSvgBadgeQueryDto(43-43)
src/repositories/leaderboard.repository.ts (2)
src/utils/date.util.ts (1)
getKSTDateStringWithOffset(44-57)src/exception/index.ts (2)
NotFoundError(12-12)DBError(2-2)
src/repositories/__test__/leaderboard.repo.test.ts (2)
src/utils/fixtures.ts (2)
mockPool(17-21)createMockQueryResult(70-78)src/exception/index.ts (2)
NotFoundError(12-12)DBError(2-2)
src/services/__test__/svg.service.test.ts (2)
src/services/svg.service.ts (1)
SvgService(5-42)src/repositories/leaderboard.repository.ts (1)
LeaderboardRepository(7-191)
🪛 GitHub Actions: Test CI
src/controllers/svg.controller.ts
[error] 9-9: ESLint: Unexpected any. Specify a different type. @typescript-eslint/no-explicit-any
🪛 GitHub Check: build-and-test (20)
src/controllers/svg.controller.ts
[failure] 9-9:
Unexpected any. Specify a different type
[failure] 9-9:
Unexpected any. Specify a different type
src/routes/svg.router.ts
[failure] 74-74:
Unexpected any. Specify a different type
🪛 GitHub Check: build-and-test (21)
src/controllers/svg.controller.ts
[failure] 9-9:
Unexpected any. Specify a different type
[failure] 9-9:
Unexpected any. Specify a different type
src/routes/svg.router.ts
[failure] 74-74:
Unexpected any. Specify a different type
🪛 GitHub Check: build-and-test (22)
src/controllers/svg.controller.ts
[failure] 9-9:
Unexpected any. Specify a different type
[failure] 9-9:
Unexpected any. Specify a different type
src/routes/svg.router.ts
[failure] 74-74:
Unexpected any. Specify a different type
🪛 GitHub Check: build-and-test (23)
src/controllers/svg.controller.ts
[failure] 9-9:
Unexpected any. Specify a different type
[failure] 9-9:
Unexpected any. Specify a different type
src/routes/svg.router.ts
[failure] 74-74:
Unexpected any. Specify a different type
🔇 Additional comments (12)
src/types/dto/requests/svgRequest.type.ts (1)
13-16: LGTM!DTO 클래스가 올바르게 구현되었습니다.
@IsOptional()과@IsEnum데코레이터를 통한 유효성 검사가 적절하며, 생성자에서 기본값 처리도 잘 되어 있습니다.src/routes/index.ts (1)
7-7: LGTM!SvgRouter가 기존 라우터 패턴과 일관되게 등록되었습니다.
Also applies to: 21-21
src/repositories/__test__/leaderboard.repo.test.ts (2)
188-230: LGTM!
getUserStats테스트 케이스들이 잘 구성되어 있습니다. CTE 쿼리 검증, 파라미터 바인딩, NotFoundError 처리 등 주요 시나리오를 포괄적으로 테스트하고 있습니다.
240-293: LGTM!
getRecentPosts테스트가 적절하게 작성되었습니다. limit 파라미터 바인딩, 정렬 순서, CTE 쿼리 포함 여부 등을 검증하고 있습니다.src/controllers/svg.controller.ts (1)
14-24: LGTM!핸들러 로직이 적절합니다.
username과type추출, 기본값 처리, 서비스 호출, JSON 응답 반환, 그리고 에러 로깅 및 전파가 올바르게 구현되어 있습니다.src/services/svg.service.ts (1)
5-41: LGTM!SvgService 구현이 깔끔합니다. 타입에 따른 조건부 데이터 조회, DB 문자열 값의 숫자 변환, 에러 로깅 및 전파가 적절하게 처리되어 있습니다.
src/routes/svg.router.ts (1)
11-13: LGTM!의존성 주입 체인이 올바르게 구성되어 있습니다. LeaderboardRepository → SvgService → SvgController 순서로 적절하게 연결되어 있습니다.
src/types/dto/responses/svgResponse.type.ts (1)
1-27: LGTM!DTO 정의가 명확하고 적절하게 타입이 지정되어 있습니다.
BadgeUserData,BadgeRecentPost인터페이스와BadgeDataResponseDto클래스가 잘 구성되어 있습니다.src/services/__test__/svg.service.test.ts (1)
61-137: LGTM!테스트 케이스들이 포괄적으로 작성되어 있습니다. default/simple 타입 분기, 숫자 변환, 게시글 데이터 변환, dateRange 전파, 에러 전파 등 핵심 시나리오를 잘 커버하고 있습니다.
src/types/index.ts (1)
41-46: LGTM!SVG 관련 타입 및 DTO export가 기존 파일 구조와 일관되게 추가되었습니다.
export type과export구분도 올바르게 적용되어 있습니다.src/repositories/leaderboard.repository.ts (2)
85-120: LGTM! 에러 핸들링이 적절합니다.
NotFoundError를 재throw하고 다른 예외는DBError로 래핑하는 패턴이 적절합니다.buildLeaderboardCteQuery재사용으로 코드 중복을 줄인 점도 좋습니다.
122-151: LGTM!게시글이 없는 경우 빈 배열을 반환하는 것이 적절한 동작입니다.
released_at DESC정렬로 최신 게시글을 먼저 가져오는 로직이 명확합니다.
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: 1
🧹 Nitpick comments (2)
src/services/svg.service.ts (1)
8-12: username 파라미터 검증을 추가하는 것을 권장합니다.빈 문자열이나 공백만 있는 username이 전달될 경우를 방지하기 위해 입력 검증을 추가하는 것이 좋습니다.
메서드 시작 부분에 검증 로직을 추가할 수 있습니다:
async getBadgeData( username: string, type: SvgBadgeType, dateRange: number = 30, ): Promise<BadgeData> { try { + if (!username || username.trim().length === 0) { + throw new Error('Username is required'); + } + const userStats = await this.leaderboardRepo.getUserStats(username, dateRange);src/types/dto/responses/svgResponse.type.ts (1)
13-24: 날짜 형식에 대한 문서화를 추가하는 것을 고려하세요.
releasedAt필드가 string 타입으로 정의되어 있습니다. ISO 8601 형식 등 예상되는 날짜 형식을 JSDoc 주석으로 명시하면 API 사용자에게 도움이 됩니다.다음과 같이 JSDoc 주석을 추가할 수 있습니다:
export interface BadgeRecentPost { title: string; + /** ISO 8601 date string (e.g., "2025-12-16T15:51:35Z") */ releasedAt: string; viewCount: number; likeCount: number; viewDiff: number; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/controllers/svg.controller.ts(1 hunks)src/services/svg.service.ts(1 hunks)src/types/dto/responses/svgResponse.type.ts(1 hunks)src/types/index.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/types/index.ts
- src/controllers/svg.controller.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/types/dto/responses/svgResponse.type.ts (1)
src/types/dto/responses/baseResponse.type.ts (1)
BaseResponseDto(28-40)
🔇 Additional comments (5)
src/services/svg.service.ts (3)
1-6: LGTM! 의존성 주입 패턴이 잘 적용되었습니다.클래스 구조와 생성자가 깔끔하고, 의존성 주입을 통해 테스트 가능성이 확보되어 있습니다.
37-40: LGTM! 에러 처리가 적절합니다.에러를 로깅하고 재throw하는 패턴은 적절하며, 호출자가 에러를 처리할 수 있도록 합니다.
14-14:andsrc/types/dto/responses/svgResponse.type.ts (2)
1-11: LGTM! 인터페이스 정의가 명확합니다.
BadgeUserData인터페이스가 잘 정의되어 있고, 필드명이 명확하며 타입이 적절합니다.
26-30: LGTM! DTO 클래스 구현이 적절합니다.
BadgeDataResponseDto가BaseResponseDto를 올바르게 확장하고 있으며, 명시적인 타입을 제공하여 코드 가독성과 타입 안정성을 향상시킵니다.
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: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/controllers/svg.controller.ts(1 hunks)src/routes/svg.router.ts(1 hunks)src/services/svg.service.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/routes/svg.router.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/controllers/svg.controller.ts (4)
src/services/svg.service.ts (1)
SvgService(10-41)src/types/index.ts (2)
GetSvgBadgeQuery(42-42)BadgeDataResponseDto(45-45)src/types/dto/requests/svgRequest.type.ts (1)
GetSvgBadgeQuery(9-11)src/types/dto/responses/svgResponse.type.ts (1)
BadgeDataResponseDto(26-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build-and-test (23)
- GitHub Check: build-and-test (20)
- GitHub Check: build-and-test (21)
- GitHub Check: build-and-test (22)
🔇 Additional comments (3)
src/controllers/svg.controller.ts (1)
18-25: 서비스 호출 및 에러 처리 로직이 적절합니다.서비스 계층 호출, 응답 DTO 구성, 그리고 에러 로깅 및 미들웨어 전달이 올바르게 구현되어 있습니다.
src/services/svg.service.ts (2)
5-8: safeNumber 헬퍼 함수가 잘 구현되었습니다.명시적인 리턴 타입과 함께 NaN 처리를 안전하게 수행하고 있으며, null/undefined/잘못된 값에 대해 기본값을 반환하는 로직이 정확합니다.
13-40: getBadgeData 메서드 구현이 우수합니다.
safeNumber함수를 모든 숫자 변환에 일관되게 사용하여 NaN 문제를 방지하고 있습니다.- 조건부로
recentPosts를 가져오는 로직이 효율적입니다.- 에러 로깅 및 재throw 처리가 적절합니다.
| @@ -0,0 +1,27 @@ | |||
| import { NextFunction, RequestHandler, Request, Response } from 'express'; | |||
| import logger from '@/configs/logger.config'; | |||
| import { GetSvgBadgeQuery, BadgeDataResponseDto } from '@/types'; | |||
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.
타입 안전성 개선이 필요합니다.
라인 3에서 GetSvgBadgeParams 타입을 import하지 않고, 라인 10에서 params 타입을 object로 지정한 후 라인 15에서 타입 단언(as { username: string })을 사용하고 있습니다. 이는 TypeScript의 타입 체크를 우회하며, 라우트가 변경될 경우 런타임 에러가 발생할 수 있습니다.
다음 diff를 적용하여 타입 안전성을 개선하세요:
-import { GetSvgBadgeQuery, BadgeDataResponseDto } from '@/types';
+import { GetSvgBadgeQuery, GetSvgBadgeParams, BadgeDataResponseDto } from '@/types';
import { SvgService } from '@/services/svg.service';
export class SvgController {
constructor(private svgService: SvgService) {}
- getSvgBadge: RequestHandler = async (
- req: Request<object, object, object, GetSvgBadgeQuery>,
+ getSvgBadge: RequestHandler<GetSvgBadgeParams, BadgeDataResponseDto, object, GetSvgBadgeQuery> = async (
+ req: Request<GetSvgBadgeParams, BadgeDataResponseDto, object, GetSvgBadgeQuery>,
res: Response<BadgeDataResponseDto>,
next: NextFunction,
) => {
try {
- const { username } = req.params as { username: string };
+ const { username } = req.params;
const { type = 'default' } = req.query;Also applies to: 9-15
🤖 Prompt for AI Agents
In src/controllers/svg.controller.ts around lines 3 and 9-15, improve TypeScript
safety by importing GetSvgBadgeParams from '@/types' and using it for the route
params instead of typed as object and then using a runtime type assertion;
change the handler signature to accept params: GetSvgBadgeParams, remove the `as
{ username: string }` assertion, and use the typed params.username directly (and
update any other uses in lines 9-15 to rely on the strongly typed property).
🔥 변경 사항
🏷 관련 이슈
📸 스크린샷 (UI 변경 시 필수)
📌 체크리스트
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.