feat(content-ranking): 랭킹 조회 fallback과 차단 필터를 적용한다
This commit is contained in:
@@ -5,10 +5,11 @@ import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreference
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRanking
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingItem
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingBlockPort
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
@@ -16,24 +17,27 @@ import java.time.ZonedDateTime
|
||||
class AudioRankingQueryService(
|
||||
private val snapshotPort: AudioRankingSnapshotPort,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
private val blockPort: AudioRankingBlockPort,
|
||||
private val jobService: AudioRankingSnapshotJobService,
|
||||
private val nowProvider: () -> ZonedDateTime = { ZonedDateTime.now() }
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
fun getRankings(type: AudioRankingType, member: Member?): AudioRanking {
|
||||
val nowUtc = nowProvider().withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
||||
val latestSnapshots = snapshotPort.findLatestVisibleSnapshots(type, nowUtc)
|
||||
val latestSnapshots = findLatestVisibleSnapshots(type, nowUtc)
|
||||
if (latestSnapshots.isEmpty()) {
|
||||
return AudioRanking(showRankChange = false, type = type, items = emptyList())
|
||||
}
|
||||
val canViewAdultContent = canViewAdultContent(member)
|
||||
val latestVisibleSnapshots = latestSnapshots.visibleTo(canViewAdultContent).take(ITEM_LIMIT)
|
||||
|
||||
val previousSnapshots = snapshotPort.findPreviousVisibleSnapshots(
|
||||
rankingType = type,
|
||||
currentAggregationStartAtUtc = latestSnapshots.first().aggregationStartAtUtc,
|
||||
nowUtc = nowUtc
|
||||
)
|
||||
val previousRankByContentId = previousSnapshots.visibleTo(canViewAdultContent)
|
||||
val blockedCreatorMemberIds = blockedCreatorMemberIds(member, latestSnapshots + previousSnapshots)
|
||||
val latestVisibleSnapshots = latestSnapshots.visibleTo(canViewAdultContent, blockedCreatorMemberIds).take(ITEM_LIMIT)
|
||||
val previousRankByContentId = previousSnapshots.visibleTo(canViewAdultContent, blockedCreatorMemberIds)
|
||||
.take(ITEM_LIMIT)
|
||||
.mapIndexed { index, snapshot -> snapshot.contentId to index + 1 }
|
||||
.toMap()
|
||||
@@ -48,13 +52,44 @@ class AudioRankingQueryService(
|
||||
)
|
||||
}
|
||||
|
||||
private fun findLatestVisibleSnapshots(
|
||||
type: AudioRankingType,
|
||||
nowUtc: java.time.LocalDateTime
|
||||
): List<AudioRankingSnapshotRecord> {
|
||||
val latestSnapshots = snapshotPort.findLatestVisibleSnapshots(type, nowUtc)
|
||||
if (latestSnapshots.isNotEmpty()) return latestSnapshots
|
||||
|
||||
runCatching { jobService.refreshLastCompletedWeekByFallback(type) }
|
||||
.onFailure { ex ->
|
||||
log.warn(
|
||||
"event=audio_ranking_query_fallback_failure rankingType={} error={}",
|
||||
type,
|
||||
ex.message,
|
||||
ex
|
||||
)
|
||||
}
|
||||
return snapshotPort.findLatestVisibleSnapshots(type, nowUtc)
|
||||
}
|
||||
|
||||
private fun canViewAdultContent(member: Member?): Boolean {
|
||||
if (member == null) return false
|
||||
return memberContentPreferenceService.canViewAdultContent(member)
|
||||
}
|
||||
|
||||
private fun List<AudioRankingSnapshotRecord>.visibleTo(canViewAdultContent: Boolean): List<AudioRankingSnapshotRecord> {
|
||||
return if (canViewAdultContent) this else filter { !it.isAdult }
|
||||
private fun blockedCreatorMemberIds(member: Member?, snapshots: List<AudioRankingSnapshotRecord>): Set<Long> {
|
||||
val memberId = member?.id ?: return emptySet()
|
||||
val creatorMemberIds = snapshots.map { it.creatorMemberId }.toSet()
|
||||
if (creatorMemberIds.isEmpty()) return emptySet()
|
||||
return blockPort.findBlockedCreatorMemberIds(memberId, creatorMemberIds)
|
||||
}
|
||||
|
||||
private fun List<AudioRankingSnapshotRecord>.visibleTo(
|
||||
canViewAdultContent: Boolean,
|
||||
blockedCreatorMemberIds: Set<Long>
|
||||
): List<AudioRankingSnapshotRecord> {
|
||||
return filter { snapshot ->
|
||||
(canViewAdultContent || !snapshot.isAdult) && snapshot.creatorMemberId !in blockedCreatorMemberIds
|
||||
}
|
||||
}
|
||||
|
||||
private fun AudioRankingSnapshotRecord.toItem(
|
||||
|
||||
Reference in New Issue
Block a user