feat(creator): 채널 홈 조회 서비스를 추가한다
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.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.Gender
|
||||
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.domain.CreatorChannelActivity
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelAudioContent
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelCommunityPost
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelCreator
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelDonation
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelFanTalk
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelFanTalkSummary
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHome
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicy
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelLive
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelSchedule
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelSeries
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelSns
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelActivityRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelAudioContentRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelCommunityPostRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelCreatorRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelDonationRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelFanTalkRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelFanTalkSummaryRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelHomeQueryPort
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelLiveRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelScheduleRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelSeriesRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelSnsRecord
|
||||
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 CreatorChannelHomeQueryService(
|
||||
private val queryPort: CreatorChannelHomeQueryPort,
|
||||
private val queryPolicy: CreatorChannelHomeQueryPolicy,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
fun getHome(
|
||||
creatorId: Long,
|
||||
viewer: Member,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
): CreatorChannelHome {
|
||||
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 isViewerCreator = viewerId == creatorId
|
||||
val effectiveViewerGender = viewer.effectiveGender()
|
||||
val latestAudioContent = queryPort
|
||||
.findLatestAudioContent(creatorId, now, canViewAdultContent)
|
||||
?.toDomain()
|
||||
val audioContents = queryPolicy.excludeLatestAudioContent(
|
||||
queryPort.findAudioContents(
|
||||
creatorId = creatorId,
|
||||
now = now,
|
||||
latestAudioContentId = latestAudioContent?.audioContentId,
|
||||
canViewAdultContent = canViewAdultContent
|
||||
).map { it.toDomain() },
|
||||
latestAudioContent?.audioContentId
|
||||
)
|
||||
|
||||
return CreatorChannelHome(
|
||||
creator = creator.toDomain(),
|
||||
currentLive = queryPort.findCurrentLive(
|
||||
creatorId = creatorId,
|
||||
now = now,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
viewerId = viewerId,
|
||||
isViewerCreator = isViewerCreator,
|
||||
effectiveViewerGender = effectiveViewerGender
|
||||
)?.toDomain(),
|
||||
latestAudioContent = latestAudioContent,
|
||||
channelDonations = queryPort.findChannelDonations(creatorId, viewerId, now).map { it.toDomain() },
|
||||
notices = queryPort.findCommunityPosts(
|
||||
creatorId = creatorId,
|
||||
viewerId = viewerId,
|
||||
isFixed = true,
|
||||
canViewAdultContent = canViewAdultContent
|
||||
).map { it.toDomain() },
|
||||
schedules = queryPolicy.limitSchedules(
|
||||
queryPort.findSchedules(
|
||||
creatorId = creatorId,
|
||||
now = now,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
viewerId = viewerId,
|
||||
isViewerCreator = isViewerCreator,
|
||||
effectiveViewerGender = effectiveViewerGender
|
||||
).map { it.toDomain() },
|
||||
now,
|
||||
canViewAdultContent
|
||||
),
|
||||
audioContents = audioContents,
|
||||
series = queryPort.findSeries(
|
||||
creatorId = creatorId,
|
||||
viewerId = viewerId,
|
||||
now = now,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
contentType = preference.contentType
|
||||
).map { it.toDomain() },
|
||||
communities = queryPort.findCommunityPosts(
|
||||
creatorId = creatorId,
|
||||
viewerId = viewerId,
|
||||
isFixed = false,
|
||||
canViewAdultContent = canViewAdultContent
|
||||
).map { it.toDomain() },
|
||||
fanTalk = queryPort.findFanTalkSummary(creatorId, viewerId).toDomain(),
|
||||
introduce = creator.introduce,
|
||||
activity = queryPort.findActivity(creatorId, now).toDomain(),
|
||||
sns = queryPort.findSns(creatorId).toDomain()
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateCreatorRole(creator: CreatorChannelCreatorRecord) {
|
||||
when (creator.role) {
|
||||
MemberRole.CREATOR -> return
|
||||
else -> throw SodaException(messageKey = "member.validation.creator_not_found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Member.effectiveGender(): Gender {
|
||||
auth?.let { return if (it.gender == 1) Gender.MALE else Gender.FEMALE }
|
||||
return gender
|
||||
}
|
||||
|
||||
private fun CreatorChannelCreatorRecord.toDomain() = CreatorChannelCreator(
|
||||
creatorId = creatorId,
|
||||
characterId = characterId,
|
||||
nickname = nickname,
|
||||
profileImageUrl = profileImagePath.toCdnUrl() ?: defaultProfileImageUrl(),
|
||||
followerCount = followerCount,
|
||||
isAiChatAvailable = isAiChatAvailable,
|
||||
isDmAvailable = isDmAvailable,
|
||||
isFollow = isFollow,
|
||||
isNotify = isNotify
|
||||
)
|
||||
|
||||
private fun CreatorChannelLiveRecord.toDomain() = CreatorChannelLive(
|
||||
liveId = liveId,
|
||||
title = title,
|
||||
coverImageUrl = coverImagePath.toCdnUrl(),
|
||||
beginDateTime = beginDateTime,
|
||||
price = price,
|
||||
isAdult = isAdult
|
||||
)
|
||||
|
||||
private fun CreatorChannelAudioContentRecord.toDomain() = CreatorChannelAudioContent(
|
||||
audioContentId = audioContentId,
|
||||
title = title,
|
||||
duration = duration,
|
||||
imageUrl = imagePath.toCdnUrl(),
|
||||
price = price,
|
||||
isAdult = isAdult,
|
||||
isPointAvailable = isPointAvailable,
|
||||
isFirstContent = isFirstContent,
|
||||
publishedAt = publishedAt,
|
||||
seriesName = seriesName,
|
||||
isOriginalSeries = isOriginalSeries
|
||||
)
|
||||
|
||||
private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation(
|
||||
nickname = nickname,
|
||||
profileImageUrl = profileImagePath.toCdnUrl() ?: defaultProfileImageUrl(),
|
||||
can = can,
|
||||
message = message,
|
||||
createdAt = createdAt
|
||||
)
|
||||
|
||||
private fun CreatorChannelScheduleRecord.toDomain() = CreatorChannelSchedule(
|
||||
scheduledAt = scheduledAt,
|
||||
title = title,
|
||||
type = type,
|
||||
targetId = targetId,
|
||||
isAdult = isAdult
|
||||
)
|
||||
|
||||
private fun CreatorChannelSeriesRecord.toDomain() = CreatorChannelSeries(
|
||||
seriesId = seriesId,
|
||||
title = title,
|
||||
coverImageUrl = coverImagePath.toCdnUrl().orEmpty(),
|
||||
numberOfContent = numberOfContent,
|
||||
isNew = isNew,
|
||||
isOriginal = isOriginal
|
||||
)
|
||||
|
||||
private fun CreatorChannelCommunityPostRecord.toDomain() = CreatorChannelCommunityPost(
|
||||
postId = postId,
|
||||
creatorId = creatorId,
|
||||
creatorNickname = creatorNickname,
|
||||
creatorProfileUrl = creatorProfilePath.toCdnUrl() ?: defaultProfileImageUrl(),
|
||||
imageUrl = imagePath.toCdnUrl(),
|
||||
audioUrl = audioPath.toCdnUrl(),
|
||||
content = content,
|
||||
price = price,
|
||||
date = date,
|
||||
existOrdered = existOrdered,
|
||||
likeCount = likeCount,
|
||||
commentCount = commentCount
|
||||
)
|
||||
|
||||
private fun CreatorChannelFanTalkSummaryRecord.toDomain() = CreatorChannelFanTalkSummary(
|
||||
totalCount = totalCount,
|
||||
latestFanTalk = latestFanTalk?.toDomain()
|
||||
)
|
||||
|
||||
private fun CreatorChannelFanTalkRecord.toDomain() = CreatorChannelFanTalk(
|
||||
fanTalkId = fanTalkId,
|
||||
memberId = memberId,
|
||||
nickname = nickname,
|
||||
profileImageUrl = profileImagePath.toCdnUrl() ?: defaultProfileImageUrl(),
|
||||
content = content,
|
||||
languageCode = languageCode,
|
||||
createdAt = createdAt
|
||||
)
|
||||
|
||||
private fun CreatorChannelActivityRecord.toDomain() = CreatorChannelActivity(
|
||||
debutDate = debutDate,
|
||||
dDay = dDay,
|
||||
liveCount = liveCount,
|
||||
liveDurationHours = liveDurationHours,
|
||||
liveContributorCount = liveContributorCount,
|
||||
audioContentCount = audioContentCount,
|
||||
seriesCount = seriesCount
|
||||
)
|
||||
|
||||
private fun CreatorChannelSnsRecord.toDomain() = CreatorChannelSns(
|
||||
instagramUrl = instagramUrl,
|
||||
fancimmUrl = fancimmUrl,
|
||||
xUrl = xUrl,
|
||||
youtubeUrl = youtubeUrl,
|
||||
kakaoOpenChatUrl = kakaoOpenChatUrl
|
||||
)
|
||||
|
||||
private fun String?.toCdnUrl(): String? {
|
||||
if (isNullOrBlank()) return null
|
||||
if (startsWith("https://") || startsWith("http://")) return this
|
||||
return "$cloudFrontHost/$this"
|
||||
}
|
||||
|
||||
private fun defaultProfileImageUrl(): String = "$cloudFrontHost/profile/default-profile.png"
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.domain
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Component
|
||||
class CreatorChannelHomeQueryPolicy {
|
||||
fun limitSchedules(
|
||||
schedules: List<CreatorChannelSchedule>,
|
||||
|
||||
Reference in New Issue
Block a user