feat(creator-channel): 오디오 탭 조회 서비스를 추가한다

This commit is contained in:
2026-06-19 16:06:45 +09:00
parent 80a06ad63d
commit 4fdb9bcb26
2 changed files with 379 additions and 99 deletions

View File

@@ -0,0 +1,140 @@
package kr.co.vividnext.sodalive.v2.creator.channel.audio.application
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.v2.creator.channel.audio.domain.CreatorChannelAudioQueryPolicy
import kr.co.vividnext.sodalive.v2.creator.channel.audio.domain.CreatorChannelAudioTab
import kr.co.vividnext.sodalive.v2.creator.channel.audio.domain.CreatorChannelAudioTheme
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioContentRecord
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioCreatorRecord
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioQueryPort
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioThemeRecord
import kr.co.vividnext.sodalive.v2.creator.channel.common.domain.CreatorChannelAudioContent
import org.springframework.beans.factory.ObjectProvider
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Service
@Transactional(readOnly = true)
class CreatorChannelAudioQueryService(
private val queryPortProvider: ObjectProvider<CreatorChannelAudioQueryPort>,
private val queryPolicy: CreatorChannelAudioQueryPolicy,
private val memberContentPreferenceService: MemberContentPreferenceService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
) {
fun getAudioTab(
creatorId: Long,
viewer: Member,
sort: String?,
themeId: Long?,
page: Int?,
size: Int?,
now: LocalDateTime = LocalDateTime.now()
): CreatorChannelAudioTab {
val resolvedSort = queryPolicy.resolveSort(sort)
val audioPage = queryPolicy.createPage(page, size)
val queryPort = queryPortProvider.getObject()
val viewerId = viewer.id!!
val creator = queryPort.findCreator(creatorId, viewerId)
?: throw SodaException(messageKey = "member.validation.user_not_found")
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
val messageTemplate = messageSource
.getMessage("explorer.creator.blocked_access", langContext.lang)
.orEmpty()
throw SodaException(message = String.format(messageTemplate, creator.nickname))
}
validateCreatorRole(creator)
val preference = memberContentPreferenceService.getStoredPreference(viewer)
val canViewAdultContent = isAdultVisibleByPolicy(viewer, preference.isAdultContentVisible)
val resolvedThemeId = themeId?.let(queryPort::findActiveThemeId)
val locale = langContext.lang.code
val fetchedContents = queryPort.findAudioContents(
creatorId = creatorId,
viewerId = viewerId,
themeId = resolvedThemeId,
now = now,
canViewAdultContent = canViewAdultContent,
sort = resolvedSort,
locale = locale,
offset = audioPage.offset,
limit = audioPage.fetchLimit
)
val paidAudioContentCount = queryPort.countPaidAudioContents(
creatorId = creatorId,
themeId = resolvedThemeId,
now = now,
canViewAdultContent = canViewAdultContent
)
val purchasedAudioContentCount = queryPort.countPurchasedAudioContents(
creatorId = creatorId,
viewerId = viewerId,
themeId = resolvedThemeId,
now = now,
canViewAdultContent = canViewAdultContent
)
return CreatorChannelAudioTab(
audioContentCount = queryPort.countAudioContents(
creatorId = creatorId,
themeId = resolvedThemeId,
now = now,
canViewAdultContent = canViewAdultContent
),
paidAudioContentCount = paidAudioContentCount,
purchasedAudioContentCount = purchasedAudioContentCount,
purchasedAudioContentRate = queryPolicy.purchaseRate(paidAudioContentCount, purchasedAudioContentCount),
themes = queryPort.findAudioThemes(locale).map { it.toDomain() },
audioContents = queryPolicy.limitItems(fetchedContents, audioPage).map { it.toDomain() },
sort = resolvedSort,
themeId = resolvedThemeId,
page = audioPage,
hasNext = queryPolicy.hasNext(fetchedContents, audioPage)
)
}
private fun validateCreatorRole(creator: CreatorChannelAudioCreatorRecord) {
when (creator.role) {
MemberRole.CREATOR -> return
else -> throw SodaException(messageKey = "member.validation.creator_not_found")
}
}
private fun CreatorChannelAudioThemeRecord.toDomain() = CreatorChannelAudioTheme(
themeId = themeId,
themeName = themeName
)
private fun CreatorChannelAudioContentRecord.toDomain() = CreatorChannelAudioContent(
audioContentId = audioContentId,
title = title,
duration = duration,
imageUrl = imagePath.toCdnUrl(),
price = price,
isAdult = isAdult,
isPointAvailable = isPointAvailable,
isFirstContent = isFirstContent,
seriesName = seriesName,
isOriginalSeries = isOriginalSeries,
isOwned = isOwned,
isRented = isRented
)
private fun String?.toCdnUrl(): String? {
if (isNullOrBlank()) return null
if (startsWith("https://") || startsWith("http://")) return this
return "$cloudFrontHost/$this"
}
}