-
Notifications
You must be signed in to change notification settings - Fork 0
20251107 #17 cart domain crud기능추가Test #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough장바구니 기능의 완전한 구현을 추가합니다. Controller, Service, Repository, Entity, DTO 및 보안 유틸리티를 포함하여 장바구니 항목 추가/업데이트, 조회, 삭제 기능을 제공합니다. User 엔티티에 Cart 관계를 추가하고 새로운 에러 코드를 정의합니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 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 |
There was a problem hiding this 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()) {참고:
CartRepository에findByUserId(Long userId)메서드가 있다고 가정합니다. 없다면 추가해야 합니다.
71-73: 중복된 장바구니 응답 생성 로직장바구니 아이템 목록을 DTO로 변환하고 총액을 계산하는 로직이
updateCartItem과getCart메서드에 중복되어 있습니다. 별도의 헬퍼 메서드로 추출하면 유지보수성이 향상됩니다.🔎 제안하는 수정 사항
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
📒 Files selected for processing (13)
src/main/java/com/campustable/be/domain/cart/controller/CartController.javasrc/main/java/com/campustable/be/domain/cart/controller/CartControllerDocs.javasrc/main/java/com/campustable/be/domain/cart/dto/CartItemDto.javasrc/main/java/com/campustable/be/domain/cart/dto/CartRequest.javasrc/main/java/com/campustable/be/domain/cart/dto/CartResponse.javasrc/main/java/com/campustable/be/domain/cart/entity/Cart.javasrc/main/java/com/campustable/be/domain/cart/entity/CartItem.javasrc/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.javasrc/main/java/com/campustable/be/domain/cart/repository/CartRepository.javasrc/main/java/com/campustable/be/domain/cart/service/CartService.javasrc/main/java/com/campustable/be/domain/user/entity/User.javasrc/main/java/com/campustable/be/global/common/SecurityUtil.javasrc/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가 관계의 소유자임을 나타내며,cascade와orphanRemoval설정은 장바구니의 생명주기 관리에 적합합니다.src/main/java/com/campustable/be/global/exception/ErrorCode.java (1)
68-71: 장바구니 관련 에러 코드가 적절하게 추가되었습니다.새로운 에러 코드
CART_NOT_FOUND와CART_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 규칙에 따라 올바르게 정의되었습니다.
findByCartAndMenu와findByCart메서드는 장바구니 항목 관리에 필요한 기능을 제공합니다.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과의 일대다 관계가 올바르게 구성되었습니다.
cascade와orphanRemoval설정은 장바구니 항목의 생명주기 관리에 적합합니다.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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요청 본문 유효성 검증 누락
@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.
| 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("/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| @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.
| @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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API 문서의 예외 처리 정보 불완전
deleteCart와 deleteCartItem 메서드의 문서에서 일부 예외 상황이 누락되었습니다. CartService 구현을 보면 다음 예외들이 발생할 수 있습니다:
deleteCart:CART_NOT_FOUND,ACCESS_DENIEDdeleteCartItem: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.
| @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.
| 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(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
생성자에 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.
| 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; |
There was a problem hiding this comment.
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_id는 menuId로 변경해야 합니다. 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.
| 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.
| @OneToOne | ||
| private User user; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
| @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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수량 필드 유효성 검증 누락
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.
| 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.
| 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(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수량 파라미터 유효성 검증 필요
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).
| 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); | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
빈 장바구니 정리 로직 누락
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.
| try { | ||
| return Long.parseLong(authentication.getName()); | ||
| } catch (NumberFormatException e) { | ||
| throw new RuntimeException("유저 ID 형식이 올바르지 않습니다."); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예외 처리 일관성 문제
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.
✨ 변경 사항
20251107 #17 cart domain crud기능추가
✅ 테스트
Summary by CodeRabbit
릴리스 노트
새로운 기능
오류 처리
✏️ Tip: You can customize this high-level summary in your review settings.