Conversation
FE 요청 반영. 페이지 내 클라이언트 정렬로는 전체 기준 좋아요순이 불가능해 productReviews와 동일 의미론의 sort(LATEST/LIKES)를 추가한다. - repository를 id 페이지 + hydrate 구조로 재편(product 미러) - 좋아요순은 soft-delete 좋아요 제외 집계 기준이라 raw 키셋 페이지네이션((likeCount, id) 커서) 사용 - "<likeCount>:<id>" 커서 파싱에 안전 정수 검증 포함 - 기존 호출 영향 없음(sort 기본 LATEST, LATEST 커서는 기존 id 방식)
📝 WalkthroughWalkthrough스토어 리뷰 조회에 Changes스토어 리뷰 정렬 및 페이지네이션
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant StoreReviewsResolver
participant StoreReviewService
participant StoreReviewRepository
StoreReviewsResolver->>StoreReviewService: storeReviews(sort, cursor, photoOnly)
StoreReviewService->>StoreReviewRepository: 정렬별 ID 페이지 조회
StoreReviewRepository-->>StoreReviewService: 리뷰 ID 페이지 반환
StoreReviewService->>StoreReviewRepository: 리뷰 본문과 미디어 일괄 조회
StoreReviewRepository-->>StoreReviewService: 리뷰 행 반환
StoreReviewService-->>StoreReviewsResolver: hydrate된 리뷰와 nextCursor 반환
Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
🧹 knip — dead-code 리포트전체 리포트
|
🩺 NestJS Doctor — 89/100 (Good)진단 271건 (error 0).
architecture / security 상위 항목
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/store/services/store-review.service.spec.ts`:
- Around line 60-79: Isolate tests from the real database: in
src/features/store/services/store-review.service.spec.ts lines 60-79, stub
StoreReviewRepository instead of creating accounts or calling
prisma.reviewLike.create; in lines 250-256, remove truncateAll and use a fixed
date for soft-delete assertions. In
src/features/store/resolvers/store-review-query.resolver.spec.ts lines 70-82,
mock StoreReviewService.storeReviews so the resolver tests cover only routing
and response transformation.
In `@src/features/store/services/store-review.service.ts`:
- Around line 55-89: Replace the LIKE cursor contract in store-review.service.ts
lines 55-89 with a stable snapshot/version-based contract that preserves
pagination without duplicates or omissions. In store-review.service.ts lines
129-146, reuse the like counts captured during ID selection or hydrate from the
same snapshot so ordering and returned counts match. In
store-review.repository.ts lines 88-108, make subsequent pages filter and sort
by the same snapshot ordering key instead of the current COUNT(l.id); update the
cursor payload and parsing as needed across these symbols.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: f899cb73-3320-409d-a5e4-82a3b690ce2b
📒 Files selected for processing (7)
src/features/store/constants/store-review-error-messages.tssrc/features/store/dto/inputs/store-reviews.input.tssrc/features/store/repositories/store-review.repository.tssrc/features/store/resolvers/store-review-query.resolver.spec.tssrc/features/store/services/store-review.service.spec.tssrc/features/store/services/store-review.service.tssrc/features/store/store-reviews.graphql
| async function addLikes(reviewId: bigint, count: number) { | ||
| for (let i = 0; i < count; i += 1) { | ||
| const liker = await createAccount(prisma, { account_type: 'USER' }); | ||
| await prisma.reviewLike.create({ | ||
| data: { review_id: reviewId, account_id: liker.id }, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| async function addMedia(reviewId: bigint) { | ||
| await prisma.reviewMedia.create({ | ||
| data: { | ||
| review_id: reviewId, | ||
| media_type: 'IMAGE', | ||
| media_url: 'a.png', | ||
| sort_order: 0, | ||
| }, | ||
| }); | ||
| } | ||
|
|
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline src/features/store/services/store-review.service.spec.ts --items all
ast-grep outline src/features/store/resolvers/store-review-query.resolver.spec.ts --items all
rg -n -C 3 \
'jest\.mock|mockImplementation|mockResolvedValue|stub|PrismaClient|new Prisma|beforeAll|beforeEach|new Date' \
src/features/store/services/store-review.service.spec.ts \
src/features/store/resolvers/store-review-query.resolver.spec.tsRepository: CaQuick/caquick-be
Length of output: 9539
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== store-review.service.spec.ts relevant sections =="
sed -n '1,90p;230,265p' src/features/store/services/store-review.service.spec.ts
echo
echo "== store-review-query.resolver.spec.ts relevant sections =="
sed -n '1,55p;60,85p' src/features/store/resolvers/store-review-query.resolver.spec.ts
echo
echo "== service implementation outline =="
ast-grep outline src/features/store/services/store-review.service.ts --items all || true
echo
echo "== repository implementation outline =="
ast-grep outline src/features/store/repositories/store-review.repository.ts --items all || trueRepository: CaQuick/caquick-be
Length of output: 8530
테스트 의존성을 고립시키세요.
StoreReviewService.spec](src/features/store/services/store-review.service.spec.ts)는 real DB라서 prisma.reviewLike.create, truncateAll, 실제 Prisma 클라이언트가 테스트를 실행 환경과 DB 상태에 의존하게 만듭니다. 서비스 테스트에서는 StoreReviewRepository를 stub하고, soft-delete 시각은 고정된 날짜로 사용하세요. Resolver 테스트는 DB 경로가 아니라 StoreReviewService.storeReviews를 mock해 resolver 라우팅/변환만 검증하세요.
📍 Affects 2 files
src/features/store/services/store-review.service.spec.ts#L60-L79(this comment)src/features/store/services/store-review.service.spec.ts#L250-L256src/features/store/resolvers/store-review-query.resolver.spec.ts#L70-L82
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/store/services/store-review.service.spec.ts` around lines 60 -
79, Isolate tests from the real database: in
src/features/store/services/store-review.service.spec.ts lines 60-79, stub
StoreReviewRepository instead of creating accounts or calling
prisma.reviewLike.create; in lines 250-256, remove truncateAll and use a fixed
date for soft-delete assertions. In
src/features/store/resolvers/store-review-query.resolver.spec.ts lines 70-82,
mock StoreReviewService.storeReviews so the resolver tests cover only routing
and response transformation.
Source: Path instructions
There was a problem hiding this comment.
real-DB 통합 spec은 레포 전역 테스트 컨벤션(createTestingModuleWithRealDb + testcontainers + truncate). 특히 좋아요순은 raw 키셋 SQL·soft-delete 집계 제외가 핵심이라 stub으로는 검증 불가, real DB 경로가 목적에 부합. resolver spec도 기존 전 feature와 동일한 통합 경로 검증 패턴이라 유지.
| /** | ||
| * 정렬별 리뷰 id 페이지 + 다음 커서 계산. | ||
| * | ||
| * 좋아요순 커서는 "<likeCount>:<id>" 불투명 토큰 — 경계 시점의 좋아요 수를 | ||
| * 담아, 이후 좋아요 수가 변해도 페이지가 중복/누락되지 않는다. | ||
| * 최신순 커서는 마지막 리뷰 id. 커서는 동일 sort 안에서만 유효하다. | ||
| */ | ||
| private async fetchReviewIdPage(args: { | ||
| storeId: bigint; | ||
| photoOnly: boolean; | ||
| sort: 'LATEST' | 'LIKES'; | ||
| limit: number; | ||
| cursorRaw?: string; | ||
| }): Promise<{ | ||
| pageIds: bigint[]; | ||
| hasMore: boolean; | ||
| nextCursor: string | null; | ||
| }> { | ||
| if (args.sort === 'LIKES') { | ||
| const rows = await this.repo.listStoreReviewIdsByLikes({ | ||
| storeId: args.storeId, | ||
| photoOnly: args.photoOnly, | ||
| limit: args.limit, | ||
| cursor: args.cursorRaw | ||
| ? this.parseLikesCursor(args.cursorRaw) | ||
| : undefined, | ||
| }); | ||
| const hasMore = rows.length > args.limit; | ||
| const page = hasMore ? rows.slice(0, args.limit) : rows; | ||
| const last = page[page.length - 1]; | ||
| return { | ||
| pageIds: page.map((row) => row.id), | ||
| hasMore, | ||
| nextCursor: hasMore ? `${last.likeCount}:${last.id.toString()}` : null, | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
변경 가능한 좋아요 수로는 현재 커서 계약을 보장할 수 없습니다.
첫 페이지의 경계가 2:100일 때, 이미 반환한 id=200 리뷰의 좋아요 수가 3에서 1로 감소하면 다음 요청의 COUNT(l.id) < 2 조건에 다시 포함됩니다. 아직 반환하지 않은 리뷰의 좋아요 수가 증가하면 반대로 누락됩니다. 또한 hydrate 단계가 좋아요 수를 다시 집계하므로 반환된 likeCount와 항목 순서가 일치하지 않을 수 있습니다.
PR의 중복·누락 없음 계약을 유지하려면 페이지 간 안정적인 스냅샷 또는 버전 토큰을 구현하세요. 안정성을 제공하지 않을 경우 해당 계약을 제거하고 eventual consistency를 명시하세요.
src/features/store/services/store-review.service.ts#L55-L89: 좋아요 수 변경 후에도 안정적이라는 커서 계약을 스냅샷 기반 계약으로 변경하세요.src/features/store/services/store-review.service.ts#L129-L146: ID 페이지 선택 시점의 좋아요 수를 재사용하거나 같은 스냅샷에서 hydrate하세요.src/features/store/repositories/store-review.repository.ts#L88-L108: 후속 페이지가 현재COUNT(l.id)가 아닌 동일 스냅샷의 정렬 키를 사용하도록 변경하세요.
📍 Affects 2 files
src/features/store/services/store-review.service.ts#L55-L89(this comment)src/features/store/services/store-review.service.ts#L129-L146src/features/store/repositories/store-review.repository.ts#L88-L108
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/store/services/store-review.service.ts` around lines 55 - 89,
Replace the LIKE cursor contract in store-review.service.ts lines 55-89 with a
stable snapshot/version-based contract that preserves pagination without
duplicates or omissions. In store-review.service.ts lines 129-146, reuse the
like counts captured during ID selection or hydrate from the same snapshot so
ordering and returned counts match. In store-review.repository.ts lines 88-108,
make subsequent pages filter and sort by the same snapshot ordering key instead
of the current COUNT(l.id); update the cursor payload and parsing as needed
across these symbols.
There was a problem hiding this comment.
#173 Codex 동일 지적에 회신한 수용 트레이드오프. 경계 스냅샷 커서는 인접 페이지 안정화 목적이고, 전역 좋아요 변동까지 막으려면 랭킹 스냅샷/seen-set이 필요해 공개 목록 조회에 과한 비용. 릴리즈된 productReviews와 동일 구현·동일 기준.
Coverage report
Test suite run success1472 tests passing in 173 suites. Report generated by 🧪jest coverage report action from b75c39c |
Summary
storeReviews좋아요순 정렬 추가 릴리즈입니다.feat:StoreReviewsInput.sort(LATEST/LIKES)추가 — FE가 페이지 내 재정렬로는 전체 기준 좋아요순을 만들 수 없던 문제 해결.productReviews와 동일 의미론(soft-delete 좋아요 제외 집계, 동률 최신순,"<likeCount>:<id>"키셋 커서 + 안전 정수 검증).Scope
src/features/store/— SDL(enum·sort 입력)·DTO·repository(id 페이지 + hydrate 재편, raw 키셋)·service·에러 상수 (additive 변경)진행 상황
yarn validate전체 greenImpact
sort: LIKES만 넘기면 됨).productReviews와 동일 규칙. DB 마이그레이션 없음.Test plan
Summary by CodeRabbit
새 기능
버그 수정