feat(content-ranking): 랭킹 스냅샷 저장소를 추가한다
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.EnumType
|
||||
import javax.persistence.Enumerated
|
||||
import javax.persistence.Table
|
||||
|
||||
@Entity
|
||||
@Table(name = "content_ranking_snapshot")
|
||||
class AudioRankingSnapshot(
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "ranking_type", nullable = false, updatable = false, length = 30)
|
||||
val rankingType: AudioRankingType,
|
||||
|
||||
@Column(name = "aggregation_start_at_utc", nullable = false, updatable = false)
|
||||
val aggregationStartAtUtc: LocalDateTime,
|
||||
|
||||
@Column(name = "aggregation_end_at_utc", nullable = false, updatable = false)
|
||||
val aggregationEndAtUtc: LocalDateTime,
|
||||
|
||||
@Column(name = "visible_from_at", nullable = false, updatable = false)
|
||||
val visibleFromAtUtc: LocalDateTime,
|
||||
|
||||
@Column(name = "content_id", nullable = false, updatable = false)
|
||||
val contentId: Long,
|
||||
|
||||
@Column(name = "title", nullable = false, updatable = false, length = 255)
|
||||
val title: String,
|
||||
|
||||
@Column(name = "creator_member_id", nullable = false, updatable = false)
|
||||
val creatorMemberId: Long,
|
||||
|
||||
@Column(name = "creator_nickname", nullable = false, updatable = false, length = 100)
|
||||
val creatorNickname: String,
|
||||
|
||||
@Column(name = "cover_image_url", updatable = false, length = 500)
|
||||
val coverImageUrl: String?,
|
||||
|
||||
@Column(name = "release_date", nullable = false, updatable = false)
|
||||
val releaseDate: LocalDateTime,
|
||||
|
||||
@Column(name = "is_adult", nullable = false, updatable = false)
|
||||
val isAdult: Boolean,
|
||||
|
||||
@Column(name = "rank_no", nullable = false, updatable = false)
|
||||
val rank: Int,
|
||||
|
||||
@Column(name = "final_score", nullable = false, updatable = false)
|
||||
val finalScore: Double,
|
||||
|
||||
@Column(name = "normalized_score", updatable = false)
|
||||
val normalizedScore: Double? = null,
|
||||
|
||||
@Column(name = "raw_score", updatable = false)
|
||||
val rawScore: Double? = null,
|
||||
|
||||
@Column(name = "revenue_can_amount", updatable = false)
|
||||
val revenueCanAmount: Long? = null,
|
||||
|
||||
@Column(name = "sales_count", updatable = false)
|
||||
val salesCount: Long? = null,
|
||||
|
||||
@Column(name = "view_count", updatable = false)
|
||||
val viewCount: Long? = null,
|
||||
|
||||
@Column(name = "like_count", updatable = false)
|
||||
val likeCount: Long? = null,
|
||||
|
||||
@Column(name = "comment_count", updatable = false)
|
||||
val commentCount: Long? = null,
|
||||
|
||||
@Column(name = "previous_sales_count", updatable = false)
|
||||
val previousSalesCount: Long? = null,
|
||||
|
||||
@Column(name = "previous_view_count", updatable = false)
|
||||
val previousViewCount: Long? = null,
|
||||
|
||||
@Column(name = "previous_like_count", updatable = false)
|
||||
val previousLikeCount: Long? = null,
|
||||
|
||||
@Column(name = "previous_comment_count", updatable = false)
|
||||
val previousCommentCount: Long? = null,
|
||||
|
||||
@Column(name = "sales_growth_rate", updatable = false)
|
||||
val salesGrowthRate: Double? = null,
|
||||
|
||||
@Column(name = "view_growth_rate", updatable = false)
|
||||
val viewGrowthRate: Double? = null,
|
||||
|
||||
@Column(name = "like_growth_rate", updatable = false)
|
||||
val likeGrowthRate: Double? = null,
|
||||
|
||||
@Column(name = "comment_growth_rate", updatable = false)
|
||||
val commentGrowthRate: Double? = null,
|
||||
|
||||
@Column(name = "content_growth_score", updatable = false)
|
||||
val contentGrowthScore: Double? = null,
|
||||
|
||||
@Column(name = "boost_multiplier", updatable = false)
|
||||
val boostMultiplier: Double? = null
|
||||
) : BaseEntity()
|
||||
@@ -0,0 +1,57 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
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 AudioRankingSnapshotRepository : JpaRepository<AudioRankingSnapshot, Long> {
|
||||
@Query(
|
||||
value = """
|
||||
select *
|
||||
from content_ranking_snapshot crs
|
||||
where crs.ranking_type = :rankingType
|
||||
and crs.visible_from_at = (
|
||||
select max(latest.visible_from_at)
|
||||
from content_ranking_snapshot latest
|
||||
where latest.ranking_type = :rankingType
|
||||
and latest.visible_from_at <= :nowUtc
|
||||
)
|
||||
order by crs.rank_no asc
|
||||
""",
|
||||
nativeQuery = true
|
||||
)
|
||||
fun findLatestVisibleSnapshots(
|
||||
@Param("rankingType") rankingType: String,
|
||||
@Param("nowUtc") nowUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshot>
|
||||
|
||||
@Query(
|
||||
value = """
|
||||
select *
|
||||
from content_ranking_snapshot crs
|
||||
where crs.ranking_type = :rankingType
|
||||
and crs.aggregation_start_at_utc = (
|
||||
select max(previous.aggregation_start_at_utc)
|
||||
from content_ranking_snapshot previous
|
||||
where previous.ranking_type = :rankingType
|
||||
and previous.aggregation_start_at_utc < :currentAggregationStartAtUtc
|
||||
and previous.visible_from_at <= :nowUtc
|
||||
)
|
||||
order by crs.rank_no asc
|
||||
""",
|
||||
nativeQuery = true
|
||||
)
|
||||
fun findPreviousVisibleSnapshots(
|
||||
@Param("rankingType") rankingType: String,
|
||||
@Param("currentAggregationStartAtUtc") currentAggregationStartAtUtc: LocalDateTime,
|
||||
@Param("nowUtc") nowUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshot>
|
||||
|
||||
fun deleteByRankingTypeAndAggregationStartAtUtcAndAggregationEndAtUtc(
|
||||
rankingType: AudioRankingType,
|
||||
aggregationStartAtUtc: LocalDateTime,
|
||||
aggregationEndAtUtc: LocalDateTime
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||
import org.springframework.stereotype.Repository
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Repository
|
||||
class DefaultAudioRankingSnapshotPersistenceAdapter(
|
||||
private val repository: AudioRankingSnapshotRepository
|
||||
) : AudioRankingSnapshotPort {
|
||||
override fun findLatestVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
nowUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotRecord> {
|
||||
return repository.findLatestVisibleSnapshots(rankingType.name, nowUtc).map { it.toRecord() }
|
||||
}
|
||||
|
||||
override fun findPreviousVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
currentAggregationStartAtUtc: LocalDateTime,
|
||||
nowUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotRecord> {
|
||||
return repository.findPreviousVisibleSnapshots(
|
||||
rankingType = rankingType.name,
|
||||
currentAggregationStartAtUtc = currentAggregationStartAtUtc,
|
||||
nowUtc = nowUtc
|
||||
).map { it.toRecord() }
|
||||
}
|
||||
|
||||
@Transactional
|
||||
override fun replaceSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
aggregationStartAtUtc: LocalDateTime,
|
||||
aggregationEndAtUtc: LocalDateTime,
|
||||
visibleFromAtUtc: LocalDateTime,
|
||||
newSnapshots: List<AudioRankingSnapshotRecord>
|
||||
) {
|
||||
repository.deleteByRankingTypeAndAggregationStartAtUtcAndAggregationEndAtUtc(
|
||||
rankingType = rankingType,
|
||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||
aggregationEndAtUtc = aggregationEndAtUtc
|
||||
)
|
||||
repository.saveAll(newSnapshots.map { it.toEntity(visibleFromAtUtc) })
|
||||
}
|
||||
|
||||
private fun AudioRankingSnapshot.toRecord(): AudioRankingSnapshotRecord {
|
||||
return AudioRankingSnapshotRecord(
|
||||
rankingType = rankingType,
|
||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
contentId = contentId,
|
||||
title = title,
|
||||
creatorMemberId = creatorMemberId,
|
||||
creatorNickname = creatorNickname,
|
||||
coverImageUrl = coverImageUrl,
|
||||
releaseDate = releaseDate,
|
||||
isAdult = isAdult,
|
||||
rank = rank,
|
||||
finalScore = finalScore,
|
||||
normalizedScore = normalizedScore,
|
||||
rawScore = rawScore,
|
||||
revenueCanAmount = revenueCanAmount,
|
||||
salesCount = salesCount,
|
||||
viewCount = viewCount,
|
||||
likeCount = likeCount,
|
||||
commentCount = commentCount,
|
||||
previousSalesCount = previousSalesCount,
|
||||
previousViewCount = previousViewCount,
|
||||
previousLikeCount = previousLikeCount,
|
||||
previousCommentCount = previousCommentCount,
|
||||
salesGrowthRate = salesGrowthRate,
|
||||
viewGrowthRate = viewGrowthRate,
|
||||
likeGrowthRate = likeGrowthRate,
|
||||
commentGrowthRate = commentGrowthRate,
|
||||
contentGrowthScore = contentGrowthScore,
|
||||
boostMultiplier = boostMultiplier
|
||||
)
|
||||
}
|
||||
|
||||
private fun AudioRankingSnapshotRecord.toEntity(visibleFromAtUtc: LocalDateTime): AudioRankingSnapshot {
|
||||
return AudioRankingSnapshot(
|
||||
rankingType = rankingType,
|
||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
contentId = contentId,
|
||||
title = title,
|
||||
creatorMemberId = creatorMemberId,
|
||||
creatorNickname = creatorNickname,
|
||||
coverImageUrl = coverImageUrl,
|
||||
releaseDate = releaseDate,
|
||||
isAdult = isAdult,
|
||||
rank = rank,
|
||||
finalScore = finalScore,
|
||||
normalizedScore = normalizedScore,
|
||||
rawScore = rawScore,
|
||||
revenueCanAmount = revenueCanAmount,
|
||||
salesCount = salesCount,
|
||||
viewCount = viewCount,
|
||||
likeCount = likeCount,
|
||||
commentCount = commentCount,
|
||||
previousSalesCount = previousSalesCount,
|
||||
previousViewCount = previousViewCount,
|
||||
previousLikeCount = previousLikeCount,
|
||||
previousCommentCount = previousCommentCount,
|
||||
salesGrowthRate = salesGrowthRate,
|
||||
viewGrowthRate = viewGrowthRate,
|
||||
likeGrowthRate = likeGrowthRate,
|
||||
commentGrowthRate = commentGrowthRate,
|
||||
contentGrowthScore = contentGrowthScore,
|
||||
boostMultiplier = boostMultiplier
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.ranking.port.out
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface AudioRankingSnapshotPort {
|
||||
fun findLatestVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
nowUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotRecord>
|
||||
|
||||
fun findPreviousVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
currentAggregationStartAtUtc: LocalDateTime,
|
||||
nowUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotRecord>
|
||||
|
||||
fun replaceSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
aggregationStartAtUtc: LocalDateTime,
|
||||
aggregationEndAtUtc: LocalDateTime,
|
||||
visibleFromAtUtc: LocalDateTime,
|
||||
newSnapshots: List<AudioRankingSnapshotRecord>
|
||||
)
|
||||
}
|
||||
|
||||
data class AudioRankingSnapshotRecord(
|
||||
val rankingType: AudioRankingType,
|
||||
val aggregationStartAtUtc: LocalDateTime,
|
||||
val aggregationEndAtUtc: LocalDateTime,
|
||||
val visibleFromAtUtc: LocalDateTime,
|
||||
val contentId: Long,
|
||||
val title: String,
|
||||
val creatorMemberId: Long,
|
||||
val creatorNickname: String,
|
||||
val coverImageUrl: String?,
|
||||
val releaseDate: LocalDateTime,
|
||||
val isAdult: Boolean,
|
||||
val rank: Int,
|
||||
val finalScore: Double,
|
||||
val normalizedScore: Double? = null,
|
||||
val rawScore: Double? = null,
|
||||
val revenueCanAmount: Long? = null,
|
||||
val salesCount: Long? = null,
|
||||
val viewCount: Long? = null,
|
||||
val likeCount: Long? = null,
|
||||
val commentCount: Long? = null,
|
||||
val previousSalesCount: Long? = null,
|
||||
val previousViewCount: Long? = null,
|
||||
val previousLikeCount: Long? = null,
|
||||
val previousCommentCount: Long? = null,
|
||||
val salesGrowthRate: Double? = null,
|
||||
val viewGrowthRate: Double? = null,
|
||||
val likeGrowthRate: Double? = null,
|
||||
val commentGrowthRate: Double? = null,
|
||||
val contentGrowthScore: Double? = null,
|
||||
val boostMultiplier: Double? = null
|
||||
)
|
||||
Reference in New Issue
Block a user