Skip to content

chore: develop → main 릴리즈 (마이페이지 figma 명세 정합화 8건)#81

Merged
chanwoo7 merged 18 commits intomainfrom
develop
Apr 29, 2026
Merged

chore: develop → main 릴리즈 (마이페이지 figma 명세 정합화 8건)#81
chanwoo7 merged 18 commits intomainfrom
develop

Conversation

@chanwoo7
Copy link
Copy Markdown
Member

@chanwoo7 chanwoo7 commented Apr 29, 2026

Summary

기획자 figma 마이페이지 명세(5화면) ↔ 백엔드 구현 정합화 8건을 6개 PR로 분해해 develop에 머지 완료. 본 릴리즈는 그 결과를 main으로 반영한다.

정합화 결과 (8건)

# 항목 화면 PR 변경 유형
1 회원정보 수정 name 필드 추가 + 필수값 검증 03 #77 SDL/service/repo 신규
2 찜 토글 mutation + isWishlisted 노출 + 카운트 일관성 수정 02 #80 SDL/service/repo 신규 + 카운트 버그 수정
3 리뷰 미디어 분리 제한 (사진 10 / 동영상 1) 05 #78 service 정책 강화
4 주문 카드에 hasReviewableItem 노출 04 #79 SDL/service 신규
5 전화번호 정규식 강화 (010-XXXX-XXXX 13자 고정) 03 #76 service 정책 강화
6 생년월일 1900-01-01 이전 거부 (UTC 기준) 03 #75 service 정책 추가
7 카운트 카드 매핑 (커스텀/쿠폰함/찜/나의리뷰) 02 (코드 변경 없음) 매핑 박제
8 주문 상태 탭 매핑 (전체는 CANCELED 포함) 04 (코드 변경 없음) 박제

부수 효과

  • 카운트 일관성 버그 수정: countWishlistItems / getViewerCounts.wishlistCount 가 wishlist soft-delete + product/store active+not-deleted 조건을 모두 적용하도록 통일. 마이페이지 카드 카운트와 myWishlist 목록 길이 불일치 회피
  • DateTime timezone 의존성 제거: normalizeBirthDate를 UTC 자정 기준으로 정규화하여 운영/CI timezone과 무관하게 동작

정량 결과

  • 820 → 867 tests (+47)
  • branches 86.51% → 86.5% (임계값 86 통과 유지)
  • statements 96.57% → 96.69% / functions 93.08% → 93.31% / lines 96.92% → 97.02%
  • 신규 도메인 1: UserWishlistService + 2개 resolver (myWishlist / addToWishlist / removeFromWishlist)

Breaking 여부

  • 부분 Breaking 2건 (DB 데이터 0건 + 프론트 미개발 → 운영 영향 없음)
    • 전화번호 정규식 강화: 비-010 번호, 자릿수 다른 번호 reject
    • 리뷰 미디어 합산 10 → 사진 10 + 동영상 1로 재정의
  • 약한 Breaking 3건 (GraphQL 옵셔널 필드 추가 또는 모르는 필드 무시)
    • UpdateMyProfileInput.name 추가
    • RecentViewedProductSummary.isWishlisted 추가 (non-null)
    • MyOrderSummary.hasReviewableItem 추가 (non-null)
  • 마이그레이션: 없음

Test plan

  • 6개 PR 모두 develop에서 CI 통과 후 머지
  • 로컬 yarn test:cov 통과 (867 tests, 임계값 96/86/92/96 모두 통과)
  • main 머지 시 CI check / coverage-report / pr-title / CodeQL 모두 통과 확인

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • 위시리스트 기능 추가: 상품 추가/삭제 및 페이징 조회 지원
    • 프로필에서 이름 입력/수정 가능
    • 최근 본 상품에 위시리스트 여부(isWishlisted) 표시
    • 주문 목록에 리뷰 작성 가능 여부(hasReviewableItem) 표시
  • 개선사항

    • 휴대폰·생년월일 검증 강화(형식·경계 검증)
    • 미디어 업로드 제한을 이미지/비디오별 개별 기준으로 명확화

