Skip to content

feat(store): 인기 매장 리스트 API (popularStores·실시간 랭킹 집계)#145

Merged
chanwoo7 merged 2 commits into
developfrom
feat/popular-stores
Jun 17, 2026
Merged

feat(store): 인기 매장 리스트 API (popularStores·실시간 랭킹 집계)#145
chanwoo7 merged 2 commits into
developfrom
feat/popular-stores

Conversation

@chanwoo7

Copy link
Copy Markdown
Member

Summary

인기 매장 리스트(figma 화면 01) 백엔드인 PR2 / 인기 매장 + 랭킹입니다. 지역 필터·인기순 정렬·매장 카드(평점·리뷰수·위치·케이크 이미지)를 제공하는 popularStores 쿼리를 신설했습니다.

@nestjs/schedule 같은 배치 인프라 도입(위험 게이트)을 피하기 위해 실시간 집계 방식으로 구현했습니다. 스키마 변경·마이그레이션·의존성 추가가 전혀 없습니다.

Scope

  • store feature 신설: popularStores(input) — 비로그인 public query
  • 인기순 추천 공식(상수 분리): w_order·ln(1+최근30일주문) + w_wishlist·ln(1+찜수) + w_rating·베이지안평점
    • 베이지안 평점으로 리뷰 적은 신규 매장 콜드스타트 보정, 동점은 리뷰수→id로 안정 정렬
  • 지역 필터: regionIds(2차 시군구 다중) → store.region_id IN
  • 집계: 찜수·평점/리뷰수·최근 주문수를 Prisma groupBy로 실시간 산출
  • 매장 카드: 순위·매장명·평균평점(소수1)·리뷰수·위치 표기·대표 케이크 이미지(최대 4장)
  • rankedAt: 랭킹 기준 시각(조회 시점) → 화면 'N시 기준' 표기용
  • 테스트 26개: 순수 랭킹 단위 / service 통합 / resolver 통합 / mapper / DTO + factory 보강(store region_id, store-wishlist)

진행 상황

전체 4개 PR 중 2번째 (PR1 지역 마스터는 머지 완료):

Impact

  • DB: 스키마 변경 없음(실시간 집계). 기존 인덱스(store_id·region_id) 활용.
  • API: public query 1종 신규. 기존 변경 없음.
  • 의존성: 추가 없음.
  • 테스트 factory에 store region_id override·store-wishlist 추가(기존 테스트 무영향, 기본값 유지).

Test plan

  • yarn validate 전체 통과 — 152 suites / 1296 tests / 커버리지 임계 충족
  • store 신규 26개: 인기순 정렬·rank, 지역 필터, 평점/리뷰수 집계, 최근 30일 주문 한정, 케이크 이미지 4장 제한, 페이지네이션·hasMore, 비활성 제외, 빈 결과, 위치 표기(시·동/지역명/null), 평점 반올림

후속(이 PR 범위 밖)

  • isWishlisted(매장 찜 상태): PR3에서 매장 찜과 함께 SDL·로직 추가
  • 랭킹 캐시 컬럼 + 배치 스냅샷(@nestjs/schedule): 매장 규모 확대 시 실시간→사전계산 최적화. 의존성 추가라 별도 결정·PR로 분리
  • 위치 표기 규칙(spec "인천 청라동" 형식) 기획 확정 후 정교화
  • 매장 상세 화면(리스트 클릭 진입): 상세 spec 수령 후

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

🧹 knip — dead-code 리포트

요약 항목 없음
전체 리포트
(knip 출력 없음 — 이슈 0이거나 실행 실패)

청소 후보(오탐 가능) · 기준 docs/guide/architecture-conventions.md

@github-actions

Copy link
Copy Markdown

🩺 NestJS Doctor — 88/100 (Good)

진단 252건 (error 0).

Category error warning info
architecture 0 0 12
correctness 0 105 0
performance 0 24 15
schema 0 0 83
security 0 13 0
architecture / security 상위 항목
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'IAuditLogRepository'.
  • warning security/security/no-exposed-env-vars: Direct 'process.env.NODE_ENV' access in 'AuthController'. Use ConfigService instead.
  • warning security/security/require-guards-on-endpoints: Endpoint 'start' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'callback' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'refresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'logout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogin' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerRefresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'devIssueToken' has no @UseGuards() at class or method level.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/conversation/repositories/conversation.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'ConversationRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/order/repositories/order.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'OrderRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/product/repositories/product.repository'.

오탐 포함 가능 · 기준 docs/guide/architecture-conventions.md

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 10c385f8-6738-4a1a-b1ab-bdf95aa9c91f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/popular-stores

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.26214% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...rc/features/store/repositories/store.repository.ts 82.14% 4 Missing and 1 partial ⚠️
...c/features/store/services/store-listing.service.ts 90.24% 2 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 97.38% 3719/3819
🟢 Branches 93.31% 1172/1256
🟢 Functions 94.43% 695/736
🟢 Lines 97.73% 3402/3481

Test suite run success

1302 tests passing in 153 suites.

Report generated by 🧪jest coverage report action from 4ea7aeb

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f33849638

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +94 to +96
order: {
status: { in: [...RANKING_VALID_ORDER_STATUSES] },
created_at: { gte: since },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude soft-deleted orders from rank counts

When an order row has been soft-deleted but its order items remain, this groupBy still counts those items as recent valid orders because the nested order predicate only checks status and created_at; the soft-delete extension in src/prisma/soft-delete.middleware.ts only amends the root OrderItem read, not nested relation filters. That can let removed/test orders continue boosting a store's popularity, so add deleted_at: null inside the order filter.

Useful? React with 👍 / 👎.

Comment on lines +118 to +120
const products = await this.prisma.product.findMany({
where: { store_id: { in: storeIds }, is_active: true, deleted_at: null },
orderBy: [{ store_id: 'asc' }, { id: 'desc' }],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit cake image lookup before materializing products

For stores with more than four active products, this query loads every active product and its first image for every page store, then discards extras in JS after acc.length reaches the card limit. A popular store with thousands of products would make each popularStores request scan and return thousands of rows despite needing at most four URLs per store; cap the query per store before materializing the product rows.

Useful? React with 👍 / 👎.

@chanwoo7

Copy link
Copy Markdown
Member Author

Codex P2 2건 반영 (4ea7aeb): (1) aggregateRecentOrderCounts에 order.deleted_at IS NULL 추가 — nested relation은 soft-delete extension 미적용이라 명시. (2) findStoreCakeImages 매장당 take 4로 쿼리 제한 — 전체 product materialize 제거.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant