feat(creator-channel): 시리즈 탭 조회 서비스를 추가한다

This commit is contained in:
2026-06-20 04:35:26 +09:00
parent 6c4df431b9
commit e8b8287968
2 changed files with 361 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
package kr.co.vividnext.sodalive.v2.creator.channel.series.application
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
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.common.domain.toCdnUrl
import kr.co.vividnext.sodalive.v2.creator.channel.series.domain.CreatorChannelSeries
import kr.co.vividnext.sodalive.v2.creator.channel.series.domain.CreatorChannelSeriesQueryPolicy
import kr.co.vividnext.sodalive.v2.creator.channel.series.domain.CreatorChannelSeriesTab
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesCreatorRecord
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesQueryPort
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesRecord
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 CreatorChannelSeriesQueryService(
private val queryPortProvider: ObjectProvider<CreatorChannelSeriesQueryPort>,
private val queryPolicy: CreatorChannelSeriesQueryPolicy,
private val memberContentPreferenceService: MemberContentPreferenceService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
) {
fun getSeriesTab(
creatorId: Long,
viewer: Member,
sort: String?,
page: Int?,
size: Int?,
now: LocalDateTime = LocalDateTime.now()
): CreatorChannelSeriesTab {
val resolvedSort = queryPolicy.resolveSort(sort)
val seriesPage = 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 locale = langContext.lang.code
val fetchedSeries = queryPort.findSeries(
creatorId = creatorId,
viewerId = viewerId,
now = now,
canViewAdultContent = canViewAdultContent,
sort = resolvedSort,
locale = locale,
offset = seriesPage.offset,
limit = seriesPage.fetchLimit
)
return CreatorChannelSeriesTab(
seriesCount = queryPort.countSeries(
creatorId = creatorId,
now = now,
canViewAdultContent = canViewAdultContent
),
series = queryPolicy.limitItems(fetchedSeries, seriesPage).map { it.toDomain(creatorId, viewerId, locale) },
sort = resolvedSort,
page = seriesPage,
hasNext = queryPolicy.hasNext(fetchedSeries, seriesPage)
)
}
private fun validateCreatorRole(creator: CreatorChannelSeriesCreatorRecord) {
when (creator.role) {
MemberRole.CREATOR -> return
else -> throw SodaException(messageKey = "member.validation.creator_not_found")
}
}
private fun CreatorChannelSeriesRecord.toDomain(creatorId: Long, viewerId: Long, locale: String): CreatorChannelSeries {
val isCreatorSelf = viewerId == creatorId
val domainPurchasedContentCount = if (isCreatorSelf) null else purchasedContentCount
val domainPaidContentCount = if (isCreatorSelf) null else paidContentCount
return CreatorChannelSeries(
seriesId = seriesId,
title = title,
coverImageUrl = coverImagePath.toCdnUrl(cloudFrontHost),
publishedDaysOfWeek = queryPolicy.publishedDaysOfWeekText(publishedDaysOfWeek, locale),
isOriginal = isOriginal,
isAdult = isAdult,
isProceeding = state == SeriesState.PROCEEDING,
contentCount = contentCount,
purchasedContentCount = domainPurchasedContentCount,
paidContentCount = domainPaidContentCount,
purchasedPaidContentRate = if (isCreatorSelf) {
null
} else {
queryPolicy.purchaseRate(domainPaidContentCount ?: 0, domainPurchasedContentCount ?: 0)
}
)
}
}