feat(community): 게시물 댓글 조회 서비스를 추가한다
This commit is contained in:
@@ -2,15 +2,22 @@ package kr.co.vividnext.sodalive.v2.creator.channel.community.application
|
||||
|
||||
import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityComment
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityComments
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityPost
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityPostDetail
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityQueryPolicy
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityReplies
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityReply
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityTab
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCommentRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCreatorRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityPostRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityQueryPort
|
||||
@@ -93,6 +100,115 @@ class CreatorChannelCommunityQueryService(
|
||||
.map { it.toDomain(viewerId) }
|
||||
}
|
||||
|
||||
fun getCommunityPostDetail(
|
||||
postId: Long,
|
||||
viewer: Member,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
): CreatorChannelCommunityPostDetail {
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
val viewerId = viewer.id!!
|
||||
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(viewer)
|
||||
val postRecord = queryPort.findCommunityPost(postId, viewerId, canViewAdultContent)
|
||||
?: throw SodaException(messageKey = INVALID_COMMUNITY_REQUEST_MESSAGE_KEY)
|
||||
|
||||
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
||||
|
||||
return CreatorChannelCommunityPostDetail(
|
||||
post = postRecord.toDomain(viewerId),
|
||||
comments = getCommunityComments(postId, viewer, page = 0, size = 20, now = now)
|
||||
)
|
||||
}
|
||||
|
||||
fun getCommunityComments(
|
||||
postId: Long,
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
): CreatorChannelCommunityComments {
|
||||
val communityPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
val viewerId = viewer.id!!
|
||||
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(viewer)
|
||||
val postRecord = queryPort.findCommunityPost(postId, viewerId, canViewAdultContent)
|
||||
?: throw SodaException(messageKey = INVALID_COMMUNITY_REQUEST_MESSAGE_KEY)
|
||||
|
||||
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
||||
|
||||
if (!postRecord.isCommentAvailable) {
|
||||
return CreatorChannelCommunityComments(
|
||||
commentCount = 0,
|
||||
comments = emptyList(),
|
||||
page = communityPage,
|
||||
hasNext = false
|
||||
)
|
||||
}
|
||||
|
||||
val fetchedComments = queryPort.findCommunityComments(
|
||||
postId = postId,
|
||||
viewerId = viewerId,
|
||||
creatorId = postRecord.creatorId,
|
||||
offset = communityPage.offset,
|
||||
limit = communityPage.fetchLimit
|
||||
)
|
||||
val limitedComments = queryPolicy.limitItems(fetchedComments, communityPage)
|
||||
val latestReplies = queryPort.findLatestRepliesByCommentIds(
|
||||
commentIds = limitedComments.map { it.commentId },
|
||||
viewerId = viewerId
|
||||
).associateBy { it.parentCommentId }
|
||||
|
||||
return CreatorChannelCommunityComments(
|
||||
commentCount = queryPort.countCommunityComments(
|
||||
postId = postId,
|
||||
viewerId = viewerId,
|
||||
creatorId = postRecord.creatorId
|
||||
),
|
||||
comments = limitedComments.map { it.toCommentDomain(latestReplies[it.commentId]?.toReplyDomain()) },
|
||||
page = communityPage,
|
||||
hasNext = queryPolicy.hasNext(fetchedComments, communityPage)
|
||||
)
|
||||
}
|
||||
|
||||
fun getCommunityReplies(
|
||||
commentId: Long,
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
): CreatorChannelCommunityReplies {
|
||||
val communityPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
val viewerId = viewer.id!!
|
||||
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(viewer)
|
||||
val context = queryPort.findRootCommentContext(commentId, viewerId, canViewAdultContent)
|
||||
?: throw SodaException(messageKey = INVALID_COMMUNITY_REQUEST_MESSAGE_KEY)
|
||||
|
||||
validateNotBlocked(queryPort, viewerId, context.creatorId)
|
||||
|
||||
if (!context.isCommentAvailable) {
|
||||
return CreatorChannelCommunityReplies(
|
||||
replyCount = 0,
|
||||
replies = emptyList(),
|
||||
page = communityPage,
|
||||
hasNext = false
|
||||
)
|
||||
}
|
||||
|
||||
val fetchedReplies = queryPort.findCommunityReplies(
|
||||
commentId = commentId,
|
||||
viewerId = viewerId,
|
||||
offset = communityPage.offset,
|
||||
limit = communityPage.fetchLimit
|
||||
)
|
||||
|
||||
return CreatorChannelCommunityReplies(
|
||||
replyCount = queryPort.countCommunityReplies(commentId, viewerId),
|
||||
replies = queryPolicy.limitItems(fetchedReplies, communityPage).map { it.toReplyDomain() },
|
||||
page = communityPage,
|
||||
hasNext = queryPolicy.hasNext(fetchedReplies, communityPage)
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateCreatorRole(creator: CreatorChannelCommunityCreatorRecord) {
|
||||
when (creator.role) {
|
||||
MemberRole.CREATOR -> return
|
||||
@@ -100,6 +216,12 @@ class CreatorChannelCommunityQueryService(
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateNotBlocked(queryPort: CreatorChannelCommunityQueryPort, viewerId: Long, creatorId: Long) {
|
||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||
throw SodaException(messageKey = INVALID_COMMUNITY_REQUEST_MESSAGE_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CreatorChannelCommunityPostRecord.toDomain(viewerId: Long): CreatorChannelCommunityPost {
|
||||
val canAccessPaidContent = price <= 0 || viewerId == creatorId || existOrdered
|
||||
return CreatorChannelCommunityPost(
|
||||
@@ -126,6 +248,29 @@ class CreatorChannelCommunityQueryService(
|
||||
)
|
||||
}
|
||||
|
||||
private fun CreatorChannelCommunityCommentRecord.toCommentDomain(
|
||||
latestReply: CreatorChannelCommunityReply?
|
||||
): CreatorChannelCommunityComment {
|
||||
return CreatorChannelCommunityComment(
|
||||
commentId = commentId,
|
||||
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
||||
content = content,
|
||||
createdAt = createdAt,
|
||||
latestReply = latestReply
|
||||
)
|
||||
}
|
||||
|
||||
private fun CreatorChannelCommunityCommentRecord.toReplyDomain(): CreatorChannelCommunityReply {
|
||||
return CreatorChannelCommunityReply(
|
||||
commentId = commentId,
|
||||
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
||||
content = content,
|
||||
createdAt = createdAt
|
||||
)
|
||||
}
|
||||
|
||||
private fun String?.toSignedAudioUrl(): String? {
|
||||
if (isNullOrBlank()) return null
|
||||
return audioContentCloudFront.generateSignedURL(this, AUDIO_SIGNED_URL_EXPIRATION_MILLIS)
|
||||
@@ -135,5 +280,6 @@ class CreatorChannelCommunityQueryService(
|
||||
|
||||
companion object {
|
||||
private const val AUDIO_SIGNED_URL_EXPIRATION_MILLIS = 1000L * 60 * 30
|
||||
private const val INVALID_COMMUNITY_REQUEST_MESSAGE_KEY = "creator.community.invalid_request_retry"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user