Skip to content

feat(store): 찜한 매장 목록 조회(myWishlistedStores)와 매장 프로필 이미지 - #213

Merged
chanwoo7 merged 3 commits into
developfrom
feat/store-wishlist-list
Aug 26, 2026
Merged

feat(store): 찜한 매장 목록 조회(myWishlistedStores)와 매장 프로필 이미지#213
chanwoo7 merged 3 commits into
developfrom
feat/store-wishlist-list

Conversation

@chanwoo7

@chanwoo7 chanwoo7 commented Aug 26, 2026

Copy link
Copy Markdown
Member

배경

찜 화면 시안 중 매장 찜 목록 화면의 백엔드 API입니다.
매장 찜은 지금까지 추가/해제 뮤테이션만 있었고 목록 조회가 없었습니다.
카드에는 매장 프로필(로고)·매장명·평점(리뷰 수)·지역·대표 이미지 3장이 필요한데, 프로필 이미지는 스키마에 없어 컬럼 신설이 함께 들어갑니다.
스펙 문서가 없는 화면이라 정책은 사전에 사용자 문답으로 전부 확정했습니다.

주요 결정 사항

  • 매장 프로필 이미지는 Store 전용 컬럼(profile_image_url) 신설.
    StoreImage는 매장 상세 캐러셀 용도라 첫 장을 프로필로 겸용하지 않기로 했습니다.
    셀러 등록은 기존 sellerUpdateStoreBasicInfo에 옵셔널 profileImageUrl 필드 추가로 처리합니다(URL 문자열 직접 전달 — 셀러 이미지 기존 패턴).
    null 전달 시 제거, 미전달(undefined) 시 유지.
  • 카드 대표 이미지는 StoreImage sort_order asc 상위 3장.
    매장 상세(StoreDetail.images)와 동일 소스입니다.
  • 정렬은 찜 최신순, offset 페이지네이션.
    기존 myWishlist(상품 찜)와 동일한 { items, totalCount, hasMore } 패턴으로 통일했습니다.
  • 비활성·삭제 매장은 목록·카운트에서 제외.
    상품 찜 및 findWishlistedStoreIds의 가시성 조건과 일치시켰습니다.
    soft-delete extension이 nested select에 닿지 않으므로 deleted_at: null을 직접 명시했습니다.
  • 평점은 aggregateReviewStats 재사용.
    리뷰 없으면 0.0/0건, 소수 첫째 자리 반올림(PopularStore와 동일 정책).
  • 목록 조회에 계정 타입 제한은 두지 않았습니다.
    찜 추가가 USER 전용이므로 타 계정은 자연히 빈 목록입니다.

검증

  • yarn validate 통과 (189 suites / 1,620 tests).
  • 회귀 테스트: service 6건(정렬·매핑 / 이미지 3장 제한·삭제 제외 / 평점 집계 / 가시성 제외 / 사용자 격리 / 페이지네이션 경계), resolver 통합 2건, input spec 5건, seller 로고 등록·수정·제거·유지 3건 + DTO 검증 2건.

찜 화면 시안(매장 찜 목록) 기준.
카드에 매장 프로필(로고)·매장명·평점(리뷰 수)·지역·대표 이미지 3장을 표시한다.
스펙 문서가 없어 정책은 사용자 문답으로 확정했다.

정책 결정(시안 외, 사용자 확정):
- 프로필 이미지는 Store 전용 컬럼(profile_image_url) 신설. 셀러 등록은 기존
  sellerUpdateStoreBasicInfo에 필드 추가로 처리(null 전달 시 제거, 미전달 시 유지).
- 카드 대표 이미지는 StoreImage sort_order asc 상위 3장(매장 상세 캐러셀과 동일 소스).
- 정렬은 찜 최신순, offset 페이지네이션(기존 myWishlist와 통일).
- 비활성·삭제 매장은 목록·카운트 제외(상품 찜 가시성 정책과 동일).
- 평점은 리뷰 없으면 0.0/0건, 소수 첫째 자리 반올림(PopularStore와 동일).

변경점:
- prisma: Store.profile_image_url(VarChar 2048, nullable) 마이그레이션 + 시드 반영
- SDL: myWishlistedStores 쿼리·MyWishlistedStoresConnection·WishlistedStoreSummary 신설,
  SellerStore/SellerUpdateStoreBasicInfoInput에 profileImageUrl 추가
- StoreWishlistRepository.findWishlistedStores: 단일 트랜잭션(목록+카운트),
  nested select에 deleted_at: null 명시
- StoreWishlistService.myWishlistedStores: aggregateReviewStats·buildRegionLabel 재사용
- StoreWishlistQueryResolver 신설, 모듈 등록
- store.factory에 profile_image_url override 추가

회귀 테스트 12건:
- service 6: 최신순 정렬·필드 매핑 / 이미지 3장 제한·삭제 이미지 제외 / 평점 집계·0건 0.0 /
  비활성·삭제 매장 및 soft-delete 찜 제외 / 타 사용자 격리 / 페이지네이션 경계
- resolver 2: 전체 경로 통합(목록 반환·빈 목록)
- input 5종·seller 3: profileImageUrl 등록/수정/제거/유지, DTO 검증

Claude-Session: https://claude.ai/code/session_01KfiGgWooJdsa4iPQPmqvBj
@coderabbitai

coderabbitai Bot commented Aug 26, 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 Plus

Run ID: cc9ac8e7-9ae1-4624-b7f1-d56a7e6cbf81

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

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.

@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: 9ef7b0c5b4

ℹ️ 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".

