diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt index c8aba58a..97d291c1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt @@ -13,7 +13,6 @@ import kr.co.vividnext.sodalive.v2.recommend.port.out.HomeRecommendationQueryPor import kr.co.vividnext.sodalive.v2.recommend.port.out.RecentDebutCreatorRecord import kr.co.vividnext.sodalive.v2.recommend.port.out.RecentlyActiveCreatorRecord import kr.co.vividnext.sodalive.v2.recommend.port.out.RecommendationSnapshotPort -import kr.co.vividnext.sodalive.v2.recommend.port.out.RecommendationSnapshotRecord import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime @@ -24,36 +23,48 @@ class HomeRecommendationQueryService( private val queryPort: HomeRecommendationQueryPort, private val snapshotPort: RecommendationSnapshotPort ) { - fun findLiveRecommendations(limit: Int = DEFAULT_LIVE_LIMIT): List { - return queryPort.findLiveRecommendations(limit) + fun findLiveRecommendations( + offset: Int = 0, + limit: Int = DEFAULT_LIVE_LIMIT, + includeAdultLives: Boolean = false + ): List { + return queryPort.findLiveRecommendations(offset, limit, includeAdultLives) } fun findHomeBanners(limit: Int = DEFAULT_BANNER_LIMIT): List { return queryPort.findHomeBanners(limit) } - fun findRecentlyActiveCreators(limit: Int = DEFAULT_ACTIVE_CREATOR_LIMIT): List { - return queryPort.findRecentlyActiveCreators(limit) + fun findRecentlyActiveCreators( + limit: Int = DEFAULT_ACTIVE_CREATOR_LIMIT, + includeAdultActivities: Boolean = false + ): List { + return queryPort.findRecentlyActiveCreators(limit, includeAdultActivities) } fun findRecentDebutCreators( now: LocalDateTime, - limit: Int = DEFAULT_RECENT_DEBUT_CREATOR_LIMIT + offset: Int = 0, + limit: Int = DEFAULT_RECENT_DEBUT_CREATOR_LIMIT, + includeAdultContents: Boolean = false ): List { - return queryPort.findRecentDebutCreators(now, limit) + return queryPort.findRecentDebutCreators(now, offset, limit, includeAdultContents) } fun findFirstAudioContents( now: LocalDateTime, - limit: Int = DEFAULT_FIRST_AUDIO_CONTENT_LIMIT + offset: Int = 0, + limit: Int = DEFAULT_FIRST_AUDIO_CONTENT_LIMIT, + includeAdultContents: Boolean = false ): List { - return queryPort.findFirstAudioContents(now, limit) + return queryPort.findFirstAudioContents(now, offset, limit, includeAdultContents) } fun findAiCharacterRecommendations( + offset: Int = 0, limit: Int = DEFAULT_AI_CHARACTER_LIMIT ): List { - val snapshots = latestSnapshots(RecommendedSectionType.AI_CHARACTER, limit) + val snapshots = snapshotPort.findLatestSnapshots(RecommendedSectionType.AI_CHARACTER, offset, limit) val detailsById = queryPort.findAiCharacterRecommendationDetails(snapshots.map { it.targetId }) .associateBy { it.characterId } @@ -63,7 +74,7 @@ class HomeRecommendationQueryService( fun findCheerCreatorRecommendations( limit: Int = DEFAULT_CHEER_CREATOR_LIMIT ): List { - val snapshots = latestSnapshots(RecommendedSectionType.CHEER_CREATOR, limit) + val snapshots = snapshotPort.findLatestSnapshots(RecommendedSectionType.CHEER_CREATOR).take(limit) val detailsById = queryPort.findCheerCreatorRecommendationDetails(snapshots.map { it.targetId }) .associateBy { it.creatorId } @@ -74,7 +85,8 @@ class HomeRecommendationQueryService( limit: Int = DEFAULT_POPULAR_COMMUNITY_LIMIT, includeAdultCommunities: Boolean = false ): List { - val snapshots = latestSnapshots(RecommendedSectionType.POPULAR_COMMUNITY, POPULAR_COMMUNITY_CANDIDATE_LIMIT) + val snapshots = snapshotPort.findLatestSnapshots(RecommendedSectionType.POPULAR_COMMUNITY) + .take(POPULAR_COMMUNITY_CANDIDATE_LIMIT) val detailsById = queryPort.findPopularCommunityRecommendationDetails( snapshots.map { it.targetId }, includeAdultCommunities @@ -112,10 +124,6 @@ class HomeRecommendationQueryService( } } - private fun latestSnapshots(sectionType: RecommendedSectionType, limit: Int): List { - return snapshotPort.findLatestSnapshots(sectionType).take(limit) - } - companion object { private const val DEFAULT_LIVE_LIMIT = 20 private const val DEFAULT_BANNER_LIMIT = 20 diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt index 5a9946a0..8c239cf8 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt @@ -4,15 +4,29 @@ import kr.co.vividnext.sodalive.v2.recommend.domain.RecommendedActivityType import java.time.LocalDateTime interface HomeRecommendationQueryPort { - fun findLiveRecommendations(limit: Int): List + fun findLiveRecommendations( + offset: Int = 0, + limit: Int, + includeAdultLives: Boolean = false + ): List fun findHomeBanners(limit: Int): List - fun findRecentlyActiveCreators(limit: Int): List + fun findRecentlyActiveCreators(limit: Int, includeAdultActivities: Boolean = false): List - fun findRecentDebutCreators(now: LocalDateTime, limit: Int): List + fun findRecentDebutCreators( + now: LocalDateTime, + offset: Int = 0, + limit: Int, + includeAdultContents: Boolean = false + ): List - fun findFirstAudioContents(now: LocalDateTime, limit: Int): List + fun findFirstAudioContents( + now: LocalDateTime, + offset: Int = 0, + limit: Int, + includeAdultContents: Boolean = false + ): List fun findAiCharacterSnapshots( windowStart: LocalDateTime, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/RecommendationSnapshotPort.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/RecommendationSnapshotPort.kt index f62eaab4..fa6113fe 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/RecommendationSnapshotPort.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/RecommendationSnapshotPort.kt @@ -4,7 +4,11 @@ import kr.co.vividnext.sodalive.v2.recommend.domain.RecommendedSectionType import java.time.LocalDateTime interface RecommendationSnapshotPort { - fun findLatestSnapshots(sectionType: RecommendedSectionType): List + fun findLatestSnapshots( + sectionType: RecommendedSectionType, + offset: Int = 0, + limit: Int = Int.MAX_VALUE + ): List fun replaceSnapshots( sectionType: RecommendedSectionType, diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt index 9530fb30..bac0a74a 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt @@ -88,6 +88,17 @@ class HomeRecommendationQueryServiceTest { val creators = service.findRecentlyActiveCreators() assertEquals(10, port.activeCreatorLimit) + assertEquals(false, port.activeCreatorIncludeAdultActivities) + assertEquals(port.activeCreators, creators) + } + + @Test + @DisplayName("최근 활동 크리에이터는 성인 활동 노출 여부를 조회 포트에 위임한다") + fun shouldFindRecentlyActiveCreatorsWithAdultVisibilityPolicy() { + val creators = service.findRecentlyActiveCreators(limit = 8, includeAdultActivities = true) + + assertEquals(8, port.activeCreatorLimit) + assertEquals(true, port.activeCreatorIncludeAdultActivities) assertEquals(port.activeCreators, creators) } @@ -389,12 +400,19 @@ class HomeRecommendationQueryServiceTest { private class FakeHomeRecommendationQueryPort : HomeRecommendationQueryPort { var liveLimit: Int? = null + var liveOffset: Int? = null + var liveIncludeAdultLives: Boolean? = null var bannerLimit: Int? = null var activeCreatorLimit: Int? = null + var activeCreatorIncludeAdultActivities: Boolean? = null var recentDebutNow: LocalDateTime? = null var recentDebutLimit: Int? = null + var recentDebutOffset: Int? = null + var recentDebutIncludeAdultContents: Boolean? = null var firstAudioNow: LocalDateTime? = null var firstAudioLimit: Int? = null + var firstAudioOffset: Int? = null + var firstAudioIncludeAdultContents: Boolean? = null var aiCharacterDetailIds: List = emptyList() var cheerCreatorDetailIds: List = emptyList() var popularCommunityDetailIds: List = emptyList() @@ -466,8 +484,14 @@ class HomeRecommendationQueryServiceTest { var popularCommunityDetails: List = emptyList() var genreCreatorRecommendations: List = emptyList() - override fun findLiveRecommendations(limit: Int): List { + override fun findLiveRecommendations( + offset: Int, + limit: Int, + includeAdultLives: Boolean + ): List { + liveOffset = offset liveLimit = limit + liveIncludeAdultLives = includeAdultLives return liveRecommendations } @@ -476,20 +500,38 @@ class HomeRecommendationQueryServiceTest { return banners } - override fun findRecentlyActiveCreators(limit: Int): List { + override fun findRecentlyActiveCreators( + limit: Int, + includeAdultActivities: Boolean + ): List { activeCreatorLimit = limit + activeCreatorIncludeAdultActivities = includeAdultActivities return activeCreators } - override fun findRecentDebutCreators(now: LocalDateTime, limit: Int): List { + override fun findRecentDebutCreators( + now: LocalDateTime, + offset: Int, + limit: Int, + includeAdultContents: Boolean + ): List { recentDebutNow = now + recentDebutOffset = offset recentDebutLimit = limit + recentDebutIncludeAdultContents = includeAdultContents return recentDebutCreators } - override fun findFirstAudioContents(now: LocalDateTime, limit: Int): List { + override fun findFirstAudioContents( + now: LocalDateTime, + offset: Int, + limit: Int, + includeAdultContents: Boolean + ): List { firstAudioNow = now + firstAudioOffset = offset firstAudioLimit = limit + firstAudioIncludeAdultContents = includeAdultContents return firstAudioContents } @@ -548,14 +590,21 @@ class HomeRecommendationQueryServiceTest { private class FakeHomeRecommendationSnapshotPort : RecommendationSnapshotPort { private val snapshots = mutableListOf() - override fun findLatestSnapshots(sectionType: RecommendedSectionType): List { + override fun findLatestSnapshots( + sectionType: RecommendedSectionType, + offset: Int, + limit: Int + ): List { val latestSnapshotAt = snapshots .filter { it.sectionType == sectionType } .maxOfOrNull { it.snapshotAt } - return snapshots + val all = snapshots .filter { it.sectionType == sectionType && it.snapshotAt == latestSnapshotAt } .sortedWith(compareByDescending { it.score }.thenBy { it.randomTieBreaker }) + + if (offset == 0 && limit == Int.MAX_VALUE) return all + return all.drop(offset).take(limit) } override fun replaceSnapshots( diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt index a3ce5ddf..91e2baee 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt @@ -176,14 +176,21 @@ class RecommendationSnapshotRefreshServiceTest { private class FakeRecommendationSnapshotPort : RecommendationSnapshotPort { private val snapshots = mutableListOf() - override fun findLatestSnapshots(sectionType: RecommendedSectionType): List { + override fun findLatestSnapshots( + sectionType: RecommendedSectionType, + offset: Int, + limit: Int + ): List { val latestSnapshotAt = snapshots .filter { it.sectionType == sectionType } .maxOfOrNull { it.snapshotAt } - return snapshots + val all = snapshots .filter { it.sectionType == sectionType && it.snapshotAt == latestSnapshotAt } .sortedWith(compareByDescending { it.score }.thenBy { it.randomTieBreaker }) + + if (offset == 0 && limit == Int.MAX_VALUE) return all + return all.drop(offset).take(limit) } override fun replaceSnapshots(