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

View File

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

View File

@@ -1234,7 +1234,12 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
val creator = saveMember("community-detail-creator", MemberRole.CREATOR) val creator = saveMember("community-detail-creator", MemberRole.CREATOR)
val inactiveCreator = saveMember("community-detail-inactive-creator", MemberRole.CREATOR, isActive = false) val inactiveCreator = saveMember("community-detail-inactive-creator", MemberRole.CREATOR, isActive = false)
val member = saveMember("community-detail-member", MemberRole.USER) 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 paid = saveCommunity(creator, isCommentAvailable = true, price = 10)
val fixed = saveCommunity(creator, isCommentAvailable = true, isFixed = true) val fixed = saveCommunity(creator, isCommentAvailable = true, isFixed = true)
val adult = saveCommunity(creator, isCommentAvailable = true, isAdult = 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", like1.id!!, LocalDateTime.of(2026, 5, 29, 2, 0))
updateCreatedAt("CreatorCommunityLike", like2.id!!, LocalDateTime.of(2026, 5, 29, 3, 0)) updateCreatedAt("CreatorCommunityLike", like2.id!!, LocalDateTime.of(2026, 5, 29, 3, 0))
updateCreatedAt("CreatorCommunityComment", comment1.id!!, LocalDateTime.of(2026, 5, 29, 4, 0)) updateCreatedAt("CreatorCommunityComment", comment1.id!!, LocalDateTime.of(2026, 5, 29, 4, 0))
saveCommunityOrder(member, paid, isRefund = false)
flushAndClear() flushAndClear()
val details = repository.findPopularCommunityRecommendationDetails( val details = repository.findPopularCommunityRecommendationDetails(
listOf(eligible.id!!, paid.id!!, fixed.id!!, adult.id!!, inactivePost.id!!, inactiveCreatorPost.id!!, 999L), listOf(eligible.id!!, paid.id!!, fixed.id!!, adult.id!!, inactivePost.id!!, inactiveCreatorPost.id!!, 999L),
memberId = member.id,
includeAdultCommunities = false includeAdultCommunities = false
) )
val detailById = details.associateBy { it.communityId } 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("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(LocalDateTime.of(2026, 5, 29, 1, 0), detailById[eligible.id]!!.createdAt)
assertEquals(2L, detailById[eligible.id]!!.likeCount) assertEquals(2L, detailById[eligible.id]!!.likeCount)
assertEquals(1L, detailById[eligible.id]!!.commentCount) 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(creator.id, detailById[eligible.id]!!.creatorId)
assertEquals("community-detail-creator", detailById[eligible.id]!!.creatorNickname) 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 @Test
@DisplayName("인기 커뮤니티 상세는 성인 노출 가능 회원에게 성인 게시글을 포함한다") @DisplayName("인기 커뮤니티 상세는 성인 노출 가능 회원에게 성인 게시글을 포함한다")
fun shouldFindAdultPopularCommunityDetailsWhenAdultVisible() { fun shouldFindAdultPopularCommunityDetailsWhenAdultVisible() {
@@ -1656,13 +1684,17 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
price: Int = 0, price: Int = 0,
isAdult: Boolean = false, isAdult: Boolean = false,
isActive: Boolean = true, isActive: Boolean = true,
isFixed: Boolean = false isFixed: Boolean = false,
imagePath: String? = null,
audioPath: String? = null
): CreatorCommunity { ): CreatorCommunity {
val community = CreatorCommunity( val community = CreatorCommunity(
content = "content", content = "content",
price = price, price = price,
isCommentAvailable = isCommentAvailable, isCommentAvailable = isCommentAvailable,
isAdult = isAdult, isAdult = isAdult,
audioPath = audioPath,
imagePath = imagePath,
isActive = isActive, isActive = isActive,
isFixed = isFixed isFixed = isFixed
) )
@@ -1671,6 +1703,14 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
return community 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( private fun saveAudioContent(
creator: Member, creator: Member,
releaseDate: LocalDateTime, releaseDate: LocalDateTime,

View File

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