Skip to content
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

feature: 봉달 목록 조회 api #38

Merged
merged 12 commits into from
Feb 2, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.petqua.application.cart

import com.petqua.application.cart.dto.CartProductResponse
import com.petqua.application.cart.dto.DeleteCartProductCommand
import com.petqua.application.cart.dto.SaveCartProductCommand
import com.petqua.application.cart.dto.UpdateCartProductOptionCommand
Expand Down Expand Up @@ -76,4 +77,10 @@ class CartProductService(
cartProduct.validateOwner(command.memberId)
cartProductRepository.delete(cartProduct)
}

@Transactional(readOnly = true)
fun readAll(memberId: Long): List<CartProductResponse> {
memberRepository.existByIdOrThrow(memberId, MemberException(NOT_FOUND_MEMBER))
return cartProductRepository.findAllCartResultsByMemberId(memberId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.petqua.application.cart.dto
import com.petqua.domain.cart.CartProduct
import com.petqua.domain.cart.CartProductQuantity
import com.petqua.domain.cart.DeliveryMethod
import com.petqua.domain.product.Product

data class SaveCartProductCommand(
val memberId: Long,
Expand Down Expand Up @@ -35,3 +36,34 @@ data class DeleteCartProductCommand(
val memberId: Long,
val cartProductId: Long,
)

data class CartProductResponse(
val id: Long,
val storeName: String,
val productId: Long,
val productName: String,
val productThumbnailUrl: String,
val productPrice: Int,
val productDiscountRate: Int,
val productDiscountPrice: Int,
val quantity: Int,
val isMale: Boolean,
val deliveryMethod: String,
val isOnSale: Boolean,
) {

constructor(cartProduct: CartProduct, product: Product?, storeName: String?) : this(
id = cartProduct.id,
storeName = storeName ?: "",
productId = product?.id ?: 0L,
productName = product?.name ?: "",
productThumbnailUrl = product?.thumbnailUrl ?: "",
productPrice = product?.price?.intValueExact() ?: 0,
productDiscountRate = product?.discountRate ?: 0,
productDiscountPrice = product?.discountPrice?.intValueExact() ?: 0,
quantity = cartProduct.quantity.value,
isMale = cartProduct.isMale,
deliveryMethod = cartProduct.deliveryMethod.name,
Comment on lines +57 to +66
Copy link
Contributor

Choose a reason for hiding this comment

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

null 을 이렇게 사용할 수 있군요!

isOnSale = product != null
)
}
11 changes: 11 additions & 0 deletions src/main/kotlin/com/petqua/common/util/EntityManagerUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ inline fun <reified T> EntityManager.createQuery(
.resultList
}

inline fun <reified T> EntityManager.createQuery(
query: SelectQuery<*>,
context: JpqlRenderContext,
renderer: JpqlRenderer,
): List<T> {
val rendered = renderer.render(query, context)
return this.createQuery(rendered.query, T::class.java)
.apply { rendered.params.forEach { (name, value) -> setParameter(name, value) } }
.resultList
}

inline fun <reified T> EntityManager.createCountQuery(
query: SelectQuery<*>,
context: JpqlRenderContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.petqua.domain.cart

import com.petqua.application.cart.dto.CartProductResponse

interface CartProductCustomRepository {

fun findAllCartResultsByMemberId(memberId: Long): List<CartProductResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.petqua.domain.cart

import com.linecorp.kotlinjdsl.dsl.jpql.jpql
import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderContext
import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderer
import com.petqua.application.cart.dto.CartProductResponse
import com.petqua.common.util.createQuery
import com.petqua.domain.product.Product
import com.petqua.domain.store.Store
import jakarta.persistence.EntityManager
import org.springframework.stereotype.Repository

@Repository
class CartProductCustomRepositoryImpl(
private val entityManager: EntityManager,
private val jpqlRenderContext: JpqlRenderContext,
private val jpqlRenderer: JpqlRenderer,
) : CartProductCustomRepository {

override fun findAllCartResultsByMemberId(memberId: Long): List<CartProductResponse> {
val query = jpql {
selectNew<CartProductResponse>(
entity(CartProduct::class),
entity(Product::class),
path(Store::name)
).from(
entity(CartProduct::class),
leftJoin(Product::class).on(path(CartProduct::productId).eq(path(Product::id))),
leftJoin(Store::class).on(path(Product::storeId).eq(path(Store::id))),
).where(
path(CartProduct::memberId).eq(memberId)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

o..or..orderBy 해주시면 안될까요..ㅎㅎ

Copy link
Member Author

Choose a reason for hiding this comment

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

반영하겠습니다!! 깜빡했네요ㅠㅠ

Copy link
Member Author

Choose a reason for hiding this comment

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

5a50036 최신순으로 반영하였습니다!

}

return entityManager.createQuery(
query,
jpqlRenderContext,
jpqlRenderer
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.petqua.domain.cart

import org.springframework.data.jpa.repository.JpaRepository

interface CartProductRepository : JpaRepository<CartProduct, Long> {
interface CartProductRepository : JpaRepository<CartProduct, Long>, CartProductCustomRepository {

fun findByMemberIdAndProductIdAndIsMaleAndDeliveryMethod(
memberId: Long,
Expand All @@ -11,5 +11,5 @@ interface CartProductRepository : JpaRepository<CartProduct, Long> {
deliveryMethod: DeliveryMethod
): CartProduct?

fun findAllByIdIn(ids: List<Long>): List<CartProduct>
fun findAllByMemberId(id: Long): List<CartProduct>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface ProductCustomRepository {
fun findAllByCondition(condition: ProductReadCondition, paging: ProductPaging): List<ProductResponse>

fun countByCondition(condition: ProductReadCondition): Int

fun findAllProductResponseByIdIn(ids: List<Long>): List<ProductResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,26 @@ class ProductCustomRepositoryImpl(
jpqlRenderer
)
}

override fun findAllProductResponseByIdIn(ids: List<Long>): List<ProductResponse> {
val query = jpql {
selectNew<ProductResponse>(
entity(Product::class),
path(Store::name)
).from(
entity(Product::class),
join(Store::class).on(path(Product::storeId).eq(path(Store::id))),
).where(
predicateByIds(ids)
)
}
Comment on lines +101 to +111
Copy link
Contributor

Choose a reason for hiding this comment

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

멋지게 잘 쓰셨네요!
혹시 데이터 순서도 명시하면 어떨까요?


return entityManager.createQuery(
query,
jpqlRenderContext,
jpqlRenderer
)
}

private fun Jpql.predicateByIds(ids: List<Long>) = if (ids.isEmpty()) null else path(Product::id).`in`(ids)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package com.petqua.presentation.cart

import com.petqua.application.cart.CartProductService
import com.petqua.application.cart.dto.DeleteCartProductCommand
import com.petqua.application.cart.dto.CartProductResponse
import com.petqua.application.product.dto.ProductDetailResponse
import com.petqua.domain.auth.Auth
import com.petqua.domain.auth.LoginMember
import com.petqua.presentation.cart.dto.SaveCartProductRequest
import com.petqua.presentation.cart.dto.UpdateCartProductOptionRequest
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.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
Expand Down Expand Up @@ -60,4 +62,12 @@ class CartProductController(
cartProductService.delete(command)
return ResponseEntity.noContent().build()
}

@GetMapping
fun readAll(
@Auth loginMember: LoginMember,
): ResponseEntity<List<CartProductResponse>> {
val responses = cartProductService.readAll(loginMember.memberId)
return ResponseEntity.ok(responses)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.petqua.domain.cart.DeliveryMethod.COMMON
import com.petqua.domain.cart.DeliveryMethod.SAFETY
import com.petqua.domain.member.MemberRepository
import com.petqua.domain.product.ProductRepository
import com.petqua.domain.store.StoreRepository
import com.petqua.exception.cart.CartProductException
import com.petqua.exception.cart.CartProductExceptionType.DUPLICATED_PRODUCT
import com.petqua.exception.cart.CartProductExceptionType.FORBIDDEN_CART_PRODUCT
Expand All @@ -22,6 +23,7 @@ import com.petqua.test.DataCleaner
import com.petqua.test.fixture.cartProduct
import com.petqua.test.fixture.member
import com.petqua.test.fixture.product
import com.petqua.test.fixture.store
import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
Expand All @@ -35,6 +37,7 @@ class CartProductServiceTest(
private val cartProductRepository: CartProductRepository,
private val productRepository: ProductRepository,
private val memberRepository: MemberRepository,
private val storeRepository: StoreRepository,
private val dataCleaner: DataCleaner,
) : BehaviorSpec({

Expand Down Expand Up @@ -173,16 +176,16 @@ class CartProductServiceTest(
cartProduct(
memberId = memberId,
productId = productId,
isMale = true,
deliveryMethod = COMMON
isMale = false,
deliveryMethod = SAFETY
)
)
val command = UpdateCartProductOptionCommand(
cartProductId = cartProduct.id,
memberId = memberId,
quantity = CartProductQuantity(3),
isMale = true,
deliveryMethod = COMMON,
isMale = false,
deliveryMethod = SAFETY,
)
Then("예외가 발생 한다") {
shouldThrow<CartProductException> {
Expand Down Expand Up @@ -251,6 +254,58 @@ class CartProductServiceTest(
}
}

Given("봉달 상품 조회시") {
val store = storeRepository.save(store(name = "store"))
val productAId = productRepository.save(product(storeId = store.id)).id
val productBId = productRepository.save(product(storeId = store.id)).id
val productCId = productRepository.save(product(storeId = store.id)).id
val memberId = memberRepository.save(member()).id
cartProductRepository.saveAll(
listOf(
cartProduct(memberId = memberId, productId = productAId),
cartProduct(memberId = memberId, productId = productBId),
cartProduct(memberId = memberId, productId = productCId),
)
)

When("봉달 상품이 있는 회원이 조회 하는 경우") {
val result = cartProductService.readAll(memberId)

Then("봉달 상품 리스트를 반환 한다") {
result.size shouldBe 3
}
}

When("봉달 상품이 없는 회원이 조회 하는 경우") {
val newMemberId = memberRepository.save(member()).id
val results = cartProductService.readAll(newMemberId)

Then("빈 리스트를 반환 한다") {
results.size shouldBe 0
}
}

When("봉달에 담아둔 상품이 삭제된 경우") {
productRepository.deleteById(productAId)
val results = cartProductService.readAll(memberId)

Then("상품의 판매 여부를 포함한 리스트를 반환 한다") {
assertSoftly(results) {
size shouldBe 3
find { it.productId == 0L }!!.isOnSale shouldBe false
}
}
}

When("존재 하지 않는 회원이 조회 하는 경우") {
Then("예외가 발생 한다") {
shouldThrow<MemberException> {
cartProductService.readAll(Long.MIN_VALUE)
}.exceptionType() shouldBe NOT_FOUND_MEMBER
}
}
}

afterContainer {
dataCleaner.clean()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,22 @@ class ProductCustomRepositoryImplTest(
}
}

Given("다중 id로 ProductResponse를 조회 할 때") {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

val product1 = productRepository.save(product(name = "상품1", storeId = store.id))
val product2 = productRepository.save(product(name = "상품2", storeId = store.id))

When("id 목록을 입력하면") {
val products = productRepository.findAllProductResponseByIdIn(listOf(product1.id, product2.id))

Then("해당 id의 ProductResponse를 반환한다") {
products shouldContainExactly listOf(
ProductResponse(product1, store.name),
ProductResponse(product2, store.name),
)
}
}
}

afterContainer {
dataCleaner.clean()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,18 @@ fun requestDeleteCartProduct(
response()
}
}

fun requestReadAllCartProducts(
accessToken: String
): Response {
return Given {
log().all()
.header(HttpHeaders.AUTHORIZATION, accessToken)
} When {
get("/carts")
} Then {
log().all()
} Extract {
response()
}
}
Loading
Loading