Compare commits
3 Commits
1649d0b36e
...
bd753a4710
| Author | SHA1 | Date | |
|---|---|---|---|
| bd753a4710 | |||
| 1bed34425c | |||
| ae725e72c4 |
@@ -10,7 +10,7 @@ import kr.co.vividnext.sodalive.v2.api.home.dto.HomeCreatorItem
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomeFirstAudioContentItem
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomeGenreCreatorGroupItem
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomeLiveItem
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomePopularCommunityItem
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomePopularCommunityPostItem
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomeRecommendationPageResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomeRecommendationResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.imageUrl
|
||||
@@ -81,7 +81,7 @@ class HomeRecommendationFacade(
|
||||
).map { it.toItem() },
|
||||
cheerCreators = queryService.findCheerCreatorRecommendations(HOME_CHEER_CREATOR_LIMIT, member?.id)
|
||||
.map { it.toCreatorItem() },
|
||||
popularCommunities = queryService.findPopularCommunityRecommendations(
|
||||
popularCommunityPosts = queryService.findPopularCommunityRecommendations(
|
||||
limit = HOME_POPULAR_COMMUNITY_LIMIT,
|
||||
memberId = member?.id,
|
||||
includeAdultCommunities = includeAdult
|
||||
@@ -205,7 +205,7 @@ class HomeRecommendationFacade(
|
||||
if (aiCharacters.isEmpty()) add("aiCharacters")
|
||||
if (genreCreators.isEmpty()) add("genreCreators")
|
||||
if (cheerCreators.isEmpty()) add("cheerCreators")
|
||||
if (popularCommunities.isEmpty()) add("popularCommunities")
|
||||
if (popularCommunityPosts.isEmpty()) add("popularCommunityPosts")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,15 +301,18 @@ class HomeRecommendationFacade(
|
||||
creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage)
|
||||
)
|
||||
|
||||
private fun HomePopularCommunityRecommendationRecord.toItem() = HomePopularCommunityItem(
|
||||
communityId = communityId,
|
||||
private fun HomePopularCommunityRecommendationRecord.toItem() = HomePopularCommunityPostItem(
|
||||
postId = communityId,
|
||||
creatorId = creatorId,
|
||||
creatorNickname = creatorNickname,
|
||||
creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage),
|
||||
imageUrl = imageUrl(cloudFrontHost, imagePath),
|
||||
audioUrl = imageUrl(cloudFrontHost, audioPath),
|
||||
content = content,
|
||||
createdAt = createdAt.toUtcIso(),
|
||||
likeCount = likeCount,
|
||||
commentCount = commentCount
|
||||
commentCount = commentCount,
|
||||
existOrdered = existOrdered
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -21,7 +21,7 @@ data class HomeRecommendationResponse(
|
||||
val aiCharacters: List<HomeAiCharacterItem>,
|
||||
val genreCreators: List<HomeGenreCreatorGroupItem>,
|
||||
val cheerCreators: List<HomeCreatorItem>,
|
||||
val popularCommunities: List<HomePopularCommunityItem>
|
||||
val popularCommunityPosts: List<HomePopularCommunityPostItem>
|
||||
)
|
||||
|
||||
data class HomeLiveItem(
|
||||
@@ -87,13 +87,16 @@ data class HomeGenreCreatorGroupItem(
|
||||
val creators: List<HomeCreatorItem>
|
||||
)
|
||||
|
||||
data class HomePopularCommunityItem(
|
||||
val communityId: Long,
|
||||
data class HomePopularCommunityPostItem(
|
||||
val postId: Long,
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val imageUrl: String?,
|
||||
val audioUrl: String?,
|
||||
val content: String,
|
||||
val createdAt: String,
|
||||
val likeCount: Long,
|
||||
val commentCount: Long
|
||||
val commentCount: Long,
|
||||
val existOrdered: Boolean
|
||||
)
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -219,7 +219,7 @@ class HomeRecommendationControllerTest @Autowired constructor(
|
||||
.andExpect(jsonPath("$.data.aiCharacters").isArray)
|
||||
.andExpect(jsonPath("$.data.genreCreators").isArray)
|
||||
.andExpect(jsonPath("$.data.cheerCreators").isArray)
|
||||
.andExpect(jsonPath("$.data.popularCommunities").isArray)
|
||||
.andExpect(jsonPath("$.data.popularCommunityPosts").isArray)
|
||||
|
||||
assertTrue(output.out.contains("event=home_recommendations_query_success"))
|
||||
assertTrue(output.out.contains("emptySections="))
|
||||
|
||||
@@ -39,7 +39,21 @@ class HomeRecommendationResponseTest {
|
||||
),
|
||||
genreCreators = emptyList(),
|
||||
cheerCreators = emptyList(),
|
||||
popularCommunities = emptyList()
|
||||
popularCommunityPosts = listOf(
|
||||
HomePopularCommunityPostItem(
|
||||
postId = 5L,
|
||||
creatorId = 6L,
|
||||
creatorNickname = "community-creator",
|
||||
creatorProfileImage = "https://cdn.test/profile/community.png",
|
||||
imageUrl = "https://cdn.test/community/image.png",
|
||||
audioUrl = "https://cdn.test/community/audio.mp3",
|
||||
content = "community content",
|
||||
createdAt = "2026-06-01T00:00:00Z",
|
||||
likeCount = 7L,
|
||||
commentCount = 8L,
|
||||
existOrdered = true
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val json = objectMapper.readTree(objectMapper.writeValueAsString(response))
|
||||
@@ -47,5 +61,9 @@ class HomeRecommendationResponseTest {
|
||||
assertEquals(true, json["firstAudioContents"][0]["isPointAvailable"].asBoolean())
|
||||
assertFalse(json["firstAudioContents"][0].has("pointAvailable"))
|
||||
assertEquals("https://cdn.test/profile/character.png", json["aiCharacters"][0]["profileImage"].asText())
|
||||
assertEquals(5L, json["popularCommunityPosts"][0]["postId"].asLong())
|
||||
assertEquals("https://cdn.test/community/image.png", json["popularCommunityPosts"][0]["imageUrl"].asText())
|
||||
assertEquals("https://cdn.test/community/audio.mp3", json["popularCommunityPosts"][0]["audioUrl"].asText())
|
||||
assertEquals(true, json["popularCommunityPosts"][0]["existOrdered"].asBoolean())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user