feat(audio-recommendation): 추천 섹션 매핑 서비스를 추가한다

This commit is contained in:
2026-06-23 16:12:45 +09:00
parent 3df66d98ef
commit 9c4ec03624
4 changed files with 153 additions and 1 deletions

View File

@@ -0,0 +1,73 @@
package kr.co.vividnext.sodalive.v2.audio.recommendation.application
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.v2.audio.recommendation.domain.AudioRecommendationVisibility
import kr.co.vividnext.sodalive.v2.audio.recommendation.domain.AudioRecommendations
import kr.co.vividnext.sodalive.v2.audio.recommendation.port.out.AudioRecommendationQueryPort
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Service
class AudioRecommendationQueryService(
private val queryPort: AudioRecommendationQueryPort,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@Transactional(readOnly = true)
fun getRecommendations(member: Member?): AudioRecommendations {
val now = LocalDateTime.now()
val canViewAdultContent = canViewAdultContent(member)
return AudioRecommendations(
banners = queryPort.findBanners(BANNER_LIMIT, member?.id, canViewAdultContent),
originalSeries = queryPort.findOriginalSeries(ORIGINAL_SERIES_LIMIT, member?.id, canViewAdultContent, now),
latestAudios = queryPort.findLatestAudios(LATEST_AUDIO_LIMIT, member?.id, canViewAdultContent, now),
newAndHotAudios = emptyList(),
freeAudios = queryPort.findFreeAudios(FREE_AUDIO_LIMIT, member?.id, canViewAdultContent, now),
pointAudios = queryPort.findPointAudios(POINT_AUDIO_LIMIT, member?.id, canViewAdultContent, now),
mostCommentedAudios = emptyList(),
recommendedAudios = emptyList()
)
}
fun resolveVisibility(member: Member?): AudioRecommendationVisibility {
return if (canViewAdultContent(member)) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE
}
fun newAndHotSectionType(visibility: AudioRecommendationVisibility): RecommendedSectionType {
return when (visibility) {
AudioRecommendationVisibility.SAFE -> RecommendedSectionType.NEW_AND_HOT_AUDIO_SAFE
AudioRecommendationVisibility.ALL -> RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL
}
}
fun mostCommentedSectionType(visibility: AudioRecommendationVisibility): RecommendedSectionType {
return when (visibility) {
AudioRecommendationVisibility.SAFE -> RecommendedSectionType.MOST_COMMENTED_AUDIO_SAFE
AudioRecommendationVisibility.ALL -> RecommendedSectionType.MOST_COMMENTED_AUDIO_ALL
}
}
fun recommendedAudioSectionType(visibility: AudioRecommendationVisibility): RecommendedSectionType {
return when (visibility) {
AudioRecommendationVisibility.SAFE -> RecommendedSectionType.RECOMMENDED_AUDIO_SAFE
AudioRecommendationVisibility.ALL -> RecommendedSectionType.RECOMMENDED_AUDIO_ALL
}
}
private fun canViewAdultContent(member: Member?): Boolean {
if (member == null) return false
val preference = memberContentPreferenceService.initializeDefaultPreference(member)
return isAdultVisibleByPolicy(member, preference.isAdultContentVisible)
}
companion object {
const val BANNER_LIMIT = 20
const val ORIGINAL_SERIES_LIMIT = 12
const val LATEST_AUDIO_LIMIT = 12
const val FREE_AUDIO_LIMIT = 10
const val POINT_AUDIO_LIMIT = 10
}
}

View File

@@ -0,0 +1,22 @@
package kr.co.vividnext.sodalive.v2.audio.recommendation.port.out
import kr.co.vividnext.sodalive.v2.audio.recommendation.domain.AudioCard
import kr.co.vividnext.sodalive.v2.audio.recommendation.domain.CommentedAudio
import kr.co.vividnext.sodalive.v2.audio.recommendation.domain.OriginalSeries
import kr.co.vividnext.sodalive.v2.common.domain.RecommendationBanner
import java.time.LocalDateTime
interface AudioRecommendationQueryPort {
fun findBanners(limit: Int, memberId: Long?, canViewAdultContent: Boolean): List<RecommendationBanner>
fun findOriginalSeries(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<OriginalSeries>
fun findLatestAudios(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<AudioCard>
fun findFreeAudios(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<AudioCard>
fun findPointAudios(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<AudioCard>
fun findAudioCardsByIds(
contentIds: List<Long>,
memberId: Long?,
canViewAdultContent: Boolean,
now: LocalDateTime
): List<AudioCard>
fun findCommentedAudiosByIds(contentIds: List<Long>, memberId: Long?, canViewAdultContent: Boolean): List<CommentedAudio>
}

View File

@@ -9,5 +9,11 @@ enum class RecommendedSectionType(val code: String) {
AI_CHARACTER("AI_CHARACTER"),
GENRE_CREATOR("GENRE_CREATOR"),
CHEER_CREATOR("CHEER_CREATOR"),
POPULAR_COMMUNITY("POPULAR_COMMUNITY")
POPULAR_COMMUNITY("POPULAR_COMMUNITY"),
NEW_AND_HOT_AUDIO_SAFE("NEW_AND_HOT_AUDIO_SAFE"),
NEW_AND_HOT_AUDIO_ALL("NEW_AND_HOT_AUDIO_ALL"),
MOST_COMMENTED_AUDIO_SAFE("MOST_COMMENTED_AUDIO_SAFE"),
MOST_COMMENTED_AUDIO_ALL("MOST_COMMENTED_AUDIO_ALL"),
RECOMMENDED_AUDIO_SAFE("RECOMMENDED_AUDIO_SAFE"),
RECOMMENDED_AUDIO_ALL("RECOMMENDED_AUDIO_ALL")
}