From 721a1cd456ade6f7dabb9f6f035d7249480d0440 Mon Sep 17 00:00:00 2001 From: seojeong Date: Sat, 20 Dec 2025 12:06:55 +0900 Subject: [PATCH 1/3] =?UTF-8?q?cart=5FDomain=5Fcrud=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20:=20feat=20:=20{=EC=9E=A5=EB=B0=94?= =?UTF-8?q?=EA=B5=AC=EB=8B=88=20=EC=83=9D=EC=84=B1=EB=B0=8F=20cartItem?= =?UTF-8?q?=EC=A1=B0=EC=A0=88=20api}=20https://github.com/CampusTable/camp?= =?UTF-8?q?us-table-be/issues/17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cart/controller/CartController.java | 59 ++++++++ .../cart/controller/CartControllerDocs.java | 138 +++++++++++++++++ .../be/domain/cart/dto/CartItemDto.java | 24 +++ .../be/domain/cart/dto/CartRequest.java | 11 ++ .../be/domain/cart/dto/CartResponse.java | 15 ++ .../be/domain/cart/entity/Cart.java | 41 +++++ .../be/domain/cart/entity/CartItem.java | 41 +++++ .../cart/repository/CartItemRepository.java | 18 +++ .../cart/repository/CartRepository.java | 15 ++ .../be/domain/cart/service/CartService.java | 143 ++++++++++++++++++ .../be/domain/user/entity/User.java | 4 + .../be/global/common/SecurityUtil.java | 33 ++++ .../be/global/exception/ErrorCode.java | 5 +- 13 files changed, 546 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/campustable/be/domain/cart/controller/CartController.java create mode 100644 src/main/java/com/campustable/be/domain/cart/controller/CartControllerDocs.java create mode 100644 src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java create mode 100644 src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java create mode 100644 src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java create mode 100644 src/main/java/com/campustable/be/domain/cart/entity/Cart.java create mode 100644 src/main/java/com/campustable/be/domain/cart/entity/CartItem.java create mode 100644 src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java create mode 100644 src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java create mode 100644 src/main/java/com/campustable/be/domain/cart/service/CartService.java create mode 100644 src/main/java/com/campustable/be/global/common/SecurityUtil.java diff --git a/src/main/java/com/campustable/be/domain/cart/controller/CartController.java b/src/main/java/com/campustable/be/domain/cart/controller/CartController.java new file mode 100644 index 0000000..c7a748c --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/controller/CartController.java @@ -0,0 +1,59 @@ +package com.campustable.be.domain.cart.controller; + + +import com.campustable.be.domain.cart.dto.CartRequest; +import com.campustable.be.domain.cart.dto.CartResponse; +import com.campustable.be.domain.cart.service.CartService; +import com.campustable.be.global.aop.LogMonitoringInvocation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/cart") +@RequiredArgsConstructor +@Slf4j +public class CartController implements CartControllerDocs{ + + private final CartService cartService; + + @LogMonitoringInvocation + @Override + @PostMapping("/items") + public ResponseEntity addOrUpdateItems(@RequestBody CartRequest request) { + CartResponse response = cartService.updateCartItem(request.getMenu_id(), request.getQuantity()); + + return ResponseEntity.ok(response); + } + + @LogMonitoringInvocation + @Override + @PostMapping("/") + public ResponseEntity getCart(){ + + CartResponse response = cartService.getCart(); + + return ResponseEntity.ok(response); + } + + + @LogMonitoringInvocation + @Override + @DeleteMapping("/{cartId}") + public void deleteCart(@PathVariable Long cartId) { + cartService.deleteCart(cartId); + } + + @LogMonitoringInvocation + @Override + @DeleteMapping("/cart/items/{cartItemId}") + public void deleteCartItem(@PathVariable Long cartItemId) { + cartService.deleteCartItem(cartItemId); + } +} diff --git a/src/main/java/com/campustable/be/domain/cart/controller/CartControllerDocs.java b/src/main/java/com/campustable/be/domain/cart/controller/CartControllerDocs.java new file mode 100644 index 0000000..88a24c2 --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/controller/CartControllerDocs.java @@ -0,0 +1,138 @@ +package com.campustable.be.domain.cart.controller; + +import com.campustable.be.domain.cart.dto.CartRequest; +import com.campustable.be.domain.cart.dto.CartResponse; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; + +public interface CartControllerDocs { + + @Operation( + summary = "장바구니 메뉴 추가 또는 수량 변경", + description = """ + ### 요청 파라미터 + - `menu_id` (Long, required): 장바구니에 담을 메뉴 ID + - `quantity` (int, required): 담을 수량 + - 1 이상: 해당 수량으로 장바구니에 추가 또는 수정 + - 0: 해당 메뉴를 장바구니에서 삭제 + + ### 응답 데이터 + - `cartId` (Long): 장바구니 ID + - `items` (List): 장바구니에 담긴 메뉴 목록 + - `cartItemId` (Long): 장바구니 아이템 ID + - `menuName` (String): 메뉴 이름 + - `quantity` (int): 담긴 수량 + - `price` (int): 메뉴 단가 + - `menuUrl` (String): 메뉴 이미지 URL + - `totalPrice` (int): 장바구니 전체 금액 (메뉴 단가 × 수량 합계) + + ### 사용 방법 + 1. 로그인 후 JWT 인증이 완료된 상태에서 요청합니다. + 2. 메뉴 ID와 수량을 전달하면, + - 이미 장바구니에 존재하는 메뉴인 경우 수량이 변경됩니다. + - 장바구니에 없는 메뉴인 경우 새로 추가됩니다. + 3. 수량을 0으로 전달하면 해당 메뉴는 장바구니에서 삭제됩니다. + 4. 장바구니에 담긴 모든 메뉴가 삭제되면 장바구니 자체도 함께 삭제됩니다. + 5. 처리 완료 후 현재 장바구니 상태를 응답으로 반환합니다. + + ### 유의 사항 + - 인증되지 않은 사용자는 요청할 수 없습니다. + - `menu_id`는 실제 존재하는 메뉴 ID여야 합니다. + - 수량은 음수 값을 허용하지 않습니다. + - 장바구니는 사용자당 하나만 생성되며, 존재하지 않을 경우 자동 생성됩니다. + - 장바구니가 완전히 비워지면 DB에서 장바구니 엔티티가 삭제됩니다. + + ### 예외 처리 + - `MENU_NOT_FOUND` (404 NOT_FOUND): 해당 메뉴를 찾을 수 없습니다. + - `USER_NOT_FOUND` (404 NOT_FOUND): 유저를 찾을 수 없습니다. + - `ACCESS_DENIED` (403 FORBIDDEN): 인증되지 않은 사용자의 접근 + """ + ) + ResponseEntity addOrUpdateItems( + @RequestBody CartRequest request + ); + + @Operation( + summary = "장바구니 조회", + description = """ + ### 요청 파라미터 + - 없음 (JWT 인증 정보로 사용자 식별) + + ### 응답 데이터 + - `cartId` (Long): 장바구니 ID (존재하는 경우에만 반환) + - `items` (List): 장바구니에 담긴 메뉴 목록 + - `cartItemId` (Long): 장바구니 아이템 ID + - `menuName` (String): 메뉴 이름 + - `quantity` (int): 담긴 수량 + - `price` (int): 메뉴 단가 + - `menuUrl` (String): 메뉴 이미지 URL + - `totalPrice` (int): 장바구니 전체 금액 + + ### 사용 방법 + 1. 로그인 후 JWT 인증이 완료된 상태에서 요청합니다. + 2. 사용자의 장바구니가 존재하면 현재 장바구니 정보를 반환합니다. + 3. 장바구니가 존재하지 않는 경우, + - `items`는 빈 배열 + - `totalPrice`는 0 + - `cartId`는 반환되지 않습니다. + + ### 유의 사항 + - 장바구니가 없는 경우 새로 생성하지 않습니다. + - 조회 API는 장바구니 상태를 변경하지 않습니다. + + ### 예외 처리 + - `USER_NOT_FOUND` (404 NOT_FOUND): 유저를 찾을 수 없습니다. + - `ACCESS_DENIED` (403 FORBIDDEN): 인증되지 않은 사용자의 접근 + """ + ) + ResponseEntity getCart(); + + @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); +} diff --git a/src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java b/src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java new file mode 100644 index 0000000..c814d05 --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/dto/CartItemDto.java @@ -0,0 +1,24 @@ +package com.campustable.be.domain.cart.dto; + + +import com.campustable.be.domain.cart.entity.CartItem; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CartItemDto { + private String menuName; + private int quantity; + private int price; + private String menuUrl; + private Long cartItemId; + + 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(); + } +} diff --git a/src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java b/src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java new file mode 100644 index 0000000..7298afe --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/dto/CartRequest.java @@ -0,0 +1,11 @@ +package com.campustable.be.domain.cart.dto; + + +import lombok.Getter; + +@Getter +public class CartRequest { + + private Long menu_id; + private int quantity; +} diff --git a/src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java b/src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java new file mode 100644 index 0000000..ac677b3 --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/dto/CartResponse.java @@ -0,0 +1,15 @@ +package com.campustable.be.domain.cart.dto; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class CartResponse { + private List items; + private int totalPrice; + private Long cartId; +} diff --git a/src/main/java/com/campustable/be/domain/cart/entity/Cart.java b/src/main/java/com/campustable/be/domain/cart/entity/Cart.java new file mode 100644 index 0000000..f6141ff --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/entity/Cart.java @@ -0,0 +1,41 @@ +package com.campustable.be.domain.cart.entity; + +import com.campustable.be.domain.user.entity.User; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Cart { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "cart_id") + private Long cart_id; + + @OneToOne + private User user; + + @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true) + private List cartItems = new ArrayList<>(); + + public Cart(User user) { + this.user = user; + } + + +} diff --git a/src/main/java/com/campustable/be/domain/cart/entity/CartItem.java b/src/main/java/com/campustable/be/domain/cart/entity/CartItem.java new file mode 100644 index 0000000..3d26381 --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/entity/CartItem.java @@ -0,0 +1,41 @@ +package com.campustable.be.domain.cart.entity; + +import com.campustable.be.domain.menu.entity.Menu; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.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; + + private int quantity; + + + + +} diff --git a/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java b/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java new file mode 100644 index 0000000..39701c2 --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java @@ -0,0 +1,18 @@ +package com.campustable.be.domain.cart.repository; + +import com.campustable.be.domain.cart.entity.Cart; +import com.campustable.be.domain.cart.entity.CartItem; +import com.campustable.be.domain.menu.entity.Menu; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CartItemRepository extends JpaRepository { + + Optional findByCartAndMenu(Cart cart, Menu menu); + + + List findByCart(Cart cart); +} diff --git a/src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java b/src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java new file mode 100644 index 0000000..e83ecfb --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java @@ -0,0 +1,15 @@ +package com.campustable.be.domain.cart.repository; + +import com.campustable.be.domain.cart.entity.Cart; +import com.campustable.be.domain.user.entity.User; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CartRepository extends JpaRepository { + + Optional findByUser(User user); + + +} diff --git a/src/main/java/com/campustable/be/domain/cart/service/CartService.java b/src/main/java/com/campustable/be/domain/cart/service/CartService.java new file mode 100644 index 0000000..26897d2 --- /dev/null +++ b/src/main/java/com/campustable/be/domain/cart/service/CartService.java @@ -0,0 +1,143 @@ +package com.campustable.be.domain.cart.service; + +import com.campustable.be.domain.cart.dto.CartItemDto; +import com.campustable.be.domain.cart.dto.CartResponse; +import com.campustable.be.domain.cart.entity.Cart; +import com.campustable.be.domain.cart.entity.CartItem; +import com.campustable.be.domain.cart.repository.CartItemRepository; +import com.campustable.be.domain.cart.repository.CartRepository; +import com.campustable.be.domain.menu.entity.Menu; +import com.campustable.be.domain.menu.repository.MenuRepository; +import com.campustable.be.domain.user.entity.User; +import com.campustable.be.domain.user.repository.UserRepository; +import com.campustable.be.global.common.SecurityUtil; +import com.campustable.be.global.exception.CustomException; +import com.campustable.be.global.exception.ErrorCode; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional +public class CartService { + + private final CartItemRepository cartItemRepository; + private final UserRepository userRepository; + private final MenuRepository menuRepository; + private final CartRepository cartRepository; + + 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 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 cartItems = cartItemRepository.findByCart(cart).stream(). + map(CartItemDto::new). + toList(); + + if (cartItems.isEmpty()) { + 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(); + } + + public void deleteCartItem(Long cartItemId) { + + cartItemRepository.deleteById(cartItemId); + } + + public void deleteCart(Long cartId) { + Cart cart = cartRepository.findById(cartId) + .orElseThrow(()-> new CustomException(ErrorCode.CART_NOT_FOUND)); + + cart.setUser(null); + + cartRepository.deleteById(cartId); + } + + 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 = cartRepository.findByUser(user); + + if (cart.isPresent()) { + List 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(); + } + else{ + return CartResponse.builder() + .items(List.of()) + .totalPrice(0) + .build(); + } + + + } +} diff --git a/src/main/java/com/campustable/be/domain/user/entity/User.java b/src/main/java/com/campustable/be/domain/user/entity/User.java index be4174e..8843298 100644 --- a/src/main/java/com/campustable/be/domain/user/entity/User.java +++ b/src/main/java/com/campustable/be/domain/user/entity/User.java @@ -1,5 +1,6 @@ package com.campustable.be.domain.user.entity; +import com.campustable.be.domain.cart.entity.Cart; import com.campustable.be.domain.user.dto.UserRequest; import com.campustable.be.global.common.BaseTimeEntity; import jakarta.persistence.*; @@ -23,6 +24,9 @@ public class User extends BaseTimeEntity { @Column(name = "user_id") private Long userId; + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private Cart cart; + @Column(name = "student_number", unique = true, length = 20) private String studentNumber; diff --git a/src/main/java/com/campustable/be/global/common/SecurityUtil.java b/src/main/java/com/campustable/be/global/common/SecurityUtil.java new file mode 100644 index 0000000..562019d --- /dev/null +++ b/src/main/java/com/campustable/be/global/common/SecurityUtil.java @@ -0,0 +1,33 @@ +package com.campustable.be.global.common; + +import com.campustable.be.global.exception.CustomException; +import com.campustable.be.global.exception.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +@Slf4j +public class SecurityUtil { + // 인스턴스화 방지 + private SecurityUtil() {} + + public static Long getCurrentUserId() { + // 1. SecurityContext에서 인증 정보를 꺼냄 + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // 2. 인증 정보가 없거나 익명 사용자인 경우 처리 + if (authentication == null || authentication.getName() == null) { + log.error("SecurityUtil class에서 시큐리티 컨텍스트 홀더에 유저가 존재하지않아 에러발생."); + throw new CustomException(ErrorCode.USER_NOT_FOUND); + } + + // 3. 저장된 유저 ID 반환 (String으로 저장되므로 Long으로 변환) + + try { + return Long.parseLong(authentication.getName()); + } catch (NumberFormatException e) { + throw new RuntimeException("유저 ID 형식이 올바르지 않습니다."); + } + } + +} diff --git a/src/main/java/com/campustable/be/global/exception/ErrorCode.java b/src/main/java/com/campustable/be/global/exception/ErrorCode.java index 3efca12..fe5a8a8 100644 --- a/src/main/java/com/campustable/be/global/exception/ErrorCode.java +++ b/src/main/java/com/campustable/be/global/exception/ErrorCode.java @@ -2,6 +2,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import org.springframework.boot.autoconfigure.graphql.GraphQlProperties.Http; import org.springframework.http.HttpStatus; @Getter @@ -63,8 +64,10 @@ public enum ErrorCode { //User USER_NOT_FOUND(HttpStatus.NOT_FOUND, "유저를 찾을 수 없습니다."), - USER_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 유저입니다."); + USER_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 유저입니다."), + //Cart + CART_NOT_FOUND(HttpStatus.NOT_FOUND, "장바구니를 찾을수 없습니다."); private final HttpStatus status; private final String message; From 0958d0106f53a02bf2b462a4ea3a6812445d3ba3 Mon Sep 17 00:00:00 2001 From: seojeong Date: Mon, 22 Dec 2025 22:33:58 +0900 Subject: [PATCH 2/3] =?UTF-8?q?url=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EB=B0=8F=20=EC=9E=A5=EB=B0=94=EA=B5=AC=EB=8B=88=EC=86=8C?= =?UTF-8?q?=EC=9C=A0=EC=9E=90=EB=A7=8C=EC=9D=B4=20=EC=9E=A5=EB=B0=94?= =?UTF-8?q?=EA=B5=AC=EB=8B=88=20=EC=82=AD=EC=A0=9C=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cart/controller/CartController.java | 5 ++-- .../cart/repository/CartItemRepository.java | 2 ++ .../cart/repository/CartRepository.java | 2 +- .../be/domain/cart/service/CartService.java | 26 ++++++++++++++++--- .../be/global/common/SecurityUtil.java | 2 +- .../be/global/exception/ErrorCode.java | 7 +++-- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/campustable/be/domain/cart/controller/CartController.java b/src/main/java/com/campustable/be/domain/cart/controller/CartController.java index c7a748c..1e10644 100644 --- a/src/main/java/com/campustable/be/domain/cart/controller/CartController.java +++ b/src/main/java/com/campustable/be/domain/cart/controller/CartController.java @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -34,7 +35,7 @@ public ResponseEntity addOrUpdateItems(@RequestBody CartRequest re @LogMonitoringInvocation @Override - @PostMapping("/") + @GetMapping("/") public ResponseEntity getCart(){ CartResponse response = cartService.getCart(); @@ -52,7 +53,7 @@ public void deleteCart(@PathVariable Long cartId) { @LogMonitoringInvocation @Override - @DeleteMapping("/cart/items/{cartItemId}") + @DeleteMapping("/items/{cartItemId}") public void deleteCartItem(@PathVariable Long cartItemId) { cartService.deleteCartItem(cartItemId); } diff --git a/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java b/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java index 39701c2..1c8b924 100644 --- a/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java +++ b/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java @@ -15,4 +15,6 @@ public interface CartItemRepository extends JpaRepository { List findByCart(Cart cart); + + Optional findByCartIdAndCart(Long cartId, Cart cart); } diff --git a/src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java b/src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java index e83ecfb..db5edcc 100644 --- a/src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java +++ b/src/main/java/com/campustable/be/domain/cart/repository/CartRepository.java @@ -11,5 +11,5 @@ public interface CartRepository extends JpaRepository { Optional findByUser(User user); - + Optional findByUserId(Long userId); } diff --git a/src/main/java/com/campustable/be/domain/cart/service/CartService.java b/src/main/java/com/campustable/be/domain/cart/service/CartService.java index 26897d2..f71b274 100644 --- a/src/main/java/com/campustable/be/domain/cart/service/CartService.java +++ b/src/main/java/com/campustable/be/domain/cart/service/CartService.java @@ -73,6 +73,7 @@ public CartResponse updateCartItem(Long menuId, int quantity) { toList(); if (cartItems.isEmpty()) { + cart.getUser().setCart(null); cartRepository.delete(cart); return CartResponse.builder() .items(List.of()) @@ -93,16 +94,33 @@ public CartResponse updateCartItem(Long menuId, int quantity) { public void deleteCartItem(Long cartItemId) { - cartItemRepository.deleteById(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); + } public void deleteCart(Long cartId) { + + Long userId = SecurityUtil.getCurrentUserId(); + Cart cart = cartRepository.findById(cartId) - .orElseThrow(()-> new CustomException(ErrorCode.CART_NOT_FOUND)); + .orElseThrow(() -> new CustomException(ErrorCode.CART_NOT_FOUND)); + + if (!cart.getUser().getUserId().equals(userId)) { + throw new CustomException(ErrorCode.ACCESS_DENIED); + } - cart.setUser(null); + cart.getUser().setCart(null); - cartRepository.deleteById(cartId); + cartRepository.delete(cart); } public CartResponse getCart(){ diff --git a/src/main/java/com/campustable/be/global/common/SecurityUtil.java b/src/main/java/com/campustable/be/global/common/SecurityUtil.java index 562019d..1850d64 100644 --- a/src/main/java/com/campustable/be/global/common/SecurityUtil.java +++ b/src/main/java/com/campustable/be/global/common/SecurityUtil.java @@ -16,7 +16,7 @@ public static Long getCurrentUserId() { final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 2. 인증 정보가 없거나 익명 사용자인 경우 처리 - if (authentication == null || authentication.getName() == null) { + if (authentication == null || authentication.getName() == null || authentication.getName().isBlank()) { log.error("SecurityUtil class에서 시큐리티 컨텍스트 홀더에 유저가 존재하지않아 에러발생."); throw new CustomException(ErrorCode.USER_NOT_FOUND); } diff --git a/src/main/java/com/campustable/be/global/exception/ErrorCode.java b/src/main/java/com/campustable/be/global/exception/ErrorCode.java index fe5a8a8..ca8b8af 100644 --- a/src/main/java/com/campustable/be/global/exception/ErrorCode.java +++ b/src/main/java/com/campustable/be/global/exception/ErrorCode.java @@ -2,7 +2,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import org.springframework.boot.autoconfigure.graphql.GraphQlProperties.Http; import org.springframework.http.HttpStatus; @Getter @@ -67,7 +66,11 @@ public enum ErrorCode { USER_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 유저입니다."), //Cart - CART_NOT_FOUND(HttpStatus.NOT_FOUND, "장바구니를 찾을수 없습니다."); + CART_NOT_FOUND(HttpStatus.NOT_FOUND, "장바구니를 찾을수 없습니다."), + + CART_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "장바구니 개별목록을 찾을수 없습니다."); + + private final HttpStatus status; private final String message; From 1fcfcfa4ab95b38627b2bdf6986d1b3ad5d17e3b Mon Sep 17 00:00:00 2001 From: seojeong Date: Mon, 22 Dec 2025 22:41:00 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EC=BF=BC=EB=A6=AC=EB=AC=B8=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../be/domain/cart/repository/CartItemRepository.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java b/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java index 1c8b924..7a25615 100644 --- a/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java +++ b/src/main/java/com/campustable/be/domain/cart/repository/CartItemRepository.java @@ -15,6 +15,5 @@ public interface CartItemRepository extends JpaRepository { List findByCart(Cart cart); - - Optional findByCartIdAndCart(Long cartId, Cart cart); + }