Skip to content

Conversation

@seojelee9999
Copy link
Contributor

@seojelee9999 seojelee9999 commented Dec 22, 2025

✨ 변경 사항


20251107 #17 cart domain crud기능추가

✅ 테스트


  • 수동 테스트 완료
  • 테스트 코드 완료

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 장바구니 기능 추가: 상품 추가/수정, 조회, 삭제 가능
    • REST API 엔드포인트를 통한 장바구니 관리 지원
    • 장바구니 항목별 수량, 가격, 상품 정보 표시
    • 사용자별 개인 장바구니 자동 생성 및 관리
    • 장바구니 총액 자동 계산
  • 오류 처리

    • 장바구니 및 항목 관련 새로운 오류 코드 추가

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 22, 2025

Walkthrough

장바구니 기능의 완전한 구현을 추가합니다. Controller, Service, Repository, Entity, DTO 및 보안 유틸리티를 포함하여 장바구니 항목 추가/업데이트, 조회, 삭제 기능을 제공합니다. User 엔티티에 Cart 관계를 추가하고 새로운 에러 코드를 정의합니다.

Changes

Cohort / File(s) Summary
Controller & Documentation
src/main/java/com/campustable/be/domain/cart/controller/CartController.java, CartControllerDocs.java
CartController 구현 및 CartControllerDocs 인터페이스 추가. POST /items (추가/업데이트), GET / (조회), DELETE /{cartId}, DELETE /items/{cartItemId} 엔드포인트 포함.
DTOs
src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java, CartResponse.java, CartItemDto.java
요청/응답 DTO 추가. CartRequest는 menu_id와 quantity 필드, CartResponse는 items, totalPrice, cartId 필드, CartItemDto는 메뉴 정보와 수량을 포함합니다.
Entities
src/main/java/com/campustable/be/domain/cart/entity/Cart.java, CartItem.java
JPA 엔티티 추가. Cart는 User와 일대일, CartItem은 Cart와 Menu와 다대일 관계입니다.
Repositories
src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java, CartItemRepository.java
Spring Data JPA 리포지토리 추가. Cart는 User/userId로 조회, CartItem은 Cart/Cart+Menu 조합으로 조회 가능합니다.
Service
src/main/java/com/campustable/be/domain/cart/service/CartService.java
트랜잭션 범위의 비즈니스 로직 구현. updateCartItem, getCart, deleteCart, deleteCartItem 메서드 포함. 사용자 검증, 메뉴 존재 확인, 접근 권한 확인 로직 포함합니다.
User Entity & Security
src/main/java/com/campustable/be/domain/user/entity/User.java, src/main/java/com/campustable/be/global/common/SecurityUtil.java
User 엔티티에 Cart 일대일 관계 필드 추가. SecurityUtil 유틸리티 클래스 추가하여 보안 컨텍스트에서 현재 사용자 ID 조회 기능 제공합니다.
Exception Handling
src/main/java/com/campustable/be/global/exception/ErrorCode.java
CART_NOT_FOUND, CART_ITEM_NOT_FOUND 에러 코드 추가합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 주의 필요 영역:
    • CartService의 updateCartItem 메서드: 사용자 해석, 메뉴 유효성 검증, 장바구니 생성/업데이트 로직 및 빈 장바구니 처리 확인 필요
    • 보안 관련 코드: SecurityUtil.getCurrentUserId()의 에러 처리 및 USER_NOT_FOUND 예외 발생 로직 검증
    • 양방향 관계: User ↔ Cart ↔ CartItem 관계 설정 및 CASCADE/orphanRemoval 설정 검증
    • 리포지토리 쿼리 메서드: JPA 쿼리 유도 메커니즘이 의도된 동작을 수행하는지 확인

Possibly related PRs

Poem

🐰 장바구니를 가득 담았네요,
Cart와 Item이 춤을 춥니다,
Service 흐르고 Security 지키며,
사용자 정보 안전히 담아,
완성된 기능, 당근처럼 달콤해요! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive PR 제목이 CRUD 기능 추가의 핵심 의도를 포함하지만 날짜/이슈 번호/테스트 라벨로 인해 불필요한 노이즈가 있으며, '20251107', '#17', 'Test'는 실제 변경 내용을 명확히 설명하지 않습니다. PR 제목을 '장바구니 도메인 CRUD 기능 추가' 또는 '장바구니 기능 구현 (추가/조회/삭제)' 등으로 변경하여 더 명확하고 간결하게 개선하세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test

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.

Copy link

@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: 11

🧹 Nitpick comments (6)
src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java (1)

10-10: 수량 필드에 유효성 검증 추가를 고려하세요.

