feat(recommend): 홈 추천 전체보기 페이징 조회를 추가한다
This commit is contained in:
@@ -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<HomeLiveRecommendationRecord> {
|
||||
return queryPort.findLiveRecommendations(limit)
|
||||
fun findLiveRecommendations(
|
||||
offset: Int = 0,
|
||||
limit: Int = DEFAULT_LIVE_LIMIT,
|
||||
includeAdultLives: Boolean = false
|
||||
): List<HomeLiveRecommendationRecord> {
|
||||
return queryPort.findLiveRecommendations(offset, limit, includeAdultLives)
|
||||
}
|
||||
|
||||
fun findHomeBanners(limit: Int = DEFAULT_BANNER_LIMIT): List<HomeBannerRecommendationRecord> {
|
||||
return queryPort.findHomeBanners(limit)
|
||||
}
|
||||
|
||||
fun findRecentlyActiveCreators(limit: Int = DEFAULT_ACTIVE_CREATOR_LIMIT): List<RecentlyActiveCreatorRecord> {
|
||||
return queryPort.findRecentlyActiveCreators(limit)
|
||||
fun findRecentlyActiveCreators(
|
||||
limit: Int = DEFAULT_ACTIVE_CREATOR_LIMIT,
|
||||
includeAdultActivities: Boolean = false
|
||||
): List<RecentlyActiveCreatorRecord> {
|
||||
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<RecentDebutCreatorRecord> {
|
||||
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<HomeFirstAudioContentRecord> {
|
||||
return queryPort.findFirstAudioContents(now, limit)
|
||||
return queryPort.findFirstAudioContents(now, offset, limit, includeAdultContents)
|
||||
}
|
||||
|
||||
fun findAiCharacterRecommendations(
|
||||
offset: Int = 0,
|
||||
limit: Int = DEFAULT_AI_CHARACTER_LIMIT
|
||||
): List<HomeAiCharacterRecommendationRecord> {
|
||||
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<HomeCheerCreatorRecommendationRecord> {
|
||||
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<HomePopularCommunityRecommendationRecord> {
|
||||
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<RecommendationSnapshotRecord> {
|
||||
return snapshotPort.findLatestSnapshots(sectionType).take(limit)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_LIVE_LIMIT = 20
|
||||
private const val DEFAULT_BANNER_LIMIT = 20
|
||||
|
||||
@@ -4,15 +4,29 @@ import kr.co.vividnext.sodalive.v2.recommend.domain.RecommendedActivityType
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface HomeRecommendationQueryPort {
|
||||
fun findLiveRecommendations(limit: Int): List<HomeLiveRecommendationRecord>
|
||||
fun findLiveRecommendations(
|
||||
offset: Int = 0,
|
||||
limit: Int,
|
||||
includeAdultLives: Boolean = false
|
||||
): List<HomeLiveRecommendationRecord>
|
||||
|
||||
fun findHomeBanners(limit: Int): List<HomeBannerRecommendationRecord>
|
||||
|
||||
fun findRecentlyActiveCreators(limit: Int): List<RecentlyActiveCreatorRecord>
|
||||
fun findRecentlyActiveCreators(limit: Int, includeAdultActivities: Boolean = false): List<RecentlyActiveCreatorRecord>
|
||||
|
||||
fun findRecentDebutCreators(now: LocalDateTime, limit: Int): List<RecentDebutCreatorRecord>
|
||||
fun findRecentDebutCreators(
|
||||
now: LocalDateTime,
|
||||
offset: Int = 0,
|
||||
limit: Int,
|
||||
includeAdultContents: Boolean = false
|
||||
): List<RecentDebutCreatorRecord>
|
||||
|
||||
fun findFirstAudioContents(now: LocalDateTime, limit: Int): List<HomeFirstAudioContentRecord>
|
||||
fun findFirstAudioContents(
|
||||
now: LocalDateTime,
|
||||
offset: Int = 0,
|
||||
limit: Int,
|
||||
includeAdultContents: Boolean = false
|
||||
): List<HomeFirstAudioContentRecord>
|
||||
|
||||
fun findAiCharacterSnapshots(
|
||||
windowStart: LocalDateTime,
|
||||
|
||||
@@ -4,7 +4,11 @@ import kr.co.vividnext.sodalive.v2.recommend.domain.RecommendedSectionType
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface RecommendationSnapshotPort {
|
||||
fun findLatestSnapshots(sectionType: RecommendedSectionType): List<RecommendationSnapshotRecord>
|
||||
fun findLatestSnapshots(
|
||||
sectionType: RecommendedSectionType,
|
||||
offset: Int = 0,
|
||||
limit: Int = Int.MAX_VALUE
|
||||
): List<RecommendationSnapshotRecord>
|
||||
|
||||
fun replaceSnapshots(
|
||||
sectionType: RecommendedSectionType,
|
||||
|
||||
@@ -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<Long> = emptyList()
|
||||
var cheerCreatorDetailIds: List<Long> = emptyList()
|
||||
var popularCommunityDetailIds: List<Long> = emptyList()
|
||||
@@ -466,8 +484,14 @@ class HomeRecommendationQueryServiceTest {
|
||||
var popularCommunityDetails: List<HomePopularCommunityRecommendationRecord> = emptyList()
|
||||
var genreCreatorRecommendations: List<HomeGenreCreatorRecommendationGroup> = emptyList()
|
||||
|
||||
override fun findLiveRecommendations(limit: Int): List<HomeLiveRecommendationRecord> {
|
||||
override fun findLiveRecommendations(
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
includeAdultLives: Boolean
|
||||
): List<HomeLiveRecommendationRecord> {
|
||||
liveOffset = offset
|
||||
liveLimit = limit
|
||||
liveIncludeAdultLives = includeAdultLives
|
||||
return liveRecommendations
|
||||
}
|
||||
|
||||
@@ -476,20 +500,38 @@ class HomeRecommendationQueryServiceTest {
|
||||
return banners
|
||||
}
|
||||
|
||||
override fun findRecentlyActiveCreators(limit: Int): List<RecentlyActiveCreatorRecord> {
|
||||
override fun findRecentlyActiveCreators(
|
||||
limit: Int,
|
||||
includeAdultActivities: Boolean
|
||||
): List<RecentlyActiveCreatorRecord> {
|
||||
activeCreatorLimit = limit
|
||||
activeCreatorIncludeAdultActivities = includeAdultActivities
|
||||
return activeCreators
|
||||
}
|
||||
|
||||
override fun findRecentDebutCreators(now: LocalDateTime, limit: Int): List<RecentDebutCreatorRecord> {
|
||||
override fun findRecentDebutCreators(
|
||||
now: LocalDateTime,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
includeAdultContents: Boolean
|
||||
): List<RecentDebutCreatorRecord> {
|
||||
recentDebutNow = now
|
||||
recentDebutOffset = offset
|
||||
recentDebutLimit = limit
|
||||
recentDebutIncludeAdultContents = includeAdultContents
|
||||
return recentDebutCreators
|
||||
}
|
||||
|
||||
override fun findFirstAudioContents(now: LocalDateTime, limit: Int): List<HomeFirstAudioContentRecord> {
|
||||
override fun findFirstAudioContents(
|
||||
now: LocalDateTime,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
includeAdultContents: Boolean
|
||||
): List<HomeFirstAudioContentRecord> {
|
||||
firstAudioNow = now
|
||||
firstAudioOffset = offset
|
||||
firstAudioLimit = limit
|
||||
firstAudioIncludeAdultContents = includeAdultContents
|
||||
return firstAudioContents
|
||||
}
|
||||
|
||||
@@ -548,14 +590,21 @@ class HomeRecommendationQueryServiceTest {
|
||||
private class FakeHomeRecommendationSnapshotPort : RecommendationSnapshotPort {
|
||||
private val snapshots = mutableListOf<RecommendationSnapshotRecord>()
|
||||
|
||||
override fun findLatestSnapshots(sectionType: RecommendedSectionType): List<RecommendationSnapshotRecord> {
|
||||
override fun findLatestSnapshots(
|
||||
sectionType: RecommendedSectionType,
|
||||
offset: Int,
|
||||
limit: Int
|
||||
): List<RecommendationSnapshotRecord> {
|
||||
val latestSnapshotAt = snapshots
|
||||
.filter { it.sectionType == sectionType }
|
||||
.maxOfOrNull { it.snapshotAt }
|
||||
|
||||
return snapshots
|
||||
val all = snapshots
|
||||
.filter { it.sectionType == sectionType && it.snapshotAt == latestSnapshotAt }
|
||||
.sortedWith(compareByDescending<RecommendationSnapshotRecord> { it.score }.thenBy { it.randomTieBreaker })
|
||||
|
||||
if (offset == 0 && limit == Int.MAX_VALUE) return all
|
||||
return all.drop(offset).take(limit)
|
||||
}
|
||||
|
||||
override fun replaceSnapshots(
|
||||
|
||||
@@ -176,14 +176,21 @@ class RecommendationSnapshotRefreshServiceTest {
|
||||
private class FakeRecommendationSnapshotPort : RecommendationSnapshotPort {
|
||||
private val snapshots = mutableListOf<RecommendationSnapshotRecord>()
|
||||
|
||||
override fun findLatestSnapshots(sectionType: RecommendedSectionType): List<RecommendationSnapshotRecord> {
|
||||
override fun findLatestSnapshots(
|
||||
sectionType: RecommendedSectionType,
|
||||
offset: Int,
|
||||
limit: Int
|
||||
): List<RecommendationSnapshotRecord> {
|
||||
val latestSnapshotAt = snapshots
|
||||
.filter { it.sectionType == sectionType }
|
||||
.maxOfOrNull { it.snapshotAt }
|
||||
|
||||
return snapshots
|
||||
val all = snapshots
|
||||
.filter { it.sectionType == sectionType && it.snapshotAt == latestSnapshotAt }
|
||||
.sortedWith(compareByDescending<RecommendationSnapshotRecord> { it.score }.thenBy { it.randomTieBreaker })
|
||||
|
||||
if (offset == 0 && limit == Int.MAX_VALUE) return all
|
||||
return all.drop(offset).take(limit)
|
||||
}
|
||||
|
||||
override fun replaceSnapshots(
|
||||
|
||||
Reference in New Issue
Block a user