feat(community): 게시물 댓글 조회 포트를 구현한다

This commit is contained in:
2026-07-07 11:55:46 +09:00
parent fe9adf35e7
commit cf8acacedf
3 changed files with 370 additions and 0 deletions

View File

@@ -12,6 +12,8 @@ import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreat
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.member.block.QBlockMember
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCommentContextRecord
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 org.springframework.stereotype.Repository
@@ -119,6 +121,122 @@ class DefaultCreatorChannelCommunityQueryRepository(
return rows.toCommunityPostRecords(creatorId, viewerId)
}
override fun findCommunityPost(
postId: Long,
viewerId: Long,
canViewAdultContent: Boolean
): CreatorChannelCommunityPostRecord? {
val rows = queryFactory
.selectCommunityPostRow()
.from(creatorCommunity)
.where(communityPostByIdCondition(postId, canViewAdultContent))
.fetch()
val creatorId = rows.firstOrNull()?.creatorId ?: return null
return rows.toCommunityPostRecords(creatorId, viewerId).firstOrNull()
}
override fun countCommunityComments(
postId: Long,
viewerId: Long,
creatorId: Long
): Int {
return queryFactory
.select(creatorCommunityComment.id.count())
.from(creatorCommunityComment)
.where(rootCommentCondition(postId, creatorId, viewerId))
.fetchOne()
?.toInt()
?: 0
}
override fun findCommunityComments(
postId: Long,
viewerId: Long,
creatorId: Long,
offset: Long,
limit: Int
): List<CreatorChannelCommunityCommentRecord> {
return queryFactory
.selectCommunityCommentRow()
.from(creatorCommunityComment)
.where(rootCommentCondition(postId, creatorId, viewerId))
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
.offset(offset)
.limit(limit.toLong())
.fetch()
.map { it.toCommunityCommentRecord() }
}
override fun findLatestRepliesByCommentIds(
commentIds: List<Long>,
viewerId: Long
): List<CreatorChannelCommunityCommentRecord> {
if (commentIds.isEmpty()) return emptyList()
return queryFactory
.selectCommunityCommentRow()
.from(creatorCommunityComment)
.where(replyCondition(commentIds, viewerId))
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
.fetch()
.map { it.toCommunityCommentRecord() }
.groupBy { it.parentCommentId }
.values
.map { it.first() }
}
override fun findRootCommentContext(
commentId: Long,
viewerId: Long,
canViewAdultContent: Boolean
): CreatorChannelCommunityCommentContextRecord? {
val row = queryFactory
.select(
creatorCommunityComment.id,
creatorCommunityComment.creatorCommunity.id,
creatorCommunityComment.creatorCommunity.member.id,
creatorCommunityComment.creatorCommunity.isCommentAvailable
)
.from(creatorCommunityComment)
.where(rootCommentContextCondition(commentId, viewerId, canViewAdultContent))
.fetchFirst() ?: return null
return CreatorChannelCommunityCommentContextRecord(
commentId = row.get(creatorCommunityComment.id)!!,
postId = row.get(creatorCommunityComment.creatorCommunity.id)!!,
creatorId = row.get(creatorCommunityComment.creatorCommunity.member.id)!!,
isCommentAvailable = row.get(creatorCommunityComment.creatorCommunity.isCommentAvailable)!!
)
}
override fun countCommunityReplies(commentId: Long, viewerId: Long): Int {
return queryFactory
.select(creatorCommunityComment.id.count())
.from(creatorCommunityComment)
.where(replyCondition(commentId, viewerId))
.fetchOne()
?.toInt()
?: 0
}
override fun findCommunityReplies(
commentId: Long,
viewerId: Long,
offset: Long,
limit: Int
): List<CreatorChannelCommunityCommentRecord> {
return queryFactory
.selectCommunityCommentRow()
.from(creatorCommunityComment)
.where(replyCondition(commentId, viewerId))
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
.offset(offset)
.limit(limit.toLong())
.fetch()
.map { it.toCommunityCommentRecord() }
}
private fun JPAQueryFactory.selectCommunityPostRow() = select(
creatorCommunity.id,
creatorCommunity.member.id,
@@ -134,6 +252,17 @@ class DefaultCreatorChannelCommunityQueryRepository(
creatorCommunity.isCommentAvailable
)
private fun JPAQueryFactory.selectCommunityCommentRow() = select(
creatorCommunityComment.id,
creatorCommunityComment.parent.id,
creatorCommunityComment.creatorCommunity.id,
creatorCommunityComment.member.id,
creatorCommunityComment.member.profileImage,
creatorCommunityComment.member.nickname,
creatorCommunityComment.comment,
creatorCommunityComment.createdAt
)
private fun communityPostCondition(creatorId: Long, canViewAdultContent: Boolean): BooleanExpression {
val condition = creatorCommunity.isActive.isTrue
.and(creatorCommunity.member.id.eq(creatorId))
@@ -142,6 +271,62 @@ class DefaultCreatorChannelCommunityQueryRepository(
return if (canViewAdultContent) condition else condition.and(creatorCommunity.isAdult.isFalse)
}
private fun communityPostByIdCondition(postId: Long, canViewAdultContent: Boolean): BooleanExpression {
val condition = creatorCommunity.id.eq(postId)
.and(creatorCommunity.isActive.isTrue)
.and(creatorCommunity.member.isActive.isTrue)
return if (canViewAdultContent) condition else condition.and(creatorCommunity.isAdult.isFalse)
}
private fun rootCommentCondition(
postId: Long,
creatorId: Long,
viewerId: Long
): BooleanExpression {
return creatorCommunityComment.creatorCommunity.id.eq(postId)
.and(creatorCommunityComment.isActive.isTrue)
.and(creatorCommunityComment.parent.isNull)
.and(visibleSecretCommentCondition(creatorId, viewerId))
.and(notBlockedCommentWriterCondition(viewerId))
}
private fun rootCommentContextCondition(
commentId: Long,
viewerId: Long,
canViewAdultContent: Boolean
): BooleanExpression {
val condition = creatorCommunityComment.id.eq(commentId)
.and(creatorCommunityComment.isActive.isTrue)
.and(creatorCommunityComment.parent.isNull)
.and(creatorCommunityComment.creatorCommunity.isActive.isTrue)
.and(creatorCommunityComment.creatorCommunity.member.isActive.isTrue)
.and(
creatorCommunityComment.isSecret.isFalse
.or(creatorCommunityComment.creatorCommunity.member.id.eq(viewerId))
.or(creatorCommunityComment.member.id.eq(viewerId))
)
.and(notBlockedCommentWriterCondition(viewerId))
return if (canViewAdultContent) condition else condition.and(creatorCommunityComment.creatorCommunity.isAdult.isFalse)
}
private fun replyCondition(commentId: Long, viewerId: Long): BooleanExpression {
return creatorCommunityComment.isActive.isTrue
.and(creatorCommunityComment.parent.isNotNull)
.and(creatorCommunityComment.parent.id.eq(commentId))
.and(visibleSecretReplyCondition(viewerId))
.and(notBlockedCommentWriterCondition(viewerId))
}
private fun replyCondition(commentIds: List<Long>, viewerId: Long): BooleanExpression {
return creatorCommunityComment.isActive.isTrue
.and(creatorCommunityComment.parent.isNotNull)
.and(creatorCommunityComment.parent.id.`in`(commentIds))
.and(visibleSecretReplyCondition(viewerId))
.and(notBlockedCommentWriterCondition(viewerId))
}
private fun homeCommunityPostCondition(
creatorId: Long,
viewerId: Long,
@@ -302,6 +487,12 @@ class DefaultCreatorChannelCommunityQueryRepository(
.or(creatorCommunityComment.member.id.eq(viewerId))
}
private fun visibleSecretReplyCondition(viewerId: Long): BooleanExpression {
return creatorCommunityComment.isSecret.isFalse
.or(creatorCommunityComment.creatorCommunity.member.id.eq(viewerId))
.or(creatorCommunityComment.member.id.eq(viewerId))
}
private fun notBlockedCommentWriterCondition(viewerId: Long): BooleanExpression {
val viewerBlock = QBlockMember("communityCommentViewerBlockWriter")
val writerBlock = QBlockMember("communityCommentWriterBlockViewer")
@@ -355,4 +546,17 @@ class DefaultCreatorChannelCommunityQueryRepository(
private val Tuple.isCommentAvailable: Boolean
get() = get(creatorCommunity.isCommentAvailable)!!
private fun Tuple.toCommunityCommentRecord(): CreatorChannelCommunityCommentRecord {
return CreatorChannelCommunityCommentRecord(
commentId = get(creatorCommunityComment.id)!!,
parentCommentId = get(creatorCommunityComment.parent.id),
postId = get(creatorCommunityComment.creatorCommunity.id)!!,
writerId = get(creatorCommunityComment.member.id)!!,
writerProfilePath = get(creatorCommunityComment.member.profileImage),
writerNickname = get(creatorCommunityComment.member.nickname)!!,
content = get(creatorCommunityComment.comment)!!,
createdAt = get(creatorCommunityComment.createdAt)!!
)
}
}

View File

@@ -29,6 +29,49 @@ interface CreatorChannelCommunityQueryPort {
canViewAdultContent: Boolean,
limit: Int
): List<CreatorChannelCommunityPostRecord>
fun findCommunityPost(
postId: Long,
viewerId: Long,
canViewAdultContent: Boolean
): CreatorChannelCommunityPostRecord?
fun countCommunityComments(
postId: Long,
viewerId: Long,
creatorId: Long
): Int
fun findCommunityComments(
postId: Long,
viewerId: Long,
creatorId: Long,
offset: Long,
limit: Int
): List<CreatorChannelCommunityCommentRecord>
fun findLatestRepliesByCommentIds(
commentIds: List<Long>,
viewerId: Long
): List<CreatorChannelCommunityCommentRecord>
fun findRootCommentContext(
commentId: Long,
viewerId: Long,
canViewAdultContent: Boolean
): CreatorChannelCommunityCommentContextRecord?
fun countCommunityReplies(
commentId: Long,
viewerId: Long
): Int
fun findCommunityReplies(
commentId: Long,
viewerId: Long,
offset: Long,
limit: Int
): List<CreatorChannelCommunityCommentRecord>
}
data class CreatorChannelCommunityCreatorRecord(
@@ -54,3 +97,21 @@ data class CreatorChannelCommunityPostRecord(
val isPinned: Boolean,
val isLiked: Boolean
)
data class CreatorChannelCommunityCommentRecord(
val commentId: Long,
val parentCommentId: Long?,
val postId: Long,
val writerId: Long,
val writerProfilePath: String?,
val writerNickname: String,
val content: String,
val createdAt: LocalDateTime
)
data class CreatorChannelCommunityCommentContextRecord(
val commentId: Long,
val postId: Long,
val creatorId: Long,
val isCommentAvailable: Boolean
)