chanwoo7 added 16 commits April 30, 2026 00:12
기획자 화면 명세를 로컬에서 관리하는 .figma/ 디렉터리를 ignore에 등록.
스크린샷/스펙 텍스트는 작업자 본인 로컬에서만 참고하도록 한다.
기획 명세에 별도 하한 정책이 없으나, 봇/오입력 방지를 위해
normalizeBirthDate에 1900-01-01 이전 날짜 거부 로직을 추가한다.
비교 기준은 normalize와 동일한 로컬 타임존 자정으로 둔다.

- MIN_BIRTH_DATE 상수 추가 (user.constants.ts)
- 회귀 테스트 2건 추가 (1899-12-31/1850 reject, 1900-01-01 통과)
기존 setHours(0,0,0,0) 로컬 자정 정규화 + 로컬 자정 MIN_BIRTH_DATE는
KST 환경에선 동작하나 음수 오프셋(예: America/New_York) 또는 UTC 환경에서는
'1900-01-01' 같은 ISO date string이 1899-12-31로 정렬되어 부당하게 reject되는
회귀가 있었음 (Codex 리뷰 P2).

GraphQL DateTime은 ISO string을 UTC로 해석하고 DB 컬럼은 @db.Date(시간 무시)이므로
UTC 자정 기준으로 정규화/비교하도록 통일한다.

- MIN_BIRTH_DATE = new Date(Date.UTC(1900, 0, 1))
- normalize: getUTCFullYear/getUTCMonth/getUTCDate로 UTC 자정 정렬
- 미래 비교의 today도 동일하게 UTC midnight 기준
- 회귀 테스트 입력을 ISO string으로 조정 + getUTCFullYear 검증
feat(user): 생년월일 1900-01-01 이전 입력 거부
기존 ^[0-9-]+\$ + 7~20자 정책은 '01-2-3' 같은 비정상 패턴도 통과시켜
사실상 비검증 상태였음. 기획 figma 명세에 맞춰 010만 13자 고정 규칙으로 강화한다.

- PHONE_REGEX = /^010-\\d{4}-\\d{4}\$/ 상수 추가
- PHONE_FORMAT_EXAMPLE 예시 문자열 상수 추가
- MIN_PHONE_LENGTH/MAX_PHONE_LENGTH 제거 (정규식으로 대체)
- normalizePhoneNumber: 정규식 단일 검증으로 단순화 + 명확한 에러 메시지
- 회귀 테스트: 정상 3건 + trim 1건 + 비정상 10건으로 보강
feat(user): 전화번호 정규식 010-XXXX-XXXX 고정
마이페이지 figma 명세에 따라 회원정보 수정 화면에서 이름 변경을 지원한다.
'필수값' 표시에 따라 전송 시 trim 후 빈 문자열은 reject.

- SDL UpdateMyProfileInput에 name: String 추가
- TS DTO 동기화
- service: hasName 분기 + normalizeName 검증 + repository 호출
- repository: updateProfile args에 name 추가, account/user_profile 양쪽 update를
  transaction으로 묶어 부분 실패 방지
- 회귀 테스트 6건 (name 단독/trim/빈값/공백/동시업데이트/미지정 시 유지)
feat(user): 회원정보 수정에 name 필드 추가
기존 정책은 IMAGE/VIDEO 합산 10개였으나, 마이페이지 figma 명세에 따라
사진 10장 / 동영상 1개로 분리한다 (총 11개까지 허용).

- MAX_IMAGE_COUNT(10) / MAX_VIDEO_COUNT(1) 상수 분리
- validateMedia: 단일 카운트에서 type별 카운트로 변경
- 에러 메시지 분리: TOO_MANY_MEDIA → TOO_MANY_IMAGES, TOO_MANY_VIDEOS
- 회귀 테스트 4건 (총 11개 통과 / 사진 11 reject / 동영상 2 reject / 동영상 단독 통과)
feat(user): 리뷰 미디어 분리 제한 (사진 10 / 동영상 1)
마이페이지 figma 04-order-list 명세 '픽업 완료 상태일 경우 리뷰 작성 버튼 노출'을
백엔드 카드 단위로 노출 가능하도록 hasReviewableItem 필드를 추가한다.

조건: status === PICKED_UP && (active 리뷰 미작성 item이 1건이라도 존재).

