-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #46 from YAPP-Github/feature/geng/issue-44-comment
feat : 상품 코멘트 평가 api 구현
- Loading branch information
Showing
19 changed files
with
437 additions
and
24 deletions.
There are no files selected for viewing
57 changes: 57 additions & 0 deletions
57
cvs-api/src/main/kotlin/com/yapp/cvs/api/comment/ProductCommentController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.yapp.cvs.api.comment | ||
|
||
import com.yapp.cvs.api.comment.dto.ProductCommentContentDTO | ||
import com.yapp.cvs.api.comment.dto.ProductCommentDTO | ||
import com.yapp.cvs.api.comment.dto.ProductCommentDetailDTO | ||
import com.yapp.cvs.api.comment.dto.ProductCommentSearchDTO | ||
import com.yapp.cvs.api.common.dto.OffsetPageDTO | ||
import com.yapp.cvs.domain.comment.application.ProductCommentProcessor | ||
import com.yapp.cvs.domain.like.application.ProductLikeProcessor | ||
import io.swagger.v3.oas.annotations.Operation | ||
import org.springdoc.api.annotations.ParameterObject | ||
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 | ||
import org.springframework.web.bind.annotation.RequestMapping | ||
import org.springframework.web.bind.annotation.RestController | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/product") | ||
class ProductCommentController( | ||
private val productCommentProcessor: ProductCommentProcessor, | ||
private val productLikeProcessor: ProductLikeProcessor | ||
) { | ||
private val memberId = 1L //TODO : security context 에서 memberId 가져오기 | ||
|
||
@GetMapping("/{productId}/comments") | ||
@Operation(description = "해당 상품에 작성된 평가 코멘트를 조건만큼 가져옵니다.") | ||
fun getProductComments(@PathVariable productId: Long, | ||
@ParameterObject productCommentSearchDTO: ProductCommentSearchDTO): OffsetPageDTO<ProductCommentDetailDTO> { | ||
val result = productCommentProcessor.getCommentDetails(productId, memberId, productCommentSearchDTO.toVO()) | ||
return OffsetPageDTO(result.lastId, result.content.map { ProductCommentDetailDTO.from(it) }) | ||
} | ||
|
||
@PostMapping("/{productId}/comment/write") | ||
@Operation(description = "상품에 대한 평가 코멘트를 작성합니다.") | ||
fun writeComment(@PathVariable productId: Long, | ||
@RequestBody productCommentContentDTO: ProductCommentContentDTO): ProductCommentDTO { | ||
val comment = productCommentProcessor.createComment(productId, memberId, productCommentContentDTO.content) | ||
return ProductCommentDTO.from(comment) | ||
} | ||
|
||
@PostMapping("/{productId}/comment/edit") | ||
@Operation(description = "상품에 대한 평가 코멘트를 수정합니다.") | ||
fun updateComment(@PathVariable productId: Long, | ||
@RequestBody productCommentContentDTO: ProductCommentContentDTO): ProductCommentDTO { | ||
val comment = productCommentProcessor.updateComment(productId, memberId, productCommentContentDTO.content) | ||
return ProductCommentDTO.from(comment) | ||
} | ||
|
||
@PostMapping("/{productId}/comment/delete") | ||
@Operation(description = "상품에 대한 평가 코멘트를 삭제합니다.") | ||
fun deleteComment(@PathVariable productId: Long) { | ||
productCommentProcessor.inactivateComment(productId, memberId) | ||
productLikeProcessor.cancelEvaluation(productId, memberId) | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
cvs-api/src/main/kotlin/com/yapp/cvs/api/comment/dto/ProductCommentContentDTO.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.yapp.cvs.api.comment.dto | ||
|
||
data class ProductCommentContentDTO( | ||
val content: String | ||
) |
21 changes: 21 additions & 0 deletions
21
cvs-api/src/main/kotlin/com/yapp/cvs/api/comment/dto/ProductCommentDTO.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.yapp.cvs.api.comment.dto | ||
|
||
import com.yapp.cvs.domain.comment.vo.ProductCommentVO | ||
|
||
data class ProductCommentDTO( | ||
val productCommentId: Long, | ||
val productId: Long, | ||
val memberId: Long, | ||
val content: String | ||
) { | ||
companion object { | ||
fun from(productCommentVO: ProductCommentVO): ProductCommentDTO { | ||
return ProductCommentDTO( | ||
productCommentId = productCommentVO.productCommentId, | ||
productId = productCommentVO.productId, | ||
memberId = productCommentVO.memberId, | ||
content = productCommentVO.content | ||
) | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
cvs-api/src/main/kotlin/com/yapp/cvs/api/comment/dto/ProductCommentDetailDTO.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.yapp.cvs.api.comment.dto | ||
|
||
import com.yapp.cvs.domain.comment.vo.ProductCommentDetailVO | ||
import com.yapp.cvs.domain.enums.ProductLikeType | ||
import java.time.LocalDateTime | ||
|
||
data class ProductCommentDetailDTO( | ||
val productCommentId: Long, | ||
val content: String, | ||
val commentLikeCount: Long, | ||
val isOwner: Boolean, | ||
val createdAt: LocalDateTime, | ||
val likeType: ProductLikeType, | ||
|
||
val memberId: Long, | ||
val nickname: String, | ||
|
||
val productId: Long | ||
) { | ||
companion object { | ||
fun from(productCommentDetailVO: ProductCommentDetailVO): ProductCommentDetailDTO { | ||
return ProductCommentDetailDTO( | ||
productCommentId = productCommentDetailVO.productCommentId, | ||
content = productCommentDetailVO.content, | ||
commentLikeCount = productCommentDetailVO.commentLikeCount, | ||
isOwner = productCommentDetailVO.isOwner, | ||
createdAt = productCommentDetailVO.createdAt, | ||
likeType = productCommentDetailVO.likeType ?: ProductLikeType.NONE, | ||
memberId = productCommentDetailVO.memberId, | ||
nickname = productCommentDetailVO.nickname, | ||
productId = productCommentDetailVO.productId | ||
) | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
cvs-api/src/main/kotlin/com/yapp/cvs/api/comment/dto/ProductCommentSearchDTO.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.yapp.cvs.api.comment.dto | ||
|
||
import com.yapp.cvs.domain.comment.entity.ProductCommentOrderType | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentSearchVO | ||
|
||
|
||
data class ProductCommentSearchDTO( | ||
val pageSize: Long = 10, | ||
val offsetProductCommentId: Long? = null, | ||
val orderBy: ProductCommentOrderType = ProductCommentOrderType.RECENT | ||
) { | ||
fun toVO(): ProductCommentSearchVO { | ||
return ProductCommentSearchVO( | ||
pageSize = pageSize, | ||
offsetProductCommentId = offsetProductCommentId, | ||
orderBy = orderBy | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
...domain/src/main/kotlin/com/yapp/cvs/domain/comment/application/ProductCommentProcessor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.yapp.cvs.domain.comment.application | ||
|
||
import com.yapp.cvs.domain.base.vo.OffsetPageVO | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentDetailVO | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentSearchVO | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentVO | ||
import com.yapp.cvs.domain.enums.DistributedLockType | ||
import com.yapp.cvs.domain.enums.ProductLikeType | ||
import com.yapp.cvs.domain.like.application.ProductLikeHistoryService | ||
import com.yapp.cvs.domain.like.application.ProductLikeSummaryService | ||
import com.yapp.cvs.infrastructure.redis.lock.DistributedLock | ||
import org.springframework.stereotype.Service | ||
import org.springframework.transaction.annotation.Transactional | ||
|
||
@Service | ||
@Transactional | ||
class ProductCommentProcessor( | ||
private val productCommentService: ProductCommentService | ||
) { | ||
fun getCommentDetails(productId: Long, memberId: Long, productCommentSearchVO: ProductCommentSearchVO): OffsetPageVO<ProductCommentDetailVO> { | ||
val result = productCommentService.findProductCommentsPage(productId, productCommentSearchVO) | ||
result.filter { it.memberId == memberId } | ||
.forEach { it.isOwner = true } | ||
return OffsetPageVO(result.lastOrNull()?.productCommentId, result) | ||
} | ||
|
||
@DistributedLock(DistributedLockType.MEMBER_PRODUCT, ["productId", "memberId"]) | ||
fun createComment(productId: Long, memberId: Long, content: String): ProductCommentVO { | ||
val commentHistory = productCommentService.write(productId, memberId, content) | ||
return ProductCommentVO.from(commentHistory) | ||
} | ||
|
||
@DistributedLock(DistributedLockType.MEMBER_PRODUCT, ["productId", "memberId"]) | ||
fun updateComment(productId: Long, memberId: Long, content: String): ProductCommentVO { | ||
val commentHistory = productCommentService.update(productId, memberId, content) | ||
return ProductCommentVO.from(commentHistory) | ||
} | ||
|
||
@DistributedLock(DistributedLockType.MEMBER_PRODUCT, ["productId", "memberId"]) | ||
fun inactivateComment(productId: Long, memberId: Long) { | ||
productCommentService.inactivate(productId, memberId) | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
cvs-domain/src/main/kotlin/com/yapp/cvs/domain/comment/application/ProductCommentService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.yapp.cvs.domain.comment.application | ||
|
||
import com.yapp.cvs.domain.comment.entity.ProductComment | ||
import com.yapp.cvs.domain.comment.repository.ProductCommentRepository | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentDetailVO | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentSearchVO | ||
import com.yapp.cvs.exception.BadRequestException | ||
import com.yapp.cvs.exception.NotFoundSourceException | ||
import org.springframework.stereotype.Service | ||
|
||
@Service | ||
class ProductCommentService( | ||
val productCommentRepository: ProductCommentRepository | ||
) { | ||
fun findProductCommentsPage(productId: Long, productCommentSearchVO: ProductCommentSearchVO): List<ProductCommentDetailVO> { | ||
return productCommentRepository.findAllByProductIdAndPageOffset(productId, productCommentSearchVO) | ||
} | ||
|
||
fun write(productId: Long, memberId: Long, content: String): ProductComment { | ||
validateCommentDuplication(productId, memberId) | ||
val comment = ProductComment(productId = productId, memberId = memberId, content = content) | ||
return productCommentRepository.save(comment) | ||
} | ||
|
||
fun update(productId: Long, memberId: Long, content: String): ProductComment { | ||
inactivate(productId, memberId) | ||
val newComment = ProductComment(productId = productId, memberId = memberId, content = content) | ||
return productCommentRepository.save(newComment) | ||
} | ||
|
||
fun activate(productId: Long, memberId: Long) { | ||
productCommentRepository.findLatestByProductIdAndMemberId(productId, memberId)?.apply { if(!valid) valid = true } | ||
} | ||
|
||
fun inactivate(productId: Long, memberId: Long) { | ||
productCommentRepository.findLatestByProductIdAndMemberId(productId, memberId)?.apply { if(valid) valid = false } | ||
?: throw NotFoundSourceException("productId: $productId 에 대한 코멘트가 존재하지 않습니다.") | ||
} | ||
|
||
fun inactivateIfExist(productId: Long, memberId: Long) { | ||
productCommentRepository.findLatestByProductIdAndMemberId(productId, memberId)?.apply { if(valid) valid = false } | ||
} | ||
|
||
private fun validateCommentDuplication(productId: Long, memberId: Long) { | ||
if (productCommentRepository.existsByProductIdAndMemberIdAndValidTrue(productId, memberId)) { | ||
throw BadRequestException("productId: $productId 에 대한 코멘트가 이미 존재합니다.") | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
cvs-domain/src/main/kotlin/com/yapp/cvs/domain/comment/entity/ProductComment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.yapp.cvs.domain.comment.entity | ||
|
||
import com.yapp.cvs.domain.base.BaseEntity | ||
import javax.persistence.Entity | ||
import javax.persistence.GeneratedValue | ||
import javax.persistence.GenerationType | ||
import javax.persistence.Id | ||
import javax.persistence.Table | ||
|
||
@Entity | ||
@Table(name = "product_comments") | ||
class ProductComment( | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
val productCommentId: Long? = null, | ||
|
||
val productId: Long, | ||
|
||
val memberId: Long, | ||
|
||
var content: String, | ||
|
||
var valid: Boolean = true, | ||
): BaseEntity() |
6 changes: 6 additions & 0 deletions
6
cvs-domain/src/main/kotlin/com/yapp/cvs/domain/comment/entity/ProductCommentOrderType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.yapp.cvs.domain.comment.entity | ||
|
||
enum class ProductCommentOrderType { | ||
RECENT, | ||
LIKE | ||
} |
17 changes: 17 additions & 0 deletions
17
...domain/src/main/kotlin/com/yapp/cvs/domain/comment/repository/ProductCommentRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.yapp.cvs.domain.comment.repository | ||
|
||
import com.yapp.cvs.domain.comment.entity.ProductComment | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentDetailVO | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentSearchVO | ||
import org.springframework.data.jpa.repository.JpaRepository | ||
|
||
interface ProductCommentRepository : JpaRepository<ProductComment, Long>, ProductCommentCustom { | ||
fun findByProductIdAndMemberIdAndValidTrue(productId: Long, memberId: Long): ProductComment? | ||
fun existsByProductIdAndMemberIdAndValidTrue(productId: Long, memberId: Long): Boolean | ||
fun deleteByProductIdAndMemberId(productId: Long, memberId: Long) | ||
} | ||
|
||
interface ProductCommentCustom { | ||
fun findLatestByProductIdAndMemberId(productId: Long, memberId: Long): ProductComment? | ||
fun findAllByProductIdAndPageOffset(productId: Long, productCommentSearchVO: ProductCommentSearchVO): List<ProductCommentDetailVO> | ||
} |
80 changes: 80 additions & 0 deletions
80
...c/main/kotlin/com/yapp/cvs/domain/comment/repository/impl/ProductCommentRepositoryImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.yapp.cvs.domain.comment.repository.impl | ||
|
||
import com.querydsl.core.types.ConstructorExpression | ||
import com.querydsl.core.types.OrderSpecifier | ||
import com.querydsl.core.types.Projections | ||
import com.querydsl.core.types.dsl.Expressions | ||
import com.yapp.cvs.domain.comment.entity.ProductComment | ||
import com.yapp.cvs.domain.comment.entity.ProductCommentOrderType | ||
import com.yapp.cvs.domain.comment.entity.QProductComment.productComment | ||
import com.yapp.cvs.domain.comment.repository.ProductCommentCustom | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentDetailVO | ||
import com.yapp.cvs.domain.comment.vo.ProductCommentSearchVO | ||
import com.yapp.cvs.domain.like.entity.QMemberProductLikeMapping.memberProductLikeMapping | ||
import com.yapp.cvs.domain.member.entity.QMember.member | ||
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport | ||
import org.springframework.stereotype.Repository | ||
|
||
@Repository | ||
class ProductCommentRepositoryImpl: QuerydslRepositorySupport(ProductComment::class.java), ProductCommentCustom { | ||
override fun findLatestByProductIdAndMemberId(productId: Long, memberId: Long): ProductComment? { | ||
return from(productComment) | ||
.where( | ||
productComment.productId.eq(productId), | ||
productComment.memberId.eq(memberId), | ||
) | ||
.orderBy(productComment.productCommentId.desc()) | ||
.fetchFirst() | ||
} | ||
|
||
override fun findAllByProductIdAndPageOffset(productId: Long, | ||
productCommentSearchVO: ProductCommentSearchVO): List<ProductCommentDetailVO> { | ||
val size = productCommentSearchVO.pageSize | ||
val offsetId = productCommentSearchVO.offsetProductCommentId | ||
var predicate = productComment.productId.eq(productId) | ||
.and(productComment.valid.isTrue) | ||
if (offsetId != null) { | ||
predicate = predicate.and(productComment.productCommentId.lt(offsetId)) | ||
} | ||
|
||
return from(productComment) | ||
.leftJoin(member) | ||
.on(productComment.memberId.eq(member.memberId)) | ||
.leftJoin(memberProductLikeMapping) | ||
.on( | ||
productComment.productId.eq(memberProductLikeMapping.productId), | ||
productComment.memberId.eq(memberProductLikeMapping.memberId) | ||
) | ||
.where(predicate) | ||
.orderBy(getOrderBy(productCommentSearchVO.orderBy)) | ||
.select(productDetailVOProjection()) | ||
.limit(size) | ||
.fetch() | ||
} | ||
|
||
private fun productDetailVOProjection(): ConstructorExpression<ProductCommentDetailVO>? { | ||
// TODO : commentLikeCount 입력 | ||
val tempCommentLikeCount = 10L | ||
|
||
return Projections.constructor( | ||
ProductCommentDetailVO::class.java, | ||
productComment.productCommentId, | ||
productComment.content, | ||
Expressions.asNumber(tempCommentLikeCount), | ||
productComment.createdAt, | ||
memberProductLikeMapping.likeType, | ||
productComment.productId, | ||
productComment.memberId, | ||
member.nickName, | ||
Expressions.FALSE | ||
) | ||
} | ||
|
||
private fun getOrderBy(productCommentOrderType: ProductCommentOrderType): OrderSpecifier<*> { | ||
return if (productCommentOrderType == ProductCommentOrderType.RECENT) { | ||
productComment.productCommentId.desc() | ||
} else { | ||
productComment.createdAt.desc() | ||
} | ||
} | ||
} |
Oops, something went wrong.