feat(home): 응원 스냅샷 marker 저장을 지원한다

This commit is contained in:
2026-07-10 06:06:02 +09:00
parent 4f348c3643
commit 64b05deee7
4 changed files with 154 additions and 6 deletions

View File

@@ -18,17 +18,30 @@ class RecommendationSnapshotPersistenceAdapter(
return repository.findLatestSnapshots(sectionType.name, offset, limit).map { it.toRecord() }
}
override fun findSnapshots(
sectionType: RecommendedSectionType,
snapshotAt: LocalDateTime,
offset: Long,
limit: Int
): List<RecommendationSnapshotRecord> {
return repository.findSnapshots(sectionType.name, snapshotAt, offset, limit).map { it.toRecord() }
}
override fun existsLatestSnapshot(sectionType: RecommendedSectionType): Boolean {
return repository.existsBySectionType(sectionType)
}
override fun existsSnapshot(sectionType: RecommendedSectionType, snapshotAt: LocalDateTime): Boolean {
return repository.existsBySectionTypeAndSnapshotAt(sectionType, snapshotAt)
}
override fun replaceSnapshots(
sectionType: RecommendedSectionType,
snapshotAt: LocalDateTime,
newSnapshots: List<RecommendationSnapshotRecord>
) {
if (newSnapshots.isEmpty() && sectionType == RecommendedSectionType.AI_CHARACTER) {
repository.deleteBySectionType(sectionType)
if (newSnapshots.isEmpty() && supportsEmptySnapshotMarker(sectionType)) {
repository.deleteBySectionTypeAndSnapshotAt(sectionType, snapshotAt)
repository.save(emptySnapshotMarker(sectionType, snapshotAt).toEntity())
return
}
@@ -70,6 +83,10 @@ class RecommendationSnapshotPersistenceAdapter(
)
}
private fun supportsEmptySnapshotMarker(sectionType: RecommendedSectionType): Boolean {
return sectionType == RecommendedSectionType.AI_CHARACTER || sectionType == RecommendedSectionType.CHEER_CREATOR
}
companion object {
private const val EMPTY_SNAPSHOT_MARKER_TARGET_ID = 0L
}

View File

@@ -29,9 +29,28 @@ interface RecommendationSnapshotRepository : JpaRepository<RecommendationSnapsho
@Param("limit") limit: Int
): List<RecommendationSnapshot>
@Query(
value = """
select *
from recommendation_snapshot rs
where rs.section_type = :sectionType
and rs.snapshot_at = :snapshotAt
and rs.target_id <> 0
order by rs.score desc, rs.random_tie_breaker asc
limit :limit offset :offset
""",
nativeQuery = true
)
fun findSnapshots(
@Param("sectionType") sectionType: String,
@Param("snapshotAt") snapshotAt: LocalDateTime,
@Param("offset") offset: Long,
@Param("limit") limit: Int
): List<RecommendationSnapshot>
fun deleteBySectionTypeAndSnapshotAt(sectionType: RecommendedSectionType, snapshotAt: LocalDateTime)
fun deleteBySectionType(sectionType: RecommendedSectionType)
fun existsBySectionType(sectionType: RecommendedSectionType): Boolean
fun existsBySectionTypeAndSnapshotAt(sectionType: RecommendedSectionType, snapshotAt: LocalDateTime): Boolean
}

View File

@@ -10,8 +10,17 @@ interface RecommendationSnapshotPort {
limit: Int = Int.MAX_VALUE
): List<RecommendationSnapshotRecord>
fun findSnapshots(
sectionType: RecommendedSectionType,
snapshotAt: LocalDateTime,
offset: Long = 0,
limit: Int = Int.MAX_VALUE
): List<RecommendationSnapshotRecord>
fun existsLatestSnapshot(sectionType: RecommendedSectionType): Boolean
fun existsSnapshot(sectionType: RecommendedSectionType, snapshotAt: LocalDateTime): Boolean
fun replaceSnapshots(
sectionType: RecommendedSectionType,
snapshotAt: LocalDateTime,