feat(home): 크리에이터 채널 홈 정책을 보강한다
This commit is contained in:
@@ -36,6 +36,7 @@ import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationEvent
|
||||
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType
|
||||
import kr.co.vividnext.sodalive.i18n.translation.ResourceTranslationJobScheduler
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
@@ -57,6 +58,8 @@ import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
||||
private const val MAX_PIN_CONTENT_COUNT = 9
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class AudioContentService(
|
||||
@@ -72,6 +75,7 @@ class AudioContentService(
|
||||
private val commentRepository: AudioContentCommentRepository,
|
||||
private val audioContentLikeRepository: AudioContentLikeRepository,
|
||||
private val pinContentRepository: PinContentRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
|
||||
private val resourceTranslationJobScheduler: ResourceTranslationJobScheduler,
|
||||
private val contentTranslationRepository: ContentTranslationRepository,
|
||||
@@ -795,7 +799,7 @@ class AudioContentService(
|
||||
|
||||
val pinContentListCount = pinContentRepository.getPinContentList(memberId = member.id!!, active = true).size
|
||||
val isAvailablePin = if (member.id!! == audioContent.member!!.id!!) {
|
||||
pinContentListCount < 3
|
||||
pinContentListCount < MAX_PIN_CONTENT_COUNT
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -1227,7 +1231,9 @@ class AudioContentService(
|
||||
|
||||
@Transactional
|
||||
fun pinToTheTop(contentId: Long, member: Member) {
|
||||
val audioContent = repository.findByIdAndCreatorId(contentId = contentId, creatorId = member.id!!)
|
||||
val creator = memberRepository.findByIdForUpdate(member.id!!)
|
||||
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
|
||||
val audioContent = repository.findByIdAndCreatorId(contentId = contentId, creatorId = creator.id!!)
|
||||
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
|
||||
|
||||
if (audioContent.releaseDate != null && audioContent.releaseDate!! >= LocalDateTime.now()) {
|
||||
@@ -1236,21 +1242,24 @@ class AudioContentService(
|
||||
|
||||
var pinContent = pinContentRepository.findByContentIdAndMemberId(
|
||||
contentId = contentId,
|
||||
memberId = member.id!!
|
||||
memberId = creator.id!!
|
||||
)
|
||||
val activePinContentList = pinContentRepository.getPinContentList(memberId = creator.id!!, active = true)
|
||||
|
||||
if (pinContent != null) {
|
||||
if (!pinContent.isActive && activePinContentList.size >= MAX_PIN_CONTENT_COUNT) {
|
||||
activePinContentList[0].isActive = false
|
||||
}
|
||||
pinContent.isActive = true
|
||||
} else {
|
||||
val pinContentList = pinContentRepository.getPinContentList(memberId = member.id!!)
|
||||
pinContent = if (pinContentList.size >= 3) {
|
||||
pinContentList[0]
|
||||
pinContent = if (activePinContentList.size >= MAX_PIN_CONTENT_COUNT) {
|
||||
activePinContentList[0]
|
||||
} else {
|
||||
PinContent()
|
||||
}
|
||||
|
||||
pinContent.isActive = true
|
||||
pinContent.member = member
|
||||
pinContent.member = creator
|
||||
pinContent.content = audioContent
|
||||
pinContentRepository.save(pinContent)
|
||||
}
|
||||
@@ -1258,9 +1267,11 @@ class AudioContentService(
|
||||
|
||||
@Transactional
|
||||
fun unpinAtTheTop(contentId: Long, member: Member) {
|
||||
val creator = memberRepository.findByIdForUpdate(member.id!!)
|
||||
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
|
||||
val pinContent = pinContentRepository.findByContentIdAndMemberId(
|
||||
contentId = contentId,
|
||||
memberId = member.id!!
|
||||
memberId = creator.id!!
|
||||
) ?: throw SodaException(messageKey = "content.error.invalid_content_retry")
|
||||
|
||||
pinContent.isActive = false
|
||||
|
||||
@@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.home.application.CreatorChann
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
@@ -15,7 +16,7 @@ class CreatorChannelHomeFacade(
|
||||
fun getHome(
|
||||
creatorId: Long,
|
||||
viewer: Member,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
): CreatorChannelHomeResponse {
|
||||
return CreatorChannelHomeResponse.from(
|
||||
creatorChannelHomeQueryService.getHome(
|
||||
|
||||
@@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.content.ContentType
|
||||
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||
import kr.co.vividnext.sodalive.content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.content.order.QOrder.order
|
||||
import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
|
||||
@@ -269,7 +270,7 @@ class DefaultCreatorChannelHomeQueryRepository(
|
||||
viewerId: Long?,
|
||||
limit: Int
|
||||
): List<CreatorChannelAudioContentRecord> {
|
||||
val rows = findAudioContentRows(creatorId, now, latestAudioContentId, canViewAdultContent, limit)
|
||||
val rows = findAudioContentRows(creatorId, now, latestAudioContentId, canViewAdultContent, limit, pinFirst = true)
|
||||
val contentIds = rows.map { itAudioId(it) }
|
||||
val firstContentId = firstAudioContentId(creatorId, now, canViewAdultContent)
|
||||
val seriesByContentId = audioSeriesByContentIds(contentIds)
|
||||
@@ -435,21 +436,33 @@ class DefaultCreatorChannelHomeQueryRepository(
|
||||
now: LocalDateTime,
|
||||
excludedContentId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int
|
||||
) = queryFactory
|
||||
.select(
|
||||
audioContent.id,
|
||||
audioContent.title,
|
||||
audioContent.duration,
|
||||
audioContent.coverImage,
|
||||
audioContent.price,
|
||||
audioContent.isAdult,
|
||||
audioContent.isPointAvailable,
|
||||
audioContent.releaseDate,
|
||||
audioContent.createdAt
|
||||
)
|
||||
.from(audioContent)
|
||||
.where(
|
||||
limit: Int,
|
||||
pinFirst: Boolean = false
|
||||
): List<com.querydsl.core.Tuple> {
|
||||
val query = queryFactory
|
||||
.select(
|
||||
audioContent.id,
|
||||
audioContent.title,
|
||||
audioContent.duration,
|
||||
audioContent.coverImage,
|
||||
audioContent.price,
|
||||
audioContent.isAdult,
|
||||
audioContent.isPointAvailable,
|
||||
audioContent.releaseDate,
|
||||
audioContent.createdAt
|
||||
)
|
||||
.from(audioContent)
|
||||
|
||||
if (pinFirst) {
|
||||
query.leftJoin(pinContent)
|
||||
.on(
|
||||
pinContent.member.id.eq(creatorId),
|
||||
pinContent.content.id.eq(audioContent.id),
|
||||
pinContent.isActive.isTrue
|
||||
)
|
||||
}
|
||||
|
||||
query.where(
|
||||
audioContent.member.id.eq(creatorId),
|
||||
audioContent.member.isActive.isTrue,
|
||||
audioContent.isActive.isTrue,
|
||||
@@ -459,9 +472,22 @@ class DefaultCreatorChannelHomeQueryRepository(
|
||||
excludedContentId?.let { audioContent.id.ne(it) },
|
||||
adultAudioCondition(canViewAdultContent)
|
||||
)
|
||||
.orderBy(audioContent.releaseDate.desc(), audioContent.id.desc())
|
||||
.limit(limit.toLong())
|
||||
.fetch()
|
||||
|
||||
if (pinFirst) {
|
||||
query.orderBy(
|
||||
pinContent.isActive.desc(),
|
||||
pinContent.updatedAt.desc(),
|
||||
audioContent.releaseDate.desc(),
|
||||
audioContent.id.desc()
|
||||
)
|
||||
} else {
|
||||
query.orderBy(audioContent.releaseDate.desc(), audioContent.id.desc())
|
||||
}
|
||||
|
||||
return query
|
||||
.limit(limit.toLong())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
private fun itAudioId(row: com.querydsl.core.Tuple): Long = row.get(audioContent.id)!!
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.home.application
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Gender
|
||||
@@ -36,6 +37,7 @@ import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
@@ -52,7 +54,7 @@ class CreatorChannelHomeQueryService(
|
||||
fun getHome(
|
||||
creatorId: Long,
|
||||
viewer: Member,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
): CreatorChannelHome {
|
||||
val viewerId = viewer.id!!
|
||||
val creator = queryPort.findCreator(creatorId, viewerId)
|
||||
@@ -68,8 +70,8 @@ class CreatorChannelHomeQueryService(
|
||||
validateCreatorRole(creator)
|
||||
|
||||
val preference = memberContentPreferenceService.getStoredPreference(viewer)
|
||||
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(viewer)
|
||||
val isViewerCreator = viewerId == creatorId
|
||||
val canViewAdultContent = preference.isAdult
|
||||
val isViewerCreator = viewer.role == MemberRole.CREATOR
|
||||
val effectiveViewerGender = viewer.effectiveGender()
|
||||
val latestAudioContent = queryPort
|
||||
.findLatestAudioContent(creatorId, now, canViewAdultContent, viewerId)
|
||||
@@ -187,7 +189,7 @@ class CreatorChannelHomeQueryService(
|
||||
)
|
||||
|
||||
private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation(
|
||||
nickname = nickname,
|
||||
nickname = nickname.removeDeletedNicknamePrefix(),
|
||||
profileImageUrl = profileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
can = can,
|
||||
message = message,
|
||||
|
||||
Reference in New Issue
Block a user