diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt index 182ca9d..9bc79fe 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt @@ -69,6 +69,7 @@ class CanService(private val repository: CanRepository) { CanUsage.CHANGE_NICKNAME -> "닉네임 변경" CanUsage.ORDER_CONTENT -> "콘텐츠 구매" + CanUsage.PAID_COMMUNITY_POST -> "게시글 보기" } val createdAt = it.createdAt!! diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt index 73e04dc..e36e8c1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt @@ -15,6 +15,7 @@ import kr.co.vividnext.sodalive.can.use.UseCanRepository import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.content.AudioContent import kr.co.vividnext.sodalive.content.order.Order +import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity import kr.co.vividnext.sodalive.live.room.LiveRoom import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.MemberRepository @@ -38,6 +39,7 @@ class CanPaymentService( liveRoom: LiveRoom? = null, order: Order? = null, audioContent: AudioContent? = null, + communityPost: CreatorCommunity? = null, container: String ) { val member = memberRepository.findByIdOrNull(id = memberId) @@ -90,6 +92,10 @@ class CanPaymentService( recipientId = liveRoom.member!!.id!! useCan.room = liveRoom useCan.member = member + } else if (canUsage == CanUsage.PAID_COMMUNITY_POST && communityPost != null) { + recipientId = communityPost.member!!.id!! + useCan.communityPost = communityPost + useCan.member = member } else { throw SodaException("잘못된 요청입니다.") } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/use/CanUsage.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/use/CanUsage.kt index 58bbde9..9d4f218 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/use/CanUsage.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/use/CanUsage.kt @@ -5,5 +5,6 @@ enum class CanUsage { DONATION, CHANGE_NICKNAME, ORDER_CONTENT, - SPIN_ROULETTE + SPIN_ROULETTE, + PAID_COMMUNITY_POST } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunity.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunity.kt index 5399427..68143c7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunity.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunity.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.GetCommunityPostCommentListItem import kr.co.vividnext.sodalive.member.Member import javax.persistence.Column import javax.persistence.Entity @@ -22,4 +23,37 @@ data class CreatorCommunity( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) var member: Member? = null + + fun toCommunityPostListResponse( + imageHost: String, + content: String, + date: String, + isLike: Boolean, + existOrdered: Boolean, + likeCount: Int, + commentCount: Int, + firstComment: GetCommunityPostCommentListItem? + ): GetCommunityPostListResponse { + return GetCommunityPostListResponse( + postId = id!!, + creatorId = member!!.id!!, + creatorNickname = member!!.nickname, + creatorProfileUrl = "$imageHost/${member?.profileImage ?: "profile/default-profile.png"}", + imageUrl = if (imagePath != null) { + "$imageHost/$imagePath" + } else { + null + }, + content = content, + price = price, + date = date, + isCommentAvailable = isCommentAvailable, + isAdult = false, + isLike = isLike, + existOrdered = existOrdered, + likeCount = likeCount, + commentCount = commentCount, + firstComment = firstComment + ) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityController.kt index 9c18a29..0307940 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityController.kt @@ -195,4 +195,22 @@ class CreatorCommunityController(private val service: CreatorCommunityService) { ) ) } + + @PostMapping("/purchase") + fun purchasePost( + @RequestBody request: PurchasePostRequest, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok( + service.purchasePost( + postId = request.postId, + memberId = member.id!!, + timezone = request.timezone, + isAdult = member.auth != null, + container = request.container + ) + ) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityRepository.kt index 8c345db..16247ae 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityRepository.kt @@ -24,6 +24,8 @@ interface CreatorCommunityQueryRepository { fun findByIdAndActive(postId: Long, isAdult: Boolean): CreatorCommunity? fun getLatestPostListFromCreatorsYouFollow(memberId: Long, isAdult: Boolean): List + + fun getCommunityPost(postId: Long, isAdult: Boolean): SelectCommunityPostResponse? } class CreatorCommunityQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CreatorCommunityQueryRepository { @@ -146,4 +148,31 @@ class CreatorCommunityQueryRepositoryImpl(private val queryFactory: JPAQueryFact .limit(10) .fetch() } + + override fun getCommunityPost(postId: Long, isAdult: Boolean): SelectCommunityPostResponse? { + var where = creatorCommunity.id.eq(postId) + .and(creatorCommunity.isActive.isTrue) + + if (!isAdult) { + where = where.and(creatorCommunity.isAdult.isFalse) + } + + return queryFactory + .select( + QSelectCommunityPostResponse( + creatorCommunity.id, + creatorCommunity.member.id, + creatorCommunity.member.nickname, + creatorCommunity.member.profileImage.coalesce("profile/default_profile.png"), + creatorCommunity.imagePath, + creatorCommunity.content, + creatorCommunity.createdAt, + creatorCommunity.isCommentAvailable, + creatorCommunity.price + ) + ) + .from(creatorCommunity) + .where(where) + .fetchFirst() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityService.kt index ec34a95..24d0073 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityService.kt @@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity import com.amazonaws.services.s3.model.ObjectMetadata import com.fasterxml.jackson.databind.ObjectMapper import kr.co.vividnext.sodalive.aws.s3.S3Uploader +import kr.co.vividnext.sodalive.can.payment.CanPaymentService +import kr.co.vividnext.sodalive.can.use.CanUsage import kr.co.vividnext.sodalive.can.use.UseCanRepository import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.CreatorCommunityComment @@ -29,6 +31,8 @@ import java.time.LocalDateTime @Service class CreatorCommunityService( + private val canPaymentService: CanPaymentService, + private val repository: CreatorCommunityRepository, private val blockMemberRepository: BlockMemberRepository, private val likeRepository: CreatorCommunityLikeRepository, @@ -152,7 +156,6 @@ class CreatorCommunityService( ) return postList - .asSequence() .map { val isLike = likeRepository.findByPostIdAndMemberId(postId = it.id, memberId = memberId)?.isActive ?: false @@ -192,21 +195,10 @@ class CreatorCommunityService( it.content } - GetCommunityPostListResponse( - postId = it.id, - creatorId = it.creatorId, - creatorNickname = it.creatorNickname, - creatorProfileUrl = "$imageHost/${it.creatorProfileUrl}", - imageUrl = if (it.imagePath != null) { - "$imageHost/${it.imagePath}" - } else { - null - }, + it.toCommunityPostListResponse( + imageHost = imageHost, content = content, - price = it.price, date = getTimeAgoString(it.date), - isCommentAvailable = it.isCommentAvailable, - isAdult = false, isLike = isLike, existOrdered = if (memberId == it.creatorId) { true @@ -218,7 +210,6 @@ class CreatorCommunityService( firstComment = firstComment ) } - .toList() } fun getCommunityPostDetail( @@ -227,16 +218,16 @@ class CreatorCommunityService( timezone: String, isAdult: Boolean ): GetCommunityPostListResponse { - val post = repository.findByIdAndActive(postId, isAdult = isAdult) + val post = repository.getCommunityPost(postId, isAdult = isAdult) ?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") - val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = post.member!!.id!!) - if (isBlocked) throw SodaException("${post.member!!.nickname}님의 요청으로 접근이 제한됩니다.") + val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = post.creatorId) + if (isBlocked) throw SodaException("${post.creatorNickname}님의 요청으로 접근이 제한됩니다.") - val isLike = likeRepository.findByPostIdAndMemberId(postId = post.id!!, memberId = memberId)?.isActive ?: false - val likeCount = likeRepository.totalCountCommunityPostLikeByPostId(post.id!!) + val isLike = likeRepository.findByPostIdAndMemberId(postId = post.id, memberId = memberId)?.isActive ?: false + val likeCount = likeRepository.totalCountCommunityPostLikeByPostId(post.id) val commentCount = if (post.isCommentAvailable) { - commentRepository.totalCountCommentByPostId(postId = post.id!!) + commentRepository.totalCountCommentByPostId(postId = post.id) } else { 0 } @@ -244,7 +235,7 @@ class CreatorCommunityService( commentRepository.findByPostId( cloudFrontHost = imageHost, timezone = timezone, - id = post.id!!, + id = post.id, offset = 0, limit = 1 ) @@ -258,9 +249,9 @@ class CreatorCommunityService( null } - val existOrdered = useCanRepository.isExistOrdered(postId = post.id!!, memberId = memberId) + val existOrdered = useCanRepository.isExistOrdered(postId = post.id, memberId = memberId) - val content = if (post.price > 0 && memberId != post.member!!.id) { + val content = if (post.price > 0 && memberId != post.creatorId) { if (existOrdered) { post.content } else { @@ -270,27 +261,12 @@ class CreatorCommunityService( post.content } - return GetCommunityPostListResponse( - postId = post.id!!, - creatorId = post.member!!.id!!, - creatorNickname = post.member!!.nickname, - creatorProfileUrl = if (post.member!!.profileImage != null) { - "$imageHost/${post.member!!.profileImage}" - } else { - "$imageHost/profile/default-profile.png" - }, - imageUrl = if (post.imagePath != null) { - "$imageHost/${post.imagePath!!}" - } else { - null - }, + return post.toCommunityPostListResponse( + imageHost = imageHost, content = content, - price = post.price, - date = getTimeAgoString(post.createdAt!!), - isCommentAvailable = post.isCommentAvailable, - isAdult = post.isAdult, + date = getTimeAgoString(post.date), isLike = isLike, - existOrdered = if (memberId == post.member!!.id!!) { + existOrdered = if (memberId == post.creatorId) { true } else { existOrdered @@ -420,7 +396,6 @@ class CreatorCommunityService( val postList = repository.getLatestPostListFromCreatorsYouFollow(memberId = memberId, isAdult = isAdult) return postList - .asSequence() .filter { !blockMemberRepository.isBlocked( blockedMemberId = memberId, @@ -440,7 +415,7 @@ class CreatorCommunityService( commentRepository.findByPostId( cloudFrontHost = imageHost, timezone = timezone, - id = it.id!!, + id = it.id, offset = 0, limit = 1 ) @@ -466,21 +441,10 @@ class CreatorCommunityService( it.content } - GetCommunityPostListResponse( - postId = it.id, - creatorId = it.creatorId, - creatorNickname = it.creatorNickname, - creatorProfileUrl = "$imageHost/${it.creatorProfileUrl}", - imageUrl = if (it.imagePath != null) { - "$imageHost/${it.imagePath}" - } else { - null - }, + it.toCommunityPostListResponse( + imageHost = imageHost, content = content, - price = it.price, date = getTimeAgoString(it.date), - isCommentAvailable = it.isCommentAvailable, - isAdult = false, isLike = isLike, existOrdered = if (memberId == it.creatorId) { true @@ -492,6 +456,68 @@ class CreatorCommunityService( firstComment = firstComment ) } - .toList() + } + + @Transactional + fun purchasePost( + postId: Long, + memberId: Long, + timezone: String, + isAdult: Boolean, + container: String + ): GetCommunityPostListResponse { + val post = repository.findByIdAndActive(postId, isAdult) + ?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") + + val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = post.member!!.id!!) + if (isBlocked) throw SodaException("${post.member!!.nickname}님의 요청으로 접근이 제한됩니다.") + + val existOrdered = useCanRepository.isExistOrdered(postId = postId, memberId = memberId) + + if (!existOrdered) { + canPaymentService.spendCan( + memberId = memberId, + needCan = post.price, + canUsage = CanUsage.PAID_COMMUNITY_POST, + communityPost = post, + container = container + ) + } + + val isLike = likeRepository.findByPostIdAndMemberId(postId = post.id!!, memberId = memberId)?.isActive ?: false + val likeCount = likeRepository.totalCountCommunityPostLikeByPostId(post.id!!) + val commentCount = if (post.isCommentAvailable) { + commentRepository.totalCountCommentByPostId(postId = post.id!!) + } else { + 0 + } + val commentList = if (post.isCommentAvailable) { + commentRepository.findByPostId( + cloudFrontHost = imageHost, + timezone = timezone, + id = post.id!!, + offset = 0, + limit = 1 + ) + } else { + listOf() + } + + val firstComment = if (post.isCommentAvailable && commentList.isNotEmpty()) { + commentList[0] + } else { + null + } + + return post.toCommunityPostListResponse( + imageHost = imageHost, + content = post.content, + date = getTimeAgoString(post.createdAt!!), + isLike = isLike, + existOrdered = true, + likeCount = likeCount, + commentCount = commentCount, + firstComment = firstComment + ) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/PurchasePostRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/PurchasePostRequest.kt new file mode 100644 index 0000000..a32e4f4 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/PurchasePostRequest.kt @@ -0,0 +1,3 @@ +package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity + +data class PurchasePostRequest(val postId: Long, val timezone: String, val container: String) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/SelectCommunityPostResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/SelectCommunityPostResponse.kt index 89a953a..90aa413 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/SelectCommunityPostResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/SelectCommunityPostResponse.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity import com.querydsl.core.annotations.QueryProjection +import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.GetCommunityPostCommentListItem import java.time.LocalDateTime data class SelectCommunityPostResponse @QueryProjection constructor( @@ -13,4 +14,37 @@ data class SelectCommunityPostResponse @QueryProjection constructor( val date: LocalDateTime, val isCommentAvailable: Boolean, val price: Int -) +) { + fun toCommunityPostListResponse( + imageHost: String, + content: String, + date: String, + isLike: Boolean, + existOrdered: Boolean, + likeCount: Int, + commentCount: Int, + firstComment: GetCommunityPostCommentListItem? + ): GetCommunityPostListResponse { + return GetCommunityPostListResponse( + postId = id, + creatorId = creatorId, + creatorNickname = creatorNickname, + creatorProfileUrl = "$imageHost/$creatorProfileUrl", + imageUrl = if (imagePath != null) { + "$imageHost/$imagePath" + } else { + null + }, + content = content, + price = price, + date = date, + isCommentAvailable = isCommentAvailable, + isAdult = false, + isLike = isLike, + existOrdered = existOrdered, + likeCount = likeCount, + commentCount = commentCount, + firstComment = firstComment + ) + } +}