this.prisma.storeWishlistItem.findMany({
where,
// 같은 밀리초 생성 시 페이지 경계 흔들림 방지를 위해 id를 보조 정렬키로 둔다.
orderBy: [{ created_at: 'desc' }, { 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 Sort restored wishlists by their re-add time

When a user removes and later re-adds a store, upsertStoreWishlist restores the existing row by updating updated_at while leaving created_at unchanged. Ordering here by created_at therefore places the restored store at its original, potentially much older position and returns that stale value as addedAt, contradicting the query's newest-first contract. Use the restoration timestamp for ordering/output, or reset created_at when restoring the row.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 복원(재찜) 시 created_at을 재찜 시점으로 갱신해 최신순 정렬과 addedAt 표기가 재찜을 반영하도록 수정. 회귀 테스트 추가.
상품 찜(upsertWishlistItem)도 동일 이슈 — 후속 PR(찜 목록 카드 확장)에서 같은 방식으로 맞출 예정.

@github-actions

Copy link
Copy Markdown

🧹 knip — dead-code 리포트

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

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

@github-actions

Copy link
Copy Markdown

🩺 NestJS Doctor — 89/100 (Good)

진단 282건 (error 0).

Category error warning info
architecture 0 0 13
correctness 0 129 0
performance 0 24 17
schema 0 0 86
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

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 97.63% 4986/5107
🟢 Branches 94.08% 2033/2161
🟢 Functions 96.1% 962/1001
🟢 Lines 98.2% 4537/4620

Test suite run success

1621 tests passing in 189 suites.

Report generated by 🧪jest coverage report action from a87312e

@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.34884% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...es/store/repositories/store-wishlist.repository.ts 84.61% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

PR #213 Codex 리뷰 반영.
soft-delete 복원 시 created_at이 최초 찜 시점으로 남아
'찜 최신순' 정렬과 addedAt 표기가 재찜을 반영하지 못하는 문제 수정.
회귀 테스트 1건 추가(재찜 매장이 목록 최상단으로 온다).

Claude-Session: https://claude.ai/code/session_01KfiGgWooJdsa4iPQPmqvBj

@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: 760dff322b

ℹ️ 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".

update: { deleted_at: null, updated_at: args.now },
// 복원(재찜) 시 created_at도 재찜 시점으로 갱신한다 — 목록 '찜 최신순' 정렬과
// addedAt 표기가 재찜을 반영하도록(과거 찜 시점으로 밀리는 문제 방지).
update: { deleted_at: null, created_at: args.now, updated_at: args.now },

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 Preserve the timestamp for duplicate active wishlist adds

When a client retries addStoreToWishlist for an already-active row, Prisma still executes this update branch and resets created_at. Since findWishlistedStores both orders by and exposes that field as addedAt, a network retry or double tap moves the store to the top and changes its displayed add time despite the mutation's idempotent contract. Update created_at only when restoring a row whose deleted_at was non-null, while leaving active conflicts unchanged.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 복원(updateMany, deleted_at != null)과 신규 생성(create + P2002 멱등 처리)을 분리. active 찜 중복 요청은 created_at을 건드리지 않음. 중복 추가 케이스에 created_at 불변 검증 추가.

PR #213 Codex 리뷰 2차 반영.
upsert update 브랜치가 active 찜에도 실행되어 더블 탭·재시도가
created_at을 리셋(목록 순서·addedAt 변경)하는 문제 수정.
복원(updateMany, deleted_at != null)과 신규 생성(create + P2002 멱등 처리)을
분리해 active 찜 중복 요청은 아무것도 변경하지 않는다.
회귀 테스트: 중복 추가 멱등 케이스에 created_at 불변 검증 추가.

Claude-Session: https://claude.ai/code/session_01KfiGgWooJdsa4iPQPmqvBj
@chanwoo7
chanwoo7 merged commit 5b95ede into develop Aug 26, 2026
11 checks passed
@chanwoo7
chanwoo7 deleted the feat/store-wishlist-list branch August 26, 2026 17:45
chanwoo7 added a commit that referenced this pull request Aug 27, 2026
PR #213 Codex 리뷰 반영.
soft-delete 복원 시 created_at이 최초 찜 시점으로 남아
'찜 최신순' 정렬과 addedAt 표기가 재찜을 반영하지 못하는 문제 수정.
회귀 테스트 1건 추가(재찜 매장이 목록 최상단으로 온다).
chanwoo7 added a commit that referenced this pull request Aug 27, 2026
PR #213 Codex 리뷰 2차 반영.
upsert update 브랜치가 active 찜에도 실행되어 더블 탭·재시도가
created_at을 리셋(목록 순서·addedAt 변경)하는 문제 수정.
복원(updateMany, deleted_at != null)과 신규 생성(create + P2002 멱등 처리)을
분리해 active 찜 중복 요청은 아무것도 변경하지 않는다.
회귀 테스트: 중복 추가 멱등 케이스에 created_at 불변 검증 추가.
chanwoo7 added a commit that referenced this pull request Aug 27, 2026
feat(store): 찜한 매장 목록 조회(myWishlistedStores)와 매장 프로필 이미지
chanwoo7 added a commit that referenced this pull request Aug 27, 2026
PR #213 Codex 리뷰(매장 찜 동일 이슈 2건)를 상품 찜에도 반영.
- soft-delete 복원 시 created_at이 최초 찜 시점으로 남아 '찜 최신순' 정렬과
  addedAt 표기가 재찜을 반영하지 못하는 문제 수정.
- 단, active 찜에 대한 중복 요청(더블 탭·재시도)은 created_at을 건드리지 않도록
  복원(updateMany, deleted_at != null)과 신규 생성(create + P2002 멱등 처리)을 분리.
회귀 테스트 2건: 재찜 상품 최상단 정렬 / 중복 추가 시 created_at 불변.
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