- SDL MyOrderSummary에 hasReviewableItem: Boolean! 추가
- TS DTO 동기화
- OrderRepository.findReviewableOrderIds 메서드 신규 (단일 IN 쿼리, N+1 회피)
- user-order.service에서 list 매핑 시 set 조회 후 hasReviewableItem 계산
- 회귀 테스트 6건 (미작성/active리뷰/soft-delete/CONFIRMED/CANCELED/혼합)
orderRepository.findReviewableOrderIds의 빈 배열 early return 분기 커버.
codecov patch coverage가 100%가 되도록 보강.
feat(user): 주문 카드에 hasReviewableItem 노출
마이페이지 figma 02-main-login의 찜 기능을 백엔드에 도입한다.
하트 버튼 클릭 시 추가/해제하는 멱등 mutation과 목록 조회, 그리고
최근 본 상품에 isWishlisted 매핑까지 함께 반영한다.

## 변경 사항

- SDL 신규: user-wishlist.graphql (myWishlist / addToWishlist / removeFromWishlist)
- SDL 갱신: RecentViewedProductSummary.isWishlisted: Boolean!
- 신규 service: UserWishlistService (UserBaseService 상속)
- 신규 resolver: UserWishlistQueryResolver / UserWishlistMutationResolver
- UserRepository: upsertWishlistItem / softDeleteWishlistItem /
  findWishlistedProductIds (단일 IN 쿼리, N+1 회피) / findWishlistItems
- ProductRepository: existsActiveProduct (active+soft-delete 검증, 재사용 가능한 가벼운 헬퍼)
- 카운트 일관성 버그 수정: countWishlistItems / getViewerCounts.wishlistCount에
  deleted_at: null 필터 누락 → soft-delete된 위시리스트도 카운트에 포함되던 문제 수정
- user-mypage.service / user-recent-view.service: 매핑 시 isWishlisted 계산
  (findWishlistedProductIds set으로 일괄 조회, N+1 회피)
- 회귀 테스트 다수
  - UserWishlistService spec 15건
  - UserWishlistResolver spec 2건 (NotFound 전파 / 추가→목록→해제 시나리오)
  - mypage spec: recentViewedProducts.isWishlisted 매핑 검증 보강
  - recent-view spec: list isWishlisted 매핑 검증 추가
countWishlistItems / getViewerCounts.wishlistCount 가 wishlist soft-delete만
필터링하고 비활성/삭제 product/store에 연결된 row는 카운트에 포함하던 문제 수정.
findWishlistItems와 동일한 가시성 조건(product/store active+not-deleted)을 공유하도록
visibleWishlistWhere helper로 통일하여 카운트와 목록 길이가 항상 일치하도록 한다.

(Codex 리뷰 P2: count badge vs. list contents 불일치 회피)

- visibleWishlistWhere private helper 도입 (UserRepository)
- getViewerCounts.wishlistCount / countWishlistItems / findWishlistItems 모두 동일 helper 사용
- 회귀 테스트 1건: 4건 wishlist 중 inactive product / soft-delete product /
  inactive store 의 product 3건은 카운트에서 제외, visible 1건만 카운트
feat(user): 찜 토글 + isWishlisted 노출 + 카운트 일관성 수정
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

사용자 위시리스트 기능을 추가하고, 주문 리뷰 가능 판정·프로필의 이름 필드 업데이트·휴대폰·생년월일 검증 규칙 및 이미지/비디오별 미디어 제한을 도입했습니다.

Changes

