feat(recommend): 인기 커뮤니티 게시글 상세 필드를 조회한다

This commit is contained in:
2026-06-01 22:40:05 +09:00
parent 6304c67cde
commit 12b446c4ae
4 changed files with 88 additions and 10 deletions

View File

@@ -6,6 +6,8 @@ import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.JPAExpressions
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.chat.original.QOriginalWork
import kr.co.vividnext.sodalive.chat.room.ParticipantType
@@ -773,10 +775,13 @@ class DefaultHomeRecommendationQueryRepository(
member.id,
member.nickname,
member.profileImage,
creatorCommunity.imagePath,
creatorCommunity.audioPath,
creatorCommunity.content,
creatorCommunity.createdAt,
creatorCommunityLike.id.countDistinct(),
creatorCommunityComment.id.countDistinct()
creatorCommunityComment.id.countDistinct(),
orderedCommunityPostCondition(memberId)
)
)
.from(creatorCommunity)
@@ -792,7 +797,6 @@ class DefaultHomeRecommendationQueryRepository(
.where(
creatorCommunity.isActive.isTrue,
member.isActive.isTrue,
creatorCommunity.price.eq(0),
creatorCommunity.isFixed.isFalse,
includeAdultCommunityCondition(includeAdultCommunities),
notBlockedCreatorCondition(memberId, member.id),
@@ -803,6 +807,8 @@ class DefaultHomeRecommendationQueryRepository(
member.id,
member.nickname,
member.profileImage,
creatorCommunity.imagePath,
creatorCommunity.audioPath,
creatorCommunity.content,
creatorCommunity.createdAt
)
@@ -1054,6 +1060,20 @@ class DefaultHomeRecommendationQueryRepository(
.notExists()
}
private fun orderedCommunityPostCondition(memberId: Long?): BooleanExpression {
if (memberId == null) return Expressions.FALSE
return JPAExpressions
.selectOne()
.from(useCan)
.where(
useCan.member.id.eq(memberId),
useCan.isRefund.isFalse,
useCan.communityPost.id.eq(creatorCommunity.id),
useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST)
)
.exists()
}
private fun notBlockedCreatorSql(creatorIdExpression: String): String {
return """
not exists (

View File

@@ -151,10 +151,13 @@ data class HomePopularCommunityRecommendationRecord(
val creatorId: Long,
val creatorNickname: String,
val creatorProfileImage: String?,
val imagePath: String?,
val audioPath: String?,
val content: String,
val createdAt: LocalDateTime,
val likeCount: Long,
val commentCount: Long
val commentCount: Long,
val existOrdered: Boolean
)
data class HomeGenreCreatorRecommendationGroup(

View File

@@ -1234,7 +1234,12 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
val creator = saveMember("community-detail-creator", MemberRole.CREATOR)
val inactiveCreator = saveMember("community-detail-inactive-creator", MemberRole.CREATOR, isActive = false)
val member = saveMember("community-detail-member", MemberRole.USER)
val eligible = saveCommunity(creator, isCommentAvailable = true)
val eligible = saveCommunity(
creator,
isCommentAvailable = true,
imagePath = "community/detail-image.png",
audioPath = "community/detail-audio.mp3"
)
val paid = saveCommunity(creator, isCommentAvailable = true, price = 10)
val fixed = saveCommunity(creator, isCommentAvailable = true, isFixed = true)
val adult = saveCommunity(creator, isCommentAvailable = true, isAdult = true)
@@ -1249,23 +1254,46 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
updateCreatedAt("CreatorCommunityLike", like1.id!!, LocalDateTime.of(2026, 5, 29, 2, 0))
updateCreatedAt("CreatorCommunityLike", like2.id!!, LocalDateTime.of(2026, 5, 29, 3, 0))
updateCreatedAt("CreatorCommunityComment", comment1.id!!, LocalDateTime.of(2026, 5, 29, 4, 0))
saveCommunityOrder(member, paid, isRefund = false)
flushAndClear()
val details = repository.findPopularCommunityRecommendationDetails(
listOf(eligible.id!!, paid.id!!, fixed.id!!, adult.id!!, inactivePost.id!!, inactiveCreatorPost.id!!, 999L),
memberId = member.id,
includeAdultCommunities = false
)
val detailById = details.associateBy { it.communityId }
assertEquals(setOf(eligible.id), detailById.keys)
assertEquals(setOf(eligible.id, paid.id), detailById.keys)
assertEquals("content", detailById[eligible.id]!!.content)
assertEquals("community/detail-image.png", detailById[eligible.id]!!.imagePath)
assertEquals("community/detail-audio.mp3", detailById[eligible.id]!!.audioPath)
assertEquals(LocalDateTime.of(2026, 5, 29, 1, 0), detailById[eligible.id]!!.createdAt)
assertEquals(2L, detailById[eligible.id]!!.likeCount)
assertEquals(1L, detailById[eligible.id]!!.commentCount)
assertEquals(false, detailById[eligible.id]!!.existOrdered)
assertEquals(true, detailById[paid.id]!!.existOrdered)
assertEquals(creator.id, detailById[eligible.id]!!.creatorId)
assertEquals("community-detail-creator", detailById[eligible.id]!!.creatorNickname)
}
@Test
@DisplayName("인기 커뮤니티 상세는 비회원에게 구매 여부를 false로 반환한다")
fun shouldReturnFalseOrderStatusForAnonymousPopularCommunityDetails() {
val creator = saveMember("anonymous-community-creator", MemberRole.CREATOR)
val paid = saveCommunity(creator, isCommentAvailable = true, price = 10)
flushAndClear()
val details = repository.findPopularCommunityRecommendationDetails(
listOf(paid.id!!),
memberId = null,
includeAdultCommunities = false
)
assertEquals(listOf(paid.id), details.map { it.communityId })
assertEquals(listOf(false), details.map { it.existOrdered })
}
@Test
@DisplayName("인기 커뮤니티 상세는 성인 노출 가능 회원에게 성인 게시글을 포함한다")
fun shouldFindAdultPopularCommunityDetailsWhenAdultVisible() {
@@ -1656,13 +1684,17 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
price: Int = 0,
isAdult: Boolean = false,
isActive: Boolean = true,
isFixed: Boolean = false
isFixed: Boolean = false,
imagePath: String? = null,
audioPath: String? = null
): CreatorCommunity {
val community = CreatorCommunity(
content = "content",
price = price,
isCommentAvailable = isCommentAvailable,
isAdult = isAdult,
audioPath = audioPath,
imagePath = imagePath,
isActive = isActive,
isFixed = isFixed
)
@@ -1671,6 +1703,14 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
return community
}
private fun saveCommunityOrder(member: Member, community: CreatorCommunity, isRefund: Boolean): UseCan {
val useCan = UseCan(canUsage = CanUsage.PAID_COMMUNITY_POST, can = community.price, rewardCan = 0, isRefund = isRefund)
useCan.member = member
useCan.communityPost = community
entityManager.persist(useCan)
return useCan
}
private fun saveAudioContent(
creator: Member,
releaseDate: LocalDateTime,

View File

@@ -227,30 +227,39 @@ class HomeRecommendationQueryServiceTest {
creatorId = 10L,
creatorNickname = "creator-10",
creatorProfileImage = "profile-10.png",
imagePath = "community-1.png",
audioPath = "community-1.mp3",
content = "content-1",
createdAt = LocalDateTime.of(2026, 5, 29, 1, 0),
likeCount = 3L,
commentCount = 2L
commentCount = 2L,
existOrdered = true
),
HomePopularCommunityRecommendationRecord(
communityId = 2L,
creatorId = 10L,
creatorNickname = "creator-10",
creatorProfileImage = "profile-10.png",
imagePath = null,
audioPath = null,
content = "content-2",
createdAt = LocalDateTime.of(2026, 5, 29, 2, 0),
likeCount = 1L,
commentCount = 1L
commentCount = 1L,
existOrdered = false
),
HomePopularCommunityRecommendationRecord(
communityId = 3L,
creatorId = 11L,
creatorNickname = "creator-11",
creatorProfileImage = null,
imagePath = null,
audioPath = null,
content = "content-3",
createdAt = LocalDateTime.of(2026, 5, 29, 3, 0),
likeCount = 0L,
commentCount = 0L
commentCount = 0L,
existOrdered = false
)
)
@@ -262,6 +271,9 @@ class HomeRecommendationQueryServiceTest {
assertEquals(listOf(1L, 3L), communities.map { it.communityId })
assertEquals(listOf(10L, 11L), communities.map { it.creatorId })
assertEquals(LocalDateTime.of(2026, 5, 29, 1, 0), communities.first().createdAt)
assertEquals("community-1.png", communities.first().imagePath)
assertEquals("community-1.mp3", communities.first().audioPath)
assertEquals(true, communities.first().existOrdered)
}
@Test
@@ -281,10 +293,13 @@ class HomeRecommendationQueryServiceTest {
creatorId = if (communityId <= 10L) 1L else communityId,
creatorNickname = "creator-$communityId",
creatorProfileImage = null,
imagePath = null,
audioPath = null,
content = "content-$communityId",
createdAt = LocalDateTime.of(2026, 5, 29, 1, 0).plusMinutes(communityId),
likeCount = 0L,
commentCount = 0L
commentCount = 0L,
existOrdered = false
)
}