quantity 필드에 대한 유효성 검증(양수 값)을 추가하는 것이 좋습니다. Bean Validation 어노테이션(@Positive 또는 @Min(1))을 사용하거나 서비스 레이어에서 검증을 수행하세요.

🔎 수정 제안
+  @Positive
   private int quantity;

또는

+  @Min(1)
   private int quantity;
src/main/java/com/campustable/be/domain/cart/entity/CartItem.java (2)

16-18: 엔티티 필드 변경 제어 개선 권장

클래스 레벨의 @Setter는 연관 관계 필드(cart, menu)까지 노출하여 의도하지 않은 변경이 가능합니다. quantity 필드만 변경 가능하도록 필드 레벨 @Setter를 사용하는 것이 좋습니다.

🔎 제안하는 수정 사항
 @Entity
 @Getter
-@Setter
 @NoArgsConstructor
 public class CartItem {
 
   public CartItem(Cart cart, Menu menu){
     this.cart = cart;
     this.menu = menu;
   }
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name= "cart_item_id")
   private Long cartItemId;
 
   @ManyToOne(fetch = FetchType.LAZY)
   private Menu menu;
 
   @ManyToOne(fetch = FetchType.LAZY)
   private Cart cart;
 
+  @Setter
   private int quantity;

21-24: 생성자에 수량 파라미터 추가 고려

생성자에서 quantity를 초기화하지 않아 별도로 setQuantity()를 호출해야 합니다. 생성자에 quantity 파라미터를 추가하면 더 명확한 API가 됩니다.

