Skip to content

Commit

Permalink
[feat] 구매자는 카트를 조회할 수 있다 (#137)
Browse files Browse the repository at this point in the history
* [feat] 조회하고자 하는 카트가 없을 때 발생시킬 예외 구현

* [feat] 구매자의 카트를 조회하는 Dao

* [feat] 카트 조회 앤드포인트

* [feat] 카트를 조회할 수 있는 Dao를 Redis를 이용해서 구현 (#135)

* [test] menu 더미를 저장하고 반환하는 메서드 추가 및 상점을 24시간 열려있도록 수정

* [feat] getter 및 생성자 추가

* [feat] redis용 dao를 임시로 inMemoryCartRepository를 이용해서 구현

* [test] RedisCartDao 테스트코드 추가

* [feat] CartItemInfo에도 getter 추가

* [fix] 테스트 이후 데이터를 롤백시키기 위해 @transactional 추가

* [fix] CartResponse의 amount를 Integer로 수정

* [feat] Jpa를 이용한 카트 조회 dao

---------

Co-authored-by: 김현욱 <43038815+Hyeon-Uk@users.noreply.github.com>
  • Loading branch information
Dr-KoKo and Hyeon-Uk authored Aug 23, 2024
1 parent 8d43c2f commit 0c1986e
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
public enum CartErrorCode implements ErrorCode {
MENU_NOT_FOUND(HttpStatus.NOT_FOUND, "ca_1_1", "해당 메뉴가 존재하지 않습니다."),
OTHER_STORE_MENU(HttpStatus.BAD_REQUEST, "ca_1_2", "다른 매장의 메뉴는 등록할 수 없습니다."),
STORE_NOT_OPEN(HttpStatus.BAD_REQUEST, "ca_1_3", "주문 가능한 시간이 아닙니다.");
STORE_NOT_OPEN(HttpStatus.BAD_REQUEST, "ca_1_3", "주문 가능한 시간이 아닙니다."),
NOT_FOUND(HttpStatus.BAD_REQUEST, "ca_2_1", "카트에 담긴 상품이 없습니다.");

private final int status;
private final String errorCode;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package camp.woowak.lab.cart.exception;

import camp.woowak.lab.common.exception.BadRequestException;

public class NotFoundCartException extends BadRequestException {
public NotFoundCartException(String message) {
super(CartErrorCode.NOT_FOUND, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import camp.woowak.lab.cart.exception.NotFoundCartException;
import camp.woowak.lab.cart.service.CartService;
import camp.woowak.lab.cart.service.command.AddCartCommand;
import camp.woowak.lab.cart.service.command.CartTotalPriceCommand;
import camp.woowak.lab.web.authentication.LoginCustomer;
import camp.woowak.lab.web.authentication.annotation.AuthenticationPrincipal;
import camp.woowak.lab.web.dao.cart.CartDao;
import camp.woowak.lab.web.dto.request.cart.AddCartRequest;
import camp.woowak.lab.web.dto.response.CartResponse;
import camp.woowak.lab.web.dto.response.cart.AddCartResponse;
import camp.woowak.lab.web.dto.response.cart.CartTotalPriceResponse;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -21,9 +24,11 @@
@Slf4j
public class CartApiController {
private final CartService cartService;
private final CartDao cartDao;

public CartApiController(CartService cartService) {
public CartApiController(CartService cartService, CartDao cartDao) {
this.cartService = cartService;
this.cartDao = cartDao;
}

@PostMapping
Expand All @@ -47,4 +52,13 @@ public CartTotalPriceResponse getCartTotalPrice(
log.info("Customer({})'s total price in cart is {}", loginCustomer.getId(), totalPrice);
return new CartTotalPriceResponse(totalPrice);
}

@GetMapping
public CartResponse getMyCart(@AuthenticationPrincipal LoginCustomer loginCustomer) {
CartResponse cartResponse = cartDao.findByCustomerId(loginCustomer.getId());
if (cartResponse == null) {
throw new NotFoundCartException(loginCustomer.getId() + "의 카트가 조회되지 않았습니다.");
}
return cartResponse;
}
}
12 changes: 12 additions & 0 deletions src/main/java/camp/woowak/lab/web/dao/cart/CartDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package camp.woowak.lab.web.dao.cart;

import java.util.UUID;

import camp.woowak.lab.web.dto.response.CartResponse;

public interface CartDao {
/**
* @throws camp.woowak.lab.cart.exception.NotFoundCartException
*/
CartResponse findByCustomerId(UUID customerId);
}
57 changes: 57 additions & 0 deletions src/main/java/camp/woowak/lab/web/dao/cart/JpaCartDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package camp.woowak.lab.web.dao.cart;

import static camp.woowak.lab.cart.persistence.jpa.entity.QCartEntity.*;
import static camp.woowak.lab.cart.persistence.jpa.entity.QCartItemEntity.*;
import static camp.woowak.lab.menu.domain.QMenu.*;
import static camp.woowak.lab.store.domain.QStore.*;

import java.util.UUID;

import org.springframework.stereotype.Repository;

import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;

import camp.woowak.lab.web.dto.response.CartResponse;

@Repository
public class JpaCartDao implements CartDao {
private final JPAQueryFactory queryFactory;

public JpaCartDao(JPAQueryFactory queryFactory) {
this.queryFactory = queryFactory;
}

@Override
public CartResponse findByCustomerId(UUID customerId) {
CartResponse cartResponse = queryFactory
.select(Projections.constructor(CartResponse.class,
store.id,
store.name,
store.minOrderPrice,
Projections.list(
Projections.constructor(CartResponse.CartItemInfo.class,
menu.id,
menu.name,
menu.price,
cartItemEntity.amount,
menu.stockCount
)
)
))
.from(cartEntity)
.leftJoin(cartEntity.cartItems, cartItemEntity)
.join(menu).on(menu.id.eq(cartItemEntity.menuId))
.join(store).on(store.id.eq(cartItemEntity.storeId))
.where(
eqCustomerId(customerId)
)
.fetchOne();
return cartResponse;
}

private BooleanExpression eqCustomerId(UUID customerId) {
return cartEntity.customerId.eq(customerId);
}
}
93 changes: 93 additions & 0 deletions src/main/java/camp/woowak/lab/web/dao/cart/RedisCartDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package camp.woowak.lab.web.dao.cart;

import static camp.woowak.lab.menu.domain.QMenu.*;
import static camp.woowak.lab.store.domain.QStore.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;

import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQueryFactory;

import camp.woowak.lab.cart.domain.Cart;
import camp.woowak.lab.cart.repository.CartRepository;
import camp.woowak.lab.web.dto.response.CartResponse;

@Repository
public class RedisCartDao implements CartDao {
private final CartRepository cartRepository;
private final JPAQueryFactory queryFactory;

public RedisCartDao(@Qualifier("inMemoryCartRepository") CartRepository cartRepository,
JPAQueryFactory queryFactory) {
this.cartRepository = cartRepository;
this.queryFactory = queryFactory;
}

@Override
public CartResponse findByCustomerId(UUID customerId) {
Optional<Cart> optionalCart = cartRepository.findByCustomerId(customerId.toString());
if (optionalCart.isEmpty()) {
return new CartResponse();
}
Cart cart = optionalCart.get();

if (cart.getCartItems().isEmpty()) {
return new CartResponse();
}

Long sId = cart.getCartItems().stream().findAny().get().getStoreId();
Map<Long, Integer> menuIdsCount = new HashMap<>();
Set<Long> menuIds = cart.getCartItems().stream()
.map((cartItem) -> {
int amount = cartItem.getAmount();
menuIdsCount.put(cartItem.getMenuId(), amount);
return cartItem.getMenuId();
})
.collect(Collectors.toSet());

List<Tuple> results = queryFactory
.select(
store.id,
store.name,
store.minOrderPrice,
menu.id,
menu.name,
menu.price,
menu.stockCount
)
.from(store)
.join(menu).on(menu.store.id.eq(store.id))
.where(store.id.eq(sId).and(menu.id.in(menuIds)))
.fetch();

if (results.isEmpty()) {
return null; // 또는 적절한 예외 처리
}

Tuple firstResult = results.get(0);
Long storeId = firstResult.get(store.id);
String storeName = firstResult.get(store.name);
Integer minOrderPrice = firstResult.get(store.minOrderPrice);

List<CartResponse.CartItemInfo> menuList = results.stream()
.map(tuple -> new CartResponse.CartItemInfo(
tuple.get(menu.id),
tuple.get(menu.name),
tuple.get(menu.price),
menuIdsCount.get(tuple.get(menu.id)), // amount 대신 stockCount 사용
tuple.get(menu.stockCount)
))
.collect(Collectors.toList());

return new CartResponse(storeId, storeName, minOrderPrice, menuList);
}
}
41 changes: 41 additions & 0 deletions src/main/java/camp/woowak/lab/web/dto/response/CartResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package camp.woowak.lab.web.dto.response;

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;

@Getter
public class CartResponse {
private Long storeId;
private String storeName;
private Integer minOrderPrice;
private List<CartItemInfo> menus = new ArrayList<>();

public CartResponse() {
}

public CartResponse(Long storId, String storeName, Integer minOrderPrice, List<CartItemInfo> menus) {
this.storeId = storId;
this.storeName = storeName;
this.minOrderPrice = minOrderPrice;
this.menus = menus;
}

@Getter
public static class CartItemInfo {
private Long menuId;
private String menuName;
private Long menuPrice;
private Integer amount;
private Long leftAmount;

public CartItemInfo(Long menuId, String menuName, Long menuPrice, Integer amount, Long leftAmount) {
this.menuId = menuId;
this.menuName = menuName;
this.menuPrice = menuPrice;
this.amount = amount;
this.leftAmount = leftAmount;
}
}
}
84 changes: 84 additions & 0 deletions src/test/java/camp/woowak/lab/web/dao/cart/RedisCartDaoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package camp.woowak.lab.web.dao.cart;

import static org.assertj.core.api.Assertions.*;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import camp.woowak.lab.cart.domain.Cart;
import camp.woowak.lab.cart.repository.CartRepository;
import camp.woowak.lab.customer.domain.Customer;
import camp.woowak.lab.customer.repository.CustomerRepository;
import camp.woowak.lab.menu.domain.Menu;
import camp.woowak.lab.menu.domain.MenuCategory;
import camp.woowak.lab.menu.repository.MenuCategoryRepository;
import camp.woowak.lab.menu.repository.MenuRepository;
import camp.woowak.lab.order.repository.OrderRepository;
import camp.woowak.lab.payaccount.repository.PayAccountRepository;
import camp.woowak.lab.store.domain.Store;
import camp.woowak.lab.store.repository.StoreCategoryRepository;
import camp.woowak.lab.store.repository.StoreRepository;
import camp.woowak.lab.vendor.repository.VendorRepository;
import camp.woowak.lab.web.dao.store.StoreDummiesFixture;
import camp.woowak.lab.web.dto.response.CartResponse;

@SpringBootTest
@Transactional
class RedisCartDaoTest extends StoreDummiesFixture {
private final CartRepository cartRepository;
@Autowired
private RedisCartDao redisCartDao;

@Autowired
public RedisCartDaoTest(StoreRepository storeRepository,
StoreCategoryRepository storeCategoryRepository,
VendorRepository vendorRepository,
PayAccountRepository payAccountRepository,
OrderRepository orderRepository,
CustomerRepository customerRepository,
MenuRepository menuRepository,
MenuCategoryRepository menuCategoryRepository,
CartRepository cartRepository) {
super(storeRepository, storeCategoryRepository, vendorRepository, payAccountRepository, orderRepository,
customerRepository, menuRepository, menuCategoryRepository);
this.cartRepository = cartRepository;
}

private Customer customer;
private Store store;
private MenuCategory menuCategory;
private List<Menu> menus;

@BeforeEach
void setUp() {
customer = createDummyCustomers(1).get(0);
store = createDummyStores(1).get(0);
menuCategory = createDummyMenuCategories(store,1).get(0);
menus = createDummyMenus(store,menuCategory,5);
}

@Test
@DisplayName("findByCustomerId 메서드는 장바구니에 담긴 메뉴의 아이템을 가져온다.")
void findByCustomerIdTest(){
//given
Cart cart = new Cart(customer.getId().toString());
cartRepository.save(cart);
menus.stream()
.forEach(cart::addMenu);
Cart save = cartRepository.save(cart);

//when
CartResponse response = redisCartDao.findByCustomerId(customer.getId());

//then
assertThat(response.getStoreId()).isEqualTo(store.getId());
assertThat(response.getStoreName()).isEqualTo(store.getName());
assertThat(response.getMinOrderPrice()).isEqualTo(store.getMinOrderPrice());
}
}
Loading

0 comments on commit 0c1986e

Please sign in to comment.