feat(content-ranking): 스냅샷 공개 조회 저장소를 추가한다

This commit is contained in:
2026-06-24 23:44:58 +09:00
parent 9489458b35
commit da1a63da23
5 changed files with 217 additions and 7 deletions

View File

@@ -1,20 +1,30 @@
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
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 = "creator_ranking_snapshot")
class CreatorRankingSnapshot(
@Enumerated(EnumType.STRING)
@Column(name = "ranking_type", nullable = false, updatable = false, length = 30)
val rankingType: CreatorRankingType,
@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 = "creator_id", nullable = false, updatable = false)
val creatorId: Long,

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
@@ -43,7 +44,50 @@ interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapsho
)
fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshot>
fun deleteByAggregationStartAtUtcAndAggregationEndAtUtc(
@Query(
value = """
select *
from creator_ranking_snapshot crs
where crs.ranking_type = :rankingType
and crs.visible_from_at = (
select max(latest.visible_from_at)
from creator_ranking_snapshot latest
where latest.ranking_type = :rankingType
and latest.visible_from_at <= :nowUtc
)
order by crs.final_score desc
""",
nativeQuery = true
)
fun findLatestVisibleSnapshots(
@Param("rankingType") rankingType: String,
@Param("nowUtc") nowUtc: LocalDateTime
): List<CreatorRankingSnapshot>
@Query(
value = """
select *
from creator_ranking_snapshot crs
where crs.ranking_type = :rankingType
and crs.aggregation_start_at_utc = (
select max(previous.aggregation_start_at_utc)
from creator_ranking_snapshot previous
where previous.ranking_type = :rankingType
and previous.aggregation_start_at_utc < :currentAggregationStartAtUtc
and previous.visible_from_at <= :nowUtc
)
order by crs.final_score desc
""",
nativeQuery = true
)
fun findPreviousVisibleSnapshots(
@Param("rankingType") rankingType: String,
@Param("currentAggregationStartAtUtc") currentAggregationStartAtUtc: LocalDateTime,
@Param("nowUtc") nowUtc: LocalDateTime
): List<CreatorRankingSnapshot>
fun deleteByRankingTypeAndAggregationStartAtUtcAndAggregationEndAtUtc(
@Param("rankingType") rankingType: CreatorRankingType,
@Param("aggregationStartAtUtc") aggregationStartAtUtc: LocalDateTime,
@Param("aggregationEndAtUtc") aggregationEndAtUtc: LocalDateTime
)

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotPort
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
import org.springframework.stereotype.Repository
@@ -28,27 +29,51 @@ class DefaultCreatorRankingSnapshotRepository(
return repository.findPreviousCompletedSnapshots().map { it.toRecord() }
}
override fun findLatestVisibleSnapshots(
rankingType: CreatorRankingType,
nowUtc: LocalDateTime
): List<CreatorRankingSnapshotRecord> {
return repository.findLatestVisibleSnapshots(rankingType.name, nowUtc).map { it.toRecord() }
}
override fun findPreviousVisibleSnapshots(
rankingType: CreatorRankingType,
currentAggregationStartAtUtc: LocalDateTime,
nowUtc: LocalDateTime
): List<CreatorRankingSnapshotRecord> {
return repository.findPreviousVisibleSnapshots(
rankingType = rankingType.name,
currentAggregationStartAtUtc = currentAggregationStartAtUtc,
nowUtc = nowUtc
).map { it.toRecord() }
}
override fun isSnapshotTableEmpty(): Boolean {
return repository.count() == 0L
}
@Transactional
override fun replaceSnapshots(
rankingType: CreatorRankingType,
aggregationStartAtUtc: LocalDateTime,
aggregationEndAtUtc: LocalDateTime,
visibleFromAtUtc: LocalDateTime,
newSnapshots: List<CreatorRankingSnapshotRecord>
) {
repository.deleteByAggregationStartAtUtcAndAggregationEndAtUtc(
repository.deleteByRankingTypeAndAggregationStartAtUtcAndAggregationEndAtUtc(
rankingType = rankingType,
aggregationStartAtUtc = aggregationStartAtUtc,
aggregationEndAtUtc = aggregationEndAtUtc
)
repository.saveAll(newSnapshots.map { it.toEntity() })
repository.saveAll(newSnapshots.map { it.toEntity(rankingType, visibleFromAtUtc) })
}
private fun CreatorRankingSnapshot.toRecord(): CreatorRankingSnapshotRecord {
return CreatorRankingSnapshotRecord(
rankingType = rankingType,
aggregationStartAtUtc = aggregationStartAtUtc,
aggregationEndAtUtc = aggregationEndAtUtc,
visibleFromAtUtc = visibleFromAtUtc,
creatorId = creatorId,
nickname = nickname,
profileImageUrl = profileImageUrl,
@@ -69,10 +94,15 @@ class DefaultCreatorRankingSnapshotRepository(
)
}
private fun CreatorRankingSnapshotRecord.toEntity(): CreatorRankingSnapshot {
private fun CreatorRankingSnapshotRecord.toEntity(
rankingType: CreatorRankingType,
visibleFromAtUtc: LocalDateTime
): CreatorRankingSnapshot {
return CreatorRankingSnapshot(
rankingType = rankingType,
aggregationStartAtUtc = aggregationStartAtUtc,
aggregationEndAtUtc = aggregationEndAtUtc,
visibleFromAtUtc = visibleFromAtUtc,
creatorId = creatorId,
nickname = nickname,
profileImageUrl = profileImageUrl,

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.ranking.port.out
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
import java.time.LocalDateTime
interface CreatorRankingSnapshotPort {
@@ -12,18 +13,33 @@ interface CreatorRankingSnapshotPort {
fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshotRecord>
fun findLatestVisibleSnapshots(
rankingType: CreatorRankingType,
nowUtc: LocalDateTime
): List<CreatorRankingSnapshotRecord>
fun findPreviousVisibleSnapshots(
rankingType: CreatorRankingType,
currentAggregationStartAtUtc: LocalDateTime,
nowUtc: LocalDateTime
): List<CreatorRankingSnapshotRecord>
fun isSnapshotTableEmpty(): Boolean
fun replaceSnapshots(
rankingType: CreatorRankingType,
aggregationStartAtUtc: LocalDateTime,
aggregationEndAtUtc: LocalDateTime,
visibleFromAtUtc: LocalDateTime,
newSnapshots: List<CreatorRankingSnapshotRecord>
)
}
data class CreatorRankingSnapshotRecord(
val rankingType: CreatorRankingType,
val aggregationStartAtUtc: LocalDateTime,
val aggregationEndAtUtc: LocalDateTime,
val visibleFromAtUtc: LocalDateTime,
val creatorId: Long,
val nickname: String,
val profileImageUrl: String?,