fix(block-member): 양방향 차단 관계의 댓글·응원·콘텐츠 노출을 차단한다

This commit is contained in:
2026-02-23 14:08:23 +09:00
parent 07fb6202a8
commit cc74628107
28 changed files with 528 additions and 155 deletions
@@ -98,6 +98,7 @@ class AudioContentCommentController(
return ApiResponse.ok(
service.getCommentReplyList(
commentId = commentId,
memberId = member.id!!,
timezone = timezone,
pageable = pageable
)
@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.content.comment
import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.JPAExpressions
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
@@ -9,6 +10,7 @@ import kr.co.vividnext.sodalive.fcm.PushTokenInfo
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
import kr.co.vividnext.sodalive.fcm.QPushTokenInfo
import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@@ -28,10 +30,11 @@ interface AudioContentCommentQueryRepository {
): List<GetAudioContentCommentListItem>
fun totalCountCommentByContentId(contentId: Long, memberId: Long, isContentCreator: Boolean): Int
fun commentReplyCountByAudioContentCommentId(commentId: Long): Int
fun commentReplyCountByAudioContentCommentId(commentId: Long, memberId: Long): Int
fun getAudioContentCommentReplyList(
cloudFrontHost: String,
commentId: Long,
memberId: Long,
timezone: String,
offset: Long,
limit: Int
@@ -60,6 +63,8 @@ class AudioContentCommentQueryRepositoryImpl(
var where = audioContentComment.audioContent.id.eq(contentId)
.and(audioContentComment.isActive.isTrue)
.and(audioContentComment.parent.isNull)
.and(audioContentComment.member.id.notIn(blockedMemberIdSubQuery(memberId)))
.and(audioContentComment.member.id.notIn(blockingMemberIdSubQuery(memberId)))
if (!isContentCreator) {
where = where.and(
@@ -106,7 +111,7 @@ class AudioContentCommentQueryRepositoryImpl(
.map {
it.copy(
nickname = it.nickname.removeDeletedNicknamePrefix(),
replyCount = commentReplyCountByAudioContentCommentId(it.id)
replyCount = commentReplyCountByAudioContentCommentId(it.id, memberId)
)
}
}
@@ -115,6 +120,8 @@ class AudioContentCommentQueryRepositoryImpl(
var where = audioContentComment.audioContent.id.eq(contentId)
.and(audioContentComment.isActive.isTrue)
.and(audioContentComment.parent.isNull)
.and(audioContentComment.member.id.notIn(blockedMemberIdSubQuery(memberId)))
.and(audioContentComment.member.id.notIn(blockingMemberIdSubQuery(memberId)))
if (!isContentCreator) {
where = where.and(
@@ -133,13 +140,15 @@ class AudioContentCommentQueryRepositoryImpl(
.size
}
override fun commentReplyCountByAudioContentCommentId(commentId: Long): Int {
override fun commentReplyCountByAudioContentCommentId(commentId: Long, memberId: Long): Int {
return queryFactory.select(audioContentComment.id)
.from(audioContentComment)
.where(
audioContentComment.parent.isNotNull
.and(audioContentComment.parent.id.eq(commentId))
.and(audioContentComment.isActive.isTrue)
.and(audioContentComment.member.id.notIn(blockedMemberIdSubQuery(memberId)))
.and(audioContentComment.member.id.notIn(blockingMemberIdSubQuery(memberId)))
)
.fetch()
.size
@@ -148,6 +157,7 @@ class AudioContentCommentQueryRepositoryImpl(
override fun getAudioContentCommentReplyList(
cloudFrontHost: String,
commentId: Long,
memberId: Long,
timezone: String,
offset: Long,
limit: Int
@@ -185,6 +195,8 @@ class AudioContentCommentQueryRepositoryImpl(
audioContentComment.parent.isNotNull
.and(audioContentComment.parent.id.eq(commentId))
.and(audioContentComment.isActive.isTrue)
.and(audioContentComment.member.id.notIn(blockedMemberIdSubQuery(memberId)))
.and(audioContentComment.member.id.notIn(blockingMemberIdSubQuery(memberId)))
)
.offset(offset)
.limit(limit.toLong())
@@ -244,4 +256,20 @@ class AudioContentCommentQueryRepositoryImpl(
return response
}
private fun blockedMemberIdSubQuery(memberId: Long) = JPAExpressions
.select(blockMember.blockedMember.id)
.from(blockMember)
.where(
blockMember.member.id.eq(memberId)
.and(blockMember.isActive.isTrue)
)
private fun blockingMemberIdSubQuery(memberId: Long) = JPAExpressions
.select(blockMember.member.id)
.from(blockMember)
.where(
blockMember.blockedMember.id.eq(memberId)
.and(blockMember.isActive.isTrue)
)
}
@@ -45,7 +45,7 @@ class AudioContentCommentService(
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
val creator = audioContent.member!!
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = creator.id!!)
val isBlocked = isBlockedBetweenMembers(memberId = member.id!!, creatorId = creator.id!!)
if (isBlocked) {
throw SodaException(formatMessage("content.comment.error.blocked_by_creator", creator.nickname))
}
@@ -136,6 +136,13 @@ class AudioContentCommentService(
timezone: String,
pageable: Pageable
): GetAudioContentCommentListResponse {
val audioContent = audioContentRepository.findByIdOrNull(id = audioContentId)
?: return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
if (isBlockedBetweenMembers(memberId = memberId, creatorId = audioContent.member!!.id!!)) {
return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
}
val isContentCreator = audioContentRepository.isContentCreator(audioContentId, memberId)
val commentList = repository.findByContentId(
cloudFrontHost = cloudFrontHost,
@@ -157,17 +164,26 @@ class AudioContentCommentService(
fun getCommentReplyList(
commentId: Long,
memberId: Long,
timezone: String,
pageable: Pageable
): GetAudioContentCommentListResponse {
val parentComment = repository.findByIdOrNull(id = commentId)
?: return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
if (isBlockedBetweenMembers(memberId = memberId, creatorId = parentComment.audioContent!!.member!!.id!!)) {
return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
}
val commentList = repository.getAudioContentCommentReplyList(
cloudFrontHost = cloudFrontHost,
commentId = commentId,
memberId = memberId,
timezone = timezone,
offset = pageable.offset,
limit = pageable.pageSize
)
val totalCount = repository.commentReplyCountByAudioContentCommentId(commentId)
val totalCount = repository.commentReplyCountByAudioContentCommentId(commentId, memberId)
return GetAudioContentCommentListResponse(totalCount, commentList)
}
@@ -176,4 +192,9 @@ class AudioContentCommentService(
val template = messageSource.getMessage(key, langContext.lang) ?: return ""
return String.format(template, *args)
}
private fun isBlockedBetweenMembers(memberId: Long, creatorId: Long): Boolean {
return blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = creatorId) ||
blockMemberRepository.isBlocked(blockedMemberId = creatorId, memberId = memberId)
}
}