Cohort / File(s) Summary
Project Configuration
/.gitignore
.figma/ 디렉터리 무시 추가.
Order Repository
src/features/order/repositories/order.repository.ts
PICKED_UP 상태이면서 리뷰가 없거나 소프트 삭제된 주문 ID를 한 쿼리로 조회하는 findReviewableOrderIds 추가.
Product Repository
src/features/product/repositories/product.repository.ts
활성 제품 및 활성 스토어 존재 여부를 경량 조회하는 existsActiveProduct 추가.
User Constants
src/features/user/constants/user-review-error-messages.ts, src/features/user/constants/user-wishlist-error-messages.ts, src/features/user/constants/user.constants.ts
미디어 에러 메시지를 이미지/비디오 분리로 변경(TOO_MANY_IMAGES, TOO_MANY_VIDEOS), 위시리스트 에러 상수 추가, 전화번호 포맷(PHONE_REGEX, PHONE_FORMAT_EXAMPLE) 및 최소 생년월일(MIN_BIRTH_DATE) 도입(길이 상수 제거).
User Repository
src/features/user/repositories/user.repository.ts
updateProfilename? 지원 추가, 위시리스트 관련 idempotent upsert/soft-delete 및 조회/페이징 메서드(upsertWishlistItem, softDeleteWishlistItem, findWishlistedProductIds, findWishlistItems) 추가.
User Services - Core
src/features/user/services/user-base.service.ts, src/features/user/services/user-profile.service.ts
전화번호 정규식 기반 검증 및 에러 메시지 표준화, 생년월일 UTC 기준 검증 및 최소 날짜 적용, 프로필 업데이트에서 name 입력 처리·검증 추가.
User Services - Wishlist
src/features/user/services/user-wishlist.service.ts
위시리스트 추가/제거/조회 기능을 제공하는 UserWishlistService 추가(입력 검증, 페이징, 존재/활성 검사, idempotency, soft-delete 복구).
User Services - Order & View
src/features/user/services/user-order.service.ts, src/features/user/services/user-recent-view.service.ts, src/features/user/services/user-mypage.service.ts
주문 목록에 hasReviewableItem 필드 추가, 최근 본 상품과 마이페이지 항목에 isWishlisted 필드 추가 및 배치 조회로 성능 개선.
User Resolvers
src/features/user/resolvers/user-wishlist-query.resolver.ts, src/features/user/resolvers/user-wishlist-mutation.resolver.ts
myWishlist, addToWishlist, removeFromWishlist GraphQL 리졸버 추가(인증 가드 적용).
User GraphQL Schema
src/features/user/user-wishlist.graphql, src/features/user/user-profile.graphql, src/features/user/user-mypage.graphql, src/features/user/user-order.graphql
위시리스트 쿼리/뮤테이션·타입 추가, 프로필 업데이트 입력에 name 추가, 최근 본 상품·주문에 새 필드 노출.
User Types
src/features/user/types/...
src/features/user/types/user-input.type.ts, src/features/user/types/user-mypage-output.type.ts, src/features/user/types/user-order-output.type.ts, src/features/user/types/user-wishlist-output.type.ts
UpdateMyProfileInputname 추가, RecentViewedProductSummaryisWishlisted 추가, MyOrderSummaryhasReviewableItem 추가, 위시리스트 관련 출력/입력 타입(WishlistItemSummary, MyWishlistConnection, MyWishlistInput) 추가.
User Module & DI
src/features/user/user.module.ts
UserWishlistService, UserWishlistQueryResolver, UserWishlistMutationResolver를 모듈 프로바이더로 등록.
Tests
src/features/user/.../*.spec.ts
전화번호·생년월일 검증 테스트 보강, 리뷰 가능 여부·위시리스트 매핑 테스트 추가, 위시리스트 서비스/리졸버 통합(real DB) 테스트 대거 추가 및 DI 보강(예: UserRepository 제공).

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant WishlistMutationResolver as WishlistMutation<br/>Resolver
    participant WishlistService as UserWishlist<br/>Service
    participant ProductRepo as ProductRepository
    participant UserRepo as UserRepository

    Client->>WishlistMutationResolver: addToWishlist(productId)
    WishlistMutationResolver->>WishlistService: addToWishlist(accountId, productId)
    WishlistService->>ProductRepo: existsActiveProduct(productId)
    ProductRepo-->>WishlistService: boolean
    alt Product exists
        WishlistService->>UserRepo: upsertWishlistItem(accountId, productId, now)
        UserRepo-->>WishlistService: void
        WishlistService-->>WishlistMutationResolver: true
    else Product not found
        WishlistService-->>WishlistMutationResolver: NotFoundException
    end
    WishlistMutationResolver-->>Client: boolean or error
Loading
sequenceDiagram
    actor Client
    participant WishlistQueryResolver as WishlistQuery<br/>Resolver
    participant WishlistService as UserWishlist<br/>Service
    participant UserRepo as UserRepository

    Client->>WishlistQueryResolver: myWishlist(offset, limit)
    WishlistQueryResolver->>WishlistService: myWishlist(accountId, input)
    WishlistService->>UserRepo: findWishlistItems(accountId, offset, limit)
    UserRepo-->>WishlistService: items[], totalCount
    WishlistService->>WishlistService: map items -> WishlistItemSummary[] (대표이미지 선택)
    WishlistService->>WishlistService: compute hasMore from offset+limit vs totalCount
    WishlistService-->>WishlistQueryResolver: MyWishlistConnection
    WishlistQueryResolver-->>Client: paginated wishlist response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed PR 제목이 변경 내용의 핵심을 명확하게 설명합니다. '마이페이지 figma 명세 정합화'는 이 PR의 주요 목표를 정확히 반영하며, '8건'은 구체적인 범위를 나타냅니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 29, 2026

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 96.69% 2894/2993
🟢 Branches 86.5% 1647/1904
🟢 Functions 93.32% 615/659
🟢 Lines 97.02% 2638/2719

Test suite run success

868 tests passing in 77 suites.

Report generated by 🧪jest coverage report action from 97af31f

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Copy link
Copy Markdown

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

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: 911dfecc2e

ℹ️ 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 +445 to +449
where: {
account_id: args.accountId,
deleted_at: null,
product_id: { in: args.productIds },
},
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 Apply visibility filters when marking wishlisted products

findWishlistedProductIds only checks account_id, deleted_at, and product_id, while this same commit made myWishlist/wishlistCount depend on stricter visibility (product.is_active and store.is_active, non-deleted). Because recent-view queries still include products from inactive stores, this can return isWishlisted=true for items that are excluded from both myWishlist and the wishlist count, creating inconsistent mypage/recent-view state for users. Reuse the same visibility predicate here to keep the flag aligned with the exposed wishlist surface.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a 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 (2)
src/features/user/services/user-order.service.ts (1)

54-79: 빈 결과일 때는 리뷰 가능 조회를 생략해도 됩니다.

sliced가 비어 있으면 findReviewableOrderIds를 호출할 필요가 없습니다. 결과는 동일하므로 빈 Set으로 바로 처리하면 불필요한 repository 호출을 줄일 수 있습니다.

♻️ 제안하는 수정
-    const reviewableOrderIds =
-      await this.orderRepository.findReviewableOrderIds({
-        accountId,
-        orderIds: sliced.map((o) => o.id),
-      });
+    const reviewableOrderIds =
+      sliced.length === 0
+        ? new Set<string>()
+        : await this.orderRepository.findReviewableOrderIds({
+            accountId,
+            orderIds: sliced.map((o) => o.id),
+          });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/user/services/user-order.service.ts` around lines 54 - 79, When
sliced is empty, avoid calling this.orderRepository.findReviewableOrderIds and
instead set reviewableOrderIds to an empty Set to save an unnecessary DB query;
update the logic around orderRepository.findReviewableOrderIds (the call that
assigns reviewableOrderIds) to check if sliced.length === 0 and assign new Set()
in that branch, otherwise call findReviewableOrderIds with { accountId,
orderIds: sliced.map(o => o.id) } so the rest of the mapping (using
reviewableOrderIds.has(order.id.toString())) works unchanged.
src/features/user/repositories/user.repository.ts (1)

397-415: 멱등 추가에서 updated_at 갱신은 다시 한번 보세요.

현재 upsertWishlistItem은 이미 active인 항목을 다시 추가해도 updated_at을 바꿉니다. addToWishlist를 진짜 no-op에 가깝게 유지하려면, 여기서는 deleted_at만 복원하도록 두는 편이 더 안전합니다.

🔧 Possible tweak
-      update: { deleted_at: null, updated_at: args.now },
+      update: { deleted_at: null },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/user/repositories/user.repository.ts` around lines 397 - 415,
The upsertWishlistItem implementation currently updates updated_at on every
upsert which refreshes timestamp for already-active items; change the
prisma.wishlistItem.upsert call inside upsertWishlistItem so the update payload
only restores deleted_at (e.g., update: { deleted_at: null }) and does not set
updated_at, leaving timestamps untouched for already-active wishlist rows;
locate the upsertWishlistItem function and modify the prisma.wishlistItem.upsert
update object accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/user/repositories/user.repository.ts`:
- Around line 478-507: The wishlist query in this.prisma.wishlistItem.findMany
uses only orderBy: { created_at: 'desc' } which can produce non-deterministic
ordering for items with identical timestamps; add a stable tie-breaker by
changing the orderBy to a compound sort (e.g., [{ created_at: 'desc' }, { id:
'desc' }] or another unique column) so the results and pagination are
deterministic — update the orderBy in the wishlistItem.findMany call inside the
method that returns { items: rows, totalCount } to include the secondary key.

In `@src/features/user/services/user-review.service.ts`:
- Around line 235-249: Change the validateMedia method signature and related
locals to use a stricter literal union for mediaType instead of string: update
the parameter type from { mediaType: string } to { mediaType: 'IMAGE' | 'VIDEO'
} (and the media array's element type accordingly) in validateMedia; then
simplify the loop inside validateMedia (the for-of over media) to use explicit
checks for 'IMAGE' and 'VIDEO' (or a switch) so the "not VIDEO => IMAGE"
assumption is enforced by the type system; keep the existing throw checks
against MAX_IMAGE_COUNT, MAX_VIDEO_COUNT and USER_REVIEW_ERRORS intact.

---

Nitpick comments:
In `@src/features/user/repositories/user.repository.ts`:
- Around line 397-415: The upsertWishlistItem implementation currently updates
updated_at on every upsert which refreshes timestamp for already-active items;
change the prisma.wishlistItem.upsert call inside upsertWishlistItem so the
update payload only restores deleted_at (e.g., update: { deleted_at: null }) and
does not set updated_at, leaving timestamps untouched for already-active
wishlist rows; locate the upsertWishlistItem function and modify the
prisma.wishlistItem.upsert update object accordingly.

In `@src/features/user/services/user-order.service.ts`:
- Around line 54-79: When sliced is empty, avoid calling
this.orderRepository.findReviewableOrderIds and instead set reviewableOrderIds
to an empty Set to save an unnecessary DB query; update the logic around
orderRepository.findReviewableOrderIds (the call that assigns
reviewableOrderIds) to check if sliced.length === 0 and assign new Set() in that
branch, otherwise call findReviewableOrderIds with { accountId, orderIds:
sliced.map(o => o.id) } so the rest of the mapping (using
reviewableOrderIds.has(order.id.toString())) works unchanged.
🪄 Autofix (Beta)

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

Run ID: a162ef24-44e1-43a9-8dee-3eba2b41a151

📥 Commits

Reviewing files that changed from the base of the PR and between 5582479 and 911dfec.

📒 Files selected for processing (34)
  • .gitignore
  • src/features/order/repositories/order.repository.ts
  • src/features/product/repositories/product.repository.ts
  • src/features/user/constants/user-review-error-messages.ts
  • src/features/user/constants/user-wishlist-error-messages.ts
  • src/features/user/constants/user.constants.ts
  • src/features/user/repositories/user.repository.ts
  • src/features/user/resolvers/user-recent-view.resolver.spec.ts
  • src/features/user/resolvers/user-wishlist-mutation.resolver.ts
  • src/features/user/resolvers/user-wishlist-query.resolver.ts
  • src/features/user/resolvers/user-wishlist.resolver.spec.ts
  • src/features/user/services/user-base.service.spec.ts
  • src/features/user/services/user-base.service.ts
  • src/features/user/services/user-mypage.service.spec.ts
  • src/features/user/services/user-mypage.service.ts
  • src/features/user/services/user-order.service.spec.ts
  • src/features/user/services/user-order.service.ts
  • src/features/user/services/user-profile.service.spec.ts
  • src/features/user/services/user-profile.service.ts
  • src/features/user/services/user-recent-view.service.spec.ts
  • src/features/user/services/user-recent-view.service.ts
  • src/features/user/services/user-review.service.spec.ts
  • src/features/user/services/user-review.service.ts
  • src/features/user/services/user-wishlist.service.spec.ts
  • src/features/user/services/user-wishlist.service.ts
  • src/features/user/types/user-input.type.ts
  • src/features/user/types/user-mypage-output.type.ts
  • src/features/user/types/user-order-output.type.ts
  • src/features/user/types/user-wishlist-output.type.ts
  • src/features/user/user-mypage.graphql
  • src/features/user/user-order.graphql
  • src/features/user/user-profile.graphql
  • src/features/user/user-wishlist.graphql
  • src/features/user/user.module.ts

Comment thread src/features/user/repositories/user.repository.ts
Comment on lines +235 to 249
if (!media || media.length === 0) return;

let imageCount = 0;
let videoCount = 0;
for (const m of media) {
if (m.mediaType === 'VIDEO') videoCount++;
else imageCount++;
}

if (imageCount > MAX_IMAGE_COUNT) {
throw new BadRequestException(USER_REVIEW_ERRORS.TOO_MANY_IMAGES);
}
if (videoCount > MAX_VIDEO_COUNT) {
throw new BadRequestException(USER_REVIEW_ERRORS.TOO_MANY_VIDEOS);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 3 '\bWriteReviewInput\b|mediaType' src/features/user

Repository: CaQuick/caquick-be

Length of output: 16923


🏁 Script executed:

# Find GraphQL enum definition for ReviewMediaType
rg -n 'enum ReviewMediaType|ReviewMediaType' src/features/user --type graphql

Repository: CaQuick/caquick-be

Length of output: 94


🏁 Script executed:

# Check for validation decorators on input types
rg -n '@Is|@Validate|class.*Input' src/features/user/types/user-review-input.type.ts -A 10

Repository: CaQuick/caquick-be

Length of output: 44


🏁 Script executed:

# Check resolver for any validation logic
rg -n '@Mutation|writeReview|@Body|@Validate' src/features/user/resolvers/user-review-mutation.resolver.ts -B 2 -A 10

Repository: CaQuick/caquick-be

Length of output: 1063


🏁 Script executed:

cat src/features/user/user-review.graphql

Repository: CaQuick/caquick-be

Length of output: 1728


🏁 Script executed:

cat src/features/user/types/user-review-input.type.ts

Repository: CaQuick/caquick-be

Length of output: 545


🏁 Script executed:

# Search for ReviewMediaType enum definition anywhere
rg -n 'enum.*ReviewMediaType|ReviewMediaType.*=' src/

Repository: CaQuick/caquick-be

Length of output: 126


내부 validateMedia 메서드의 타입 안정성을 개선하세요.

GraphQL 스키마에서 enum ReviewMediaType { IMAGE VIDEO }로 정의되어 있고, Apollo Server가 API 경계에서 enum 값을 검증하기 때문에 외부 입력은 보호됩니다. 다만 service 내부 메서드는 개선이 필요합니다:

  • Line 233: validateMedia 메서드의 매개변수 타입이 { mediaType: string } 로 제너릭 문자열을 사용합니다.
  • 타입을 { mediaType: 'IMAGE' | 'VIDEO' } 로 변경하면 컴파일 타임에 타입 안정성이 강화됩니다.
  • Line 240-241의 "VIDEO가 아니면 IMAGE" 로직도 타입으로 보호받게 됩니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/user/services/user-review.service.ts` around lines 235 - 249,
Change the validateMedia method signature and related locals to use a stricter
literal union for mediaType instead of string: update the parameter type from {
mediaType: string } to { mediaType: 'IMAGE' | 'VIDEO' } (and the media array's
element type accordingly) in validateMedia; then simplify the loop inside
validateMedia (the for-of over media) to use explicit checks for 'IMAGE' and
'VIDEO' (or a switch) so the "not VIDEO => IMAGE" assumption is enforced by the
type system; keep the existing throw checks against MAX_IMAGE_COUNT,
MAX_VIDEO_COUNT and USER_REVIEW_ERRORS intact.

PR #81 develop→main 릴리즈에 달린 Codex/CodeRabbit 리뷰 반영.

- Codex P2: findWishlistedProductIds도 visibleWishlistWhere 사용하여
  myWishlist/wishlistCount와 동일한 가시성 기준 공유. 비활성 store에 속한
  product가 recent-view에서 isWishlisted=true로 보이지만 myWishlist에는
  안 보이는 모순 회피.
- CodeRabbit Major: findWishlistItems orderBy에 product_id 보조 정렬키 추가.
  같은 밀리초에 생성된 항목의 페이지 경계 흔들림 방지.
- 회귀 테스트 1건: 비활성 store의 product에 wishlist가 있어도
  recent-view list에서 isWishlisted=false로 매핑됨 (가시성 일관)
fix(user): wishlist 가시성 일관성 + 목록 정렬 안정화 (PR #81 리뷰 반영)
@chanwoo7 chanwoo7 changed the title Release: 마이페이지 figma 명세 정합화 (8건) chore: develop → main 릴리즈 (마이페이지 figma 명세 정합화 8건) Apr 29, 2026
@chanwoo7 chanwoo7 merged commit 2c30a2e into main Apr 29, 2026
15 of 16 checks passed
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