Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.petqua.common.domain.dto.DEFAULT_LAST_VIEWED_ID
import com.petqua.common.domain.dto.PADDING_FOR_HAS_NEXT_PAGE
import com.petqua.common.domain.dto.PAGING_LIMIT_CEILING
import com.petqua.domain.auth.LoginMemberOrGuest
import com.petqua.domain.product.dto.MemberProductReview
import com.petqua.domain.product.dto.ProductReviewReadCondition
import com.petqua.domain.product.dto.ProductReviewWithMemberResponse
import com.petqua.domain.product.review.ProductReview
Expand Down Expand Up @@ -58,6 +59,17 @@ data class ProductReviewReadQuery(
}
}

data class MemberProductReviewReadQuery(
val memberId: Long,
val lastViewedId: Long = DEFAULT_LAST_VIEWED_ID,
val limit: Int = PAGING_LIMIT_CEILING,
) {

fun toPaging(): CursorBasedPaging {
return CursorBasedPaging.of(lastViewedId, limit)
}
}

data class ProductReviewsResponse(
val productReviews: List<ProductReviewResponse>,

Expand Down Expand Up @@ -245,3 +257,148 @@ data class ProductReviewStatisticsResponse(
}
}
}

data class MemberProductReviewsResponse(
val memberProductReviews: List<MemberProductReviewResponse>,

@Schema(
description = "다음 페이지 존재 여부",
example = "true"
)
val hasNextPage: Boolean,
) {
companion object {
fun of(memberProductReviews: List<MemberProductReviewResponse>, limit: Int): MemberProductReviewsResponse {
return if (memberProductReviews.size > limit) {
MemberProductReviewsResponse(
memberProductReviews.dropLast(PADDING_FOR_HAS_NEXT_PAGE),
hasNextPage = true
)
} else {
MemberProductReviewsResponse(memberProductReviews, hasNextPage = false)
}
}
}
}

