From 45c3bb72b9f4e295015d0524b55fda27ba7575c7 Mon Sep 17 00:00:00 2001 From: mirageoasis Date: Sun, 28 Jul 2024 16:39:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=B9=B4=ED=8A=B8=EC=99=80=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EC=9D=98=20=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: mirageoasis --- .../kotlin/store/baribari/demo/MyDataInit.kt | 18 +- .../common/service/CustomOAuth2UserService.kt | 14 +- .../demo/domain/auth/dto/UserSignUpDto.kt | 4 +- .../domain/auth/service/AuthServiceImpl.kt | 14 +- .../baribari/demo/domain/cart/domain/Cart.kt | 4 + .../domain/cart/repository/CartRepository.kt | 12 ++ .../domain/cart/service/CartServiceImpl.kt | 2 + .../domain/cart/service/NewCartServiceImpl.kt | 188 ++++++++++++++++++ .../baribari/demo/domain/user/entity/User.kt | 3 +- 9 files changed, 247 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/store/baribari/demo/domain/cart/service/NewCartServiceImpl.kt diff --git a/src/main/kotlin/store/baribari/demo/MyDataInit.kt b/src/main/kotlin/store/baribari/demo/MyDataInit.kt index 4e4dfe1..bb80116 100644 --- a/src/main/kotlin/store/baribari/demo/MyDataInit.kt +++ b/src/main/kotlin/store/baribari/demo/MyDataInit.kt @@ -198,12 +198,17 @@ class MyDataInit( private fun userCreate(): List { // 37.4927015,127.0615472 400미터 예상 + val customerCart = Cart() + val storeCart = Cart() + // 1차로 flush + cartRepository.saveAllAndFlush(listOf(customerCart, storeCart)) + val customer = User( email = "customer@test.com", password = "customer", role = Role.ROLE_CUSTOMER, - userCart = Cart(), + userCart = customerCart, phoneNumber = "010-1234-5678", nickname = "testuser", position = @@ -220,15 +225,20 @@ class MyDataInit( email = "store@test.com", password = "store", role = Role.ROLE_STORE, - userCart = Cart(), + userCart = storeCart, phoneNumber = "010-9876-5432", nickname = "testuser2", ) val encodedPassword2 = passwordEncoder.encode(storeOwner.password) storeOwner.encodePassword(encodedPassword2) - userRepository.saveAll(listOf(customer, storeOwner)) - userRepository.flush() + val newCustomer = userRepository.saveAndFlush(customer) + val newStoreOwner = userRepository.saveAndFlush(storeOwner) + + // 2차로 save + customerCart.userId = newCustomer.id + storeCart.userId = newStoreOwner.id + cartRepository.saveAll(listOf(customerCart, storeCart)) return listOf(customer, storeOwner) } diff --git a/src/main/kotlin/store/baribari/demo/common/service/CustomOAuth2UserService.kt b/src/main/kotlin/store/baribari/demo/common/service/CustomOAuth2UserService.kt index 2f6b93c..4728cbb 100644 --- a/src/main/kotlin/store/baribari/demo/common/service/CustomOAuth2UserService.kt +++ b/src/main/kotlin/store/baribari/demo/common/service/CustomOAuth2UserService.kt @@ -12,6 +12,7 @@ import store.baribari.demo.domain.auth.OAuth2UserInfoFactory import store.baribari.demo.domain.auth.ProviderType import store.baribari.demo.domain.auth.UserPrincipal import store.baribari.demo.domain.cart.domain.Cart +import store.baribari.demo.domain.cart.repository.CartRepository import store.baribari.demo.domain.user.entity.User import store.baribari.demo.domain.user.repository.UserRepository import java.util.Locale @@ -19,6 +20,7 @@ import java.util.Locale @Service class CustomOAuth2UserService( private val userRepository: UserRepository, + private val cartRepository: CartRepository, ) : DefaultOAuth2UserService() { // 받아온 token을 분석해서 필요한 정보를 넘겨주는 역할 override fun loadUser(userRequest: OAuth2UserRequest?): OAuth2User { @@ -65,17 +67,25 @@ class CustomOAuth2UserService( userInfo: OAuth2UserInfo, providerType: ProviderType, ): User { + // TODO: 추후 삭제하기 + val cart = Cart() + cartRepository.saveAndFlush(cart) + val user = User( email = userInfo.email, providerId = userInfo.id, providerType = providerType, profileImageUrl = userInfo.imageUrl, - userCart = Cart(), + userCart = cart, phoneNumber = userInfo.phoneNumber ?: "", nickname = userInfo.name, ) - return userRepository.saveAndFlush(user) + val uuidUser = userRepository.saveAndFlush(user) + cart.userId = uuidUser.id + cartRepository.saveAndFlush(cart) + + return uuidUser } } diff --git a/src/main/kotlin/store/baribari/demo/domain/auth/dto/UserSignUpDto.kt b/src/main/kotlin/store/baribari/demo/domain/auth/dto/UserSignUpDto.kt index bf82d72..e570cbe 100644 --- a/src/main/kotlin/store/baribari/demo/domain/auth/dto/UserSignUpDto.kt +++ b/src/main/kotlin/store/baribari/demo/domain/auth/dto/UserSignUpDto.kt @@ -11,12 +11,12 @@ data class UserSignUpDto( val password: String, val phoneNumber: String?, ) { - fun toUser(): User = + fun toUser(cart: Cart): User = User( email = email, nickname = nickname, password = password, - userCart = Cart(), + userCart = cart, phoneNumber = phoneNumber, ) } diff --git a/src/main/kotlin/store/baribari/demo/domain/auth/service/AuthServiceImpl.kt b/src/main/kotlin/store/baribari/demo/domain/auth/service/AuthServiceImpl.kt index 6eb03bd..a74a9e3 100644 --- a/src/main/kotlin/store/baribari/demo/domain/auth/service/AuthServiceImpl.kt +++ b/src/main/kotlin/store/baribari/demo/domain/auth/service/AuthServiceImpl.kt @@ -19,6 +19,8 @@ import store.baribari.demo.domain.auth.dto.TokenDto import store.baribari.demo.domain.auth.dto.UserInfoDto import store.baribari.demo.domain.auth.dto.UserLoginRequestDto import store.baribari.demo.domain.auth.dto.UserSignUpDto +import store.baribari.demo.domain.cart.domain.Cart +import store.baribari.demo.domain.cart.repository.CartRepository import store.baribari.demo.domain.user.entity.User import store.baribari.demo.domain.user.repository.UserRepository import java.util.Date @@ -34,13 +36,21 @@ class AuthServiceImpl( private val authTokenProvider: AuthTokenProvider, private val passwordEncoder: BCryptPasswordEncoder, private val redisRepository: RedisRepository, + private val cartRepository: CartRepository, ) : AuthService { override fun saveUser(signUpDto: UserSignUpDto): UUID { checkEmailAndUserName(signUpDto.email, signUpDto.nickname) - val user = signUpDto.toUser() + // TODO: cart 추후에 수정 + val cart = Cart() + cartRepository.saveAndFlush(cart) + val user = signUpDto.toUser(cart) val encodedPassword = passwordEncoder.encode(user.password) user.encodePassword(encodedPassword) - return userRepository.save(user).id!! + val newUser = userRepository.save(user) + cart.userId = newUser.id + cartRepository.saveAndFlush(cart) + + return newUser.id!! } override fun loginUser(userLoginRequestDto: UserLoginRequestDto): UserInfoDto { diff --git a/src/main/kotlin/store/baribari/demo/domain/cart/domain/Cart.kt b/src/main/kotlin/store/baribari/demo/domain/cart/domain/Cart.kt index 0c7c4db..7efc035 100644 --- a/src/main/kotlin/store/baribari/demo/domain/cart/domain/Cart.kt +++ b/src/main/kotlin/store/baribari/demo/domain/cart/domain/Cart.kt @@ -1,6 +1,7 @@ package store.baribari.demo.domain.cart.domain import store.baribari.demo.common.entity.BaseEntity +import java.util.UUID import javax.persistence.Column import javax.persistence.Entity import javax.persistence.GeneratedValue @@ -14,6 +15,9 @@ class Cart( @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "cart_id") var id: Long? = null, + // 임시로 user_id를 배치해 놓았지만 추후에는 제거하는 방식으로 + @Column(name = "user_id", columnDefinition = "BINARY(16)") + var userId: UUID? = null, @OneToMany(mappedBy = "cart") val cartItemList: MutableList = mutableListOf(), ) : BaseEntity() { diff --git a/src/main/kotlin/store/baribari/demo/domain/cart/repository/CartRepository.kt b/src/main/kotlin/store/baribari/demo/domain/cart/repository/CartRepository.kt index 6c9e6e2..a76f995 100644 --- a/src/main/kotlin/store/baribari/demo/domain/cart/repository/CartRepository.kt +++ b/src/main/kotlin/store/baribari/demo/domain/cart/repository/CartRepository.kt @@ -3,6 +3,7 @@ package store.baribari.demo.domain.cart.repository import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import store.baribari.demo.domain.cart.domain.Cart +import java.util.UUID interface CartRepository : JpaRepository { @Query( @@ -15,4 +16,15 @@ interface CartRepository : JpaRepository { """, ) fun findByIdFetchItemListAndDosirak(id: Long): Cart? + + @Query( + """ + SELECT c + FROM Cart c + LEFT JOIN FETCH c.cartItemList ci + left JOIN FETCH ci.dosirak d + WHERE c.userId = :userId + """, + ) + fun findByUserId(userId: UUID): Cart? } diff --git a/src/main/kotlin/store/baribari/demo/domain/cart/service/CartServiceImpl.kt b/src/main/kotlin/store/baribari/demo/domain/cart/service/CartServiceImpl.kt index 120eec2..6201b08 100644 --- a/src/main/kotlin/store/baribari/demo/domain/cart/service/CartServiceImpl.kt +++ b/src/main/kotlin/store/baribari/demo/domain/cart/service/CartServiceImpl.kt @@ -1,5 +1,6 @@ package store.baribari.demo.domain.cart.service +import org.springframework.context.annotation.Primary import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -23,6 +24,7 @@ import store.baribari.demo.domain.user.repository.UserRepository const val MAX_CART_ITEM_AMOUNT = 3 @Service +@Primary class CartServiceImpl( private val cartRepository: CartRepository, private val cartItemRepository: CartItemRepository, diff --git a/src/main/kotlin/store/baribari/demo/domain/cart/service/NewCartServiceImpl.kt b/src/main/kotlin/store/baribari/demo/domain/cart/service/NewCartServiceImpl.kt new file mode 100644 index 0000000..f6e0792 --- /dev/null +++ b/src/main/kotlin/store/baribari/demo/domain/cart/service/NewCartServiceImpl.kt @@ -0,0 +1,188 @@ +package store.baribari.demo.domain.cart.service + +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import store.baribari.demo.common.enums.ErrorCode +import store.baribari.demo.common.exception.ConditionConflictException +import store.baribari.demo.common.exception.EntityNotFoundException +import store.baribari.demo.domain.cart.domain.Cart +import store.baribari.demo.domain.cart.domain.CartItem +import store.baribari.demo.domain.cart.dto.request.AddItemRequestDto +import store.baribari.demo.domain.cart.dto.response.AddCartItemResponseDto +import store.baribari.demo.domain.cart.dto.response.CartInfoResponseDto +import store.baribari.demo.domain.cart.dto.response.CartItemResponseDto +import store.baribari.demo.domain.cart.dto.response.ClearCartResponseDto +import store.baribari.demo.domain.cart.dto.response.DeleteCartItemResponseDto +import store.baribari.demo.domain.cart.dto.response.UpdateItemQuantityResponseDto +import store.baribari.demo.domain.cart.repository.CartItemRepository +import store.baribari.demo.domain.cart.repository.CartRepository +import store.baribari.demo.domain.menu.repository.DosirakRepository +import store.baribari.demo.domain.user.repository.UserRepository + +// userCart를 사용하지 않고 cart를 사용하는 방식의 service +@Service +class NewCartServiceImpl( + private val userRepository: UserRepository, + private val cartRepository: CartRepository, + private val cartItemRepository: CartItemRepository, + private val dosirakRepository: DosirakRepository, +) : CartService { + @Transactional(readOnly = true) + override fun getCart(username: String): CartInfoResponseDto { + // 유저만 가져오기 + val user = + userRepository.findByEmail(username) + ?: throw EntityNotFoundException("$username 이라는 유저는 존재하지 않습니다.") + // itemlist를 조회할 때 fetch join으로 내부에 있는 dosiraklist의 정보를 가져온다. + // querydsl의 시간인가? + // 아니면 entity graph를 사용해야하나? -> 이게 답인거 같다. + // 일단 itemlist를 가져오면서 + val cart = + cartRepository.findByUserId(user.id!!) + ?: throw EntityNotFoundException("해당하는 장바구니가 존재하지 않습니다.") + + val itemList = user.userCart.cartItemList + + return CartInfoResponseDto( + cartId = user.userCart.id!!, + items = itemList.map { CartItemResponseDto.fromCartItem(it) }, + price = itemList.sumOf { it.dosirak.price * it.count }, + ) + } + + @Transactional + override fun addItem( + username: String, + request: AddItemRequestDto, + ): AddCartItemResponseDto { + val itemMap = request.items.associate { it.dosirakId to it.amount } + + val user = + userRepository.findByEmailFetchCart(username) + ?: throw EntityNotFoundException("$username 이라는 유저는 존재하지 않습니다.") + + val userCart = + cartRepository.findByIdFetchItemListAndDosirak(user.userCart.id!!) + ?: throw EntityNotFoundException("해당하는 장바구니가 존재하지 않습니다.") + + // 0으로 초기화 + initializeItemZero(itemMap, userCart) + // 기존에 있던 아이템에 더하기 + plusItemAmount(itemMap, userCart.cartItemList) + // 3개 넘는지 점검 + checkAmountLimit(userCart.cartItemList) + + return AddCartItemResponseDto( + itemKindAmount = userCart.cartItemList.size, + ) + } + + private fun checkAmountLimit(cartItemList: MutableList) { + require(cartItemList.all { it.count <= MAX_CART_ITEM_AMOUNT }) { + throw ConditionConflictException(ErrorCode.CONDITION_NOT_FULFILLED, "카트에 담긴 도시락의 개수는 한 품목당 3개를 넘을 수 없습니다.") + } + } + + private fun initializeItemZero( + itemMap: Map, + cart: Cart, + ) { + val cartItems = cart.cartItemList + val dosirakIdSet = cartItems.map { it.dosirak.id!! }.toSet() // 도시락 id의 목록을 나타냄 + val itemListToAdd = mutableListOf() + + itemMap + .filterNot { it.key in dosirakIdSet } + .mapNotNull { filteredMap -> + dosirakRepository.findByIdOrNull(filteredMap.key)?.let { dosirak -> + itemListToAdd.add( + CartItem(dosirak = dosirak, cart = cart, count = 0), + ) + } ?: throw EntityNotFoundException("${filteredMap.key}라는 도시락이 존재하지 않습니다.") + } + + cartItemRepository.saveAll(itemListToAdd) + cartItems.addAll(itemListToAdd) + } + + private fun plusItemAmount( + itemMap: Map, + cartItems: MutableList, + ) { + cartItems + .filter { it.dosirak.id in itemMap.keys } + .forEach { it.count += itemMap[it.dosirak.id!!]!! } + } + + @Transactional + override fun updateItemQuantity( + username: String, + itemId: Long, + quantity: Int, + ): UpdateItemQuantityResponseDto { + val user = + userRepository.findByEmailFetchCart(username) + ?: throw EntityNotFoundException("$username 이라는 유저는 존재하지 않습니다.") + + val userCart = + cartRepository.findByIdFetchItemListAndDosirak(user.userCart.id!!) + ?: throw EntityNotFoundException("해당하는 장바구니가 존재하지 않습니다.") + + val targetCartItem = + userCart.cartItemList.find { it.dosirak.id == itemId } + ?: throw EntityNotFoundException("해당 아이템이 존재하지 않습니다.") + + targetCartItem.count = quantity + + return UpdateItemQuantityResponseDto( + itemId = itemId, + quantity = quantity, + ) + } + + @Transactional + override fun deleteItem( + username: String, + itemId: Long, + ): DeleteCartItemResponseDto { + val user = + userRepository.findByEmailFetchCart(username) + ?: throw EntityNotFoundException("$username 이라는 유저는 존재하지 않습니다.") + + val userCart = + cartRepository.findByIdFetchItemListAndDosirak(user.userCart.id!!) + ?: throw EntityNotFoundException("해당하는 장바구니가 존재하지 않습니다.") + + val cartItems = userCart.cartItemList + + val targetCartItem = + cartItems.find { it.dosirak.id == itemId } + ?: throw EntityNotFoundException("해당 아이템이 존재하지 않습니다.") + + cartItems.remove(targetCartItem) + cartItemRepository.delete(targetCartItem) + + return DeleteCartItemResponseDto( + itemId = itemId, + ) + } + + @Transactional + override fun clearCart(username: String): ClearCartResponseDto { + val user = + userRepository.findByEmailFetchCart(username) + ?: throw EntityNotFoundException("$username 이라는 유저는 존재하지 않습니다.") + + val userCart = + cartRepository.findByIdFetchItemListAndDosirak(user.userCart.id!!) + ?: throw EntityNotFoundException("해당하는 장바구니가 존재하지 않습니다.") + + cartItemRepository.deleteAll(userCart.cartItemList) + userCart.cartItemList.clear() + + return ClearCartResponseDto( + user.userCart.id!!, + ) + } +} diff --git a/src/main/kotlin/store/baribari/demo/domain/user/entity/User.kt b/src/main/kotlin/store/baribari/demo/domain/user/entity/User.kt index fc9d772..382b38d 100644 --- a/src/main/kotlin/store/baribari/demo/domain/user/entity/User.kt +++ b/src/main/kotlin/store/baribari/demo/domain/user/entity/User.kt @@ -10,7 +10,6 @@ import store.baribari.demo.domain.order.entity.Order import store.baribari.demo.domain.store.entity.LikeStore import store.baribari.demo.domain.store.entity.Store import java.util.UUID -import javax.persistence.CascadeType import javax.persistence.Column import javax.persistence.Embedded import javax.persistence.Entity @@ -49,7 +48,7 @@ class User( @Embedded var position: Position = Position(0.0, 0.0), // 장바구니 - @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], optional = false) + @OneToOne(fetch = FetchType.LAZY, optional = false) var userCart: Cart, // 프로필 이미지 var profileImageUrl: String? = null,