refactor(creator-channel): 홈 repository 커뮤니티 조회 책임을 제거한다

This commit is contained in:
2026-06-21 23:19:52 +09:00
parent 6ab3c50c32
commit 014511668a
3 changed files with 0 additions and 544 deletions

View File

@@ -2,8 +2,6 @@ package kr.co.vividnext.sodalive.v2.creator.channel.home.adapter.out.persistence
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.can.use.UseCan
import kr.co.vividnext.sodalive.chat.character.ChatCharacter
import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.content.AudioContent
@@ -15,9 +13,6 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationMessage
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.CreatorCommunityComment
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.CreatorCommunityLike
import kr.co.vividnext.sodalive.live.room.GenderRestriction
import kr.co.vividnext.sodalive.live.room.LiveRoom
import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisit
@@ -143,14 +138,10 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
"audio queries in this repository must project required columns"
)
assertFalse(source.contains(".selectFrom(channelDonationMessage)"), "donation query must project required columns")
assertFalse(source.contains(".selectFrom(creatorCommunity)"), "community query must project required columns")
assertFalse(source.contains(".selectFrom(series)"), "series query must project required columns")
assertFalse(source.contains(".select(series)"), "series query must not fetch full Series entity")
assertFalse(source.contains(".selectFrom(creatorCheers)"), "fan talk latest query must project required columns")
assertFalse(source.contains(".fetch()\n .size"), "counts must use DB count instead of fetching ids")
assertFalse(source.contains("existsCommunityOrder("), "community orders must be bulk calculated")
assertFalse(source.contains("countCommunityLikes("), "community likes must be bulk calculated")
assertFalse(source.contains("countCommunityComments("), "community comments must be bulk calculated")
assertFalse(source.contains("publishedSeriesContents("), "series contents must be bulk calculated")
assertFalse(source.contains("hasNewSeriesContent("), "series new flags must be bulk calculated")
assertTrue(
@@ -214,17 +205,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
saveOrder(viewer, creator, latestAudio, OrderType.KEEP)
saveOrder(viewer, creator, listAudio, OrderType.RENTAL, endDate = now.plusDays(1))
val donation = saveDonation(creator, donor, 500, now.minusHours(3), additionalMessage = "integrated thanks")
val notice = saveCommunity(creator, isFixed = true, fixedAt = now.minusHours(4), price = 0)
val community = saveCommunity(
creator,
isFixed = false,
price = 100,
imagePath = "community.png",
audioPath = "community.mp3"
)
saveCommunityOrder(viewer, community, isRefund = false)
saveCommunityLike(viewer, community, isActive = true)
saveCommunityComment(viewer, community, isActive = true)
val fanTalk = saveCheers(fan, creator, "integrated fan talk", isActive = true, now.minusMinutes(30))
saveVisit(currentLive, viewer)
flushAndClear()
@@ -247,7 +227,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
viewerId = viewer.id!!
)
val donations = repository.findChannelDonations(creator.id!!, viewer.id!!, now, limit = 8)
val notices = repository.findCommunityPosts(creator.id!!, viewer.id!!, isFixed = true, false, limit = 3)
val schedules = repository.findSchedules(
creator.id!!,
now,
@@ -266,7 +245,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
limit = 9
)
val seriesRecords = repository.findSeries(creator.id!!, viewer.id!!, now, false, ContentType.ALL, limit = 8)
val communities = repository.findCommunityPosts(creator.id!!, viewer.id!!, isFixed = false, false, limit = 3)
val fanTalkSummary = repository.findFanTalkSummary(creator.id!!, viewer.id!!)
val activity = repository.findActivity(creator.id!!, now)
val sns = repository.findSns(creator.id!!)
@@ -279,7 +257,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
assertFalse(latestAudioRecord.isRented)
assertEquals(listOf(donation.can), donations.map { it.can })
assertEquals("integrated thanks", donations.single().message)
assertEquals(listOf(notice.id), notices.map { it.postId })
assertEquals(listOf(liveSchedule.id, audioSchedule.id), schedules.map { it.targetId })
assertEquals(listOf(CreatorActivityType.LIVE, CreatorActivityType.AUDIO), schedules.map { it.type })
assertEquals(listOf(listAudio.id, firstAudio.id), audioContents.map { it.audioContentId })
@@ -287,10 +264,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
assertEquals(listOf(true, false), audioContents.map { it.isRented })
assertEquals(listOf(series.id), seriesRecords.map { it.seriesId })
assertEquals(true, seriesRecords.single().isOriginal)
assertEquals(listOf(community.id), communities.map { it.postId })
assertEquals(1, communities.single().likeCount)
assertEquals(1, communities.single().commentCount)
assertTrue(communities.single().existOrdered)
assertEquals(1, fanTalkSummary.totalCount)
assertEquals(fanTalk.id, fanTalkSummary.latestFanTalk!!.fanTalkId)
assertEquals(now.minusDays(3), activity.debutDate)
@@ -642,54 +615,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
assertTrue(records.single().isFirstContent)
}
@Test
@DisplayName("채널 후원, 공지, 커뮤니티, 팬 Talk는 기존 전체보기 의미에 맞는 요약을 조회한다")
fun shouldFindDonationsCommunitiesAndFanTalkSummary() {
val now = LocalDateTime.of(2026, 6, 12, 12, 0)
val creator = saveMember("community-creator", MemberRole.CREATOR)
val viewer = saveMember("community-viewer", MemberRole.USER)
val donor = saveMember("community-donor", MemberRole.USER)
val blockedWriter = saveMember("blocked-talk-writer", MemberRole.USER)
val donation = saveDonation(creator, donor, 300, now.minusDays(1))
saveDonation(creator, donor, 100, now.minusMonths(1))
val notice = saveCommunity(creator, isFixed = true, fixedAt = now.minusHours(1), price = 0)
saveCommunity(creator, isFixed = true, fixedAt = null, price = 0)
val post = saveCommunity(creator, isFixed = false, price = 100, imagePath = "community.png", audioPath = "community.mp3")
saveCommunityLike(viewer, post, isActive = true)
saveCommunityComment(viewer, post, isActive = true)
saveCommunityOrder(viewer, post, isRefund = false)
val latestTalk = saveCheers(viewer, creator, "latest", isActive = true, now.minusMinutes(1))
saveCheers(blockedWriter, creator, "blocked", isActive = true, now)
saveBlock(viewer, blockedWriter)
flushAndClear()
val donations = repository.findChannelDonations(creator.id!!, viewer.id!!, now, limit = 8)
val notices = repository.findCommunityPosts(
creator.id!!,
viewer.id!!,
isFixed = true,
canViewAdultContent = false,
limit = 3
)
val posts = repository.findCommunityPosts(
creator.id!!,
viewer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
val fanTalk = repository.findFanTalkSummary(creator.id!!, viewer.id!!)
assertEquals(listOf(donation.can), donations.map { it.can })
assertEquals(listOf(notice.id), notices.map { it.postId })
assertEquals(listOf(post.id), posts.map { it.postId })
assertEquals(1, posts.single().likeCount)
assertEquals(1, posts.single().commentCount)
assertTrue(posts.single().existOrdered)
assertEquals(1, fanTalk.totalCount)
assertEquals(latestTalk.id, fanTalk.latestFanTalk!!.fanTalkId)
}
@Test
@DisplayName("팬 Talk 요약은 활성 최상위 글 전체 개수와 최신 1개만 조회한다")
fun shouldSummarizeFanTalkWithTotalCountAndLatestOnly() {
@@ -709,181 +634,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
assertEquals("latest", summary.latestFanTalk!!.content)
}
@Test
@DisplayName("커뮤니티는 성인 정책과 작성자 본인의 구매 여부 의미를 반영한다")
fun shouldFilterAdultCommunityAndTreatCreatorAsOrdered() {
val creator = saveMember("adult-community-creator", MemberRole.CREATOR)
val visiblePost = saveCommunity(creator, isFixed = false, price = 100)
saveCommunity(creator, isFixed = false, price = 100, isAdult = true)
flushAndClear()
val viewerPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = creator.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals(listOf(visiblePost.id), viewerPosts.map { it.postId })
assertTrue(viewerPosts.single().existOrdered)
}
@Test
@DisplayName("유료 커뮤니티는 비구매자에게 본문을 축약하고 오디오를 숨긴다")
fun shouldMaskPaidCommunityContentAndAudioForNonBuyer() {
val creator = saveMember("paid-community-creator", MemberRole.CREATOR)
val viewer = saveMember("paid-community-viewer", MemberRole.USER)
val content = "12345678901234567890"
saveCommunity(
creator,
isFixed = false,
price = 100,
audioPath = "paid-audio.mp3",
content = content
)
flushAndClear()
val posts = repository.findCommunityPosts(
creator.id!!,
viewerId = viewer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals("123456789012345...", posts.single().content)
assertEquals(null, posts.single().audioPath)
assertFalse(posts.single().existOrdered)
}
@Test
@DisplayName("유료 커뮤니티는 구매자와 작성자에게 본문과 오디오를 노출한다")
fun shouldExposePaidCommunityContentAndAudioForBuyerAndCreator() {
val creator = saveMember("paid-community-owner", MemberRole.CREATOR)
val buyer = saveMember("paid-community-buyer", MemberRole.USER)
val post = saveCommunity(
creator,
isFixed = false,
price = 100,
audioPath = "paid-visible.mp3",
content = "paid full content"
)
saveCommunityOrder(buyer, post, isRefund = false)
flushAndClear()
val buyerPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = buyer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
val creatorPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = creator.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals("paid full content", buyerPosts.single().content)
assertEquals("paid-visible.mp3", buyerPosts.single().audioPath)
assertTrue(buyerPosts.single().existOrdered)
assertEquals("paid full content", creatorPosts.single().content)
assertEquals("paid-visible.mp3", creatorPosts.single().audioPath)
assertTrue(creatorPosts.single().existOrdered)
}
@Test
@DisplayName("구매한 유료 커뮤니티는 크리에이터가 삭제해도 구매자에게 조회된다")
fun shouldExposeDeletedPaidCommunityContentToBuyer() {
val creator = saveMember("deleted-paid-community-creator", MemberRole.CREATOR)
val buyer = saveMember("deleted-paid-community-buyer", MemberRole.USER)
val nonBuyer = saveMember("deleted-paid-community-non-buyer", MemberRole.USER)
val post = saveCommunity(
creator,
isFixed = false,
price = 100,
audioPath = "deleted-paid.mp3",
content = "deleted paid content",
isActive = false
)
saveCommunityOrder(buyer, post, isRefund = false)
flushAndClear()
val buyerPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = buyer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
val nonBuyerPosts = repository.findCommunityPosts(
creator.id!!,
viewerId = nonBuyer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals(listOf(post.id), buyerPosts.map { it.postId })
assertEquals("deleted paid content", buyerPosts.single().content)
assertEquals("deleted-paid.mp3", buyerPosts.single().audioPath)
assertTrue(buyerPosts.single().existOrdered)
assertEquals(emptyList<Long>(), nonBuyerPosts.map { it.postId })
}
@Test
@DisplayName("커뮤니티 댓글 수는 기존 목록처럼 보이는 최상위 댓글만 계산한다")
fun shouldCountVisibleRootCommunityCommentsOnly() {
val creator = saveMember("comment-count-creator", MemberRole.CREATOR)
val viewer = saveMember("comment-count-viewer", MemberRole.USER)
val blockedWriter = saveMember("comment-count-blocked", MemberRole.USER)
val blockingWriter = saveMember("comment-count-blocking", MemberRole.USER)
val secretWriter = saveMember("comment-count-secret", MemberRole.USER)
val post = saveCommunity(creator, isFixed = false, price = 0)
val visibleRoot = saveCommunityComment(viewer, post, isActive = true)
saveCommunityComment(viewer, post, isActive = true, parent = visibleRoot)
saveCommunityComment(viewer, post, isActive = false)
saveCommunityComment(blockedWriter, post, isActive = true)
saveCommunityComment(blockingWriter, post, isActive = true)
saveCommunityComment(secretWriter, post, isActive = true, isSecret = true)
saveBlock(viewer, blockedWriter)
saveBlock(blockingWriter, viewer)
flushAndClear()
val posts = repository.findCommunityPosts(
creator.id!!,
viewerId = viewer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals(1, posts.single().commentCount)
}
@Test
@DisplayName("커뮤니티 댓글 수는 댓글 불가 게시글이면 기존 목록처럼 0으로 계산한다")
fun shouldReturnZeroCommentCountWhenCommunityCommentUnavailable() {
val creator = saveMember("comment-unavailable-creator", MemberRole.CREATOR)
val viewer = saveMember("comment-unavailable-viewer", MemberRole.USER)
val post = saveCommunity(creator, isFixed = false, price = 0, isCommentAvailable = false)
saveCommunityComment(viewer, post, isActive = true)
flushAndClear()
val posts = repository.findCommunityPosts(
creator.id!!,
viewerId = viewer.id!!,
isFixed = false,
canViewAdultContent = false,
limit = 3
)
assertEquals(0, posts.single().commentCount)
}
@Test
@DisplayName("채널 후원은 KST 기준 이번 달과 크리에이터의 비밀 후원 열람을 반영한다")
fun shouldFindKstMonthDonationsAndExposeSecretDonationToCreator() {
@@ -1421,34 +1171,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
return donation
}
private fun saveCommunity(
creator: Member,
isFixed: Boolean,
fixedAt: LocalDateTime? = null,
price: Int,
imagePath: String? = null,
audioPath: String? = null,
isAdult: Boolean = false,
isCommentAvailable: Boolean = true,
content: String = "community",
isActive: Boolean = true
): CreatorCommunity {
val community = CreatorCommunity(
content = content,
price = price,
isCommentAvailable = isCommentAvailable,
isAdult = isAdult,
audioPath = audioPath,
imagePath = imagePath,
isActive = isActive,
isFixed = isFixed,
fixedAt = fixedAt
)
community.member = creator
entityManager.persist(community)
return community
}
private fun saveAuth(member: Member, gender: Int): Auth {
val auth = Auth(
name = member.nickname,
@@ -1462,37 +1184,6 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
return auth
}
private fun saveCommunityLike(member: Member, community: CreatorCommunity, isActive: Boolean): CreatorCommunityLike {
val like = CreatorCommunityLike(isActive = isActive)
like.member = member
like.creatorCommunity = community
entityManager.persist(like)
return like
}
private fun saveCommunityComment(
member: Member,
community: CreatorCommunity,
isActive: Boolean,
isSecret: Boolean = false,
parent: CreatorCommunityComment? = null
): CreatorCommunityComment {
val comment = CreatorCommunityComment(comment = "comment", isSecret = isSecret, isActive = isActive)
comment.member = member
comment.creatorCommunity = community
comment.parent = parent
entityManager.persist(comment)
return comment
}
private fun saveCommunityOrder(member: Member, community: CreatorCommunity, isRefund: Boolean): UseCan {
val useCan = UseCan(CanUsage.PAID_COMMUNITY_POST, community.price, rewardCan = 0, isRefund = isRefund)
useCan.member = member
useCan.communityPost = community
entityManager.persist(useCan)
return useCan
}
private fun saveOrder(
member: Member,
creator: Member,