🔎 제안하는 수정 사항
-  public CartItem(Cart cart, Menu menu){
+  public CartItem(Cart cart, Menu menu, int quantity){
     this.cart = cart;
     this.menu = menu;
+    this.quantity = quantity;
   }

CartService (line 63-65)에서도 이에 맞춰 수정:

-    CartItem newItem = new CartItem(cart, menu);
-    newItem.setQuantity(quantity);
-    cartItemRepository.save(newItem);
+    cartItemRepository.save(new CartItem(cart, menu, quantity));
src/main/java/com/campustable/be/domain/cart/service/CartService.java (3)

60-60: 조건문 중괄호 누락

else 블록에 중괄호가 없어 가독성이 떨어지고 향후 수정 시 버그가 발생할 수 있습니다.

🔎 제안하는 수정 사항
       if (quantity == 0){
         cartItemRepository.delete(cartItemOpt.get());
       }
-      else cartItemOpt.get().setQuantity(quantity);
+      else {
+        cartItemOpt.get().setQuantity(quantity);
+      }

129-135: 불필요한 User 엔티티 조회

SecurityUtil.getCurrentUserId()에서 이미 사용자 존재 여부를 검증하므로(USER_NOT_FOUND 예외 발생), User 엔티티를 다시 조회할 필요가 없습니다. cartRepository.findByUserId(userId)를 사용하면 더 효율적입니다.

🔎 제안하는 수정 사항
   public CartResponse getCart(){
     Long userId = SecurityUtil.getCurrentUserId(); // JWT에서 추출한 유저 ID
 
-    User user = userRepository.findById(userId).
-        orElseThrow(() -> {
-          log.error("getCart userId : {}를 db에서 발견하지못했음",userId);
-          return new CustomException(ErrorCode.USER_NOT_FOUND);
-        });
-
-    Optional<Cart> cart = cartRepository.findByUser(user);
+    Optional<Cart> cart = cartRepository.findByUserId(userId);
 
     if (cart.isPresent()) {

참고: CartRepositoryfindByUserId(Long userId) 메서드가 있다고 가정합니다. 없다면 추가해야 합니다.


71-73: 중복된 장바구니 응답 생성 로직

장바구니 아이템 목록을 DTO로 변환하고 총액을 계산하는 로직이 updateCartItemgetCart 메서드에 중복되어 있습니다. 별도의 헬퍼 메서드로 추출하면 유지보수성이 향상됩니다.

🔎 제안하는 수정 사항
private CartResponse buildCartResponse(Cart cart) {
  List<CartItemDto> cartItems = cartItemRepository.findByCart(cart).stream()
      .map(CartItemDto::new)
      .toList();

  int totalPrice = cartItems.stream()
      .mapToInt(item -> item.getPrice() * item.getQuantity())
      .sum();

  return CartResponse.builder()
      .items(cartItems)
      .totalPrice(totalPrice)
      .cartId(cart.getCart_id())
      .build();
}

그런 다음 두 메서드에서 이를 사용:

updateCartItem 메서드:

-    List<CartItemDto> cartItems = cartItemRepository.findByCart(cart).stream().
-        map(CartItemDto::new).
-        toList();
+    cartItemRepository.flush();
+    
+    List<CartItemDto> cartItems = cartItemRepository.findByCart(cart).stream()
+        .map(CartItemDto::new)
+        .toList();
 
     if (cartItems.isEmpty()) {
       cart.getUser().setCart(null);
       cartRepository.delete(cart);
       return CartResponse.builder()
           .items(List.of())
           .totalPrice(0)
           .build();
     }
 
-    int totalPrice = cartItems.stream()
-        .mapToInt(item->item.getPrice() * item.getQuantity())
-        .sum();
-
-    return CartResponse.builder().
-        items(cartItems)
-        .totalPrice(totalPrice)
-        .cartId(cart.getCart_id())
-        .build();
+    return buildCartResponse(cart);

getCart 메서드:

     if (cart.isPresent()) {
-      List<CartItemDto> cartItems = cartItemRepository.findByCart(cart.get()).stream().
-          map(CartItemDto::new).
-          toList();
-
-      int totalPrice = cartItems.stream()
-          .mapToInt(item->item.getPrice() * item.getQuantity())
-          .sum();
-
-      return CartResponse.builder().
-          items(cartItems)
-          .totalPrice(totalPrice)
-          .cartId(cart.get().getCart_id())
-          .build();
+      return buildCartResponse(cart.get());
     }

Also applies to: 138-144

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cca723 and c729bfe.

📒 Files selected for processing (13)
  • src/main/java/com/campustable/be/domain/cart/controller/CartController.java
  • src/main/java/com/campustable/be/domain/cart/controller/CartControllerDocs.java
  • src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java
  • src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java
  • src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java
  • src/main/java/com/campustable/be/domain/cart/entity/Cart.java
  • src/main/java/com/campustable/be/domain/cart/entity/CartItem.java
  • src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java
  • src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java
  • src/main/java/com/campustable/be/domain/cart/service/CartService.java
  • src/main/java/com/campustable/be/domain/user/entity/User.java
  • src/main/java/com/campustable/be/global/common/SecurityUtil.java
  • src/main/java/com/campustable/be/global/exception/ErrorCode.java
🧰 Additional context used
🧬 Code graph analysis (6)
src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java (3)
src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java (1)
  • Getter (6-11)
src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java (1)
  • Getter (8-15)
src/main/java/com/campustable/be/domain/auth/dto/ReissueRequest.java (1)
  • Setter (8-15)
src/main/java/com/campustable/be/domain/cart/controller/CartController.java (1)
src/main/java/com/campustable/be/global/common/SecurityUtil.java (1)
  • Slf4j (9-33)
src/main/java/com/campustable/be/domain/cart/entity/Cart.java (5)
src/main/java/com/campustable/be/domain/cart/entity/CartItem.java (1)
  • Entity (15-41)
src/main/java/com/campustable/be/domain/user/entity/User.java (1)
  • Entity (13-49)
src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java (1)
  • Getter (8-24)
src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java (1)
  • Getter (6-11)
src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java (1)
  • Getter (8-15)
src/main/java/com/campustable/be/domain/cart/service/CartService.java (1)
src/main/java/com/campustable/be/global/common/SecurityUtil.java (1)
  • Slf4j (9-33)
src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java (2)
src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java (1)
  • Getter (8-24)
src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java (1)
  • Getter (8-15)
src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java (3)
src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java (1)
  • Getter (8-24)
src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java (1)
  • Getter (6-11)
src/main/java/com/campustable/be/domain/auth/dto/ReissueRequest.java (1)
  • Setter (8-15)
🔇 Additional comments (7)
src/main/java/com/campustable/be/domain/user/entity/User.java (1)

27-28: 일대일 관계 설정이 적절합니다.

User와 Cart 간의 일대일 관계가 올바르게 구성되었습니다. mappedBy = "user"는 Cart가 관계의 소유자임을 나타내며, cascadeorphanRemoval 설정은 장바구니의 생명주기 관리에 적합합니다.

src/main/java/com/campustable/be/global/exception/ErrorCode.java (1)

68-71: 장바구니 관련 에러 코드가 적절하게 추가되었습니다.

새로운 에러 코드 CART_NOT_FOUNDCART_ITEM_NOT_FOUND가 올바르게 정의되었으며, 메시지도 명확합니다.

src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java (1)

8-14: DTO 구조가 적절합니다.

CartResponse DTO가 깔끔하게 구성되었습니다. Builder 패턴을 사용하여 유연성을 제공하고 있습니다.

src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java (1)

11-18: Repository 구현이 적절합니다.

CartItemRepository의 쿼리 메서드들이 Spring Data JPA 규칙에 따라 올바르게 정의되었습니다. findByCartAndMenufindByCart 메서드는 장바구니 항목 관리에 필요한 기능을 제공합니다.

src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java (1)

9-14: Repository 구현이 적절합니다.

CartRepository의 쿼리 메서드들이 올바르게 정의되었습니다. User 객체와 userId 모두로 조회할 수 있어 유연성을 제공합니다.

src/main/java/com/campustable/be/domain/cart/entity/Cart.java (1)

33-34: @onetomany 관계 설정이 적절합니다.

CartItem과의 일대다 관계가 올바르게 구성되었습니다. cascadeorphanRemoval 설정은 장바구니 항목의 생명주기 관리에 적합합니다.

src/main/java/com/campustable/be/domain/cart/service/CartService.java (1)

121-121: 양방향 관계 동기화는 올바른 패턴입니다

user.setCart(null) 호출은 양방향 관계를 동기화하는 필수적인 방식입니다. JPA 엔티티 간 양방향 관계에서는 cascade나 orphanRemoval 설정과 관계없이 양쪽을 모두 동기화하여야 메모리 내 객체 그래프의 일관성을 보장할 수 있습니다. 이 패턴은 제거 가능한 것이 아니라 유지해야 할 모범 사례입니다.

Likely an incorrect or invalid review comment.

@LogMonitoringInvocation
@Override
@PostMapping("/items")
public ResponseEntity<CartResponse> addOrUpdateItems(@RequestBody CartRequest request) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

요청 본문 유효성 검증 누락

@RequestBody 파라미터에 @Valid 애노테이션이 없어 요청 데이터의 유효성 검증이 이루어지지 않습니다. CartRequest의 필드(menu_id, quantity)에 대한 검증을 추가해야 합니다.

🔎 제안하는 수정 사항

1. Controller에 @Valid 추가

+import jakarta.validation.Valid;
+
-  public ResponseEntity<CartResponse> addOrUpdateItems(@RequestBody CartRequest request) {
+  public ResponseEntity<CartResponse> addOrUpdateItems(@Valid @RequestBody CartRequest request) {

2. CartRequest DTO에 유효성 검증 애노테이션 추가

CartRequest.java 파일에:

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public class CartRequest {
  @NotNull(message = "메뉴 ID는 필수입니다")
  private Long menu_id;
  
  @Min(value = 0, message = "수량은 0 이상이어야 합니다")
  private int quantity;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public ResponseEntity<CartResponse> addOrUpdateItems(@RequestBody CartRequest request) {
import jakarta.validation.Valid;
public ResponseEntity<CartResponse> addOrUpdateItems(@Valid @RequestBody CartRequest request) {
🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/domain/cart/controller/CartController.java
around line 30, the @RequestBody parameter lacks @Valid so incoming CartRequest
fields aren't being validated; add the javax/jakarta.validation @Valid
annotation to the method parameter and update
src/main/java/com/campustable/be/domain/cart/controller/CartRequest.java to
annotate fields with validation constraints (e.g., @NotNull on menu_id and
@Min(0) on quantity) and import the appropriate jakarta.validation annotations;
ensure the project has a validation provider (hibernate-validator) on the
classpath so Spring will enforce these constraints at request binding.


@LogMonitoringInvocation
@Override
@GetMapping("/")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

GET 매핑 경로의 불필요한 슬래시

@GetMapping("/")의 슬래시가 불필요합니다. 일반적인 REST 관례에 따라 빈 문자열을 사용하는 것이 좋습니다.

🔎 제안하는 수정 사항
-  @GetMapping("/")
+  @GetMapping
   public ResponseEntity<CartResponse> getCart(){

이렇게 하면 경로가 /api/cart가 됩니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@GetMapping("/")
@GetMapping
🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/domain/cart/controller/CartController.java
around line 38, the @GetMapping("/") uses an unnecessary trailing slash; replace
it with an empty value (e.g., @GetMapping("") or simply @GetMapping) so the
endpoint maps to /api/cart per REST conventions and avoids a double-slash path.

Comment on lines +92 to +137
@Operation(
summary = "장바구니 삭제",
description = """
### 요청 파라미터
- `cartId` (Long, required): 삭제할 장바구니 ID

### 응답 데이터
- 없음 (HTTP 200 OK)

### 사용 방법
1. 삭제할 장바구니 ID를 Path Variable로 전달합니다.
2. 해당 장바구니가 DB에서 삭제됩니다.

### 유의 사항
- 장바구니 ID는 실제 존재하는 값이어야 합니다.
- 장바구니 삭제 시, 해당 장바구니에 속한 모든 아이템도 함께 삭제됩니다.
- 인증된 사용자만 요청할 수 있습니다.

### 예외 처리
- `INVALID_REQUEST` (400 BAD_REQUEST): 잘못된 요청 형식
"""
)
void deleteCart(@PathVariable Long cartId);

@Operation(
summary = "장바구니 특정 메뉴 삭제",
description = """
### 요청 파라미터
- `cartItemId` (Long, required): 삭제할 장바구니 아이템 ID

### 응답 데이터
- 없음 (HTTP 200 OK)

### 사용 방법
1. 삭제하려는 장바구니 아이템 ID를 Path Variable로 전달합니다.
2. 해당 장바구니 아이템이 DB에서 삭제됩니다.

### 유의 사항
- 장바구니 아이템 ID는 실제 존재하는 값이어야 합니다.
- 인증된 사용자만 요청할 수 있습니다.

### 예외 처리
- `INVALID_REQUEST` (400 BAD_REQUEST): 잘못된 요청 형식
"""
)
void deleteCartItem(@PathVariable Long cartItemId);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

API 문서의 예외 처리 정보 불완전

deleteCartdeleteCartItem 메서드의 문서에서 일부 예외 상황이 누락되었습니다. CartService 구현을 보면 다음 예외들이 발생할 수 있습니다:

  • deleteCart: CART_NOT_FOUND, ACCESS_DENIED
  • deleteCartItem: CART_ITEM_NOT_FOUND, ACCESS_DENIED
🔎 제안하는 수정 사항
       ### 예외 처리
+      - `CART_NOT_FOUND` (404 NOT_FOUND): 해당 장바구니를 찾을 수 없습니다.
+      - `ACCESS_DENIED` (403 FORBIDDEN): 본인의 장바구니만 삭제할 수 있습니다.
       - `INVALID_REQUEST` (400 BAD_REQUEST): 잘못된 요청 형식

그리고 deleteCartItem의 예외 처리 섹션:

       ### 예외 처리
+      - `CART_ITEM_NOT_FOUND` (404 NOT_FOUND): 해당 장바구니 아이템을 찾을 수 없습니다.
+      - `ACCESS_DENIED` (403 FORBIDDEN): 본인의 장바구니 아이템만 삭제할 수 있습니다.
       - `INVALID_REQUEST` (400 BAD_REQUEST): 잘못된 요청 형식
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Operation(
summary = "장바구니 삭제",
description = """
### 요청 파라미터
- `cartId` (Long, required): 삭제할 장바구니 ID
### 응답 데이터
- 없음 (HTTP 200 OK)
### 사용 방법
1. 삭제할 장바구니 ID를 Path Variable로 전달합니다.
2. 해당 장바구니가 DB에서 삭제됩니다.
### 유의 사항
- 장바구니 ID는 실제 존재하는 값이어야 합니다.
- 장바구니 삭제 , 해당 장바구니에 속한 모든 아이템도 함께 삭제됩니다.
- 인증된 사용자만 요청할 있습니다.
### 예외 처리
- `INVALID_REQUEST` (400 BAD_REQUEST): 잘못된 요청 형식
"""
)
void deleteCart(@PathVariable Long cartId);
@Operation(
summary = "장바구니 특정 메뉴 삭제",
description = """
### 요청 파라미터
- `cartItemId` (Long, required): 삭제할 장바구니 아이템 ID
### 응답 데이터
- 없음 (HTTP 200 OK)
### 사용 방법
1. 삭제하려는 장바구니 아이템 ID를 Path Variable로 전달합니다.
2. 해당 장바구니 아이템이 DB에서 삭제됩니다.
### 유의 사항
- 장바구니 아이템 ID는 실제 존재하는 값이어야 합니다.
- 인증된 사용자만 요청할 있습니다.
### 예외 처리
- `INVALID_REQUEST` (400 BAD_REQUEST): 잘못된 요청 형식
"""
)
void deleteCartItem(@PathVariable Long cartItemId);
@Operation(
summary = "장바구니 삭제",
description = """
### 요청 파라미터
- `cartId` (Long, required): 삭제할 장바구니 ID
### 응답 데이터
- 없음 (HTTP 200 OK)
### 사용 방법
1. 삭제할 장바구니 ID를 Path Variable로 전달합니다.
2. 해당 장바구니가 DB에서 삭제됩니다.
### 유의 사항
- 장바구니 ID는 실제 존재하는 값이어야 합니다.
- 장바구니 삭제 , 해당 장바구니에 속한 모든 아이템도 함께 삭제됩니다.
- 인증된 사용자만 요청할 있습니다.
### 예외 처리
- `CART_NOT_FOUND` (404 NOT_FOUND): 해당 장바구니를 찾을 없습니다.
- `ACCESS_DENIED` (403 FORBIDDEN): 본인의 장바구니만 삭제할 있습니다.
- `INVALID_REQUEST` (400 BAD_REQUEST): 잘못된 요청 형식
"""
)
void deleteCart(@PathVariable Long cartId);
@Operation(
summary = "장바구니 특정 메뉴 삭제",
description = """
### 요청 파라미터
- `cartItemId` (Long, required): 삭제할 장바구니 아이템 ID
### 응답 데이터
- 없음 (HTTP 200 OK)
### 사용 방법
1. 삭제하려는 장바구니 아이템 ID를 Path Variable로 전달합니다.
2. 해당 장바구니 아이템이 DB에서 삭제됩니다.
### 유의 사항
- 장바구니 아이템 ID는 실제 존재하는 값이어야 합니다.
- 인증된 사용자만 요청할 있습니다.
### 예외 처리
- `CART_ITEM_NOT_FOUND` (404 NOT_FOUND): 해당 장바구니 아이템을 찾을 없습니다.
- `ACCESS_DENIED` (403 FORBIDDEN): 본인의 장바구니 아이템만 삭제할 있습니다.
- `INVALID_REQUEST` (400 BAD_REQUEST): 잘못된 요청 형식
"""
)
void deleteCartItem(@PathVariable Long cartItemId);
🤖 Prompt for AI Agents
In
src/main/java/com/campustable/be/domain/cart/controller/CartControllerDocs.java
around lines 92-137, the OpenAPI descriptions for deleteCart and deleteCartItem
omit service-level exceptions; update the "예외 처리" sections to list the specific
exceptions and statuses thrown by CartService — for deleteCart add
`CART_NOT_FOUND` (404 NOT_FOUND) and `ACCESS_DENIED` (403 FORBIDDEN); for
deleteCartItem add `CART_ITEM_NOT_FOUND` (404 NOT_FOUND) and `ACCESS_DENIED`
(403 FORBIDDEN); keep existing `INVALID_REQUEST` and ensure wording matches the
project’s error-code convention so the API docs accurately reflect possible
error responses.

Comment on lines +17 to +23
public CartItemDto(CartItem entity) {
this.menuName = entity.getMenu().getMenuName();
this.price = entity.getMenu().getPrice();
this.quantity = entity.getQuantity();
this.menuUrl = entity.getMenu().getMenuUrl();
this.cartItemId = entity.getCartItemId();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

생성자에 null 안전성 검사를 추가하세요.

생성자에서 entity.getMenu()의 null 체크가 없어 NPE(NullPointerException)가 발생할 수 있습니다. Menu가 null인 경우를 대비한 방어 로직이 필요합니다.

🔎 수정 제안
   public CartItemDto(CartItem entity) {
+    if (entity == null || entity.getMenu() == null) {
+      throw new IllegalArgumentException("CartItem과 Menu는 null일 수 없습니다.");
+    }
     this.menuName = entity.getMenu().getMenuName();
     this.price = entity.getMenu().getPrice();
     this.quantity = entity.getQuantity();
     this.menuUrl =  entity.getMenu().getMenuUrl();
     this.cartItemId = entity.getCartItemId();
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public CartItemDto(CartItem entity) {
this.menuName = entity.getMenu().getMenuName();
this.price = entity.getMenu().getPrice();
this.quantity = entity.getQuantity();
this.menuUrl = entity.getMenu().getMenuUrl();
this.cartItemId = entity.getCartItemId();
}
public CartItemDto(CartItem entity) {
if (entity == null || entity.getMenu() == null) {
throw new IllegalArgumentException("CartItem과 Menu는 null일 수 없습니다.");
}
this.menuName = entity.getMenu().getMenuName();
this.price = entity.getMenu().getPrice();
this.quantity = entity.getQuantity();
this.menuUrl = entity.getMenu().getMenuUrl();
this.cartItemId = entity.getCartItemId();
}
🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java around
lines 17-23, the constructor assumes entity and entity.getMenu() are non-null
which can cause NPEs; add null-safety by first validating entity (throw
IllegalArgumentException or handle null) and then check Menu menu =
entity.getMenu(); if menu is null set menuName, price, menuUrl to safe defaults
(null or 0 as appropriate) instead of calling getters, otherwise read values
from menu; preserve quantity and cartItemId from entity after validating entity
is non-null.

@Getter
public class CartRequest {

private Long menu_id;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

필드명 네이밍 컨벤션을 수정하세요.

Java 네이밍 컨벤션에 따라 menu_idmenuId로 변경해야 합니다. snake_case 대신 camelCase를 사용하는 것이 Java 표준입니다.

🔎 수정 제안
-  private Long menu_id;
+  private Long menuId;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private Long menu_id;
private Long menuId;
🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java around line
9, the field name uses snake_case (`menu_id`); rename it to camelCase (`menuId`)
to follow Java conventions, update the corresponding getter/setter names and any
constructors, JSON/property annotations (e.g., @JsonProperty) if present, and
refactor all usages across the codebase to the new name to avoid compile errors.

Comment on lines +30 to +31
@OneToOne
private User user;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

@OnetoOne 관계에 @joincolumn을 추가하세요.

Cart가 User와의 관계에서 소유 측(owning side)이므로 @JoinColumn 어노테이션을 추가하여 외래 키 컬럼을 명시적으로 지정해야 합니다. 이를 생략하면 JPA가 예상치 못한 테이블 구조를 생성할 수 있습니다.

🔎 수정 제안
-  @OneToOne
+  @OneToOne
+  @JoinColumn(name = "user_id")
   private User user;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@OneToOne
private User user;
@OneToOne
@JoinColumn(name = "user_id")
private User user;
🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/domain/cart/entity/Cart.java around lines
30-31, the @OneToOne mapping to User is missing a @JoinColumn; add a @JoinColumn
on the Cart.user field (e.g., @JoinColumn(name = "user_id", referencedColumnName
= "id", nullable = false, unique = true)) to make Cart the owning side and
explicitly define the foreign key column so JPA generates the correct table
structure.

@ManyToOne(fetch = FetchType.LAZY)
private Cart cart;

private int quantity;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

수량 필드 유효성 검증 누락

quantity가 음수가 될 수 있으나 검증이 없습니다. API 문서(CartControllerDocs)에서 음수를 허용하지 않는다고 명시했으므로, 엔티티 레벨 또는 서비스 레벨에서 검증을 추가해야 합니다.

🔎 제안하는 수정 사항

방법 1: JPA 제약 조건 추가 (권장)

+import jakarta.validation.constraints.Min;
+
 @Entity
 @Getter
 @NoArgsConstructor
 public class CartItem {
   // ...
   
+  @Min(value = 0, message = "수량은 0 이상이어야 합니다")
   private int quantity;

방법 2: CartService에서 검증 추가

updateCartItem 메서드에서 quantity 파라미터 검증:

if (quantity < 0) {
  throw new CustomException(ErrorCode.INVALID_REQUEST);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private int quantity;
import jakarta.validation.constraints.Min;
@Entity
@Getter
@NoArgsConstructor
public class CartItem {
// ... other fields
@Min(value = 0, message = "수량은 0 이상이어야 합니다")
private int quantity;
🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/domain/cart/entity/CartItem.java at line 36,
the quantity field lacks validation allowing negative values; add validation to
enforce non-negative quantities — preferably at the entity level by applying a
JPA/Bean Validation constraint (e.g., @Min(0) and make the column non-nullable)
so persistence will reject negatives, and also add a service-level guard in
CartService.updateCartItem to check quantity < 0 and throw the existing
CustomException(ErrorCode.INVALID_REQUEST) as a secondary protection.

Comment on lines +34 to +93
public CartResponse updateCartItem(Long menuId, int quantity) {

Long userId = SecurityUtil.getCurrentUserId(); // JWT에서 추출한 유저 ID

Menu menu = menuRepository.findById(menuId)
.orElseThrow(() -> new CustomException(ErrorCode.MENU_NOT_FOUND));

User user = userRepository.findById(userId).
orElseThrow(() -> {
log.error("addOrIncreaseCartItem userId : {}를 db에서 발견하지못했음",userId);
return new CustomException(ErrorCode.USER_NOT_FOUND);
});

Cart cart = cartRepository.findByUser(user)
.orElseGet(() -> {
log.info("사용자의 장바구니가 없어 새로 생성합니다. userId: {}", userId);

return cartRepository.save(new Cart(user));
});

Optional<CartItem> cartItemOpt = cartItemRepository.findByCartAndMenu(cart, menu);

if (cartItemOpt.isPresent()) {
if (quantity == 0){
cartItemRepository.delete(cartItemOpt.get());
}
else cartItemOpt.get().setQuantity(quantity);
} else {
if (quantity > 0) {
CartItem newItem = new CartItem(cart, menu);
newItem.setQuantity(quantity);
cartItemRepository.save(newItem);
}
}

cartItemRepository.flush();

List<CartItemDto> cartItems = cartItemRepository.findByCart(cart).stream().
map(CartItemDto::new).
toList();

if (cartItems.isEmpty()) {
cart.getUser().setCart(null);
cartRepository.delete(cart);
return CartResponse.builder()
.items(List.of())
.totalPrice(0)
.build();
}

int totalPrice = cartItems.stream()
.mapToInt(item->item.getPrice() * item.getQuantity())
.sum();

return CartResponse.builder().
items(cartItems)
.totalPrice(totalPrice)
.cartId(cart.getCart_id())
.build();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

수량 파라미터 유효성 검증 필요

quantity 파라미터가 음수일 경우에 대한 검증이 없습니다. API 문서에서 음수를 허용하지 않는다고 명시했으므로 메서드 시작 부분에서 검증해야 합니다.

🔎 제안하는 수정 사항
   public CartResponse updateCartItem(Long menuId, int quantity) {
 
+    if (quantity < 0) {
+      throw new CustomException(ErrorCode.INVALID_REQUEST);
+    }
+
     Long userId = SecurityUtil.getCurrentUserId(); // JWT에서 추출한 유저 ID
🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/domain/cart/service/CartService.java around
lines 34-93, add validation at the start of updateCartItem to reject negative
quantity values: if quantity < 0 throw a CustomException with the appropriate
ErrorCode (e.g., INVALID_QUANTITY or INVALID_REQUEST) so negative inputs are
rejected before any DB lookups or modifications; keep existing behavior for
quantity == 0 (delete) and quantity > 0 (create/update).

Comment on lines +95 to +108
public void deleteCartItem(Long cartItemId) {

Long userId = SecurityUtil.getCurrentUserId(); // JWT에서 추출한 유저 ID

CartItem cartItem = cartItemRepository.findById(cartItemId)
.orElseThrow(()-> new CustomException(ErrorCode.CART_ITEM_NOT_FOUND));

if (!cartItem.getCart().getUser().getUserId().equals(userId)) {
throw new CustomException(ErrorCode.ACCESS_DENIED);
}

cartItemRepository.delete(cartItem);

}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

빈 장바구니 정리 로직 누락

deleteCartItem 메서드는 마지막 아이템을 삭제한 후에도 빈 장바구니를 DB에 남겨둡니다. updateCartItem 메서드(line 75-77)에서는 빈 장바구니를 삭제하는데, 일관성을 위해 이 메서드에도 동일한 로직을 추가해야 합니다.

🔎 제안하는 수정 사항
   public void deleteCartItem(Long cartItemId) {
 
     Long userId = SecurityUtil.getCurrentUserId(); // JWT에서 추출한 유저 ID
 
     CartItem cartItem = cartItemRepository.findById(cartItemId)
             .orElseThrow(()-> new CustomException(ErrorCode.CART_ITEM_NOT_FOUND));
 
     if (!cartItem.getCart().getUser().getUserId().equals(userId)) {
       throw new CustomException(ErrorCode.ACCESS_DENIED);
     }
 
+    Cart cart = cartItem.getCart();
     cartItemRepository.delete(cartItem);
+    
+    // 장바구니가 비었으면 삭제
+    cartItemRepository.flush();
+    if (cartItemRepository.findByCart(cart).isEmpty()) {
+      cart.getUser().setCart(null);
+      cartRepository.delete(cart);
+    }
 
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/domain/cart/service/CartService.java around
lines 95-108, the deleteCartItem method removes the CartItem but leaves an empty
Cart in the DB; after deleting the CartItem, fetch the parent Cart (or use
cartItem.getCart()), check whether it has any remaining items (e.g.,
cart.getCartItems().isEmpty() or query count), and if empty call
cartRepository.delete(cart) so empty carts are removed consistently like
updateCartItem does; ensure this runs within the existing transactional context
and that you handle any persistence/cascade semantics appropriately.

Comment on lines +26 to +30
try {
return Long.parseLong(authentication.getName());
} catch (NumberFormatException e) {
throw new RuntimeException("유저 ID 형식이 올바르지 않습니다.");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

예외 처리 일관성 문제

NumberFormatException 발생 시 RuntimeException을 던지는 반면, 인증 정보가 없을 때는 CustomException을 사용하고 있습니다. 일관성을 위해 두 경우 모두 CustomException을 사용하는 것이 좋습니다.

🔎 제안하는 수정 사항
    try {
      return Long.parseLong(authentication.getName());
    } catch (NumberFormatException e) {
-      throw new RuntimeException("유저 ID 형식이 올바르지 않습니다.");
+      log.error("유저 ID 형식이 올바르지 않습니다. authentication.getName(): {}", authentication.getName());
+      throw new CustomException(ErrorCode.INVALID_USER_ID);
    }

참고: ErrorCode.INVALID_USER_ID가 존재하지 않는 경우 ErrorCode enum에 추가해야 합니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/campustable/be/global/common/SecurityUtil.java around lines
26 to 30, the NumberFormatException branch throws a plain RuntimeException which
is inconsistent with the rest of the method that uses CustomException for auth
errors; replace the RuntimeException with throwing a CustomException using
ErrorCode.INVALID_USER_ID (add the enum constant to ErrorCode if it doesn't
exist) so both the "no authentication" and "invalid id format" cases use the
same CustomException/ErrorCode handling.

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.

2 participants