diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt index 89494556..54ff412f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt @@ -4,8 +4,6 @@ import com.querydsl.core.types.Projections import com.querydsl.core.types.dsl.BooleanExpression import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory -import kr.co.vividnext.sodalive.can.use.CanUsage -import kr.co.vividnext.sodalive.can.use.QUseCan.useCan import kr.co.vividnext.sodalive.chat.character.QChatCharacter.chatCharacter import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.QAudioContent.audioContent @@ -15,9 +13,6 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage -import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity -import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment -import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix import kr.co.vividnext.sodalive.live.room.GenderRestriction import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom @@ -30,7 +25,6 @@ import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollow import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelActivityRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelAudioContentRecord -import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelCommunityPostRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelCreatorRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelDonationRecord import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelFanTalkRecord @@ -207,90 +201,6 @@ class DefaultCreatorChannelHomeQueryRepository( .fetch() } - override fun findCommunityPosts( - creatorId: Long, - viewerId: Long?, - isFixed: Boolean, - canViewAdultContent: Boolean, - limit: Int - ): List { - val posts = queryFactory - .select( - creatorCommunity.id, - creatorCommunity.member.id, - creatorCommunity.member.nickname, - creatorCommunity.member.profileImage, - creatorCommunity.imagePath, - creatorCommunity.audioPath, - creatorCommunity.content, - creatorCommunity.price, - creatorCommunity.createdAt, - creatorCommunity.fixedAt, - creatorCommunity.isFixed, - creatorCommunity.isCommentAvailable - ) - .from(creatorCommunity) - .where( - creatorCommunity.member.id.eq(creatorId), - creatorCommunity.member.isActive.isTrue, - visibleCommunityPostCondition(viewerId), - creatorCommunity.isFixed.eq(isFixed), - fixedNoticeCondition(isFixed), - adultCommunityCondition(canViewAdultContent) - ) - .orderBy( - if (isFixed) creatorCommunity.fixedAt.desc() else creatorCommunity.createdAt.desc(), - creatorCommunity.id.desc() - ) - .limit(limit.toLong()) - .fetch() - - val postIds = posts.map { it.get(creatorCommunity.id)!! } - val orderedPostIds = orderedCommunityPostIds(creatorId, viewerId, postIds) - val likeCounts = communityLikeCounts(postIds) - val commentCounts = communityCommentCounts( - postIds = posts.filter { it.get(creatorCommunity.isCommentAvailable)!! }.map { it.get(creatorCommunity.id)!! }, - viewerId = viewerId, - isContentCreator = viewerId == creatorId - ) - - return posts - .map { - val postId = it.get(creatorCommunity.id)!! - val postCreatorId = it.get(creatorCommunity.member.id)!! - val isFixedPost = it.get(creatorCommunity.isFixed)!! - val price = it.get(creatorCommunity.price)!! - val existOrdered = postId in orderedPostIds - val canAccessPaidContent = canAccessPaidCommunityContent( - price = price, - viewerId = viewerId, - creatorId = postCreatorId, - existOrdered = existOrdered - ) - CreatorChannelCommunityPostRecord( - postId = postId, - creatorId = postCreatorId, - creatorNickname = it.get(creatorCommunity.member.nickname)!!, - creatorProfilePath = it.get(creatorCommunity.member.profileImage), - imagePath = it.get(creatorCommunity.imagePath), - audioPath = if (canAccessPaidContent) it.get(creatorCommunity.audioPath) else null, - content = maskPaidCommunityContent( - content = it.get(creatorCommunity.content)!!, - canAccessPaidContent = canAccessPaidContent - ), - price = price, - date = if (isFixedPost) { - it.get(creatorCommunity.fixedAt) ?: it.get(creatorCommunity.createdAt)!! - } else { - it.get(creatorCommunity.createdAt)!! - }, - existOrdered = existOrdered, - likeCount = likeCounts[postId] ?: 0, - commentCount = commentCounts[postId] ?: 0 - ) - } - } - override fun findSchedules( creatorId: Long, now: LocalDateTime, @@ -639,103 +549,6 @@ class DefaultCreatorChannelHomeQueryRepository( .fetchFirst() } - private fun orderedCommunityPostIds(creatorId: Long, viewerId: Long?, postIds: List): Set { - if (viewerId == null || postIds.isEmpty()) return emptySet() - if (viewerId == creatorId) return postIds.toSet() - return queryFactory - .select(useCan.communityPost.id) - .from(useCan) - .where( - useCan.member.id.eq(viewerId), - useCan.communityPost.id.`in`(postIds), - useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST), - useCan.isRefund.isFalse - ) - .fetch() - .toSet() - } - - private fun communityLikeCounts(postIds: List): Map { - if (postIds.isEmpty()) return emptyMap() - return queryFactory - .select(creatorCommunityLike.creatorCommunity.id, creatorCommunityLike.id.count()) - .from(creatorCommunityLike) - .where(creatorCommunityLike.creatorCommunity.id.`in`(postIds), creatorCommunityLike.isActive.isTrue) - .groupBy(creatorCommunityLike.creatorCommunity.id) - .fetch() - .associate { - it.get(creatorCommunityLike.creatorCommunity.id)!! to - (it.get(creatorCommunityLike.id.count())?.toInt() ?: 0) - } - } - - private fun communityCommentCounts(postIds: List, viewerId: Long?, isContentCreator: Boolean): Map { - if (postIds.isEmpty()) return emptyMap() - var where = creatorCommunityComment.creatorCommunity.id.`in`(postIds) - .and(creatorCommunityComment.isActive.isTrue) - .and(creatorCommunityComment.parent.isNull) - - if (viewerId != null) { - where = where - .and(creatorCommunityComment.member.id.notIn(blockedMemberIdSubQuery(viewerId))) - .and(creatorCommunityComment.member.id.notIn(blockingMemberIdSubQuery(viewerId))) - } - - if (!isContentCreator) { - where = where.and( - creatorCommunityComment.isSecret.isFalse.or( - viewerId?.let { creatorCommunityComment.member.id.eq(it) } - ?: creatorCommunityComment.isSecret.isFalse - ) - ) - } - - return queryFactory - .select(creatorCommunityComment.creatorCommunity.id, creatorCommunityComment.id.count()) - .from(creatorCommunityComment) - .where(where) - .groupBy(creatorCommunityComment.creatorCommunity.id) - .fetch() - .associate { - it.get(creatorCommunityComment.creatorCommunity.id)!! to - (it.get(creatorCommunityComment.id.count())?.toInt() ?: 0) - } - } - - private fun blockedMemberIdSubQuery(viewerId: Long) = QBlockMember("communityCommentViewerBlock").let { viewerBlock -> - queryFactory - .select(viewerBlock.blockedMember.id) - .from(viewerBlock) - .where(viewerBlock.member.id.eq(viewerId), viewerBlock.isActive.isTrue) - } - - private fun blockingMemberIdSubQuery(viewerId: Long) = QBlockMember("communityCommentWriterBlock").let { writerBlock -> - queryFactory - .select(writerBlock.member.id) - .from(writerBlock) - .where(writerBlock.blockedMember.id.eq(viewerId), writerBlock.isActive.isTrue) - } - - private fun canAccessPaidCommunityContent( - price: Int, - viewerId: Long?, - creatorId: Long, - existOrdered: Boolean - ): Boolean { - return price <= 0 || viewerId == creatorId || existOrdered - } - - private fun maskPaidCommunityContent(content: String, canAccessPaidContent: Boolean): String { - if (canAccessPaidContent) return content - val length = content.codePointCount(0, content.length) - val endIndex = if (length > 15) { - content.offsetByCodePoints(0, 15) - } else { - content.offsetByCodePoints(0, length / 2) - } - return content.substring(0, endIndex).plus("...") - } - private fun firstAudioDebutAt(creatorId: Long, now: LocalDateTime): LocalDateTime? { val firstThreeUploads = queryFactory .select(audioContent.releaseDate, audioContent.createdAt) @@ -793,31 +606,6 @@ class DefaultCreatorChannelHomeQueryRepository( return liveRoom.isAvailableJoinCreator.isTrue.or(liveRoom.member.id.eq(viewerId)) } - private fun adultCommunityCondition(canViewAdultContent: Boolean): BooleanExpression? { - return if (canViewAdultContent) null else creatorCommunity.isAdult.isFalse - } - - private fun fixedNoticeCondition(isFixed: Boolean): BooleanExpression? { - return if (isFixed) creatorCommunity.fixedAt.isNotNull else null - } - - private fun visibleCommunityPostCondition(viewerId: Long?): BooleanExpression { - val activePost = creatorCommunity.isActive.isTrue - if (viewerId == null) return activePost - return activePost.or( - queryFactory - .select(useCan.id) - .from(useCan) - .where( - useCan.member.id.eq(viewerId), - useCan.communityPost.id.eq(creatorCommunity.id), - useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST), - useCan.isRefund.isFalse - ) - .exists() - ) - } - private fun adultSeriesCondition(canViewAdultContent: Boolean): BooleanExpression? { return if (canViewAdultContent) null else series.isAdult.isFalse } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/port/out/CreatorChannelHomeQueryPort.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/port/out/CreatorChannelHomeQueryPort.kt index ccd59f7b..b8a3e607 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/port/out/CreatorChannelHomeQueryPort.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/port/out/CreatorChannelHomeQueryPort.kt @@ -34,14 +34,6 @@ interface CreatorChannelHomeQueryPort { limit: Int = 8 ): List - fun findCommunityPosts( - creatorId: Long, - viewerId: Long?, - isFixed: Boolean, - canViewAdultContent: Boolean, - limit: Int = 3 - ): List - fun findSchedules( creatorId: Long, now: LocalDateTime, @@ -140,21 +132,6 @@ data class CreatorChannelSeriesRecord( val isOriginal: Boolean ) -data class CreatorChannelCommunityPostRecord( - val postId: Long, - val creatorId: Long, - val creatorNickname: String, - val creatorProfilePath: String?, - val imagePath: String?, - val audioPath: String?, - val content: String, - val price: Int, - val date: LocalDateTime, - val existOrdered: Boolean, - val likeCount: Int, - val commentCount: Int -) - data class CreatorChannelFanTalkSummaryRecord( val totalCount: Int, val latestFanTalk: CreatorChannelFanTalkRecord? diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt index 91bb74ae..db6599db 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt @@ -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(), 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,