feat(recommend): 홈 추천 저장소 페이징 조건을 적용한다
This commit is contained in:
@@ -43,7 +43,11 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
private val queryFactory: JPAQueryFactory,
|
||||
private val entityManager: EntityManager
|
||||
) : HomeRecommendationQueryRepository {
|
||||
override fun findLiveRecommendations(limit: Int): List<HomeLiveRecommendationRecord> {
|
||||
override fun findLiveRecommendations(
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
includeAdultLives: Boolean
|
||||
): List<HomeLiveRecommendationRecord> {
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
@@ -64,9 +68,11 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
liveRoom.isActive.isTrue,
|
||||
liveRoom.channelName.isNotNull,
|
||||
liveRoom.channelName.isNotEmpty,
|
||||
includeAdultLiveCondition(includeAdultLives),
|
||||
member.isActive.isTrue
|
||||
)
|
||||
.orderBy(liveRoom.beginDateTime.desc(), liveRoom.id.desc())
|
||||
.offset(offset.toLong())
|
||||
.limit(limit.toLong())
|
||||
.fetch()
|
||||
}
|
||||
@@ -106,7 +112,10 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun findRecentlyActiveCreators(limit: Int): List<RecentlyActiveCreatorRecord> {
|
||||
override fun findRecentlyActiveCreators(
|
||||
limit: Int,
|
||||
includeAdultActivities: Boolean
|
||||
): List<RecentlyActiveCreatorRecord> {
|
||||
val sql = """
|
||||
select ranked.creator_id,
|
||||
ranked.creator_nickname,
|
||||
@@ -133,6 +142,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
where lr.is_active = true
|
||||
and lr.channel_name is not null
|
||||
and lr.channel_name <> ''
|
||||
and (:includeAdultActivities = true or lr.is_adult = false)
|
||||
and m.is_active = true
|
||||
union all
|
||||
select m.id as creator_id,
|
||||
@@ -147,6 +157,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
join content_theme act on act.id = ac.theme_id
|
||||
where ac.is_active = true
|
||||
and ac.release_date is not null
|
||||
and (:includeAdultActivities = true or ac.is_adult = false)
|
||||
and m.is_active = true
|
||||
union all
|
||||
select m.id as creator_id,
|
||||
@@ -159,6 +170,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
from creator_community cc
|
||||
join member m on m.id = cc.member_id
|
||||
where cc.is_active = true
|
||||
and (:includeAdultActivities = true or cc.is_adult = false)
|
||||
and m.is_active = true
|
||||
) activities
|
||||
) ranked
|
||||
@@ -169,6 +181,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
|
||||
val query = entityManager.createNativeQuery(sql)
|
||||
.setParameter("liveReplayTheme", LIVE_REPLAY_THEME)
|
||||
.setParameter("includeAdultActivities", includeAdultActivities)
|
||||
.setParameter("limit", limit)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -186,7 +199,12 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
}
|
||||
}
|
||||
|
||||
override fun findRecentDebutCreators(now: LocalDateTime, limit: Int): List<RecentDebutCreatorRecord> {
|
||||
override fun findRecentDebutCreators(
|
||||
now: LocalDateTime,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
includeAdultContents: Boolean
|
||||
): List<RecentDebutCreatorRecord> {
|
||||
val sql = """
|
||||
with creator_debut as (
|
||||
select debut_events.creator_id as creator_id, min(debut_events.debut_at) as debut_at
|
||||
@@ -196,6 +214,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
where ac.is_active = true
|
||||
and ac.release_date is not null
|
||||
and ac.release_date <= :now
|
||||
and (:includeAdultContents = true or ac.is_adult = false)
|
||||
union all
|
||||
select lr.member_id as creator_id, lr.begin_date_time as debut_at
|
||||
from live_room lr
|
||||
@@ -203,6 +222,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and lr.channel_name is not null
|
||||
and lr.channel_name <> ''
|
||||
and lr.begin_date_time <= :now
|
||||
and (:includeAdultContents = true or lr.is_adult = false)
|
||||
) debut_events
|
||||
group by debut_events.creator_id
|
||||
),
|
||||
@@ -223,6 +243,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and ac.release_date is not null
|
||||
and ac.release_date >= :window30Start
|
||||
and ac.release_date <= :now
|
||||
and (:includeAdultContents = true or ac.is_adult = false)
|
||||
union all
|
||||
select lr.member_id as creator_id, lr.id as activity_id
|
||||
from live_room lr
|
||||
@@ -231,6 +252,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and lr.channel_name <> ''
|
||||
and lr.begin_date_time >= :window30Start
|
||||
and lr.begin_date_time <= :now
|
||||
and (:includeAdultContents = true or lr.is_adult = false)
|
||||
) activity
|
||||
group by activity.creator_id
|
||||
),
|
||||
@@ -290,7 +312,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
when cd.debut_at >= :boost30Start then ${RecommendationScoreSpec.NEW_BOOST_30_DAYS}
|
||||
else ${RecommendationScoreSpec.DEFAULT_NEW_BOOST}
|
||||
end) as score,
|
||||
rand() as random_tie_breaker
|
||||
m.id as random_tie_breaker
|
||||
from member m
|
||||
join creator_debut cd on cd.creator_id = m.id
|
||||
left join follow_stats fs on fs.creator_id = m.id
|
||||
@@ -301,10 +323,13 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and cd.debut_at <= :now
|
||||
order by score desc, random_tie_breaker asc
|
||||
limit :limit
|
||||
offset :offset
|
||||
""".trimIndent()
|
||||
|
||||
val query = entityManager.createNativeQuery(sql)
|
||||
.setRecommendationQueryParameters(now, limit)
|
||||
.setParameter("offset", offset)
|
||||
.setParameter("includeAdultContents", includeAdultContents)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val rows = query.resultList as List<Array<Any?>>
|
||||
@@ -321,7 +346,12 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
}
|
||||
}
|
||||
|
||||
override fun findFirstAudioContents(now: LocalDateTime, limit: Int): List<HomeFirstAudioContentRecord> {
|
||||
override fun findFirstAudioContents(
|
||||
now: LocalDateTime,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
includeAdultContents: Boolean
|
||||
): List<HomeFirstAudioContentRecord> {
|
||||
val sql = """
|
||||
with creator_debut as (
|
||||
select debut_events.creator_id as creator_id, min(debut_events.debut_at) as debut_at
|
||||
@@ -331,6 +361,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
where ac.is_active = true
|
||||
and ac.release_date is not null
|
||||
and ac.release_date <= :now
|
||||
and (:includeAdultContents = true or ac.is_adult = false)
|
||||
union all
|
||||
select lr.member_id as creator_id, lr.begin_date_time as debut_at
|
||||
from live_room lr
|
||||
@@ -338,6 +369,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and lr.channel_name is not null
|
||||
and lr.channel_name <> ''
|
||||
and lr.begin_date_time <= :now
|
||||
and (:includeAdultContents = true or lr.is_adult = false)
|
||||
) debut_events
|
||||
group by debut_events.creator_id
|
||||
),
|
||||
@@ -354,6 +386,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
) as upload_rank
|
||||
from content ac
|
||||
where ac.release_date is not null
|
||||
and (:includeAdultContents = true or ac.is_adult = false)
|
||||
),
|
||||
eligible_contents as (
|
||||
select ranked_uploads.*,
|
||||
@@ -381,7 +414,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
when ec.release_date >= :boost30Start then 20
|
||||
else 0
|
||||
end as recency_score,
|
||||
rand() as random_tie_breaker
|
||||
ec.content_id as random_tie_breaker
|
||||
from eligible_contents ec
|
||||
join member m on m.id = ec.creator_id
|
||||
join creator_debut cd on cd.creator_id = ec.creator_id
|
||||
@@ -392,11 +425,14 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and ec.release_date >= :boost30Start
|
||||
order by recency_score desc, random_tie_breaker asc
|
||||
limit :limit
|
||||
offset :offset
|
||||
""".trimIndent()
|
||||
|
||||
val query = entityManager.createNativeQuery(sql)
|
||||
.setParameter("now", now)
|
||||
.setParameter("limit", limit)
|
||||
.setParameter("offset", offset)
|
||||
.setParameter("includeAdultContents", includeAdultContents)
|
||||
.setParameter(
|
||||
"boost30Start",
|
||||
now.toLocalDate().minusDays(RecommendationScoreSpec.NEW_BOOST_30_DAY_LIMIT).atStartOfDay()
|
||||
@@ -941,6 +977,10 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
return if (includeAdultCommunities) null else creatorCommunity.isAdult.isFalse
|
||||
}
|
||||
|
||||
private fun includeAdultLiveCondition(includeAdultLives: Boolean): BooleanExpression? {
|
||||
return if (includeAdultLives) null else liveRoom.isAdult.isFalse
|
||||
}
|
||||
|
||||
private fun javax.persistence.Query.setRecommendationQueryParameters(
|
||||
now: LocalDateTime,
|
||||
limit: Int
|
||||
|
||||
@@ -10,10 +10,12 @@ import java.time.LocalDateTime
|
||||
class RecommendationSnapshotPersistenceAdapter(
|
||||
private val repository: RecommendationSnapshotRepository
|
||||
) : RecommendationSnapshotPort {
|
||||
override fun findLatestSnapshots(sectionType: RecommendedSectionType): List<RecommendationSnapshotRecord> {
|
||||
val snapshotAt = repository.findTopBySectionTypeOrderBySnapshotAtDesc(sectionType)?.snapshotAt ?: return emptyList()
|
||||
return repository.findAllBySectionTypeAndSnapshotAtOrderByScoreDescRandomTieBreakerAsc(sectionType, snapshotAt)
|
||||
.map { it.toRecord() }
|
||||
override fun findLatestSnapshots(
|
||||
sectionType: RecommendedSectionType,
|
||||
offset: Int,
|
||||
limit: Int
|
||||
): List<RecommendationSnapshotRecord> {
|
||||
return repository.findLatestSnapshots(sectionType.name, offset, limit).map { it.toRecord() }
|
||||
}
|
||||
|
||||
override fun replaceSnapshots(
|
||||
|
||||
@@ -2,14 +2,30 @@ package kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.recommend.domain.RecommendedSectionType
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface RecommendationSnapshotRepository : JpaRepository<RecommendationSnapshot, Long> {
|
||||
fun findTopBySectionTypeOrderBySnapshotAtDesc(sectionType: RecommendedSectionType): RecommendationSnapshot?
|
||||
|
||||
fun findAllBySectionTypeAndSnapshotAtOrderByScoreDescRandomTieBreakerAsc(
|
||||
sectionType: RecommendedSectionType,
|
||||
snapshotAt: LocalDateTime
|
||||
@Query(
|
||||
value = """
|
||||
select *
|
||||
from recommendation_snapshot rs
|
||||
where rs.section_type = :sectionType
|
||||
and rs.snapshot_at = (
|
||||
select max(latest.snapshot_at)
|
||||
from recommendation_snapshot latest
|
||||
where latest.section_type = :sectionType
|
||||
)
|
||||
order by rs.score desc, rs.random_tie_breaker asc
|
||||
limit :limit offset :offset
|
||||
""",
|
||||
nativeQuery = true
|
||||
)
|
||||
fun findLatestSnapshots(
|
||||
@Param("sectionType") sectionType: String,
|
||||
@Param("offset") offset: Int,
|
||||
@Param("limit") limit: Int
|
||||
): List<RecommendationSnapshot>
|
||||
|
||||
fun deleteBySectionTypeAndSnapshotAt(sectionType: RecommendedSectionType, snapshotAt: LocalDateTime)
|
||||
|
||||
Reference in New Issue
Block a user