data class MemberProductReviewResponse(
@Schema(
description = "상품 후기의 id",
example = "1"
)
val reviewId: Long,

@Schema(
description = "회원의 id",
example = "1"
)
val memberId: Long,

@Schema(
description = "주문 생성 날짜",
example = "2021-08-01T00:00:00"
)
val createdAt: LocalDateTime,

@Schema(
description = "주문 상태",
example = "PURCHASE_CONFIRMED"
)
val orderStatus: String,

@Schema(
description = "상품 판매점 id",
example = "1"
)
val storeId: Long,

@Schema(
description = "상품 판매점",
example = "S아쿠아"
)
val storeName: String,

@Schema(
description = "상품 id",
example = "1"
)
val productId: Long,

@Schema(
description = "상품 이름",
example = "알비노 풀레드 아시안 고정구피"
)
val productName: String,

@Schema(
description = "상품 썸네일 이미지",
example = "https://docs.petqua.co.kr/products/thumbnails/thumbnail1.jpeg"
)
val productThumbnailUrl: String,

@Schema(
description = "주문 수량",
example = "1"
)
val quantity: Int,

@Schema(
description = "성별",
example = "MALE",
allowableValues = ["MALE", "FEMALE", "HERMAPHRODITE"]
)
val sex: String,

@Schema(
description = "배송 방법(\"COMMON : 일반\", \"SAFETY : 안전\", \"PICK_UP : 직접\")",
example = "SAFETY",
allowableValues = ["COMMON", "SAFETY", "PICK_UP"]
)
val deliveryMethod: String,

@Schema(
description = "상품 후기 평점",
example = "5"
)
val score: Int,

@Schema(
description = "상품 후기 내용",
example = "아주 좋네요"
)
val content: String,

@Schema(
description = "상품 후기 추천 수",
example = "3"
)
val recommendCount: Int,

@Schema(
description = "상품 후기에 등록된 이미지 url 리스트",
example = "[\"http:/docs.petqua.co.kr/review1.jpg\", \"http:/docs.petqua.co.kr/review2.jpg\"]"
)
val reviewImages: List<String>,
) {
constructor(
memberProductReview: MemberProductReview,
reviewImages: List<String>,
) : this(
reviewId = memberProductReview.reviewId,
memberId = memberProductReview.memberId,
createdAt = memberProductReview.createdAt,
orderStatus = memberProductReview.orderStatus.name,
storeId = memberProductReview.storeId,
storeName = memberProductReview.storeName,
productId = memberProductReview.productId,
productName = memberProductReview.productName,
productThumbnailUrl = memberProductReview.productThumbnailUrl,
quantity = memberProductReview.quantity,
sex = memberProductReview.sex.name,
deliveryMethod = memberProductReview.deliveryMethod.name,
score = memberProductReview.score.value,
content = memberProductReview.content.value,
recommendCount = memberProductReview.recommendCount,
reviewImages = reviewImages,
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.petqua.application.product.review

import com.petqua.application.product.dto.MemberProductReviewReadQuery
import com.petqua.application.product.dto.MemberProductReviewsResponse
import com.petqua.application.product.dto.ProductReviewCreateCommand
import com.petqua.application.product.dto.ProductReviewReadQuery
import com.petqua.application.product.dto.ProductReviewStatisticsResponse
Expand All @@ -23,6 +25,10 @@ class ProductReviewFacadeService(
return productReviewService.readAll(query)
}

fun readMemberProductReviews(query: MemberProductReviewReadQuery): MemberProductReviewsResponse {
return productReviewService.readMemberProductReviews(query)
}

fun readReviewCountStatistics(productId: Long): ProductReviewStatisticsResponse {
return productReviewService.readReviewCountStatistics(productId)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.petqua.application.product.review

import com.petqua.application.product.dto.MemberProductReviewReadQuery
import com.petqua.application.product.dto.MemberProductReviewResponse
import com.petqua.application.product.dto.MemberProductReviewsResponse
import com.petqua.application.product.dto.ProductReviewReadQuery
import com.petqua.application.product.dto.ProductReviewResponse
import com.petqua.application.product.dto.ProductReviewStatisticsResponse
import com.petqua.application.product.dto.ProductReviewsResponse
import com.petqua.application.product.dto.UpdateReviewRecommendationCommand
import com.petqua.common.domain.findByIdOrThrow
import com.petqua.domain.product.dto.ProductReviewWithMemberResponse
import com.petqua.domain.product.review.ProductReview
import com.petqua.domain.product.review.ProductReviewImage
import com.petqua.domain.product.review.ProductReviewImageRepository
Expand Down Expand Up @@ -42,7 +44,8 @@ class ProductReviewService(
query.toCondition(),
query.toPaging(),
)
val imagesByReview = getImagesByReview(reviewsByCondition)
val reviewIds = reviewsByCondition.map { it.id }
val imagesByReview = getImagesByReviewIds(reviewIds)
val responses = reviewsByCondition.map {
ProductReviewResponse(it, imagesByReview[it.id] ?: emptyList())
}
Expand All @@ -59,12 +62,22 @@ class ProductReviewService(
return ProductReviewsResponse.of(responses, query.limit)
}

private fun getImagesByReview(reviewsByCondition: List<ProductReviewWithMemberResponse>): Map<Long, List<String>> {
val productReviewIds = reviewsByCondition.map { it.id }
private fun getImagesByReviewIds(productReviewIds: List<Long>): Map<Long, List<String>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

ids를 받게 바꾸고 재활용 넘 좋네요!!

return productReviewImageRepository.findAllByProductReviewIdIn(productReviewIds).groupBy { it.productReviewId }
.mapValues { it.value.map { image -> image.imageUrl } }
}

fun readMemberProductReviews(query: MemberProductReviewReadQuery): MemberProductReviewsResponse {
val memberProductReviews = productReviewRepository.findMemberProductReviewBy(query.memberId, query.toPaging())
val reviewIds = memberProductReviews.map { it.reviewId }
val imagesByReview = getImagesByReviewIds(reviewIds)

val responses = memberProductReviews.map {
MemberProductReviewResponse(it, imagesByReview[it.reviewId] ?: emptyList())
}
return MemberProductReviewsResponse.of(responses, query.limit)
}

@Transactional(readOnly = true)
fun readReviewCountStatistics(productId: Long): ProductReviewStatisticsResponse {
val reviewScoreWithCounts = productReviewRepository.findReviewScoresWithCount(productId)
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/com/petqua/domain/order/OrderStatus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum class OrderStatus(
ORDER_CONFIRMED("주문 확인"),
CANCELED("주문 취소"),
PAYMENT_CONFIRMED("결제 완료"),
PURCHASE_CONFIRMED("구매 확정"),
DELIVERY_PREPARATION("배송 준비 중"),
DELIVERING("배송 중"),
DELIVERY_COMPLETED("배송 완료"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.petqua.domain.product.dto

import com.petqua.domain.delivery.DeliveryMethod
import com.petqua.domain.member.Member
import com.petqua.domain.order.Order
import com.petqua.domain.order.OrderPayment
import com.petqua.domain.order.OrderStatus
import com.petqua.domain.product.option.Sex
import com.petqua.domain.product.review.ProductReview
import com.petqua.domain.product.review.ProductReviewContent
import com.petqua.domain.product.review.ProductReviewScore
import com.petqua.domain.product.review.ProductReviewSorter
import com.petqua.domain.product.review.ProductReviewSorter.REVIEW_DATE_DESC
import java.time.LocalDateTime
Expand Down Expand Up @@ -44,6 +51,46 @@ data class ProductReviewWithMemberResponse(
)
}

data class MemberProductReview(
val reviewId: Long,
val memberId: Long,
val createdAt: LocalDateTime,
val orderStatus: OrderStatus,
val storeId: Long,
val storeName: String,
val productId: Long,
val productName: String,
val productThumbnailUrl: String,
val quantity: Int,
val sex: Sex,
val deliveryMethod: DeliveryMethod,
val score: ProductReviewScore,
val content: ProductReviewContent,
val recommendCount: Int,
) {
constructor(
productReview: ProductReview,
order: Order,
orderPayment: OrderPayment,
) : this(
reviewId = productReview.id,
memberId = productReview.memberId,
createdAt = order.createdAt,
orderStatus = orderPayment.status,
storeId = order.orderProduct.storeId,
storeName = order.orderProduct.storeName,
productId = order.orderProduct.productId,
productName = order.orderProduct.productName,
productThumbnailUrl = order.orderProduct.thumbnailUrl,
quantity = order.orderProduct.quantity,
sex = order.orderProduct.sex,
deliveryMethod = order.orderProduct.deliveryMethod,
score = productReview.score,
content = productReview.content,
recommendCount = productReview.recommendCount,
)
}

data class ProductReviewScoreWithCount(
val score: Int,
val count: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.petqua.domain.product.review

import com.petqua.common.domain.dto.CursorBasedPaging
import com.petqua.domain.product.dto.MemberProductReview
import com.petqua.domain.product.dto.ProductReviewReadCondition
import com.petqua.domain.product.dto.ProductReviewScoreWithCount
import com.petqua.domain.product.dto.ProductReviewWithMemberResponse
Expand All @@ -9,8 +10,10 @@ interface ProductReviewCustomRepository {

fun findAllByCondition(
condition: ProductReviewReadCondition,
paging: CursorBasedPaging
paging: CursorBasedPaging,
): List<ProductReviewWithMemberResponse>

fun findReviewScoresWithCount(productId: Long): List<ProductReviewScoreWithCount>

fun findMemberProductReviewBy(memberId: Long, paging: CursorBasedPaging): List<MemberProductReview>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderer
import com.petqua.common.domain.dto.CursorBasedPaging
import com.petqua.common.util.createQuery
import com.petqua.domain.member.Member
import com.petqua.domain.order.Order
import com.petqua.domain.order.OrderPayment
import com.petqua.domain.order.OrderProduct
import com.petqua.domain.product.dto.MemberProductReview
import com.petqua.domain.product.dto.ProductReviewReadCondition
import com.petqua.domain.product.dto.ProductReviewScoreWithCount
import com.petqua.domain.product.dto.ProductReviewWithMemberResponse
Expand All @@ -21,7 +25,7 @@ class ProductReviewCustomRepositoryImpl(

override fun findAllByCondition(
condition: ProductReviewReadCondition,
paging: CursorBasedPaging
paging: CursorBasedPaging,
): List<ProductReviewWithMemberResponse> {

val query = jpql(ProductReviewDynamicJpqlGenerator) {
Expand Down Expand Up @@ -69,4 +73,35 @@ class ProductReviewCustomRepositoryImpl(
jpqlRenderer
)
}

override fun findMemberProductReviewBy(
memberId: Long,
paging: CursorBasedPaging,
): List<MemberProductReview> {
val query = jpql(ProductReviewDynamicJpqlGenerator) {
selectNew<MemberProductReview>(
entity(ProductReview::class),
entity(Order::class),
entity(OrderPayment::class),
).from(
entity(ProductReview::class),
join(Order::class).on(
path(ProductReview::memberId).eq(path(Order::memberId))
.and(path(ProductReview::productId).eq(path(Order::orderProduct)(OrderProduct::productId)))
),
join(OrderPayment::class).on(path(Order::id).eq(path(OrderPayment::orderId)))
).whereAnd(
productReviewIdLt(paging.lastViewedId),
).orderBy(
path(ProductReview::createdAt).desc(),
Copy link
Contributor

Choose a reason for hiding this comment

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

createdAt 대신 id로 정렬해도 무방한 거 맞을까요??
id가 인덱스라 더 효율적일 것 같아서요!

)
}

return entityManager.createQuery(
query,
jpqlRenderContext,
jpqlRenderer,
paging.limit
)
}
}
Loading