feat(creatorname): 레거시 조회와 검색에 언어별 이름을 적용한다
This commit is contained in:
@@ -342,6 +342,7 @@ class AdminLiveService(
|
|||||||
type = FcmEventType.CANCEL_LIVE,
|
type = FcmEventType.CANCEL_LIVE,
|
||||||
category = PushNotificationCategory.LIVE,
|
category = PushNotificationCategory.LIVE,
|
||||||
title = room.member!!.nickname,
|
title = room.member!!.nickname,
|
||||||
|
titleMemberId = room.member!!.id,
|
||||||
messageKey = "live.room.fcm.message.canceled",
|
messageKey = "live.room.fcm.message.canceled",
|
||||||
senderMemberId = room.member!!.id,
|
senderMemberId = room.member!!.id,
|
||||||
args = listOf(room.title),
|
args = listOf(room.title),
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.query.recommend.RecommendChannelQueryService
|
|||||||
import kr.co.vividnext.sodalive.rank.ContentRankingSortType
|
import kr.co.vividnext.sodalive.rank.ContentRankingSortType
|
||||||
import kr.co.vividnext.sodalive.rank.RankingRepository
|
import kr.co.vividnext.sodalive.rank.RankingRepository
|
||||||
import kr.co.vividnext.sodalive.rank.RankingService
|
import kr.co.vividnext.sodalive.rank.RankingService
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -49,6 +50,7 @@ class HomeService(
|
|||||||
private val explorerQueryRepository: ExplorerQueryRepository,
|
private val explorerQueryRepository: ExplorerQueryRepository,
|
||||||
|
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
@@ -87,7 +89,7 @@ class HomeService(
|
|||||||
timezone = timezone
|
timezone = timezone
|
||||||
)
|
)
|
||||||
|
|
||||||
val creatorRankingMembers = rankingRepository.getCreatorRankings(memberId = memberId)
|
val creatorRankingMembers = rankingRepository.getCreatorRankings(memberId = memberId, lang = langContext.lang)
|
||||||
val creatorRankingIds = creatorRankingMembers.mapNotNull { it.id }
|
val creatorRankingIds = creatorRankingMembers.mapNotNull { it.id }
|
||||||
val followedCreatorIds = if (memberId != null) {
|
val followedCreatorIds = if (memberId != null) {
|
||||||
explorerQueryRepository.getFollowedCreatorIds(creatorRankingIds, memberId)
|
explorerQueryRepository.getFollowedCreatorIds(creatorRankingIds, memberId)
|
||||||
@@ -99,7 +101,7 @@ class HomeService(
|
|||||||
val creatorId = creator.id!!
|
val creatorId = creator.id!!
|
||||||
val follow = memberId != null && followedCreatorIds.contains(creatorId)
|
val follow = memberId != null && followedCreatorIds.contains(creatorId)
|
||||||
|
|
||||||
creator.toExplorerSectionCreator(imageHost, follow)
|
creator.copy(follow = follow)
|
||||||
}
|
}
|
||||||
|
|
||||||
val latestContentThemeList = contentThemeService.getActiveThemeOfContent(
|
val latestContentThemeList = contentThemeService.getActiveThemeOfContent(
|
||||||
@@ -171,6 +173,10 @@ class HomeService(
|
|||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = resolvedContentType
|
contentType = resolvedContentType
|
||||||
)
|
)
|
||||||
|
val recommendedNames = localizedNameService.findCurrentNames(
|
||||||
|
recommendChannelList.map { it.channelId },
|
||||||
|
langContext.lang
|
||||||
|
)
|
||||||
|
|
||||||
val freeContentList = getRandomizedContentList(
|
val freeContentList = getRandomizedContentList(
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
@@ -212,7 +218,9 @@ class HomeService(
|
|||||||
dayOfWeekSeriesList = translatedDayOfWeekSeriesList,
|
dayOfWeekSeriesList = translatedDayOfWeekSeriesList,
|
||||||
popularCharacters = translatedPopularCharacters,
|
popularCharacters = translatedPopularCharacters,
|
||||||
contentRanking = contentRanking,
|
contentRanking = contentRanking,
|
||||||
recommendChannelList = recommendChannelList,
|
recommendChannelList = recommendChannelList.map {
|
||||||
|
it.copy(creatorNickname = recommendedNames[it.channelId] ?: it.creatorNickname)
|
||||||
|
},
|
||||||
freeContentList = freeContentList,
|
freeContentList = freeContentList,
|
||||||
pointAvailableContentList = pointAvailableContentList,
|
pointAvailableContentList = pointAvailableContentList,
|
||||||
recommendContentList = getRecommendContentListByPreference(
|
recommendContentList = getRecommendContentListByPreference(
|
||||||
|
|||||||
+10
-4
@@ -5,7 +5,9 @@ import kr.co.vividnext.sodalive.audition.AuditionApplicant
|
|||||||
import kr.co.vividnext.sodalive.audition.QAuditionApplicant.auditionApplicant
|
import kr.co.vividnext.sodalive.audition.QAuditionApplicant.auditionApplicant
|
||||||
import kr.co.vividnext.sodalive.audition.QAuditionRole.auditionRole
|
import kr.co.vividnext.sodalive.audition.QAuditionRole.auditionRole
|
||||||
import kr.co.vividnext.sodalive.audition.QAuditionVote.auditionVote
|
import kr.co.vividnext.sodalive.audition.QAuditionVote.auditionVote
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
@@ -18,7 +20,8 @@ interface AuditionApplicantQueryRepository {
|
|||||||
auditionRoleId: Long,
|
auditionRoleId: Long,
|
||||||
sortType: AuditionApplicantSortType,
|
sortType: AuditionApplicantSortType,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
lang: Lang = Lang.KO
|
||||||
): List<GetAuditionRoleApplicantItem>
|
): List<GetAuditionRoleApplicantItem>
|
||||||
|
|
||||||
fun findActiveApplicantByMemberIdAndRoleId(memberId: Long, roleId: Long): AuditionApplicant?
|
fun findActiveApplicantByMemberIdAndRoleId(memberId: Long, roleId: Long): AuditionApplicant?
|
||||||
@@ -64,8 +67,10 @@ class AuditionApplicantQueryRepositoryImpl(
|
|||||||
auditionRoleId: Long,
|
auditionRoleId: Long,
|
||||||
sortType: AuditionApplicantSortType,
|
sortType: AuditionApplicantSortType,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
lang: Lang
|
||||||
): List<GetAuditionRoleApplicantItem> {
|
): List<GetAuditionRoleApplicantItem> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "applicantMemberName")
|
||||||
val orderBy = if (sortType == AuditionApplicantSortType.LIKES) {
|
val orderBy = if (sortType == AuditionApplicantSortType.LIKES) {
|
||||||
auditionVote.id.count().desc()
|
auditionVote.id.count().desc()
|
||||||
} else {
|
} else {
|
||||||
@@ -77,7 +82,7 @@ class AuditionApplicantQueryRepositoryImpl(
|
|||||||
QGetAuditionRoleApplicantItem(
|
QGetAuditionRoleApplicantItem(
|
||||||
auditionApplicant.id,
|
auditionApplicant.id,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
auditionApplicant.voicePath.prepend("/").prepend(cloudFrontHost),
|
auditionApplicant.voicePath.prepend("/").prepend(cloudFrontHost),
|
||||||
auditionVote.id.count()
|
auditionVote.id.count()
|
||||||
@@ -85,6 +90,7 @@ class AuditionApplicantQueryRepositoryImpl(
|
|||||||
)
|
)
|
||||||
.from(auditionApplicant)
|
.from(auditionApplicant)
|
||||||
.innerJoin(auditionApplicant.member, member)
|
.innerJoin(auditionApplicant.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.innerJoin(auditionApplicant.role, auditionRole)
|
.innerJoin(auditionApplicant.role, auditionRole)
|
||||||
.leftJoin(auditionVote).on(auditionApplicant.id.eq(auditionVote.applicant.id))
|
.leftJoin(auditionVote).on(auditionApplicant.id.eq(auditionVote.applicant.id))
|
||||||
.where(
|
.where(
|
||||||
@@ -92,7 +98,7 @@ class AuditionApplicantQueryRepositoryImpl(
|
|||||||
auditionApplicant.isActive.isTrue,
|
auditionApplicant.isActive.isTrue,
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
)
|
)
|
||||||
.groupBy(auditionApplicant.id)
|
.groupBy(auditionApplicant.id, member.id, creatorName.name.displayName)
|
||||||
.orderBy(orderBy)
|
.orderBy(orderBy)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
|||||||
+4
-1
@@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.audition.AuditionApplicant
|
|||||||
import kr.co.vividnext.sodalive.audition.role.AuditionRoleRepository
|
import kr.co.vividnext.sodalive.audition.role.AuditionRoleRepository
|
||||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
@@ -21,6 +22,7 @@ class AuditionApplicantService(
|
|||||||
|
|
||||||
private val s3Uploader: S3Uploader,
|
private val s3Uploader: S3Uploader,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
|
private val langContext: LangContext,
|
||||||
|
|
||||||
@Value("\${cloud.aws.s3.bucket}")
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
private val bucket: String
|
private val bucket: String
|
||||||
@@ -36,7 +38,8 @@ class AuditionApplicantService(
|
|||||||
auditionRoleId = auditionRoleId,
|
auditionRoleId = auditionRoleId,
|
||||||
sortType = sortType,
|
sortType = sortType,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
return GetAuditionApplicantListResponse(
|
return GetAuditionApplicantListResponse(
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ import kr.co.vividnext.sodalive.chat.character.QChatCharacter.chatCharacter
|
|||||||
import kr.co.vividnext.sodalive.chat.character.image.QCharacterImage.characterImage
|
import kr.co.vividnext.sodalive.chat.character.image.QCharacterImage.characterImage
|
||||||
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.QMember
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
@@ -32,7 +34,7 @@ interface CanRepository : JpaRepository<Can, Long>, CanQueryRepository
|
|||||||
|
|
||||||
interface CanQueryRepository {
|
interface CanQueryRepository {
|
||||||
fun findAllByStatusAndCurrency(status: CanStatus, currency: String?): List<CanResponse>
|
fun findAllByStatusAndCurrency(status: CanStatus, currency: String?): List<CanResponse>
|
||||||
fun getCanUseStatus(member: Member, pageable: Pageable, container: String): List<UseCanQueryDto>
|
fun getCanUseStatus(member: Member, pageable: Pageable, container: String, lang: Lang = Lang.KO): List<UseCanQueryDto>
|
||||||
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
|
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
|
||||||
fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
|
fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
|
||||||
fun getCanUsedForLiveRoomNotRefund(memberId: Long, roomId: Long, canUsage: CanUsage = CanUsage.LIVE): UseCan?
|
fun getCanUsedForLiveRoomNotRefund(memberId: Long, roomId: Long, canUsage: CanUsage = CanUsage.LIVE): UseCan?
|
||||||
@@ -66,11 +68,15 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
|||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCanUseStatus(member: Member, pageable: Pageable, container: String): List<UseCanQueryDto> {
|
override fun getCanUseStatus(member: Member, pageable: Pageable, container: String, lang: Lang): List<UseCanQueryDto> {
|
||||||
val qRoomMember = QMember("roomMember")
|
val qRoomMember = QMember("roomMember")
|
||||||
val qAudioContentMember = QMember("audioContentMember")
|
val qAudioContentMember = QMember("audioContentMember")
|
||||||
val qCommunityPostMember = QMember("communityPostMember")
|
val qCommunityPostMember = QMember("communityPostMember")
|
||||||
val qRecipientMember = QMember("recipientMember")
|
val qRecipientMember = QMember("recipientMember")
|
||||||
|
val roomName = CreatorNameJoin(qRoomMember, lang, "receiptRoomCreatorName")
|
||||||
|
val audioName = CreatorNameJoin(qAudioContentMember, lang, "receiptAudioCreatorName")
|
||||||
|
val communityName = CreatorNameJoin(qCommunityPostMember, lang, "receiptCommunityCreatorName")
|
||||||
|
val recipientName = CreatorNameJoin(qRecipientMember, lang, "receiptRecipientCreatorName")
|
||||||
|
|
||||||
val gatewayCondition = when (container) {
|
val gatewayCondition = when (container) {
|
||||||
"aos" -> useCanCalculate.paymentGateway.`in`(
|
"aos" -> useCanCalculate.paymentGateway.`in`(
|
||||||
@@ -96,23 +102,26 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
|||||||
useCan.can,
|
useCan.can,
|
||||||
useCan.rewardCan,
|
useCan.rewardCan,
|
||||||
useCan.createdAt,
|
useCan.createdAt,
|
||||||
qRoomMember.nickname,
|
roomName.displayName,
|
||||||
liveRoom.title,
|
liveRoom.title,
|
||||||
qAudioContentMember.nickname,
|
audioName.displayName,
|
||||||
audioContent.title,
|
audioContent.title,
|
||||||
qCommunityPostMember.nickname,
|
communityName.displayName,
|
||||||
audition.title,
|
audition.title,
|
||||||
chatCharacter.name,
|
chatCharacter.name,
|
||||||
qRecipientMember.nickname
|
recipientName.displayName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(useCan)
|
.from(useCan)
|
||||||
.leftJoin(useCan.room, liveRoom)
|
.leftJoin(useCan.room, liveRoom)
|
||||||
.leftJoin(liveRoom.member, qRoomMember)
|
.leftJoin(liveRoom.member, qRoomMember)
|
||||||
|
.leftJoin(roomName.name).on(roomName.condition)
|
||||||
.leftJoin(useCan.audioContent, audioContent)
|
.leftJoin(useCan.audioContent, audioContent)
|
||||||
.leftJoin(audioContent.member, qAudioContentMember)
|
.leftJoin(audioContent.member, qAudioContentMember)
|
||||||
|
.leftJoin(audioName.name).on(audioName.condition)
|
||||||
.leftJoin(useCan.communityPost, creatorCommunity)
|
.leftJoin(useCan.communityPost, creatorCommunity)
|
||||||
.leftJoin(creatorCommunity.member, qCommunityPostMember)
|
.leftJoin(creatorCommunity.member, qCommunityPostMember)
|
||||||
|
.leftJoin(communityName.name).on(communityName.condition)
|
||||||
.leftJoin(useCan.auditionApplicant, auditionApplicant)
|
.leftJoin(useCan.auditionApplicant, auditionApplicant)
|
||||||
.leftJoin(auditionApplicant.role, auditionRole)
|
.leftJoin(auditionApplicant.role, auditionRole)
|
||||||
.leftJoin(auditionRole.audition, audition)
|
.leftJoin(auditionRole.audition, audition)
|
||||||
@@ -120,6 +129,7 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
|||||||
.leftJoin(characterImage.chatCharacter, chatCharacter)
|
.leftJoin(characterImage.chatCharacter, chatCharacter)
|
||||||
.innerJoin(useCan.useCanCalculates, useCanCalculate)
|
.innerJoin(useCan.useCanCalculates, useCanCalculate)
|
||||||
.leftJoin(qRecipientMember).on(useCanCalculate.recipientCreatorId.eq(qRecipientMember.id))
|
.leftJoin(qRecipientMember).on(useCanCalculate.recipientCreatorId.eq(qRecipientMember.id))
|
||||||
|
.leftJoin(recipientName.name).on(recipientName.condition)
|
||||||
.where(
|
.where(
|
||||||
useCan.member.id.eq(member.id)
|
useCan.member.id.eq(member.id)
|
||||||
.and(useCan.isRefund.isFalse)
|
.and(useCan.isRefund.isFalse)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.can
|
|||||||
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
import kr.co.vividnext.sodalive.common.CountryContext
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -14,7 +15,8 @@ import java.time.format.DateTimeFormatter
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
class CanService(
|
class CanService(
|
||||||
private val repository: CanRepository,
|
private val repository: CanRepository,
|
||||||
private val countryContext: CountryContext
|
private val countryContext: CountryContext,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
fun getCans(forcedCurrency: String? = null): List<CanResponse> {
|
fun getCans(forcedCurrency: String? = null): List<CanResponse> {
|
||||||
val currency = forcedCurrency ?: when (countryContext.countryCode) {
|
val currency = forcedCurrency ?: when (countryContext.countryCode) {
|
||||||
@@ -43,7 +45,7 @@ class CanService(
|
|||||||
ZoneId.of("UTC")
|
ZoneId.of("UTC")
|
||||||
}
|
}
|
||||||
|
|
||||||
return repository.getCanUseStatus(member, pageable, container)
|
return repository.getCanUseStatus(member, pageable, container, langContext.lang)
|
||||||
.map {
|
.map {
|
||||||
val title: String = when (it.canUsage) {
|
val title: String = when (it.canUsage) {
|
||||||
CanUsage.HEART, CanUsage.DONATION, CanUsage.SPIN_ROULETTE -> {
|
CanUsage.HEART, CanUsage.DONATION, CanUsage.SPIN_ROULETTE -> {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package kr.co.vividnext.sodalive.content
|
|||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.content.like.PutAudioContentLikeRequest
|
import kr.co.vividnext.sodalive.content.like.PutAudioContentLikeRequest
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
|
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.lang.Nullable
|
import org.springframework.lang.Nullable
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
@@ -29,7 +31,9 @@ import java.time.temporal.TemporalAdjusters
|
|||||||
@RequestMapping("/audio-content")
|
@RequestMapping("/audio-content")
|
||||||
class AudioContentController(
|
class AudioContentController(
|
||||||
private val service: AudioContentService,
|
private val service: AudioContentService,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("hasRole('CREATOR')")
|
@PreAuthorize("hasRole('CREATOR')")
|
||||||
@@ -214,7 +218,10 @@ class AudioContentController(
|
|||||||
offset = pageable.offset,
|
offset = pageable.offset,
|
||||||
limit = pageable.pageSize.toLong(),
|
limit = pageable.pageSize.toLong(),
|
||||||
sortType = sortType ?: "매출"
|
sortType = sortType ?: "매출"
|
||||||
)
|
).let { response ->
|
||||||
|
val names = localizedNameService.findCurrentNames(response.items.map { it.creatorId }, langContext.lang)
|
||||||
|
response.copy(items = response.items.map { it.copy(creatorNickname = names[it.creatorId] ?: it.creatorNickname) })
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ import kr.co.vividnext.sodalive.content.translation.QContentTranslation.contentT
|
|||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
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.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
import kr.co.vividnext.sodalive.event.QEvent.event
|
import kr.co.vividnext.sodalive.event.QEvent.event
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
@@ -85,7 +87,8 @@ interface AudioContentQueryRepository {
|
|||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 20,
|
limit: Long = 20,
|
||||||
isFree: Boolean = false
|
isFree: Boolean = false,
|
||||||
|
lang: Lang = Lang.KO
|
||||||
): List<GetAudioContentMainItem>
|
): List<GetAudioContentMainItem>
|
||||||
|
|
||||||
fun findAlarmContentByTheme(
|
fun findAlarmContentByTheme(
|
||||||
@@ -167,7 +170,7 @@ interface AudioContentQueryRepository {
|
|||||||
|
|
||||||
fun isContentCreator(contentId: Long, memberId: Long): Boolean
|
fun isContentCreator(contentId: Long, memberId: Long): Boolean
|
||||||
|
|
||||||
fun fetchContentForPlaylist(contentIdList: List<Long>): List<AudioContentPlaylistContent>
|
fun fetchContentForPlaylist(contentIdList: List<Long>, lang: Lang = Lang.KO): List<AudioContentPlaylistContent>
|
||||||
fun getCoverImageById(id: Long): String?
|
fun getCoverImageById(id: Long): String?
|
||||||
fun findPreviousContent(seriesId: Long, title: String, isAdult: Boolean): OtherContentResponse?
|
fun findPreviousContent(seriesId: Long, title: String, isAdult: Boolean): OtherContentResponse?
|
||||||
fun findNextContent(seriesId: Long, title: String, isAdult: Boolean): OtherContentResponse?
|
fun findNextContent(seriesId: Long, title: String, isAdult: Boolean): OtherContentResponse?
|
||||||
@@ -467,8 +470,10 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
isFree: Boolean
|
isFree: Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<GetAudioContentMainItem> {
|
): List<GetAudioContentMainItem> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "themeCreatorName")
|
||||||
val blockMemberCondition = blockMember.isActive.isTrue
|
val blockMemberCondition = blockMember.isActive.isTrue
|
||||||
.and(
|
.and(
|
||||||
blockMember.member.id.eq(member.id)
|
blockMember.member.id.eq(member.id)
|
||||||
@@ -535,7 +540,7 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
where = where.and(audioContent.price.loe(0))
|
where = where.and(audioContent.price.loe(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
val query = queryFactory
|
||||||
.select(
|
.select(
|
||||||
QGetAudioContentMainItem(
|
QGetAudioContentMainItem(
|
||||||
audioContent.id,
|
audioContent.id,
|
||||||
@@ -543,7 +548,7 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
audioContent.title,
|
audioContent.title,
|
||||||
member.id,
|
member.id,
|
||||||
member.profileImage.prepend("/").prepend(imageHost),
|
member.profileImage.prepend("/").prepend(imageHost),
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
audioContent.price,
|
audioContent.price,
|
||||||
audioContent.duration,
|
audioContent.duration,
|
||||||
audioContent.isPointAvailable
|
audioContent.isPointAvailable
|
||||||
@@ -557,7 +562,8 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.orderBy(*orderBy.toTypedArray())
|
.orderBy(*orderBy.toTypedArray())
|
||||||
.fetch()
|
query.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
return query.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findAlarmContentByTheme(
|
override fun findAlarmContentByTheme(
|
||||||
@@ -1210,7 +1216,8 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
return foundedContentId != null && foundedContentId == contentId
|
return foundedContentId != null && foundedContentId == contentId
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchContentForPlaylist(contentIdList: List<Long>): List<AudioContentPlaylistContent> {
|
override fun fetchContentForPlaylist(contentIdList: List<Long>, lang: Lang): List<AudioContentPlaylistContent> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "playlistCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QAudioContentPlaylistContent(
|
QAudioContentPlaylistContent(
|
||||||
@@ -1219,12 +1226,13 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
audioContentTheme.theme,
|
audioContentTheme.theme,
|
||||||
audioContent.coverImage.prepend("/").prepend(imageHost),
|
audioContent.coverImage.prepend("/").prepend(imageHost),
|
||||||
audioContent.duration,
|
audioContent.duration,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(imageHost)
|
member.profileImage.prepend("/").prepend(imageHost)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(audioContent)
|
.from(audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.innerJoin(audioContent.theme, audioContentTheme)
|
.innerJoin(audioContent.theme, audioContentTheme)
|
||||||
.where(audioContent.id.`in`(contentIdList))
|
.where(audioContent.id.`in`(contentIdList))
|
||||||
.fetch()
|
.fetch()
|
||||||
@@ -1466,6 +1474,7 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val coverImageUrl = audioContent.coverImage.prepend("/").prepend(imageHost)
|
val coverImageUrl = audioContent.coverImage.prepend("/").prepend(imageHost)
|
||||||
|
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "latestCreatorName")
|
||||||
|
|
||||||
var select = queryFactory
|
var select = queryFactory
|
||||||
.select(
|
.select(
|
||||||
@@ -1474,11 +1483,12 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
audioContent.title,
|
audioContent.title,
|
||||||
contentTranslation.renderedPayload,
|
contentTranslation.renderedPayload,
|
||||||
coverImageUrl,
|
coverImageUrl,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
audioContent.isPointAvailable
|
audioContent.isPointAvailable
|
||||||
)
|
)
|
||||||
.from(audioContent)
|
.from(audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.innerJoin(audioContent.theme, audioContentTheme)
|
.innerJoin(audioContent.theme, audioContentTheme)
|
||||||
.leftJoin(contentTranslation)
|
.leftJoin(contentTranslation)
|
||||||
.on(contentTranslation.contentId.eq(audioContent.id).and(contentTranslation.locale.eq(locale)))
|
.on(contentTranslation.contentId.eq(audioContent.id).and(contentTranslation.locale.eq(locale)))
|
||||||
@@ -1523,7 +1533,7 @@ class AudioContentQueryRepositoryImpl(
|
|||||||
val payload = row.get(contentTranslation.renderedPayload)
|
val payload = row.get(contentTranslation.renderedPayload)
|
||||||
val translatedTitle = payload?.title
|
val translatedTitle = payload?.title
|
||||||
val imageUrl = row.get(coverImageUrl)!!
|
val imageUrl = row.get(coverImageUrl)!!
|
||||||
val creatorNickname = row.get(member.nickname)!!
|
val creatorNickname = row.get(creatorName.displayName)!!
|
||||||
val isPointAvailableValue = row.get(audioContent.isPointAvailable) ?: false
|
val isPointAvailableValue = row.get(audioContent.isPointAvailable) ?: false
|
||||||
|
|
||||||
AudioContentMainItem(
|
AudioContentMainItem(
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import kr.co.vividnext.sodalive.member.MemberRepository
|
|||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import kr.co.vividnext.sodalive.v2.home.following.application.HomeFollowingNewsPublishService
|
import kr.co.vividnext.sodalive.v2.home.following.application.HomeFollowingNewsPublishService
|
||||||
import kr.co.vividnext.sodalive.v2.recommendation.application.CreatorContentViewHistoryService
|
import kr.co.vividnext.sodalive.v2.recommendation.application.CreatorContentViewHistoryService
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -87,6 +88,7 @@ class AudioContentService(
|
|||||||
|
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
|
|
||||||
private val contentThemeTranslationRepository: ContentThemeTranslationRepository,
|
private val contentThemeTranslationRepository: ContentThemeTranslationRepository,
|
||||||
private val homeFollowingNewsPublishService: HomeFollowingNewsPublishService,
|
private val homeFollowingNewsPublishService: HomeFollowingNewsPublishService,
|
||||||
@@ -512,6 +514,7 @@ class AudioContentService(
|
|||||||
type = FcmEventType.UPLOAD_CONTENT,
|
type = FcmEventType.UPLOAD_CONTENT,
|
||||||
category = PushNotificationCategory.CONTENT,
|
category = PushNotificationCategory.CONTENT,
|
||||||
title = audioContent.member!!.nickname,
|
title = audioContent.member!!.nickname,
|
||||||
|
titleMemberId = audioContent.member!!.id,
|
||||||
messageKey = "content.notification.uploaded_message",
|
messageKey = "content.notification.uploaded_message",
|
||||||
senderMemberId = audioContent.member!!.id,
|
senderMemberId = audioContent.member!!.id,
|
||||||
args = listOf(audioContent.title),
|
args = listOf(audioContent.title),
|
||||||
@@ -542,6 +545,7 @@ class AudioContentService(
|
|||||||
type = FcmEventType.UPLOAD_CONTENT,
|
type = FcmEventType.UPLOAD_CONTENT,
|
||||||
category = PushNotificationCategory.CONTENT,
|
category = PushNotificationCategory.CONTENT,
|
||||||
title = audioContent.member!!.nickname,
|
title = audioContent.member!!.nickname,
|
||||||
|
titleMemberId = audioContent.member!!.id,
|
||||||
messageKey = "content.notification.uploaded_message",
|
messageKey = "content.notification.uploaded_message",
|
||||||
senderMemberId = audioContent.member!!.id,
|
senderMemberId = audioContent.member!!.id,
|
||||||
args = listOf(audioContent.title),
|
args = listOf(audioContent.title),
|
||||||
@@ -702,7 +706,8 @@ class AudioContentService(
|
|||||||
isContentCreator = creatorId == member.id!!,
|
isContentCreator = creatorId == member.id!!,
|
||||||
timezone = timezone,
|
timezone = timezone,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 1
|
limit = 1,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
listOf()
|
listOf()
|
||||||
@@ -844,7 +849,7 @@ class AudioContentService(
|
|||||||
audioContent.limited != null &&
|
audioContent.limited != null &&
|
||||||
audioContent.remaining != null
|
audioContent.remaining != null
|
||||||
) {
|
) {
|
||||||
orderRepository.findBuyerListByContentId(id)
|
orderRepository.findBuyerListByContentId(id, langContext.lang)
|
||||||
} else {
|
} else {
|
||||||
listOf()
|
listOf()
|
||||||
}
|
}
|
||||||
@@ -920,6 +925,10 @@ class AudioContentService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val names = localizedNameService.findLocalizedNames(
|
||||||
|
listOf(creatorId),
|
||||||
|
langContext.lang
|
||||||
|
)
|
||||||
return GetAudioContentDetailResponse(
|
return GetAudioContentDetailResponse(
|
||||||
contentId = audioContent.id!!,
|
contentId = audioContent.id!!,
|
||||||
title = audioContent.title,
|
title = audioContent.title,
|
||||||
@@ -955,7 +964,7 @@ class AudioContentService(
|
|||||||
isAvailablePin = isAvailablePin,
|
isAvailablePin = isAvailablePin,
|
||||||
creator = AudioContentCreator(
|
creator = AudioContentCreator(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
nickname = creator.nickname,
|
nickname = names[creatorId] ?: creator.nickname,
|
||||||
profileImageUrl = if (creator.profileImage != null) {
|
profileImageUrl = if (creator.profileImage != null) {
|
||||||
"$coverImageHost/${creator.profileImage}"
|
"$coverImageHost/${creator.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
-3
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.content.comment
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.useraction.ActionType
|
import kr.co.vividnext.sodalive.useraction.ActionType
|
||||||
import kr.co.vividnext.sodalive.useraction.UserActionService
|
import kr.co.vividnext.sodalive.useraction.UserActionService
|
||||||
@@ -18,7 +19,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
class AudioContentCommentController(
|
class AudioContentCommentController(
|
||||||
private val service: AudioContentCommentService,
|
private val service: AudioContentCommentService,
|
||||||
private val userActionService: UserActionService
|
private val userActionService: UserActionService,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@PostMapping("/audio-content/comment")
|
@PostMapping("/audio-content/comment")
|
||||||
fun registerComment(
|
fun registerComment(
|
||||||
@@ -79,7 +81,8 @@ class AudioContentCommentController(
|
|||||||
audioContentId = audioContentId,
|
audioContentId = audioContentId,
|
||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
timezone = timezone,
|
timezone = timezone,
|
||||||
pageable = pageable
|
pageable = pageable,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -98,7 +101,8 @@ class AudioContentCommentController(
|
|||||||
commentId = commentId,
|
commentId = commentId,
|
||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
timezone = timezone,
|
timezone = timezone,
|
||||||
pageable = pageable
|
pageable = pageable,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-15
@@ -1,16 +1,18 @@
|
|||||||
package kr.co.vividnext.sodalive.content.comment
|
package kr.co.vividnext.sodalive.content.comment
|
||||||
|
|
||||||
|
import com.querydsl.core.types.dsl.CaseBuilder
|
||||||
import com.querydsl.core.types.dsl.Expressions
|
import com.querydsl.core.types.dsl.Expressions
|
||||||
import com.querydsl.jpa.JPAExpressions
|
import com.querydsl.jpa.JPAExpressions
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||||
import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
|
import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
|
||||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
|
||||||
import kr.co.vividnext.sodalive.fcm.PushTokenInfo
|
import kr.co.vividnext.sodalive.fcm.PushTokenInfo
|
||||||
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
|
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
|
||||||
import kr.co.vividnext.sodalive.fcm.QPushTokenInfo
|
import kr.co.vividnext.sodalive.fcm.QPushTokenInfo
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -26,7 +28,8 @@ interface AudioContentCommentQueryRepository {
|
|||||||
isContentCreator: Boolean,
|
isContentCreator: Boolean,
|
||||||
timezone: String,
|
timezone: String,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang? = null
|
||||||
): List<GetAudioContentCommentListItem>
|
): List<GetAudioContentCommentListItem>
|
||||||
|
|
||||||
fun totalCountCommentByContentId(contentId: Long, memberId: Long, isContentCreator: Boolean): Int
|
fun totalCountCommentByContentId(contentId: Long, memberId: Long, isContentCreator: Boolean): Int
|
||||||
@@ -37,7 +40,8 @@ interface AudioContentCommentQueryRepository {
|
|||||||
memberId: Long,
|
memberId: Long,
|
||||||
timezone: String,
|
timezone: String,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang? = null
|
||||||
): List<GetAudioContentCommentListItem>
|
): List<GetAudioContentCommentListItem>
|
||||||
|
|
||||||
fun findPushTokenByContentIdAndCommentParentIdMyMemberId(
|
fun findPushTokenByContentIdAndCommentParentIdMyMemberId(
|
||||||
@@ -58,8 +62,13 @@ class AudioContentCommentQueryRepositoryImpl(
|
|||||||
isContentCreator: Boolean,
|
isContentCreator: Boolean,
|
||||||
timezone: String,
|
timezone: String,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang?
|
||||||
): List<GetAudioContentCommentListItem> {
|
): List<GetAudioContentCommentListItem> {
|
||||||
|
val creatorName = lang?.let { CreatorNameJoin(member, it, "audioCommentWriterName") }
|
||||||
|
val originalName = CaseBuilder().`when`(member.nickname.startsWith("deleted_"))
|
||||||
|
.then(member.nickname.substring(8)).otherwise(member.nickname)
|
||||||
|
val displayName = creatorName?.name?.displayName?.coalesce(originalName) ?: originalName
|
||||||
var where = audioContentComment.audioContent.id.eq(contentId)
|
var where = audioContentComment.audioContent.id.eq(contentId)
|
||||||
.and(audioContentComment.isActive.isTrue)
|
.and(audioContentComment.isActive.isTrue)
|
||||||
.and(audioContentComment.parent.isNull)
|
.and(audioContentComment.parent.isNull)
|
||||||
@@ -85,12 +94,12 @@ class AudioContentCommentQueryRepositoryImpl(
|
|||||||
"%Y.%m.%d %W %h:%i %p"
|
"%Y.%m.%d %W %h:%i %p"
|
||||||
)
|
)
|
||||||
|
|
||||||
return queryFactory
|
val query = queryFactory
|
||||||
.select(
|
.select(
|
||||||
QGetAudioContentCommentListItem(
|
QGetAudioContentCommentListItem(
|
||||||
audioContentComment.id,
|
audioContentComment.id,
|
||||||
audioContentComment.member.id,
|
audioContentComment.member.id,
|
||||||
audioContentComment.member.nickname,
|
displayName,
|
||||||
audioContentComment.member.profileImage.prepend("/").prepend(cloudFrontHost),
|
audioContentComment.member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
audioContentComment.comment,
|
audioContentComment.comment,
|
||||||
audioContentComment.languageCode,
|
audioContentComment.languageCode,
|
||||||
@@ -107,10 +116,10 @@ class AudioContentCommentQueryRepositoryImpl(
|
|||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit.toLong())
|
.limit(limit.toLong())
|
||||||
.orderBy(audioContentComment.createdAt.desc())
|
.orderBy(audioContentComment.createdAt.desc())
|
||||||
.fetch()
|
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
return query.fetch()
|
||||||
.map {
|
.map {
|
||||||
it.copy(
|
it.copy(
|
||||||
nickname = it.nickname.removeDeletedNicknamePrefix(),
|
|
||||||
replyCount = commentReplyCountByAudioContentCommentId(it.id, memberId)
|
replyCount = commentReplyCountByAudioContentCommentId(it.id, memberId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -160,8 +169,13 @@ class AudioContentCommentQueryRepositoryImpl(
|
|||||||
memberId: Long,
|
memberId: Long,
|
||||||
timezone: String,
|
timezone: String,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang?
|
||||||
): List<GetAudioContentCommentListItem> {
|
): List<GetAudioContentCommentListItem> {
|
||||||
|
val creatorName = lang?.let { CreatorNameJoin(member, it, "audioReplyWriterName") }
|
||||||
|
val originalName = CaseBuilder().`when`(member.nickname.startsWith("deleted_"))
|
||||||
|
.then(member.nickname.substring(8)).otherwise(member.nickname)
|
||||||
|
val displayName = creatorName?.name?.displayName?.coalesce(originalName) ?: originalName
|
||||||
val formattedDate = Expressions.stringTemplate(
|
val formattedDate = Expressions.stringTemplate(
|
||||||
"DATE_FORMAT({0}, {1})",
|
"DATE_FORMAT({0}, {1})",
|
||||||
Expressions.dateTimeTemplate(
|
Expressions.dateTimeTemplate(
|
||||||
@@ -174,12 +188,12 @@ class AudioContentCommentQueryRepositoryImpl(
|
|||||||
"%Y.%m.%d %W %h:%i %p"
|
"%Y.%m.%d %W %h:%i %p"
|
||||||
)
|
)
|
||||||
|
|
||||||
return queryFactory
|
val query = queryFactory
|
||||||
.select(
|
.select(
|
||||||
QGetAudioContentCommentListItem(
|
QGetAudioContentCommentListItem(
|
||||||
audioContentComment.id,
|
audioContentComment.id,
|
||||||
audioContentComment.member.id,
|
audioContentComment.member.id,
|
||||||
audioContentComment.member.nickname,
|
displayName,
|
||||||
audioContentComment.member.profileImage.prepend("/").prepend(cloudFrontHost),
|
audioContentComment.member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
audioContentComment.comment,
|
audioContentComment.comment,
|
||||||
audioContentComment.languageCode,
|
audioContentComment.languageCode,
|
||||||
@@ -201,10 +215,8 @@ class AudioContentCommentQueryRepositoryImpl(
|
|||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit.toLong())
|
.limit(limit.toLong())
|
||||||
.orderBy(audioContentComment.createdAt.desc())
|
.orderBy(audioContentComment.createdAt.desc())
|
||||||
.fetch()
|
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.map {
|
return query.fetch()
|
||||||
it.copy(nickname = it.nickname.removeDeletedNicknamePrefix())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findPushTokenByContentIdAndCommentParentIdMyMemberId(
|
override fun findPushTokenByContentIdAndCommentParentIdMyMemberId(
|
||||||
|
|||||||
+15
-5
@@ -9,10 +9,12 @@ import kr.co.vividnext.sodalive.fcm.FcmDeepLinkValue
|
|||||||
import kr.co.vividnext.sodalive.fcm.FcmEvent
|
import kr.co.vividnext.sodalive.fcm.FcmEvent
|
||||||
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
||||||
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationCategory
|
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationCategory
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
@@ -30,6 +32,7 @@ class AudioContentCommentService(
|
|||||||
private val orderRepository: OrderRepository,
|
private val orderRepository: OrderRepository,
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
@@ -49,7 +52,9 @@ class AudioContentCommentService(
|
|||||||
val creator = audioContent.member!!
|
val creator = audioContent.member!!
|
||||||
val isBlocked = isBlockedBetweenMembers(memberId = member.id!!, creatorId = creator.id!!)
|
val isBlocked = isBlockedBetweenMembers(memberId = member.id!!, creatorId = creator.id!!)
|
||||||
if (isBlocked) {
|
if (isBlocked) {
|
||||||
throw SodaException(formatMessage("content.comment.error.blocked_by_creator", creator.nickname))
|
val name = localizedNameService.findLocalizedNames(listOf(creator.id!!), langContext.lang)[creator.id]
|
||||||
|
?: creator.nickname
|
||||||
|
throw SodaException(formatMessage("content.comment.error.blocked_by_creator", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
val (isExistsAudioContent, _) = orderRepository.isExistOrderedAndOrderType(
|
val (isExistsAudioContent, _) = orderRepository.isExistOrderedAndOrderType(
|
||||||
@@ -86,6 +91,7 @@ class AudioContentCommentService(
|
|||||||
} else {
|
} else {
|
||||||
member.nickname
|
member.nickname
|
||||||
},
|
},
|
||||||
|
titleMemberId = parent?.member?.id ?: member.id,
|
||||||
messageKey = if (parent != null) {
|
messageKey = if (parent != null) {
|
||||||
"content.comment.notification.reply"
|
"content.comment.notification.reply"
|
||||||
} else {
|
} else {
|
||||||
@@ -140,7 +146,8 @@ class AudioContentCommentService(
|
|||||||
audioContentId: Long,
|
audioContentId: Long,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
timezone: String,
|
timezone: String,
|
||||||
pageable: Pageable
|
pageable: Pageable,
|
||||||
|
lang: Lang? = null
|
||||||
): GetAudioContentCommentListResponse {
|
): GetAudioContentCommentListResponse {
|
||||||
val audioContent = audioContentRepository.findByIdOrNull(id = audioContentId)
|
val audioContent = audioContentRepository.findByIdOrNull(id = audioContentId)
|
||||||
?: return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
|
?: return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
|
||||||
@@ -157,7 +164,8 @@ class AudioContentCommentService(
|
|||||||
isContentCreator = isContentCreator,
|
isContentCreator = isContentCreator,
|
||||||
timezone = timezone,
|
timezone = timezone,
|
||||||
offset = pageable.offset,
|
offset = pageable.offset,
|
||||||
limit = pageable.pageSize
|
limit = pageable.pageSize,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
val totalCount = repository.totalCountCommentByContentId(
|
val totalCount = repository.totalCountCommentByContentId(
|
||||||
contentId = audioContentId,
|
contentId = audioContentId,
|
||||||
@@ -172,7 +180,8 @@ class AudioContentCommentService(
|
|||||||
commentId: Long,
|
commentId: Long,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
timezone: String,
|
timezone: String,
|
||||||
pageable: Pageable
|
pageable: Pageable,
|
||||||
|
lang: Lang? = null
|
||||||
): GetAudioContentCommentListResponse {
|
): GetAudioContentCommentListResponse {
|
||||||
val parentComment = repository.findByIdOrNull(id = commentId)
|
val parentComment = repository.findByIdOrNull(id = commentId)
|
||||||
?: return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
|
?: return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
|
||||||
@@ -187,7 +196,8 @@ class AudioContentCommentService(
|
|||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
timezone = timezone,
|
timezone = timezone,
|
||||||
offset = pageable.offset,
|
offset = pageable.offset,
|
||||||
limit = pageable.pageSize
|
limit = pageable.pageSize,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
val totalCount = repository.commentReplyCountByAudioContentCommentId(commentId, memberId)
|
val totalCount = repository.commentReplyCountByAudioContentCommentId(commentId, memberId)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem
|
|||||||
import kr.co.vividnext.sodalive.content.order.QOrder.order
|
import kr.co.vividnext.sodalive.content.order.QOrder.order
|
||||||
import kr.co.vividnext.sodalive.content.playlist.ContentIdAndEndDateData
|
import kr.co.vividnext.sodalive.content.playlist.ContentIdAndEndDateData
|
||||||
import kr.co.vividnext.sodalive.content.playlist.QContentIdAndEndDateData
|
import kr.co.vividnext.sodalive.content.playlist.QContentIdAndEndDateData
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
@@ -29,7 +31,8 @@ interface OrderQueryRepository {
|
|||||||
coverImageHost: String,
|
coverImageHost: String,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 10
|
limit: Long = 10,
|
||||||
|
lang: Lang = Lang.KO
|
||||||
): List<GetAudioContentOrderListItem>
|
): List<GetAudioContentOrderListItem>
|
||||||
|
|
||||||
fun totalAudioContentOrderListCount(memberId: Long, dateTime: LocalDateTime): Int
|
fun totalAudioContentOrderListCount(memberId: Long, dateTime: LocalDateTime): Int
|
||||||
@@ -38,12 +41,13 @@ interface OrderQueryRepository {
|
|||||||
coverImageHost: String,
|
coverImageHost: String,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 20
|
limit: Long = 20,
|
||||||
|
lang: Lang = Lang.KO
|
||||||
): List<GetAudioContentMainItem>
|
): List<GetAudioContentMainItem>
|
||||||
|
|
||||||
fun findOrderedContent(contentIdList: List<Long>, memberId: Long): List<Long>
|
fun findOrderedContent(contentIdList: List<Long>, memberId: Long): List<Long>
|
||||||
fun findEndDateByContentId(contentIdList: List<Long>, memberId: Long): List<ContentIdAndEndDateData>
|
fun findEndDateByContentId(contentIdList: List<Long>, memberId: Long): List<ContentIdAndEndDateData>
|
||||||
fun findBuyerListByContentId(contentId: Long): List<ContentBuyer>
|
fun findBuyerListByContentId(contentId: Long, lang: Lang = Lang.KO): List<ContentBuyer>
|
||||||
fun findByMemberIdAndContentId(memberId: Long, contentId: Long, createdAt: LocalDateTime): Order?
|
fun findByMemberIdAndContentId(memberId: Long, contentId: Long, createdAt: LocalDateTime): Order?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +138,10 @@ class OrderQueryRepositoryImpl(
|
|||||||
coverImageHost: String,
|
coverImageHost: String,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
lang: Lang
|
||||||
): List<GetAudioContentOrderListItem> {
|
): List<GetAudioContentOrderListItem> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "orderCreatorName")
|
||||||
val where = order.member.id.eq(memberId)
|
val where = order.member.id.eq(memberId)
|
||||||
.and(order.isActive.isTrue)
|
.and(order.isActive.isTrue)
|
||||||
.and(
|
.and(
|
||||||
@@ -153,7 +159,7 @@ class OrderQueryRepositoryImpl(
|
|||||||
audioContent.id,
|
audioContent.id,
|
||||||
audioContent.coverImage.prepend("/").prepend(coverImageHost),
|
audioContent.coverImage.prepend("/").prepend(coverImageHost),
|
||||||
audioContent.member.id,
|
audioContent.member.id,
|
||||||
audioContent.member.nickname,
|
creatorName.displayName,
|
||||||
audioContent.title,
|
audioContent.title,
|
||||||
audioContent.theme.theme,
|
audioContent.theme.theme,
|
||||||
audioContent.duration,
|
audioContent.duration,
|
||||||
@@ -165,6 +171,8 @@ class OrderQueryRepositoryImpl(
|
|||||||
)
|
)
|
||||||
.from(order)
|
.from(order)
|
||||||
.innerJoin(order.audioContent, audioContent)
|
.innerJoin(order.audioContent, audioContent)
|
||||||
|
.innerJoin(audioContent.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(where)
|
.where(where)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@@ -197,8 +205,10 @@ class OrderQueryRepositoryImpl(
|
|||||||
coverImageHost: String,
|
coverImageHost: String,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
lang: Lang
|
||||||
): List<GetAudioContentMainItem> {
|
): List<GetAudioContentMainItem> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "mainOrderCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QGetAudioContentMainItem(
|
QGetAudioContentMainItem(
|
||||||
@@ -209,7 +219,7 @@ class OrderQueryRepositoryImpl(
|
|||||||
member.profileImage
|
member.profileImage
|
||||||
.prepend("/")
|
.prepend("/")
|
||||||
.prepend(coverImageHost),
|
.prepend(coverImageHost),
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
audioContent.price,
|
audioContent.price,
|
||||||
audioContent.duration,
|
audioContent.duration,
|
||||||
audioContent.isPointAvailable
|
audioContent.isPointAvailable
|
||||||
@@ -218,6 +228,7 @@ class OrderQueryRepositoryImpl(
|
|||||||
.from(order)
|
.from(order)
|
||||||
.innerJoin(order.audioContent, audioContent)
|
.innerJoin(order.audioContent, audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
order.member.id.eq(memberId)
|
order.member.id.eq(memberId)
|
||||||
.and(order.isActive.isTrue)
|
.and(order.isActive.isTrue)
|
||||||
@@ -266,16 +277,18 @@ class OrderQueryRepositoryImpl(
|
|||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findBuyerListByContentId(contentId: Long): List<ContentBuyer> {
|
override fun findBuyerListByContentId(contentId: Long, lang: Lang): List<ContentBuyer> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "contentBuyerName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QContentBuyer(
|
QContentBuyer(
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(imageHost)
|
member.profileImage.prepend("/").prepend(imageHost)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(order)
|
.from(order)
|
||||||
.innerJoin(order.member, member)
|
.innerJoin(order.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
order.isActive.isTrue,
|
order.isActive.isTrue,
|
||||||
order.audioContent.id.eq(contentId),
|
order.audioContent.id.eq(contentId),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.content.PurchaseOption
|
|||||||
import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository
|
import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository
|
||||||
import kr.co.vividnext.sodalive.content.like.AudioContentLikeRepository
|
import kr.co.vividnext.sodalive.content.like.AudioContentLikeRepository
|
||||||
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
|
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.point.PointUsageService
|
import kr.co.vividnext.sodalive.point.PointUsageService
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
@@ -26,6 +27,7 @@ class OrderService(
|
|||||||
private val audioContentCommentQueryRepository: AudioContentCommentRepository,
|
private val audioContentCommentQueryRepository: AudioContentCommentRepository,
|
||||||
private val audioContentLikeQueryRepository: AudioContentLikeRepository,
|
private val audioContentLikeQueryRepository: AudioContentLikeRepository,
|
||||||
private val limitedEditionOrderRepository: LimitedEditionOrderRepository,
|
private val limitedEditionOrderRepository: LimitedEditionOrderRepository,
|
||||||
|
private val langContext: LangContext,
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val audioContentCoverImageHost: String
|
private val audioContentCoverImageHost: String
|
||||||
@@ -123,7 +125,8 @@ class OrderService(
|
|||||||
coverImageHost = audioContentCoverImageHost,
|
coverImageHost = audioContentCoverImageHost,
|
||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
val commentCount = audioContentCommentQueryRepository
|
val commentCount = audioContentCommentQueryRepository
|
||||||
@@ -154,7 +157,8 @@ class OrderService(
|
|||||||
coverImageHost = audioContentCoverImageHost,
|
coverImageHost = audioContentCoverImageHost,
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 20
|
limit = 20,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.content.playlist
|
|||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.content.AudioContentRepository
|
import kr.co.vividnext.sodalive.content.AudioContentRepository
|
||||||
import kr.co.vividnext.sodalive.content.order.OrderRepository
|
import kr.co.vividnext.sodalive.content.order.OrderRepository
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.live.roulette.RedisIdGenerator
|
import kr.co.vividnext.sodalive.live.roulette.RedisIdGenerator
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
@@ -16,7 +17,8 @@ class AudioContentPlaylistService(
|
|||||||
private val idGenerator: RedisIdGenerator,
|
private val idGenerator: RedisIdGenerator,
|
||||||
private val orderRepository: OrderRepository,
|
private val orderRepository: OrderRepository,
|
||||||
private val audioContentRepository: AudioContentRepository,
|
private val audioContentRepository: AudioContentRepository,
|
||||||
private val redisRepository: AudioContentPlaylistRedisRepository
|
private val redisRepository: AudioContentPlaylistRedisRepository,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
fun createPlaylist(request: CreatePlaylistRequest, member: Member) {
|
fun createPlaylist(request: CreatePlaylistRequest, member: Member) {
|
||||||
if (request.contentIdAndOrderList.size >= 30) {
|
if (request.contentIdAndOrderList.size >= 30) {
|
||||||
@@ -173,7 +175,8 @@ class AudioContentPlaylistService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val contentList = audioContentRepository.fetchContentForPlaylist(
|
val contentList = audioContentRepository.fetchContentForPlaylist(
|
||||||
contentIdList = contentIdAndOrderList.map { it.contentId }
|
contentIdList = contentIdAndOrderList.map { it.contentId },
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
val orderMap = contentIdAndOrderList.sortedBy { it.order }
|
val orderMap = contentIdAndOrderList.sortedBy { it.order }
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ class AudioContentThemeService(
|
|||||||
isAdult = memberContentPreferenceService.isAdultVisibleForQuery(member, isAdultContentVisible),
|
isAdult = memberContentPreferenceService.isAdultVisibleForQuery(member, isAdultContentVisible),
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
return GetContentByThemeResponse(
|
return GetContentByThemeResponse(
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import kr.co.vividnext.sodalive.live.room.LiveRoom
|
|||||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.live.room.cancel.QLiveRoomCancel.liveRoomCancel
|
import kr.co.vividnext.sodalive.live.room.cancel.QLiveRoomCancel.liveRoomCancel
|
||||||
|
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -40,6 +41,8 @@ import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
|||||||
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
||||||
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
||||||
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
|
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.creatorNameContains
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
@@ -122,13 +125,16 @@ class ExplorerQueryRepository(
|
|||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
withDonationCan: Boolean
|
withDonationCan: Boolean,
|
||||||
|
lang: Lang? = null
|
||||||
): List<MemberDonationRankingResponse> {
|
): List<MemberDonationRankingResponse> {
|
||||||
val member = QMember("user")
|
val member = QMember("user")
|
||||||
|
val creatorName = lang?.let { CreatorNameJoin(member, it, "legacyDonationRankName") }
|
||||||
|
val displayName = creatorName?.displayName ?: member.nickname
|
||||||
|
|
||||||
val donation = useCan.rewardCan.add(useCan.can).sum()
|
val donation = useCan.rewardCan.add(useCan.can).sum()
|
||||||
return queryFactory
|
val query = queryFactory
|
||||||
.select(member, donation)
|
.select(member, donation, displayName)
|
||||||
.from(useCanCalculate)
|
.from(useCanCalculate)
|
||||||
.innerJoin(useCanCalculate.useCan, useCan)
|
.innerJoin(useCanCalculate.useCan, useCan)
|
||||||
.innerJoin(useCan.member, member)
|
.innerJoin(useCan.member, member)
|
||||||
@@ -144,16 +150,17 @@ class ExplorerQueryRepository(
|
|||||||
.or(useCan.canUsage.eq(CanUsage.LIVE))
|
.or(useCan.canUsage.eq(CanUsage.LIVE))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.groupBy(useCan.member.id)
|
.groupBy(useCan.member.id, member.id, displayName)
|
||||||
.orderBy(donation.desc(), member.id.desc())
|
.orderBy(donation.desc(), member.id.desc())
|
||||||
.fetch()
|
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
return query.fetch()
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map {
|
.map {
|
||||||
val account = it.get(member)!!
|
val account = it.get(member)!!
|
||||||
val donationCan = it.get(donation)!!
|
val donationCan = it.get(donation)!!
|
||||||
MemberDonationRankingResponse(
|
MemberDonationRankingResponse(
|
||||||
account.id!!,
|
account.id!!,
|
||||||
account.nickname,
|
it.get(displayName)!!,
|
||||||
if (account.profileImage != null) {
|
if (account.profileImage != null) {
|
||||||
"$cloudFrontHost/${account.profileImage}"
|
"$cloudFrontHost/${account.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
@@ -165,9 +172,12 @@ class ExplorerQueryRepository(
|
|||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNewCreators(): List<Member> {
|
fun getNewCreators(lang: Lang = Lang.KO): List<GetExplorerSectionCreatorResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "newExplorerCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(member)
|
.select(member, creatorName.displayName)
|
||||||
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.role.eq(MemberRole.CREATOR)
|
member.role.eq(MemberRole.CREATOR)
|
||||||
.and(member.createdAt.goe(LocalDateTime.now().minusDays(30)))
|
.and(member.createdAt.goe(LocalDateTime.now().minusDays(30)))
|
||||||
@@ -176,9 +186,13 @@ class ExplorerQueryRepository(
|
|||||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||||
.limit(20)
|
.limit(20)
|
||||||
.fetch()
|
.fetch()
|
||||||
|
.map { row ->
|
||||||
|
row.get(member)!!.toExplorerSectionCreator(cloudFrontHost).copy(nickname = row.get(creatorName.displayName)!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findCreatorByGender(gender: Int): List<Member> {
|
fun findCreatorByGender(gender: Int, lang: Lang = Lang.KO): List<GetExplorerSectionCreatorResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "genderExplorerCreatorName")
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val offset = (today.dayOfMonth - 1) % 26
|
val offset = (today.dayOfMonth - 1) % 26
|
||||||
val characters = ('A'..'Z').map { it.toString() }.plus((0..9).map { it.toString() })
|
val characters = ('A'..'Z').map { it.toString() }.plus((0..9).map { it.toString() })
|
||||||
@@ -192,7 +206,9 @@ class ExplorerQueryRepository(
|
|||||||
val randomExpression = Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
|
val randomExpression = Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(member)
|
.select(member, creatorName.displayName)
|
||||||
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.innerJoin(member.auth, auth)
|
.innerJoin(member.auth, auth)
|
||||||
.where(
|
.where(
|
||||||
member.role.eq(MemberRole.CREATOR)
|
member.role.eq(MemberRole.CREATOR)
|
||||||
@@ -204,17 +220,30 @@ class ExplorerQueryRepository(
|
|||||||
.offset(0)
|
.offset(0)
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.fetch()
|
.fetch()
|
||||||
|
.map { row ->
|
||||||
|
row.get(member)!!.toExplorerSectionCreator(cloudFrontHost).copy(nickname = row.get(creatorName.displayName)!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSearchChannel(channel: String, memberId: Long): List<Member> {
|
fun getSearchChannel(channel: String, memberId: Long, lang: Lang? = null): List<GetRoomDetailUser> {
|
||||||
return queryFactory.selectFrom(member)
|
val creatorName = lang?.let { CreatorNameJoin(member, it, "channelSearchCreatorName") }
|
||||||
|
val displayName = creatorName?.displayName ?: member.nickname
|
||||||
|
val query = queryFactory.select(
|
||||||
|
Projections.constructor(
|
||||||
|
GetRoomDetailUser::class.java,
|
||||||
|
member.id,
|
||||||
|
displayName,
|
||||||
|
member.profileImage.coalesce("profile/default-profile.png").prepend("/").prepend(cloudFrontHost)
|
||||||
|
)
|
||||||
|
).from(member)
|
||||||
.where(
|
.where(
|
||||||
member.nickname.containsIgnoreCase(channel)
|
creatorNameContains(member, channel)
|
||||||
.and(member.isActive.isTrue)
|
.and(member.isActive.isTrue)
|
||||||
.and(member.id.ne(memberId))
|
.and(member.id.ne(memberId))
|
||||||
.and(member.role.eq(MemberRole.CREATOR))
|
.and(member.role.eq(MemberRole.CREATOR))
|
||||||
)
|
)
|
||||||
.fetch()
|
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
return query.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMember(memberId: Long): Member? {
|
fun getMember(memberId: Long): Member? {
|
||||||
@@ -224,7 +253,8 @@ class ExplorerQueryRepository(
|
|||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSimilarCreatorList(creatorId: Long): List<SimilarCreatorResponse> {
|
fun getSimilarCreatorList(creatorId: Long, lang: Lang = Lang.KO): List<SimilarCreatorResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "similarCreatorName")
|
||||||
val creator = queryFactory
|
val creator = queryFactory
|
||||||
.selectFrom(member)
|
.selectFrom(member)
|
||||||
.where(member.id.eq(creatorId))
|
.where(member.id.eq(creatorId))
|
||||||
@@ -242,14 +272,15 @@ class ExplorerQueryRepository(
|
|||||||
.and(member.tags.size().gt(0))
|
.and(member.tags.size().gt(0))
|
||||||
.and(memberCreatorTag.tag.isActive.isTrue)
|
.and(memberCreatorTag.tag.isActive.isTrue)
|
||||||
|
|
||||||
return getRandomCreatorList(where = where, limit = 3)
|
return getRandomCreatorList(where = where, limit = 3, lang = lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
val cnt = memberCreatorTag.member.count()
|
val cnt = memberCreatorTag.member.count()
|
||||||
val similarCreators = queryFactory
|
val similarCreators = queryFactory
|
||||||
.select(member, cnt)
|
.select(member, cnt, creatorName.displayName)
|
||||||
.from(memberCreatorTag)
|
.from(memberCreatorTag)
|
||||||
.innerJoin(memberCreatorTag.member, member)
|
.innerJoin(memberCreatorTag.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.innerJoin(memberCreatorTag.tag, creatorTag)
|
.innerJoin(memberCreatorTag.tag, creatorTag)
|
||||||
.leftJoin(member.auth, auth)
|
.leftJoin(member.auth, auth)
|
||||||
.where(
|
.where(
|
||||||
@@ -265,7 +296,7 @@ class ExplorerQueryRepository(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.groupBy(memberCreatorTag.member.id)
|
.groupBy(memberCreatorTag.member.id, member.id, creatorName.name.displayName)
|
||||||
.orderBy(cnt.desc())
|
.orderBy(cnt.desc())
|
||||||
.limit(3)
|
.limit(3)
|
||||||
.fetch()
|
.fetch()
|
||||||
@@ -273,7 +304,7 @@ class ExplorerQueryRepository(
|
|||||||
val account = it.get(member)!!
|
val account = it.get(member)!!
|
||||||
SimilarCreatorResponse(
|
SimilarCreatorResponse(
|
||||||
account.id!!,
|
account.id!!,
|
||||||
account.nickname,
|
it.get(creatorName.displayName)!!,
|
||||||
if (account.profileImage != null) {
|
if (account.profileImage != null) {
|
||||||
"$cloudFrontHost/${account.profileImage}"
|
"$cloudFrontHost/${account.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
@@ -298,29 +329,32 @@ class ExplorerQueryRepository(
|
|||||||
where = where.and(member.id.ne(userId))
|
where = where.and(member.id.ne(userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
val additionalCreator = getRandomCreatorList(where = where, limit = (3 - similarCreators.size).toLong())
|
val additionalCreator = getRandomCreatorList(where = where, limit = (3 - similarCreators.size).toLong(), lang = lang)
|
||||||
return similarCreators + additionalCreator
|
return similarCreators + additionalCreator
|
||||||
} else {
|
} else {
|
||||||
return similarCreators
|
return similarCreators
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRandomCreatorList(where: Predicate, limit: Long): List<SimilarCreatorResponse> {
|
private fun getRandomCreatorList(where: Predicate, limit: Long, lang: Lang): List<SimilarCreatorResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "randomCreatorName")
|
||||||
val result = queryFactory
|
val result = queryFactory
|
||||||
.select(member)
|
.select(member, creatorName.displayName)
|
||||||
.from(memberCreatorTag)
|
.from(memberCreatorTag)
|
||||||
.join(memberCreatorTag.member, member)
|
.join(memberCreatorTag.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.innerJoin(memberCreatorTag.tag, creatorTag)
|
.innerJoin(memberCreatorTag.tag, creatorTag)
|
||||||
.leftJoin(member.auth, auth)
|
.leftJoin(member.auth, auth)
|
||||||
.groupBy(memberCreatorTag.member.id)
|
.groupBy(memberCreatorTag.member.id, member.id, creatorName.name.displayName)
|
||||||
.where(where)
|
.where(where)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||||
.fetch()
|
.fetch()
|
||||||
.map {
|
.map { row ->
|
||||||
|
val it = row.get(member)!!
|
||||||
SimilarCreatorResponse(
|
SimilarCreatorResponse(
|
||||||
it.id!!,
|
it.id!!,
|
||||||
it.nickname,
|
row.get(creatorName.displayName)!!,
|
||||||
if (it.profileImage != null) {
|
if (it.profileImage != null) {
|
||||||
"$cloudFrontHost/${it.profileImage}"
|
"$cloudFrontHost/${it.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
@@ -474,7 +508,13 @@ class ExplorerQueryRepository(
|
|||||||
.fetchFirst() ?: ""
|
.fetchFirst() ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCheersList(creatorId: Long, memberId: Long, timezone: String, offset: Long, limit: Long): GetCheersResponse {
|
fun getCheersList(
|
||||||
|
creatorId: Long,
|
||||||
|
memberId: Long,
|
||||||
|
timezone: String,
|
||||||
|
offset: Long,
|
||||||
|
limit: Long
|
||||||
|
): GetCheersResponse {
|
||||||
val cheersDatePattern = messageSource
|
val cheersDatePattern = messageSource
|
||||||
.getMessage("explorer.date.cheers.format", langContext.lang)
|
.getMessage("explorer.date.cheers.format", langContext.lang)
|
||||||
?: messageSource.getMessage("explorer.date.cheers.format", Lang.KO).orEmpty()
|
?: messageSource.getMessage("explorer.date.cheers.format", Lang.KO).orEmpty()
|
||||||
@@ -671,8 +711,10 @@ class ExplorerQueryRepository(
|
|||||||
fun getFollowerList(
|
fun getFollowerList(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
lang: Lang = Lang.KO
|
||||||
): List<GetFollowerListDto> {
|
): List<GetFollowerListDto> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "followerMemberName")
|
||||||
val blockMemberCondition = blockMember.member.id.eq(creatorId)
|
val blockMemberCondition = blockMember.member.id.eq(creatorId)
|
||||||
.and(blockMember.isActive.isTrue)
|
.and(blockMember.isActive.isTrue)
|
||||||
.and(blockMember.blockedMember.id.eq(member.id))
|
.and(blockMember.blockedMember.id.eq(member.id))
|
||||||
@@ -682,12 +724,13 @@ class ExplorerQueryRepository(
|
|||||||
QGetFollowerListDto(
|
QGetFollowerListDto(
|
||||||
member.id,
|
member.id,
|
||||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.role
|
member.role
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(creatorFollowing)
|
.from(creatorFollowing)
|
||||||
.innerJoin(creatorFollowing.member, member)
|
.innerJoin(creatorFollowing.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(blockMember).on(blockMemberCondition)
|
.leftJoin(blockMember).on(blockMemberCondition)
|
||||||
.where(
|
.where(
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
@@ -805,12 +848,17 @@ class ExplorerQueryRepository(
|
|||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCreatorRankings(): List<Member> {
|
fun getCreatorRankings(lang: Lang = Lang.KO): List<GetExplorerSectionCreatorResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "explorerRankCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(member)
|
.select(member, creatorName.displayName)
|
||||||
.from(creatorRanking)
|
.from(creatorRanking)
|
||||||
.innerJoin(creatorRanking.member, member)
|
.innerJoin(creatorRanking.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.orderBy(creatorRanking.ranking.asc())
|
.orderBy(creatorRanking.ranking.asc())
|
||||||
.fetch()
|
.fetch()
|
||||||
|
.map { row ->
|
||||||
|
row.get(member)!!.toExplorerSectionCreator(cloudFrontHost).copy(nickname = row.get(creatorName.displayName)!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import kr.co.vividnext.sodalive.member.MemberRole
|
|||||||
import kr.co.vividnext.sodalive.member.MemberService
|
import kr.co.vividnext.sodalive.member.MemberService
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.dto.CreatorChannelFanTalkResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.dto.CreatorChannelFanTalkResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
@@ -65,15 +66,15 @@ class ExplorerService(
|
|||||||
|
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
fun getCreatorRank(memberId: Long): GetExplorerSectionResponse {
|
fun getCreatorRank(memberId: Long): GetExplorerSectionResponse {
|
||||||
val creatorRankings = queryRepository
|
val creatorRankings = queryRepository
|
||||||
.getCreatorRankings()
|
.getCreatorRankings(langContext.lang)
|
||||||
.filter { !isBlockedBetweenMembers(memberId = memberId, otherMemberId = it.id!!) }
|
.filter { !isBlockedBetweenMembers(memberId = memberId, otherMemberId = it.id!!) }
|
||||||
.map { it.toExplorerSectionCreator(cloudFrontHost) }
|
|
||||||
|
|
||||||
val currentDateTime = LocalDateTime.now()
|
val currentDateTime = LocalDateTime.now()
|
||||||
val lastMonday = currentDateTime
|
val lastMonday = currentDateTime
|
||||||
@@ -107,9 +108,8 @@ class ExplorerService(
|
|||||||
|
|
||||||
// 인기 크리에이터
|
// 인기 크리에이터
|
||||||
val creatorRankings = queryRepository
|
val creatorRankings = queryRepository
|
||||||
.getCreatorRankings()
|
.getCreatorRankings(langContext.lang)
|
||||||
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
||||||
.map { it.toExplorerSectionCreator(cloudFrontHost) }
|
|
||||||
|
|
||||||
val currentDateTime = LocalDateTime.now()
|
val currentDateTime = LocalDateTime.now()
|
||||||
val lastMonday = currentDateTime
|
val lastMonday = currentDateTime
|
||||||
@@ -140,9 +140,8 @@ class ExplorerService(
|
|||||||
|
|
||||||
// 새로 시작 (newCreators)
|
// 새로 시작 (newCreators)
|
||||||
val newCreators = queryRepository
|
val newCreators = queryRepository
|
||||||
.getNewCreators()
|
.getNewCreators(langContext.lang)
|
||||||
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
||||||
.map { it.toExplorerSectionCreator(cloudFrontHost) }
|
|
||||||
|
|
||||||
val newCreatorsSection = GetExplorerSectionResponse(
|
val newCreatorsSection = GetExplorerSectionResponse(
|
||||||
title = messageSource.getMessage("explorer.section.new_creators.title", langContext.lang).orEmpty(),
|
title = messageSource.getMessage("explorer.section.new_creators.title", langContext.lang).orEmpty(),
|
||||||
@@ -159,9 +158,8 @@ class ExplorerService(
|
|||||||
.orEmpty(),
|
.orEmpty(),
|
||||||
color = "39abde",
|
color = "39abde",
|
||||||
creators = queryRepository
|
creators = queryRepository
|
||||||
.findCreatorByGender(1)
|
.findCreatorByGender(1, langContext.lang)
|
||||||
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
||||||
.map { it.toExplorerSectionCreator(cloudFrontHost) }
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val femaleCreatorSection = GetExplorerSectionResponse(
|
val femaleCreatorSection = GetExplorerSectionResponse(
|
||||||
@@ -170,9 +168,8 @@ class ExplorerService(
|
|||||||
.orEmpty(),
|
.orEmpty(),
|
||||||
color = "ffa517",
|
color = "ffa517",
|
||||||
creators = queryRepository
|
creators = queryRepository
|
||||||
.findCreatorByGender(0)
|
.findCreatorByGender(0, langContext.lang)
|
||||||
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
||||||
.map { it.toExplorerSectionCreator(cloudFrontHost) }
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val randomInt = Random.nextInt(0, 2)
|
val randomInt = Random.nextInt(0, 2)
|
||||||
@@ -192,22 +189,23 @@ class ExplorerService(
|
|||||||
throw SodaException(messageKey = "explorer.search.channel.min_length")
|
throw SodaException(messageKey = "explorer.search.channel.min_length")
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryRepository.getSearchChannel(channel, member.id!!)
|
return queryRepository.getSearchChannel(channel, member.id!!, langContext.lang)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
|
||||||
.map { GetRoomDetailUser(it, cloudFrontHost) }
|
|
||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCreatorDetail(creatorId: Long, memberId: Long): GetCreatorDetailResponse {
|
fun getCreatorDetail(creatorId: Long, memberId: Long): GetCreatorDetailResponse {
|
||||||
val creatorAccount = queryRepository.getMember(creatorId)
|
val creatorAccount = queryRepository.getMember(creatorId)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
val creatorName = localizedNameService.findLocalizedNames(listOf(creatorId), langContext.lang)[creatorId]
|
||||||
|
?: creatorAccount.nickname
|
||||||
|
|
||||||
if (isBlockedBetweenMembers(memberId = memberId, otherMemberId = creatorId)) {
|
if (isBlockedBetweenMembers(memberId = memberId, otherMemberId = creatorId)) {
|
||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creatorAccount.nickname))
|
throw SodaException(message = String.format(messageTemplate, creatorName))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (creatorAccount.role != MemberRole.CREATOR) {
|
if (creatorAccount.role != MemberRole.CREATOR) {
|
||||||
@@ -239,7 +237,7 @@ class ExplorerService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return GetCreatorDetailResponse(
|
return GetCreatorDetailResponse(
|
||||||
nickname = creatorAccount.nickname,
|
nickname = creatorName,
|
||||||
profileImageUrl = if (creatorAccount.profileImage != null) {
|
profileImageUrl = if (creatorAccount.profileImage != null) {
|
||||||
"$cloudFrontHost/${creatorAccount.profileImage}"
|
"$cloudFrontHost/${creatorAccount.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
@@ -266,6 +264,8 @@ class ExplorerService(
|
|||||||
// 크리에이터(유저) 정보
|
// 크리에이터(유저) 정보
|
||||||
val creatorAccount = queryRepository.getMember(creatorId)
|
val creatorAccount = queryRepository.getMember(creatorId)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
val creatorName = localizedNameService.findLocalizedNames(listOf(creatorId), langContext.lang)[creatorId]
|
||||||
|
?: creatorAccount.nickname
|
||||||
|
|
||||||
// 차단된 사용자 체크
|
// 차단된 사용자 체크
|
||||||
val isBlockedByCreator = memberService.isBlocked(blockedMemberId = member.id!!, memberId = creatorId)
|
val isBlockedByCreator = memberService.isBlocked(blockedMemberId = member.id!!, memberId = creatorId)
|
||||||
@@ -274,7 +274,7 @@ class ExplorerService(
|
|||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creatorAccount.nickname))
|
throw SodaException(message = String.format(messageTemplate, creatorName))
|
||||||
}
|
}
|
||||||
|
|
||||||
val isCreator = creatorAccount.role == MemberRole.CREATOR
|
val isCreator = creatorAccount.role == MemberRole.CREATOR
|
||||||
@@ -300,7 +300,7 @@ class ExplorerService(
|
|||||||
|
|
||||||
// 추천 크리에이터
|
// 추천 크리에이터
|
||||||
val similarCreatorList = if (isCreator) {
|
val similarCreatorList = if (isCreator) {
|
||||||
queryRepository.getSimilarCreatorList(creatorId)
|
queryRepository.getSimilarCreatorList(creatorId, langContext.lang)
|
||||||
} else {
|
} else {
|
||||||
listOf()
|
listOf()
|
||||||
}
|
}
|
||||||
@@ -437,7 +437,7 @@ class ExplorerService(
|
|||||||
} else {
|
} else {
|
||||||
"$cloudFrontHost/profile/default-profile.png"
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
},
|
},
|
||||||
nickname = creatorAccount.nickname,
|
nickname = creatorName,
|
||||||
tags = creatorAccount.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(),
|
tags = creatorAccount.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(),
|
||||||
introduce = creatorAccount.introduce,
|
introduce = creatorAccount.introduce,
|
||||||
instagramUrl = creatorAccount.instagramUrl,
|
instagramUrl = creatorAccount.instagramUrl,
|
||||||
@@ -450,9 +450,9 @@ class ExplorerService(
|
|||||||
isNotification = creatorFollowing?.isFollow ?: false,
|
isNotification = creatorFollowing?.isFollow ?: false,
|
||||||
notificationRecipientCount = notificationRecipientCount
|
notificationRecipientCount = notificationRecipientCount
|
||||||
),
|
),
|
||||||
userDonationRanking = memberDonationRanking,
|
userDonationRanking = renderDonationRanking(memberDonationRanking),
|
||||||
similarCreatorList = similarCreatorList,
|
similarCreatorList = similarCreatorList,
|
||||||
liveRoomList = liveRoomList,
|
liveRoomList = liveRoomList.map { it.copy(managerNickname = creatorName) },
|
||||||
contentList = translatedContentList,
|
contentList = translatedContentList,
|
||||||
latestContent = latestContent,
|
latestContent = latestContent,
|
||||||
totalContentCount = totalContentCount,
|
totalContentCount = totalContentCount,
|
||||||
@@ -537,7 +537,7 @@ class ExplorerService(
|
|||||||
null
|
null
|
||||||
},
|
},
|
||||||
totalCount = donationMemberTotal,
|
totalCount = donationMemberTotal,
|
||||||
userDonationRanking = donationRanking
|
userDonationRanking = renderDonationRanking(donationRanking)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,7 +548,12 @@ class ExplorerService(
|
|||||||
): GetFollowerListResponse {
|
): GetFollowerListResponse {
|
||||||
val totalCount = queryRepository.getFollowerListTotalCount(creatorId)
|
val totalCount = queryRepository.getFollowerListTotalCount(creatorId)
|
||||||
|
|
||||||
val followerList = queryRepository.getFollowerList(creatorId, pageable.offset, pageable.pageSize.toLong())
|
val followerList = queryRepository.getFollowerList(
|
||||||
|
creatorId,
|
||||||
|
pageable.offset,
|
||||||
|
pageable.pageSize.toLong(),
|
||||||
|
langContext.lang
|
||||||
|
)
|
||||||
.map {
|
.map {
|
||||||
val creatorFollowing = if (it.role == MemberRole.CREATOR) {
|
val creatorFollowing = if (it.role == MemberRole.CREATOR) {
|
||||||
queryRepository.getCreatorFollowing(
|
queryRepository.getCreatorFollowing(
|
||||||
@@ -684,6 +689,7 @@ class ExplorerService(
|
|||||||
type = FcmEventType.CHANGE_NOTICE,
|
type = FcmEventType.CHANGE_NOTICE,
|
||||||
category = PushNotificationCategory.COMMUNITY,
|
category = PushNotificationCategory.COMMUNITY,
|
||||||
title = member.nickname,
|
title = member.nickname,
|
||||||
|
titleMemberId = member.id,
|
||||||
messageKey = "explorer.notice.fcm.message",
|
messageKey = "explorer.notice.fcm.message",
|
||||||
senderMemberId = member.id,
|
senderMemberId = member.id,
|
||||||
creatorId = member.id!!,
|
creatorId = member.id!!,
|
||||||
@@ -693,6 +699,11 @@ class ExplorerService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderDonationRanking(items: List<MemberDonationRankingResponse>): List<MemberDonationRankingResponse> {
|
||||||
|
val names = localizedNameService.findCurrentNames(items.map { it.userId }, langContext.lang)
|
||||||
|
return items.map { it.copy(nickname = names[it.userId] ?: it.nickname) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun isBlockedBetweenMembers(memberId: Long, otherMemberId: Long): Boolean {
|
private fun isBlockedBetweenMembers(memberId: Long, otherMemberId: Long): Boolean {
|
||||||
return memberService.isBlocked(blockedMemberId = memberId, memberId = otherMemberId) ||
|
return memberService.isBlocked(blockedMemberId = memberId, memberId = otherMemberId) ||
|
||||||
memberService.isBlocked(blockedMemberId = otherMemberId, memberId = memberId)
|
memberService.isBlocked(blockedMemberId = otherMemberId, memberId = memberId)
|
||||||
|
|||||||
+19
-6
@@ -1,7 +1,11 @@
|
|||||||
package kr.co.vividnext.sodalive.explorer.profile.channelDonation
|
package kr.co.vividnext.sodalive.explorer.profile.channelDonation
|
||||||
|
|
||||||
|
import com.querydsl.core.types.dsl.CaseBuilder
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage
|
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -18,8 +22,9 @@ interface ChannelDonationMessageQueryRepository {
|
|||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
startDateTime: LocalDateTime,
|
startDateTime: LocalDateTime,
|
||||||
endDateTime: LocalDateTime
|
endDateTime: LocalDateTime,
|
||||||
): List<ChannelDonationMessage>
|
lang: Lang = Lang.KO
|
||||||
|
): List<Pair<ChannelDonationMessage, String>>
|
||||||
|
|
||||||
fun getChannelDonationMessageTotalCount(
|
fun getChannelDonationMessageTotalCount(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
@@ -40,8 +45,13 @@ class ChannelDonationMessageQueryRepositoryImpl(
|
|||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
startDateTime: LocalDateTime,
|
startDateTime: LocalDateTime,
|
||||||
endDateTime: LocalDateTime
|
endDateTime: LocalDateTime,
|
||||||
): List<ChannelDonationMessage> {
|
lang: Lang
|
||||||
|
): List<Pair<ChannelDonationMessage, String>> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelDonorName")
|
||||||
|
val originalName = CaseBuilder().`when`(member.nickname.startsWith("deleted_"))
|
||||||
|
.then(member.nickname.substring(8)).otherwise(member.nickname)
|
||||||
|
val displayName = creatorName.name.displayName.coalesce(originalName)
|
||||||
val where = whereCondition(
|
val where = whereCondition(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
@@ -51,8 +61,10 @@ class ChannelDonationMessageQueryRepositoryImpl(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(channelDonationMessage)
|
.select(channelDonationMessage, displayName)
|
||||||
.leftJoin(channelDonationMessage.member).fetchJoin()
|
.from(channelDonationMessage)
|
||||||
|
.leftJoin(channelDonationMessage.member, member).fetchJoin()
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(where)
|
.where(where)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@@ -61,6 +73,7 @@ class ChannelDonationMessageQueryRepositoryImpl(
|
|||||||
channelDonationMessage.id.desc()
|
channelDonationMessage.id.desc()
|
||||||
)
|
)
|
||||||
.fetch()
|
.fetch()
|
||||||
|
.map { row -> row.get(channelDonationMessage)!! to row.get(displayName)!! }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getChannelDonationMessageTotalCount(
|
override fun getChannelDonationMessageTotalCount(
|
||||||
|
|||||||
+4
-4
@@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.explorer.profile.channelDonation
|
|||||||
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
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.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -99,12 +98,13 @@ class ChannelDonationService(
|
|||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit,
|
limit = limit,
|
||||||
startDateTime = startDateTime,
|
startDateTime = startDateTime,
|
||||||
endDateTime = endDateTime
|
endDateTime = endDateTime,
|
||||||
).map {
|
lang = langContext.lang
|
||||||
|
).map { (it, nickname) ->
|
||||||
GetChannelDonationListItem(
|
GetChannelDonationListItem(
|
||||||
id = it.id!!,
|
id = it.id!!,
|
||||||
memberId = it.member!!.id!!,
|
memberId = it.member!!.id!!,
|
||||||
nickname = it.member!!.nickname.removeDeletedNicknamePrefix(),
|
nickname = nickname,
|
||||||
profileUrl = if (it.member!!.profileImage != null) {
|
profileUrl = if (it.member!!.profileImage != null) {
|
||||||
"$cloudFrontHost/${it.member!!.profileImage}"
|
"$cloudFrontHost/${it.member!!.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+2
@@ -140,6 +140,7 @@ class CreatorCommunityService(
|
|||||||
type = FcmEventType.CHANGE_NOTICE,
|
type = FcmEventType.CHANGE_NOTICE,
|
||||||
category = PushNotificationCategory.COMMUNITY,
|
category = PushNotificationCategory.COMMUNITY,
|
||||||
title = member.nickname,
|
title = member.nickname,
|
||||||
|
titleMemberId = member.id,
|
||||||
messageKey = "creator.community.fcm.new_post",
|
messageKey = "creator.community.fcm.new_post",
|
||||||
senderMemberId = member.id,
|
senderMemberId = member.id,
|
||||||
creatorId = member.id!!,
|
creatorId = member.id!!,
|
||||||
@@ -533,6 +534,7 @@ class CreatorCommunityService(
|
|||||||
type = FcmEventType.INDIVIDUAL,
|
type = FcmEventType.INDIVIDUAL,
|
||||||
category = PushNotificationCategory.COMMUNITY,
|
category = PushNotificationCategory.COMMUNITY,
|
||||||
title = member.nickname,
|
title = member.nickname,
|
||||||
|
titleMemberId = member.id,
|
||||||
messageKey = "creator.community.fcm.new_comment",
|
messageKey = "creator.community.fcm.new_comment",
|
||||||
senderMemberId = member.id,
|
senderMemberId = member.id,
|
||||||
recipients = listOf(creatorId),
|
recipients = listOf(creatorId),
|
||||||
|
|||||||
+29
-12
@@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.member.MemberRole
|
|||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||||
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -70,8 +71,10 @@ class LiveRecommendRepository(
|
|||||||
fun getOnAirRecommendChannelList(
|
fun getOnAirRecommendChannelList(
|
||||||
isBlocked: (Long) -> Boolean,
|
isBlocked: (Long) -> Boolean,
|
||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean
|
isAdult: Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<GetRecommendChannelResponse> {
|
): List<GetRecommendChannelResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "onAirRecommendedCreatorName")
|
||||||
var where = member.role.eq(MemberRole.CREATOR)
|
var where = member.role.eq(MemberRole.CREATOR)
|
||||||
.and(member.isActive.isTrue)
|
.and(member.isActive.isTrue)
|
||||||
|
|
||||||
@@ -88,20 +91,21 @@ class LiveRecommendRepository(
|
|||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
GetRecommendChannelResponse::class.java,
|
GetRecommendChannelResponse::class.java,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
Expressions.asBoolean(true)
|
Expressions.asBoolean(true)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(liveRoom)
|
.from(liveRoom)
|
||||||
.rightJoin(liveRoom.member, member)
|
.rightJoin(liveRoom.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
where
|
where
|
||||||
.and(liveRoom.isActive.isTrue)
|
.and(liveRoom.isActive.isTrue)
|
||||||
.and(liveRoom.channelName.isNotNull)
|
.and(liveRoom.channelName.isNotNull)
|
||||||
.and(liveRoom.channelName.isNotEmpty)
|
.and(liveRoom.channelName.isNotEmpty)
|
||||||
)
|
)
|
||||||
.groupBy(member.id)
|
.groupBy(member.id, member.nickname, member.profileImage, creatorName.name.displayName)
|
||||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||||
.limit(20)
|
.limit(20)
|
||||||
.fetch()
|
.fetch()
|
||||||
@@ -113,8 +117,10 @@ class LiveRecommendRepository(
|
|||||||
fun getRecommendChannelList(
|
fun getRecommendChannelList(
|
||||||
withOutCreatorList: List<Long>,
|
withOutCreatorList: List<Long>,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
isBlocked: (Long) -> Boolean
|
isBlocked: (Long) -> Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<GetRecommendChannelResponse> {
|
): List<GetRecommendChannelResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "recommendedCreatorName")
|
||||||
val where = member.role.eq(MemberRole.CREATOR)
|
val where = member.role.eq(MemberRole.CREATOR)
|
||||||
.and(member.isActive.isTrue)
|
.and(member.isActive.isTrue)
|
||||||
|
|
||||||
@@ -123,12 +129,13 @@ class LiveRecommendRepository(
|
|||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
GetRecommendChannelResponse::class.java,
|
GetRecommendChannelResponse::class.java,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
Expressions.asBoolean(false)
|
Expressions.asBoolean(false)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(where.and(member.id.notIn(withOutCreatorList)))
|
.where(where.and(member.id.notIn(withOutCreatorList)))
|
||||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@@ -140,8 +147,10 @@ class LiveRecommendRepository(
|
|||||||
memberId: Long,
|
memberId: Long,
|
||||||
isBlocked: (Long) -> Boolean,
|
isBlocked: (Long) -> Boolean,
|
||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean
|
isAdult: Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<GetRecommendChannelResponse> {
|
): List<GetRecommendChannelResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "onAirFollowingCreatorName")
|
||||||
var where = member.role.eq(MemberRole.CREATOR)
|
var where = member.role.eq(MemberRole.CREATOR)
|
||||||
.and(member.isActive.isTrue)
|
.and(member.isActive.isTrue)
|
||||||
|
|
||||||
@@ -158,7 +167,7 @@ class LiveRecommendRepository(
|
|||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
GetRecommendChannelResponse::class.java,
|
GetRecommendChannelResponse::class.java,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
Expressions.asBoolean(true)
|
Expressions.asBoolean(true)
|
||||||
)
|
)
|
||||||
@@ -166,6 +175,7 @@ class LiveRecommendRepository(
|
|||||||
.from(liveRoom, creatorFollowing)
|
.from(liveRoom, creatorFollowing)
|
||||||
.rightJoin(liveRoom.member, member)
|
.rightJoin(liveRoom.member, member)
|
||||||
.innerJoin(creatorFollowing.creator, member)
|
.innerJoin(creatorFollowing.creator, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
where
|
where
|
||||||
.and(liveRoom.member.id.eq(creatorFollowing.creator.id))
|
.and(liveRoom.member.id.eq(creatorFollowing.creator.id))
|
||||||
@@ -175,7 +185,7 @@ class LiveRecommendRepository(
|
|||||||
.and(liveRoom.channelName.isNotNull)
|
.and(liveRoom.channelName.isNotNull)
|
||||||
.and(liveRoom.channelName.isNotEmpty)
|
.and(liveRoom.channelName.isNotEmpty)
|
||||||
)
|
)
|
||||||
.groupBy(member.id)
|
.groupBy(member.id, member.nickname, member.profileImage, creatorName.name.displayName)
|
||||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||||
.limit(20)
|
.limit(20)
|
||||||
.fetch()
|
.fetch()
|
||||||
@@ -188,8 +198,10 @@ class LiveRecommendRepository(
|
|||||||
memberId: Long,
|
memberId: Long,
|
||||||
withOutCreatorList: List<Long>,
|
withOutCreatorList: List<Long>,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
isBlocked: (Long) -> Boolean
|
isBlocked: (Long) -> Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<GetRecommendChannelResponse> {
|
): List<GetRecommendChannelResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "followingCreatorName")
|
||||||
val where = member.role.eq(MemberRole.CREATOR)
|
val where = member.role.eq(MemberRole.CREATOR)
|
||||||
.and(member.isActive.isTrue)
|
.and(member.isActive.isTrue)
|
||||||
|
|
||||||
@@ -198,13 +210,14 @@ class LiveRecommendRepository(
|
|||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
GetRecommendChannelResponse::class.java,
|
GetRecommendChannelResponse::class.java,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
Expressions.asBoolean(false)
|
Expressions.asBoolean(false)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(creatorFollowing)
|
.from(creatorFollowing)
|
||||||
.innerJoin(creatorFollowing.creator, member)
|
.innerJoin(creatorFollowing.creator, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
where
|
where
|
||||||
.and(member.id.notIn(withOutCreatorList))
|
.and(member.id.notIn(withOutCreatorList))
|
||||||
@@ -244,8 +257,10 @@ class LiveRecommendRepository(
|
|||||||
memberId: Long,
|
memberId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
isBlocked: (Long) -> Boolean
|
isBlocked: (Long) -> Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<GetCreatorFollowingAllListItem> {
|
): List<GetCreatorFollowingAllListItem> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "allFollowingCreatorName")
|
||||||
val where = member.role.eq(MemberRole.CREATOR)
|
val where = member.role.eq(MemberRole.CREATOR)
|
||||||
.and(member.isActive.isTrue)
|
.and(member.isActive.isTrue)
|
||||||
.and(creatorFollowing.isActive.isTrue)
|
.and(creatorFollowing.isActive.isTrue)
|
||||||
@@ -256,7 +271,7 @@ class LiveRecommendRepository(
|
|||||||
.select(
|
.select(
|
||||||
QGetCreatorFollowingAllListItem(
|
QGetCreatorFollowingAllListItem(
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
Expressions.asBoolean(true),
|
Expressions.asBoolean(true),
|
||||||
creatorFollowing.isNotify
|
creatorFollowing.isNotify
|
||||||
@@ -264,10 +279,12 @@ class LiveRecommendRepository(
|
|||||||
)
|
)
|
||||||
.from(creatorFollowing)
|
.from(creatorFollowing)
|
||||||
.innerJoin(creatorFollowing.creator, member)
|
.innerJoin(creatorFollowing.creator, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(where)
|
.where(where)
|
||||||
.groupBy(
|
.groupBy(
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
member.nickname,
|
||||||
|
creatorName.name.displayName,
|
||||||
member.profileImage,
|
member.profileImage,
|
||||||
creatorFollowing.isNotify
|
creatorFollowing.isNotify
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.live.recommend
|
package kr.co.vividnext.sodalive.live.recommend
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.i18n.Lang
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
@@ -13,7 +14,8 @@ class LiveRecommendService(
|
|||||||
private val repository: LiveRecommendRepository,
|
private val repository: LiveRecommendRepository,
|
||||||
private val blockMemberRepository: BlockMemberRepository,
|
private val blockMemberRepository: BlockMemberRepository,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
private val liveRecommendCacheService: LiveRecommendCacheService
|
private val liveRecommendCacheService: LiveRecommendCacheService,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
fun getRecommendLive(member: Member?, lang: Lang): List<GetRecommendLiveResponse> {
|
fun getRecommendLive(member: Member?, lang: Lang): List<GetRecommendLiveResponse> {
|
||||||
val isAdult = if (member != null) {
|
val isAdult = if (member != null) {
|
||||||
@@ -45,7 +47,8 @@ class LiveRecommendService(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isCreator = member?.role == MemberRole.CREATOR,
|
isCreator = member?.role == MemberRole.CREATOR,
|
||||||
isAdult = isAdult
|
isAdult = isAdult,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
if (onAirChannelList.size >= 20) {
|
if (onAirChannelList.size >= 20) {
|
||||||
@@ -63,7 +66,8 @@ class LiveRecommendService(
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
return onAirChannelList + notOnAirCreatorList
|
return onAirChannelList + notOnAirCreatorList
|
||||||
@@ -76,7 +80,8 @@ class LiveRecommendService(
|
|||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) },
|
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) },
|
||||||
isCreator = member.role == MemberRole.CREATOR,
|
isCreator = member.role == MemberRole.CREATOR,
|
||||||
isAdult = isAdult
|
isAdult = isAdult,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
if (onAirFollowingChannelList.size >= 20) {
|
if (onAirFollowingChannelList.size >= 20) {
|
||||||
@@ -89,7 +94,8 @@ class LiveRecommendService(
|
|||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
withOutCreatorList = onAirCreatorIdList,
|
withOutCreatorList = onAirCreatorIdList,
|
||||||
limit = (20 - onAirCreatorIdList.size).toLong(),
|
limit = (20 - onAirCreatorIdList.size).toLong(),
|
||||||
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) }
|
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) },
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
return onAirFollowingChannelList + notOnAirFollowingChannelList
|
return onAirFollowingChannelList + notOnAirFollowingChannelList
|
||||||
@@ -105,7 +111,8 @@ class LiveRecommendService(
|
|||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
offset = pageable.offset,
|
offset = pageable.offset,
|
||||||
limit = pageable.pageSize.toLong(),
|
limit = pageable.pageSize.toLong(),
|
||||||
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) }
|
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) },
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
return GetCreatorFollowingAllListResponse(
|
return GetCreatorFollowingAllListResponse(
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package kr.co.vividnext.sodalive.live.reservation
|
||||||
|
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
data class LiveReservationListRow(
|
||||||
|
val id: Long,
|
||||||
|
val roomId: Long,
|
||||||
|
val title: String,
|
||||||
|
val coverImage: String?,
|
||||||
|
val price: Int,
|
||||||
|
val masterNickname: String,
|
||||||
|
val beginDateTime: LocalDateTime
|
||||||
|
)
|
||||||
+61
-17
@@ -1,10 +1,17 @@
|
|||||||
package kr.co.vividnext.sodalive.live.reservation
|
package kr.co.vividnext.sodalive.live.reservation
|
||||||
|
|
||||||
|
import com.querydsl.core.types.Projections
|
||||||
|
import com.querydsl.jpa.impl.JPAQuery
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.live.reservation.QLiveReservation.liveReservation
|
import kr.co.vividnext.sodalive.live.reservation.QLiveReservation.liveReservation
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
|
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@@ -12,29 +19,45 @@ import org.springframework.stereotype.Repository
|
|||||||
interface LiveReservationRepository : JpaRepository<LiveReservation, Long>, LiveReservationQueryRepository
|
interface LiveReservationRepository : JpaRepository<LiveReservation, Long>, LiveReservationQueryRepository
|
||||||
|
|
||||||
interface LiveReservationQueryRepository {
|
interface LiveReservationQueryRepository {
|
||||||
fun getReservationList(roomId: Long): List<LiveReservation>
|
fun getReservationList(roomId: Long, lang: Lang): List<GetRoomDetailUser>
|
||||||
|
|
||||||
fun cancelReservation(roomId: Long)
|
fun cancelReservation(roomId: Long)
|
||||||
|
|
||||||
fun getReservationBookerList(roomId: Long): List<Member>
|
fun getReservationBookerList(roomId: Long): List<Member>
|
||||||
|
|
||||||
fun isExistsReservation(roomId: Long, memberId: Long): Boolean
|
fun isExistsReservation(roomId: Long, memberId: Long): Boolean
|
||||||
fun getReservationListByMemberId(memberId: Long, active: Boolean): List<LiveReservation>
|
fun getReservationListByMemberId(memberId: Long, active: Boolean, lang: Lang): List<LiveReservationListRow>
|
||||||
|
|
||||||
fun getReservationByReservationAndMemberId(
|
fun getReservationByReservationAndMemberId(
|
||||||
reservationId: Long,
|
reservationId: Long,
|
||||||
memberId: Long
|
memberId: Long,
|
||||||
): LiveReservation?
|
lang: Lang
|
||||||
|
): LiveReservationListRow?
|
||||||
|
|
||||||
fun getReservationCount(memberId: Long): Int
|
fun getReservationCount(memberId: Long): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveReservationQueryRepository {
|
class LiveReservationQueryRepositoryImpl(
|
||||||
override fun getReservationList(roomId: Long): List<LiveReservation> {
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) : LiveReservationQueryRepository {
|
||||||
|
override fun getReservationList(roomId: Long, lang: Lang): List<GetRoomDetailUser> {
|
||||||
|
val bookerName = CreatorNameJoin(member, lang, "liveReservationBookerName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(liveReservation)
|
.select(
|
||||||
|
Projections.constructor(
|
||||||
|
GetRoomDetailUser::class.java,
|
||||||
|
member.id,
|
||||||
|
bookerName.displayName,
|
||||||
|
member.profileImage.coalesce("profile/default-profile.png").prepend("/").prepend(cloudFrontHost)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(liveReservation)
|
||||||
.innerJoin(liveReservation.room, liveRoom)
|
.innerJoin(liveReservation.room, liveRoom)
|
||||||
|
.innerJoin(liveReservation.member, member)
|
||||||
|
.leftJoin(bookerName.name).on(bookerName.condition)
|
||||||
.where(
|
.where(
|
||||||
liveRoom.id.eq(roomId)
|
liveRoom.id.eq(roomId)
|
||||||
.and(liveReservation.isActive.isTrue)
|
.and(liveReservation.isActive.isTrue)
|
||||||
@@ -76,11 +99,8 @@ class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFacto
|
|||||||
.fetchFirst() != null
|
.fetchFirst() != null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReservationListByMemberId(memberId: Long, active: Boolean): List<LiveReservation> {
|
override fun getReservationListByMemberId(memberId: Long, active: Boolean, lang: Lang): List<LiveReservationListRow> {
|
||||||
return queryFactory
|
return reservationRows(lang)
|
||||||
.selectFrom(liveReservation)
|
|
||||||
.innerJoin(liveReservation.member, member)
|
|
||||||
.innerJoin(liveReservation.room, liveRoom)
|
|
||||||
.where(
|
.where(
|
||||||
liveRoom.isActive.isTrue
|
liveRoom.isActive.isTrue
|
||||||
.and(liveReservation.isActive.eq(active))
|
.and(liveReservation.isActive.eq(active))
|
||||||
@@ -89,11 +109,12 @@ class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFacto
|
|||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReservationByReservationAndMemberId(reservationId: Long, memberId: Long): LiveReservation? {
|
override fun getReservationByReservationAndMemberId(
|
||||||
return queryFactory
|
reservationId: Long,
|
||||||
.selectFrom(liveReservation)
|
memberId: Long,
|
||||||
.innerJoin(liveReservation.member, member)
|
lang: Lang
|
||||||
.innerJoin(liveReservation.room, liveRoom)
|
): LiveReservationListRow? {
|
||||||
|
return reservationRows(lang)
|
||||||
.where(
|
.where(
|
||||||
liveReservation.id.eq(reservationId)
|
liveReservation.id.eq(reservationId)
|
||||||
.and(member.id.eq(memberId))
|
.and(member.id.eq(memberId))
|
||||||
@@ -101,6 +122,29 @@ class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFacto
|
|||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun reservationRows(lang: Lang): JPAQuery<LiveReservationListRow> {
|
||||||
|
val owner = QMember("reservationRoomOwner")
|
||||||
|
val ownerName = CreatorNameJoin(owner, lang, "reservationRoomOwnerName")
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
Projections.constructor(
|
||||||
|
LiveReservationListRow::class.java,
|
||||||
|
liveReservation.id,
|
||||||
|
liveRoom.id,
|
||||||
|
liveRoom.title,
|
||||||
|
liveRoom.coverImage,
|
||||||
|
liveRoom.price,
|
||||||
|
ownerName.displayName,
|
||||||
|
liveRoom.beginDateTime
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(liveReservation)
|
||||||
|
.innerJoin(liveReservation.member, member)
|
||||||
|
.innerJoin(liveReservation.room, liveRoom)
|
||||||
|
.innerJoin(liveRoom.member, owner)
|
||||||
|
.leftJoin(ownerName.name).on(ownerName.condition)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getReservationCount(memberId: Long): Int {
|
override fun getReservationCount(memberId: Long): Int {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(liveReservation)
|
.selectFrom(liveReservation)
|
||||||
|
|||||||
+27
-22
@@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
|||||||
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
|
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
|
||||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -25,6 +26,7 @@ class LiveReservationService(
|
|||||||
private val liveReservationCancelRepository: LiveReservationCancelRepository,
|
private val liveReservationCancelRepository: LiveReservationCancelRepository,
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
@@ -78,10 +80,11 @@ class LiveReservationService(
|
|||||||
"live.reservation.datetime_format",
|
"live.reservation.datetime_format",
|
||||||
langContext.lang
|
langContext.lang
|
||||||
).orEmpty()
|
).orEmpty()
|
||||||
|
val names = localizedNameService.findLocalizedNames(listOf(room.member!!.id!!), langContext.lang)
|
||||||
|
|
||||||
return MakeLiveReservationResponse(
|
return MakeLiveReservationResponse(
|
||||||
reservationId = reservation.id!!,
|
reservationId = reservation.id!!,
|
||||||
nickname = room.member!!.nickname,
|
nickname = names[room.member!!.id] ?: room.member!!.nickname,
|
||||||
title = room.title,
|
title = room.title,
|
||||||
beginDateString = beginDateTime.format(DateTimeFormatter.ofPattern(reservationDateFormat)),
|
beginDateString = beginDateTime.format(DateTimeFormatter.ofPattern(reservationDateFormat)),
|
||||||
beginDateTimeUtc = room.beginDateTime
|
beginDateTimeUtc = room.beginDateTime
|
||||||
@@ -98,34 +101,35 @@ class LiveReservationService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
fun getReservationList(memberId: Long, active: Boolean, timezone: String): List<GetLiveReservationResponse> {
|
fun getReservationList(memberId: Long, active: Boolean, timezone: String): List<GetLiveReservationResponse> {
|
||||||
val detailDateFormat = messageSource.getMessage(
|
val detailDateFormat = messageSource.getMessage(
|
||||||
"live.room.datetime_format_detail",
|
"live.room.datetime_format_detail",
|
||||||
langContext.lang
|
langContext.lang
|
||||||
).orEmpty()
|
).orEmpty()
|
||||||
return repository
|
val reservations = repository.getReservationListByMemberId(memberId, active, langContext.lang)
|
||||||
.getReservationListByMemberId(memberId, active)
|
return reservations
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map {
|
.map {
|
||||||
val beginDateTime = it.room!!.beginDateTime
|
val beginDateTime = it.beginDateTime
|
||||||
.atZone(ZoneId.of("UTC"))
|
.atZone(ZoneId.of("UTC"))
|
||||||
.withZoneSameInstant(ZoneId.of(timezone))
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
|
||||||
GetLiveReservationResponse(
|
GetLiveReservationResponse(
|
||||||
reservationId = it.id!!,
|
reservationId = it.id,
|
||||||
roomId = it.room!!.id!!,
|
roomId = it.roomId,
|
||||||
title = it.room!!.title,
|
title = it.title,
|
||||||
coverImageUrl = if (it.room!!.coverImage != null) {
|
coverImageUrl = if (it.coverImage != null) {
|
||||||
"$cloudFrontHost/${it.room!!.coverImage}"
|
"$cloudFrontHost/${it.coverImage}"
|
||||||
} else {
|
} else {
|
||||||
"$cloudFrontHost/profile/default-profile.png"
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
},
|
},
|
||||||
price = it.room!!.price,
|
price = it.price,
|
||||||
masterNickname = it.room!!.member!!.nickname,
|
masterNickname = it.masterNickname,
|
||||||
beginDateTime = beginDateTime.format(
|
beginDateTime = beginDateTime.format(
|
||||||
DateTimeFormatter.ofPattern(detailDateFormat)
|
DateTimeFormatter.ofPattern(detailDateFormat)
|
||||||
),
|
),
|
||||||
beginDateTimeUtc = it.room!!.beginDateTime
|
beginDateTimeUtc = it.beginDateTime
|
||||||
.atZone(ZoneId.of("UTC"))
|
.atZone(ZoneId.of("UTC"))
|
||||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||||
cancelable = beginDateTime.minusHours(4).isAfter(
|
cancelable = beginDateTime.minusHours(4).isAfter(
|
||||||
@@ -138,33 +142,34 @@ class LiveReservationService(
|
|||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
fun getReservation(reservationId: Long, memberId: Long, timezone: String): GetLiveReservationResponse {
|
fun getReservation(reservationId: Long, memberId: Long, timezone: String): GetLiveReservationResponse {
|
||||||
val reservation = repository.getReservationByReservationAndMemberId(reservationId, memberId)
|
val reservation = repository.getReservationByReservationAndMemberId(reservationId, memberId, langContext.lang)
|
||||||
?: throw SodaException(messageKey = "live.reservation.invalid_reservation")
|
?: throw SodaException(messageKey = "live.reservation.invalid_reservation")
|
||||||
|
|
||||||
val detailDateFormat = messageSource.getMessage(
|
val detailDateFormat = messageSource.getMessage(
|
||||||
"live.room.datetime_format_detail",
|
"live.room.datetime_format_detail",
|
||||||
langContext.lang
|
langContext.lang
|
||||||
).orEmpty()
|
).orEmpty()
|
||||||
val beginDateTime = reservation.room!!.beginDateTime
|
val beginDateTime = reservation.beginDateTime
|
||||||
.atZone(ZoneId.of("UTC"))
|
.atZone(ZoneId.of("UTC"))
|
||||||
.withZoneSameInstant(ZoneId.of(timezone))
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
|
||||||
return GetLiveReservationResponse(
|
return GetLiveReservationResponse(
|
||||||
reservationId = reservation.id!!,
|
reservationId = reservation.id,
|
||||||
roomId = reservation.room!!.id!!,
|
roomId = reservation.roomId,
|
||||||
title = reservation.room!!.title,
|
title = reservation.title,
|
||||||
coverImageUrl = if (reservation.room!!.coverImage != null) {
|
coverImageUrl = if (reservation.coverImage != null) {
|
||||||
"$cloudFrontHost/${reservation.room!!.coverImage}"
|
"$cloudFrontHost/${reservation.coverImage}"
|
||||||
} else {
|
} else {
|
||||||
"$cloudFrontHost/profile/default-profile.png"
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
},
|
},
|
||||||
price = reservation.room!!.price,
|
price = reservation.price,
|
||||||
masterNickname = reservation.room!!.member!!.nickname,
|
masterNickname = reservation.masterNickname,
|
||||||
beginDateTime = beginDateTime.format(
|
beginDateTime = beginDateTime.format(
|
||||||
DateTimeFormatter.ofPattern(detailDateFormat)
|
DateTimeFormatter.ofPattern(detailDateFormat)
|
||||||
),
|
),
|
||||||
beginDateTimeUtc = reservation.room!!.beginDateTime
|
beginDateTimeUtc = reservation.beginDateTime
|
||||||
.atZone(ZoneId.of("UTC"))
|
.atZone(ZoneId.of("UTC"))
|
||||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||||
cancelable = beginDateTime.minusHours(4).isAfter(
|
cancelable = beginDateTime.minusHours(4).isAfter(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.live.room
|
package kr.co.vividnext.sodalive.live.room
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
|
||||||
data class GetLiveRoomUserProfileResponse(
|
data class GetLiveRoomUserProfileResponse(
|
||||||
val userId: Long,
|
val userId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
@@ -19,3 +21,5 @@ data class GetLiveRoomUserProfileResponse(
|
|||||||
val isFollowing: Boolean?,
|
val isFollowing: Boolean?,
|
||||||
val isBlock: Boolean
|
val isBlock: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class LiveRoomUserProfileRow(val member: Member, val nickname: String)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package kr.co.vividnext.sodalive.live.room
|
||||||
|
|
||||||
|
data class LiveRoomListRow(val room: LiveRoom, val creatorNickname: String)
|
||||||
@@ -8,17 +8,19 @@ import com.querydsl.jpa.impl.JPAQueryFactory
|
|||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
||||||
import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate
|
import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.live.room.QQuarterLiveRankings.quarterLiveRankings
|
import kr.co.vividnext.sodalive.live.room.QQuarterLiveRankings.quarterLiveRankings
|
||||||
import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationItem
|
import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationItem
|
||||||
import kr.co.vividnext.sodalive.live.room.donation.QGetLiveRoomDonationItem
|
import kr.co.vividnext.sodalive.live.room.donation.QGetLiveRoomDonationItem
|
||||||
import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartListItem
|
import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartListItem
|
||||||
import kr.co.vividnext.sodalive.live.room.like.QGetLiveRoomHeartListItem
|
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.cache.annotation.Cacheable
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -35,8 +37,9 @@ interface LiveRoomQueryRepository {
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?,
|
||||||
): List<LiveRoom>
|
lang: Lang
|
||||||
|
): List<LiveRoomListRow>
|
||||||
|
|
||||||
fun getLiveRoomListReservationWithDate(
|
fun getLiveRoomListReservationWithDate(
|
||||||
date: LocalDateTime,
|
date: LocalDateTime,
|
||||||
@@ -45,30 +48,40 @@ interface LiveRoomQueryRepository {
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?,
|
||||||
): List<LiveRoom>
|
lang: Lang
|
||||||
|
): List<LiveRoomListRow>
|
||||||
|
|
||||||
fun getLiveRoomListReservationWithoutDate(
|
fun getLiveRoomListReservationWithoutDate(
|
||||||
timezone: String,
|
timezone: String,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?,
|
||||||
): List<LiveRoom>
|
lang: Lang
|
||||||
|
): List<LiveRoomListRow>
|
||||||
|
|
||||||
fun getLiveRoom(id: Long): LiveRoom?
|
fun getLiveRoom(id: Long): LiveRoom?
|
||||||
|
fun getLiveRoomDetail(id: Long, lang: Lang): LiveRoomListRow?
|
||||||
|
fun getLiveRoomUserProfile(userId: Long, lang: Lang): LiveRoomUserProfileRow?
|
||||||
fun getLiveRoomAndAccountId(roomId: Long, memberId: Long): LiveRoom?
|
fun getLiveRoomAndAccountId(roomId: Long, memberId: Long): LiveRoom?
|
||||||
fun getRecentRoomInfo(memberId: Long): GetRecentRoomInfoResponse?
|
fun getRecentRoomInfo(memberId: Long): GetRecentRoomInfoResponse?
|
||||||
fun getDonationTotal(roomId: Long): Int?
|
fun getDonationTotal(roomId: Long): Int?
|
||||||
fun getDonationList(roomId: Long, isLiveCreator: Boolean, memberId: Long): List<GetLiveRoomDonationItem>
|
fun getDonationList(roomId: Long, isLiveCreator: Boolean, memberId: Long, lang: Lang): List<GetLiveRoomDonationItem>
|
||||||
fun getRoomActiveAndChannelNameIsNotNull(memberId: Long): List<LiveRoom>
|
fun getRoomActiveAndChannelNameIsNotNull(memberId: Long): List<LiveRoom>
|
||||||
fun getActiveRoomIdList(memberId: Long): Int
|
fun getActiveRoomIdList(memberId: Long): Int
|
||||||
fun getTotalHeartCount(roomId: Long): Int?
|
fun getTotalHeartCount(roomId: Long): Int?
|
||||||
fun getLiveRoomCreatorId(roomId: Long): Long?
|
fun getLiveRoomCreatorId(roomId: Long): Long?
|
||||||
fun getHeartList(roomId: Long): List<GetLiveRoomHeartListItem>
|
fun getHeartList(roomId: Long, lang: Lang): List<GetLiveRoomHeartListItem>
|
||||||
|
|
||||||
|
@Cacheable(
|
||||||
|
cacheNames = ["cache_ttl_10_minutes"],
|
||||||
|
key = "'getLatestFinishedLive:' + (#memberId ?: 'guest')"
|
||||||
|
)
|
||||||
fun getLatestFinishedLive(memberId: Long?): List<GetLatestFinishedLiveResponse>
|
fun getLatestFinishedLive(memberId: Long?): List<GetLatestFinishedLiveResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
class LiveRoomQueryRepositoryImpl(
|
class LiveRoomQueryRepositoryImpl(
|
||||||
private val queryFactory: JPAQueryFactory,
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
|
||||||
@@ -82,8 +95,10 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?,
|
||||||
): List<LiveRoom> {
|
lang: Lang
|
||||||
|
): List<LiveRoomListRow> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "liveRoomNowCreatorName")
|
||||||
var where = liveRoom.channelName.isNotNull
|
var where = liveRoom.channelName.isNotNull
|
||||||
.and(liveRoom.channelName.isNotEmpty)
|
.and(liveRoom.channelName.isNotEmpty)
|
||||||
.and(liveRoom.isActive.isTrue)
|
.and(liveRoom.isActive.isTrue)
|
||||||
@@ -116,8 +131,10 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var select = queryFactory
|
var select = queryFactory
|
||||||
.selectFrom(liveRoom)
|
.select(liveRoom, creatorName.displayName)
|
||||||
.innerJoin(liveRoom.member, member)
|
.from(liveRoom)
|
||||||
|
.innerJoin(liveRoom.member, member).fetchJoin()
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(quarterLiveRankings).on(liveRoom.id.eq(quarterLiveRankings.roomId))
|
.leftJoin(quarterLiveRankings).on(liveRoom.id.eq(quarterLiveRankings.roomId))
|
||||||
|
|
||||||
if (hasMemberId) {
|
if (hasMemberId) {
|
||||||
@@ -146,6 +163,7 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
liveRoom.member.createdAt.desc()
|
liveRoom.member.createdAt.desc()
|
||||||
)
|
)
|
||||||
.fetch()
|
.fetch()
|
||||||
|
.map { LiveRoomListRow(it.get(liveRoom)!!, it.get(creatorName.displayName)!!) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveRoomListReservationWithDate(
|
override fun getLiveRoomListReservationWithDate(
|
||||||
@@ -155,8 +173,10 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?,
|
||||||
): List<LiveRoom> {
|
lang: Lang
|
||||||
|
): List<LiveRoomListRow> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "liveRoomReservationDateCreatorName")
|
||||||
var where = liveRoom.beginDateTime.goe(date)
|
var where = liveRoom.beginDateTime.goe(date)
|
||||||
.and(liveRoom.beginDateTime.lt(date.plusDays(1)))
|
.and(liveRoom.beginDateTime.lt(date.plusDays(1)))
|
||||||
.and(
|
.and(
|
||||||
@@ -192,8 +212,10 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var select = queryFactory
|
var select = queryFactory
|
||||||
.selectFrom(liveRoom)
|
.select(liveRoom, creatorName.displayName)
|
||||||
.innerJoin(liveRoom.member, member)
|
.from(liveRoom)
|
||||||
|
.innerJoin(liveRoom.member, member).fetchJoin()
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
|
||||||
if (memberId != null) {
|
if (memberId != null) {
|
||||||
val blockMemberCondition = blockMember.isActive.isTrue
|
val blockMemberCondition = blockMember.isActive.isTrue
|
||||||
@@ -216,6 +238,7 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.limit(limit)
|
.limit(limit)
|
||||||
.orderBy(liveRoom.beginDateTime.asc())
|
.orderBy(liveRoom.beginDateTime.asc())
|
||||||
.fetch()
|
.fetch()
|
||||||
|
.map { LiveRoomListRow(it.get(liveRoom)!!, it.get(creatorName.displayName)!!) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveRoomListReservationWithoutDate(
|
override fun getLiveRoomListReservationWithoutDate(
|
||||||
@@ -223,8 +246,10 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?,
|
||||||
): List<LiveRoom> {
|
lang: Lang
|
||||||
|
): List<LiveRoomListRow> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "liveRoomReservationCreatorName")
|
||||||
var where = liveRoom.beginDateTime.gt(
|
var where = liveRoom.beginDateTime.gt(
|
||||||
LocalDateTime.now()
|
LocalDateTime.now()
|
||||||
.atZone(ZoneId.of(timezone))
|
.atZone(ZoneId.of(timezone))
|
||||||
@@ -276,8 +301,10 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var select = queryFactory
|
var select = queryFactory
|
||||||
.selectFrom(liveRoom)
|
.select(liveRoom, creatorName.displayName)
|
||||||
.innerJoin(liveRoom.member, member)
|
.from(liveRoom)
|
||||||
|
.innerJoin(liveRoom.member, member).fetchJoin()
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.limit(10)
|
.limit(10)
|
||||||
|
|
||||||
if (memberId != null) {
|
if (memberId != null) {
|
||||||
@@ -299,6 +326,7 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.where(where)
|
.where(where)
|
||||||
.orderBy(*orderBy.toTypedArray())
|
.orderBy(*orderBy.toTypedArray())
|
||||||
.fetch()
|
.fetch()
|
||||||
|
.map { LiveRoomListRow(it.get(liveRoom)!!, it.get(creatorName.displayName)!!) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveRoom(id: Long): LiveRoom? {
|
override fun getLiveRoom(id: Long): LiveRoom? {
|
||||||
@@ -312,6 +340,29 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLiveRoomDetail(id: Long, lang: Lang): LiveRoomListRow? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "liveRoomDetailCreatorName")
|
||||||
|
val row = queryFactory
|
||||||
|
.select(liveRoom, creatorName.displayName)
|
||||||
|
.from(liveRoom)
|
||||||
|
.innerJoin(liveRoom.member, member).fetchJoin()
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
.where(liveRoom.id.eq(id).and(liveRoom.isActive.isTrue))
|
||||||
|
.fetchFirst() ?: return null
|
||||||
|
return LiveRoomListRow(row.get(liveRoom)!!, row.get(creatorName.displayName)!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLiveRoomUserProfile(userId: Long, lang: Lang): LiveRoomUserProfileRow? {
|
||||||
|
val profileName = CreatorNameJoin(member, lang, "liveRoomUserProfileName")
|
||||||
|
val row = queryFactory
|
||||||
|
.select(member, profileName.displayName)
|
||||||
|
.from(member)
|
||||||
|
.leftJoin(profileName.name).on(profileName.condition)
|
||||||
|
.where(member.id.eq(userId))
|
||||||
|
.fetchOne() ?: return null
|
||||||
|
return LiveRoomUserProfileRow(row.get(member)!!, row.get(profileName.displayName)!!)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLiveRoomAndAccountId(roomId: Long, memberId: Long): LiveRoom? {
|
override fun getLiveRoomAndAccountId(roomId: Long, memberId: Long): LiveRoom? {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(liveRoom)
|
.selectFrom(liveRoom)
|
||||||
@@ -359,7 +410,13 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.fetchOne()
|
.fetchOne()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDonationList(roomId: Long, isLiveCreator: Boolean, memberId: Long): List<GetLiveRoomDonationItem> {
|
override fun getDonationList(
|
||||||
|
roomId: Long,
|
||||||
|
isLiveCreator: Boolean,
|
||||||
|
memberId: Long,
|
||||||
|
lang: Lang
|
||||||
|
): List<GetLiveRoomDonationItem> {
|
||||||
|
val donorName = CreatorNameJoin(member, lang, "liveRoomDonorName")
|
||||||
val where = liveRoom.id.eq(roomId)
|
val where = liveRoom.id.eq(roomId)
|
||||||
.and(useCan.canUsage.eq(CanUsage.DONATION).or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE)))
|
.and(useCan.canUsage.eq(CanUsage.DONATION).or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE)))
|
||||||
.and(useCan.isRefund.isFalse)
|
.and(useCan.isRefund.isFalse)
|
||||||
@@ -380,7 +437,7 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.coalesce("profile/default-profile.png")
|
.coalesce("profile/default-profile.png")
|
||||||
.prepend("/")
|
.prepend("/")
|
||||||
.prepend(cloudFrontHost),
|
.prepend(cloudFrontHost),
|
||||||
member.nickname,
|
donorName.displayName,
|
||||||
member.id.coalesce(0),
|
member.id.coalesce(0),
|
||||||
Expressions.asNumber(
|
Expressions.asNumber(
|
||||||
CaseBuilder()
|
CaseBuilder()
|
||||||
@@ -406,7 +463,8 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.from(useCan)
|
.from(useCan)
|
||||||
.innerJoin(useCan.room, liveRoom)
|
.innerJoin(useCan.room, liveRoom)
|
||||||
.innerJoin(useCan.member, member)
|
.innerJoin(useCan.member, member)
|
||||||
.groupBy(useCan.member.id)
|
.leftJoin(donorName.name).on(donorName.condition)
|
||||||
|
.groupBy(useCan.member.id, member.nickname, donorName.name.displayName)
|
||||||
.where(where)
|
.where(where)
|
||||||
.orderBy(sortExpression.sum().desc())
|
.orderBy(sortExpression.sum().desc())
|
||||||
.fetch()
|
.fetch()
|
||||||
@@ -461,26 +519,29 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getHeartList(roomId: Long): List<GetLiveRoomHeartListItem> {
|
override fun getHeartList(roomId: Long, lang: Lang): List<GetLiveRoomHeartListItem> {
|
||||||
|
val donorName = CreatorNameJoin(member, lang, "liveRoomHeartDonorName")
|
||||||
val where = liveRoom.id.eq(roomId)
|
val where = liveRoom.id.eq(roomId)
|
||||||
.and(useCan.canUsage.eq(CanUsage.HEART))
|
.and(useCan.canUsage.eq(CanUsage.HEART))
|
||||||
.and(useCan.isRefund.isFalse)
|
.and(useCan.isRefund.isFalse)
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QGetLiveRoomHeartListItem(
|
Projections.constructor(
|
||||||
|
GetLiveRoomHeartListItem::class.java,
|
||||||
member.profileImage
|
member.profileImage
|
||||||
.coalesce("profile/default-profile.png")
|
.coalesce("profile/default-profile.png")
|
||||||
.prepend("/")
|
.prepend("/")
|
||||||
.prepend(cloudFrontHost),
|
.prepend(cloudFrontHost),
|
||||||
member.nickname,
|
donorName.displayName,
|
||||||
useCan.can.add(useCan.rewardCan).sum()
|
useCan.can.add(useCan.rewardCan).sum()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(useCan)
|
.from(useCan)
|
||||||
.innerJoin(useCan.room, liveRoom)
|
.innerJoin(useCan.room, liveRoom)
|
||||||
.innerJoin(useCan.member, member)
|
.innerJoin(useCan.member, member)
|
||||||
.groupBy(useCan.member.id)
|
.leftJoin(donorName.name).on(donorName.condition)
|
||||||
|
.groupBy(useCan.member.id, member.nickname, donorName.name.displayName)
|
||||||
.where(where)
|
.where(where)
|
||||||
.orderBy(useCan.can.add(useCan.rewardCan).sum().desc())
|
.orderBy(useCan.can.add(useCan.rewardCan).sum().desc())
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import kr.co.vividnext.sodalive.fcm.FcmEvent
|
|||||||
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
||||||
import kr.co.vividnext.sodalive.fcm.PushTokenRepository
|
import kr.co.vividnext.sodalive.fcm.PushTokenRepository
|
||||||
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationCategory
|
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationCategory
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
|
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
|
||||||
@@ -67,8 +68,8 @@ import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
|||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
|
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
|
||||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.cache.annotation.Cacheable
|
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
@@ -89,6 +90,7 @@ class LiveRoomService(
|
|||||||
private val menuService: LiveRoomMenuService,
|
private val menuService: LiveRoomMenuService,
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
|
|
||||||
private val repository: LiveRoomRepository,
|
private val repository: LiveRoomRepository,
|
||||||
private val rouletteRepository: NewRouletteRepository,
|
private val rouletteRepository: NewRouletteRepository,
|
||||||
@@ -247,14 +249,14 @@ class LiveRoomService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val creatorIds = roomList.mapNotNull { it.member?.id }.distinct()
|
val creatorIds = roomList.mapNotNull { it.room.member?.id }.distinct()
|
||||||
val languageTagByMemberId = buildLanguageTagMap(creatorIds)
|
val languageTagByMemberId = buildLanguageTagMap(creatorIds)
|
||||||
|
|
||||||
return roomList
|
return roomList
|
||||||
.map {
|
.map { (room, creatorNickname) ->
|
||||||
val roomInfo = roomInfoRepository.findByIdOrNull(it.id!!)
|
val roomInfo = roomInfoRepository.findByIdOrNull(room.id!!)
|
||||||
|
|
||||||
val reservations = it.reservations
|
val reservations = room.reservations
|
||||||
.filter { reservation ->
|
.filter { reservation ->
|
||||||
if (member?.id != null) {
|
if (member?.id != null) {
|
||||||
reservation.member!!.id!! == member.id!! && reservation.isActive
|
reservation.member!!.id!! == member.id!! && reservation.isActive
|
||||||
@@ -263,7 +265,7 @@ class LiveRoomService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val beginDateTime = it.beginDateTime
|
val beginDateTime = room.beginDateTime
|
||||||
.atZone(ZoneId.of("UTC"))
|
.atZone(ZoneId.of("UTC"))
|
||||||
.withZoneSameInstant(ZoneId.of(timezone))
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
.format(
|
.format(
|
||||||
@@ -272,42 +274,42 @@ class LiveRoomService(
|
|||||||
.withLocale(langContext.lang.locale)
|
.withLocale(langContext.lang.locale)
|
||||||
)
|
)
|
||||||
|
|
||||||
val beginDateTimeUtc = it.beginDateTime
|
val beginDateTimeUtc = room.beginDateTime
|
||||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||||
|
|
||||||
val tags = it.tags
|
val tags = room.tags
|
||||||
.filter { tag -> tag.tag.isActive }
|
.filter { tag -> tag.tag.isActive }
|
||||||
.map { tag -> tag.tag.tag }
|
.map { tag -> tag.tag.tag }
|
||||||
.let { list -> applyLanguageTagToRoomTags(it.member?.id, list, languageTagByMemberId) }
|
.let { list -> applyLanguageTagToRoomTags(room.member?.id, list, languageTagByMemberId) }
|
||||||
|
|
||||||
GetRoomListResponse(
|
GetRoomListResponse(
|
||||||
roomId = it.id!!,
|
roomId = room.id!!,
|
||||||
title = it.title,
|
title = room.title,
|
||||||
content = it.notice,
|
content = room.notice,
|
||||||
beginDateTime = beginDateTime,
|
beginDateTime = beginDateTime,
|
||||||
beginDateTimeUtc = beginDateTimeUtc,
|
beginDateTimeUtc = beginDateTimeUtc,
|
||||||
numberOfParticipate = (roomInfo?.listenerCount ?: 0) +
|
numberOfParticipate = (roomInfo?.listenerCount ?: 0) +
|
||||||
(roomInfo?.speakerCount ?: 0) +
|
(roomInfo?.speakerCount ?: 0) +
|
||||||
(roomInfo?.managerCount ?: 0),
|
(roomInfo?.managerCount ?: 0),
|
||||||
numberOfPeople = it.numberOfPeople,
|
numberOfPeople = room.numberOfPeople,
|
||||||
isAdult = it.isAdult,
|
isAdult = room.isAdult,
|
||||||
price = it.price,
|
price = room.price,
|
||||||
channelName = it.channelName,
|
channelName = room.channelName,
|
||||||
creatorProfileImage = if (it.member!!.profileImage != null) {
|
creatorProfileImage = if (room.member!!.profileImage != null) {
|
||||||
"$cloudFrontHost/${it.member!!.profileImage}"
|
"$cloudFrontHost/${room.member!!.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
"$cloudFrontHost/profile/default-profile.png"
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
},
|
},
|
||||||
creatorNickname = it.member!!.nickname,
|
creatorNickname = creatorNickname,
|
||||||
creatorId = it.member!!.id!!,
|
creatorId = room.member!!.id!!,
|
||||||
tags = tags,
|
tags = tags,
|
||||||
coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
|
coverImageUrl = if (room.coverImage!!.startsWith("https://")) {
|
||||||
it.coverImage!!
|
room.coverImage!!
|
||||||
} else {
|
} else {
|
||||||
"$cloudFrontHost/${it.coverImage!!}"
|
"$cloudFrontHost/${room.coverImage!!}"
|
||||||
},
|
},
|
||||||
isReservation = reservations.isNotEmpty(),
|
isReservation = reservations.isNotEmpty(),
|
||||||
isPrivateRoom = it.type == LiveRoomType.PRIVATE
|
isPrivateRoom = room.type == LiveRoomType.PRIVATE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,7 +321,7 @@ class LiveRoomService(
|
|||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?
|
||||||
): List<LiveRoom> {
|
): List<LiveRoomListRow> {
|
||||||
return repository.getLiveRoomListNow(
|
return repository.getLiveRoomListNow(
|
||||||
offset = pageable.offset,
|
offset = pageable.offset,
|
||||||
limit = pageable.pageSize.toLong(),
|
limit = pageable.pageSize.toLong(),
|
||||||
@@ -327,7 +329,8 @@ class LiveRoomService(
|
|||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
isCreator = isCreator,
|
isCreator = isCreator,
|
||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
effectiveGender = effectiveGender
|
effectiveGender = effectiveGender,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +342,7 @@ class LiveRoomService(
|
|||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?
|
||||||
): List<LiveRoom> {
|
): List<LiveRoomListRow> {
|
||||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||||
val date = LocalDate.parse(dateString, dateTimeFormatter).atStartOfDay()
|
val date = LocalDate.parse(dateString, dateTimeFormatter).atStartOfDay()
|
||||||
.atZone(ZoneId.of(timezone))
|
.atZone(ZoneId.of(timezone))
|
||||||
@@ -353,7 +356,8 @@ class LiveRoomService(
|
|||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
isCreator = isCreator,
|
isCreator = isCreator,
|
||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
effectiveGender = effectiveGender
|
effectiveGender = effectiveGender,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,8 +367,15 @@ class LiveRoomService(
|
|||||||
isCreator: Boolean,
|
isCreator: Boolean,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
effectiveGender: Gender?
|
effectiveGender: Gender?
|
||||||
): List<LiveRoom> {
|
): List<LiveRoomListRow> {
|
||||||
return repository.getLiveRoomListReservationWithoutDate(timezone, memberId, isCreator, isAdult, effectiveGender)
|
return repository.getLiveRoomListReservationWithoutDate(
|
||||||
|
timezone,
|
||||||
|
memberId,
|
||||||
|
isCreator,
|
||||||
|
isAdult,
|
||||||
|
effectiveGender,
|
||||||
|
langContext.lang
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -504,6 +515,7 @@ class LiveRoomService(
|
|||||||
type = FcmEventType.CREATE_LIVE,
|
type = FcmEventType.CREATE_LIVE,
|
||||||
category = PushNotificationCategory.LIVE,
|
category = PushNotificationCategory.LIVE,
|
||||||
title = createdRoom.member!!.nickname,
|
title = createdRoom.member!!.nickname,
|
||||||
|
titleMemberId = createdRoom.member!!.id,
|
||||||
messageKey = if (createdRoom.channelName != null) {
|
messageKey = if (createdRoom.channelName != null) {
|
||||||
"live.room.fcm.message.started"
|
"live.room.fcm.message.started"
|
||||||
} else {
|
} else {
|
||||||
@@ -525,7 +537,7 @@ class LiveRoomService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getRoomDetail(roomId: Long, member: Member, timezone: String): GetRoomDetailResponse {
|
fun getRoomDetail(roomId: Long, member: Member, timezone: String): GetRoomDetailResponse {
|
||||||
val room = repository.getLiveRoom(id = roomId)
|
val (room, creatorNickname) = repository.getLiveRoomDetail(id = roomId, lang = langContext.lang)
|
||||||
?: throw SodaException(messageKey = "live.room.already_ended")
|
?: throw SodaException(messageKey = "live.room.already_ended")
|
||||||
|
|
||||||
val creatorId = room.member!!.id!!
|
val creatorId = room.member!!.id!!
|
||||||
@@ -573,7 +585,7 @@ class LiveRoomService(
|
|||||||
genderRestriction = room.genderRestriction,
|
genderRestriction = room.genderRestriction,
|
||||||
password = room.password
|
password = room.password
|
||||||
)
|
)
|
||||||
response.manager = GetRoomDetailManager(room.member!!, cloudFrontHost = cloudFrontHost)
|
response.manager = GetRoomDetailManager(room.member!!, cloudFrontHost, creatorNickname)
|
||||||
|
|
||||||
if (!room.channelName.isNullOrBlank()) {
|
if (!room.channelName.isNullOrBlank()) {
|
||||||
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
|
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
|
||||||
@@ -618,21 +630,14 @@ class LiveRoomService(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
response.participatingUsers = users
|
val names = findCurrentNames(users.map { it.id }, langContext.lang)
|
||||||
|
response.participatingUsers = users.map { it.copy(nickname = names[it.id] ?: it.nickname) }
|
||||||
response.numberOfParticipants = users.size
|
response.numberOfParticipants = users.size
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val reservationList = reservationRepository.getReservationList(roomId)
|
val reservationList = reservationRepository.getReservationList(roomId, langContext.lang)
|
||||||
|
response.isPaid = reservationList.any { it.id == member.id }
|
||||||
response.participatingUsers = reservationList
|
response.participatingUsers = reservationList
|
||||||
.asSequence()
|
|
||||||
.map {
|
|
||||||
if (it.member!!.id!! == member.id!!) {
|
|
||||||
response.isPaid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
GetRoomDetailUser(it.member!!, cloudFrontHost)
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
response.numberOfParticipants = reservationList.size
|
response.numberOfParticipants = reservationList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,6 +691,7 @@ class LiveRoomService(
|
|||||||
type = FcmEventType.START_LIVE,
|
type = FcmEventType.START_LIVE,
|
||||||
category = PushNotificationCategory.LIVE,
|
category = PushNotificationCategory.LIVE,
|
||||||
title = room.member!!.nickname,
|
title = room.member!!.nickname,
|
||||||
|
titleMemberId = room.member!!.id,
|
||||||
messageKey = "live.room.fcm.message.started",
|
messageKey = "live.room.fcm.message.started",
|
||||||
senderMemberId = room.member!!.id,
|
senderMemberId = room.member!!.id,
|
||||||
args = listOf(room.title),
|
args = listOf(room.title),
|
||||||
@@ -761,6 +767,7 @@ class LiveRoomService(
|
|||||||
type = FcmEventType.CANCEL_LIVE,
|
type = FcmEventType.CANCEL_LIVE,
|
||||||
category = PushNotificationCategory.LIVE,
|
category = PushNotificationCategory.LIVE,
|
||||||
title = room.member!!.nickname,
|
title = room.member!!.nickname,
|
||||||
|
titleMemberId = room.member!!.id,
|
||||||
messageKey = "live.room.fcm.message.canceled",
|
messageKey = "live.room.fcm.message.canceled",
|
||||||
senderMemberId = room.member!!.id,
|
senderMemberId = room.member!!.id,
|
||||||
args = listOf(room.title),
|
args = listOf(room.title),
|
||||||
@@ -792,8 +799,9 @@ class LiveRoomService(
|
|||||||
|
|
||||||
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = room.member!!.id!!)
|
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = room.member!!.id!!)
|
||||||
if (isBlocked) {
|
if (isBlocked) {
|
||||||
|
val names = localizedNameService.findLocalizedNames(listOf(room.member!!.id!!), langContext.lang)
|
||||||
throw SodaException(
|
throw SodaException(
|
||||||
message = formatMessage("live.room.enter_blocked_by_host", room.member!!.nickname)
|
message = formatMessage("live.room.enter_blocked_by_host", names[room.member!!.id] ?: room.member!!.nickname)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,8 +815,12 @@ class LiveRoomService(
|
|||||||
|
|
||||||
val kickOutCount = kickOutService.getKickOutCount(roomId = room.id!!, userId = member.id!!)
|
val kickOutCount = kickOutService.getKickOutCount(roomId = room.id!!, userId = member.id!!)
|
||||||
if (kickOutCount >= 2) {
|
if (kickOutCount >= 2) {
|
||||||
|
val names = localizedNameService.findLocalizedNames(listOf(room.member!!.id!!), langContext.lang)
|
||||||
throw SodaException(
|
throw SodaException(
|
||||||
message = formatMessage("live.room.participation_blocked_by_host", room.member!!.nickname)
|
message = formatMessage(
|
||||||
|
"live.room.participation_blocked_by_host",
|
||||||
|
names[room.member!!.id] ?: room.member!!.nickname
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,6 +1069,10 @@ class LiveRoomService(
|
|||||||
?.lowercase()
|
?.lowercase()
|
||||||
?.take(2)
|
?.take(2)
|
||||||
|
|
||||||
|
val names = findCurrentNames(
|
||||||
|
listOf(creatorId) + (roomInfo.speakerList + roomInfo.listenerList + roomInfo.managerList).map { it.id },
|
||||||
|
langContext.lang
|
||||||
|
)
|
||||||
return GetRoomInfoResponse(
|
return GetRoomInfoResponse(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
title = room.title,
|
title = room.title,
|
||||||
@@ -1079,7 +1095,7 @@ class LiveRoomService(
|
|||||||
rtmToken = rtmToken,
|
rtmToken = rtmToken,
|
||||||
v2vWorkerToken = v2vWorkerToken,
|
v2vWorkerToken = v2vWorkerToken,
|
||||||
creatorId = room.member!!.id!!,
|
creatorId = room.member!!.id!!,
|
||||||
creatorNickname = room.member!!.nickname,
|
creatorNickname = names[creatorId] ?: room.member!!.nickname,
|
||||||
creatorProfileUrl = if (room.member!!.profileImage != null) {
|
creatorProfileUrl = if (room.member!!.profileImage != null) {
|
||||||
"$cloudFrontHost/${room.member!!.profileImage}"
|
"$cloudFrontHost/${room.member!!.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
@@ -1090,9 +1106,9 @@ class LiveRoomService(
|
|||||||
isCaptureRecordingAvailable = room.isCaptureRecordingAvailable,
|
isCaptureRecordingAvailable = room.isCaptureRecordingAvailable,
|
||||||
participantsCount = roomInfo.listenerCount + roomInfo.speakerCount + roomInfo.managerCount,
|
participantsCount = roomInfo.listenerCount + roomInfo.speakerCount + roomInfo.managerCount,
|
||||||
totalAvailableParticipantsCount = room.numberOfPeople,
|
totalAvailableParticipantsCount = room.numberOfPeople,
|
||||||
speakerList = roomInfo.speakerList,
|
speakerList = roomInfo.speakerList.map { it.copy(nickname = names[it.id] ?: it.nickname).apply { role = it.role } },
|
||||||
listenerList = roomInfo.listenerList,
|
listenerList = roomInfo.listenerList.map { it.copy(nickname = names[it.id] ?: it.nickname).apply { role = it.role } },
|
||||||
managerList = roomInfo.managerList,
|
managerList = roomInfo.managerList.map { it.copy(nickname = names[it.id] ?: it.nickname).apply { role = it.role } },
|
||||||
donationRankingTop3UserIds = donationRankingTop3UserIds,
|
donationRankingTop3UserIds = donationRankingTop3UserIds,
|
||||||
menuPan = menuPan?.menu ?: "",
|
menuPan = menuPan?.menu ?: "",
|
||||||
creatorLanguageCode = creatorLanguageCode,
|
creatorLanguageCode = creatorLanguageCode,
|
||||||
@@ -1129,12 +1145,14 @@ class LiveRoomService(
|
|||||||
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
|
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
|
||||||
?: throw SodaException(messageKey = "live.room.info_not_found")
|
?: throw SodaException(messageKey = "live.room.info_not_found")
|
||||||
|
|
||||||
return if (liveRoomCreatorId != member.id!!) {
|
val messages = if (liveRoomCreatorId != member.id!!) {
|
||||||
roomInfo.donationMessageList
|
roomInfo.donationMessageList
|
||||||
.filter { !it.isSecret || it.memberId == member.id!! }
|
.filter { !it.isSecret || it.memberId == member.id!! }
|
||||||
} else {
|
} else {
|
||||||
roomInfo.donationMessageList
|
roomInfo.donationMessageList
|
||||||
}
|
}
|
||||||
|
val names = findCurrentNames(messages.map { it.memberId }, langContext.lang)
|
||||||
|
return messages.map { it.copy(nickname = names[it.memberId] ?: it.nickname) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteDonationMessage(request: DeleteLiveRoomDonationMessage, member: Member) {
|
fun deleteDonationMessage(request: DeleteLiveRoomDonationMessage, member: Member) {
|
||||||
@@ -1161,7 +1179,7 @@ class LiveRoomService(
|
|||||||
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
|
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
|
||||||
?: throw SodaException(messageKey = "live.room.info_not_found")
|
?: throw SodaException(messageKey = "live.room.info_not_found")
|
||||||
|
|
||||||
val user = memberRepository.findByIdOrNull(userId)
|
val (user, nickname) = repository.getLiveRoomUserProfile(userId, langContext.lang)
|
||||||
?: throw SodaException(messageKey = "common.error.invalid_request")
|
?: throw SodaException(messageKey = "common.error.invalid_request")
|
||||||
|
|
||||||
val isFollowing = if (user.role == MemberRole.CREATOR) {
|
val isFollowing = if (user.role == MemberRole.CREATOR) {
|
||||||
@@ -1194,7 +1212,7 @@ class LiveRoomService(
|
|||||||
|
|
||||||
return GetLiveRoomUserProfileResponse(
|
return GetLiveRoomUserProfileResponse(
|
||||||
userId = user.id!!,
|
userId = user.id!!,
|
||||||
nickname = user.nickname,
|
nickname = nickname,
|
||||||
profileUrl = if (user.profileImage != null) {
|
profileUrl = if (user.profileImage != null) {
|
||||||
"$cloudFrontHost/${user.profileImage}"
|
"$cloudFrontHost/${user.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
@@ -1439,7 +1457,8 @@ class LiveRoomService(
|
|||||||
val donationList = repository.getDonationList(
|
val donationList = repository.getDonationList(
|
||||||
roomId = room.id!!,
|
roomId = room.id!!,
|
||||||
isLiveCreator = isLiveCreator,
|
isLiveCreator = isLiveCreator,
|
||||||
memberId = memberId
|
memberId = memberId,
|
||||||
|
lang = langContext.lang
|
||||||
).filter { it.secretCan > 0 || it.can > 0 }
|
).filter { it.secretCan > 0 || it.can > 0 }
|
||||||
|
|
||||||
val totalCan = donationList.sumOf { it.can }
|
val totalCan = donationList.sumOf { it.can }
|
||||||
@@ -1492,6 +1511,10 @@ class LiveRoomService(
|
|||||||
return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
|
return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findCurrentNames(memberIds: Collection<Long>, lang: Lang): Map<Long, String> {
|
||||||
|
return localizedNameService.findCurrentNames(memberIds.distinct(), lang)
|
||||||
|
}
|
||||||
|
|
||||||
private fun resolvePreference(member: Member?): ViewerContentPreference {
|
private fun resolvePreference(member: Member?): ViewerContentPreference {
|
||||||
if (member == null) {
|
if (member == null) {
|
||||||
return ViewerContentPreference(
|
return ViewerContentPreference(
|
||||||
@@ -1531,7 +1554,7 @@ class LiveRoomService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getHeartList(roomId: Long): GetLiveRoomHeartListResponse {
|
fun getHeartList(roomId: Long): GetLiveRoomHeartListResponse {
|
||||||
val heartList = repository.getHeartList(roomId = roomId)
|
val heartList = repository.getHeartList(roomId = roomId, lang = langContext.lang)
|
||||||
|
|
||||||
return GetLiveRoomHeartListResponse(
|
return GetLiveRoomHeartListResponse(
|
||||||
heartList = heartList,
|
heartList = heartList,
|
||||||
@@ -1541,11 +1564,10 @@ class LiveRoomService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@Cacheable(
|
|
||||||
cacheNames = ["cache_ttl_10_minutes"],
|
|
||||||
key = "'getLatestFinishedLive:' + (#member?.id ?: 'guest')"
|
|
||||||
)
|
|
||||||
fun getLatestFinishedLive(member: Member?): List<GetLatestFinishedLiveResponse> {
|
fun getLatestFinishedLive(member: Member?): List<GetLatestFinishedLiveResponse> {
|
||||||
return repository.getLatestFinishedLive(memberId = member?.id)
|
val items = repository.getLatestFinishedLive(memberId = member?.id)
|
||||||
|
val memberIds = items.map { it.memberId }.distinct()
|
||||||
|
val names = findCurrentNames(memberIds, langContext.lang)
|
||||||
|
return items.map { it.copy(nickname = names[it.memberId] ?: it.nickname) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ data class GetRoomDetailManager(
|
|||||||
val profileImageUrl: String,
|
val profileImageUrl: String,
|
||||||
val isCreator: Boolean
|
val isCreator: Boolean
|
||||||
) {
|
) {
|
||||||
constructor(member: Member, cloudFrontHost: String) : this(
|
constructor(member: Member, cloudFrontHost: String, nickname: String) : this(
|
||||||
id = member.id!!,
|
id = member.id!!,
|
||||||
nickname = member.nickname,
|
nickname = nickname,
|
||||||
introduce = member.introduce,
|
introduce = member.introduce,
|
||||||
youtubeUrl = member.youtubeUrl,
|
youtubeUrl = member.youtubeUrl,
|
||||||
instagramUrl = member.instagramUrl,
|
instagramUrl = member.instagramUrl,
|
||||||
|
|||||||
+1
-3
@@ -1,14 +1,12 @@
|
|||||||
package kr.co.vividnext.sodalive.live.room.like
|
package kr.co.vividnext.sodalive.live.room.like
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
|
||||||
|
|
||||||
data class GetLiveRoomHeartListResponse(
|
data class GetLiveRoomHeartListResponse(
|
||||||
val heartList: List<GetLiveRoomHeartListItem>,
|
val heartList: List<GetLiveRoomHeartListItem>,
|
||||||
val totalCount: Int,
|
val totalCount: Int,
|
||||||
val totalHeart: Int
|
val totalHeart: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
data class GetLiveRoomHeartListItem @QueryProjection constructor(
|
data class GetLiveRoomHeartListItem(
|
||||||
val profileImage: String,
|
val profileImage: String,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val heart: Int
|
val heart: Int
|
||||||
|
|||||||
+24
-5
@@ -1,12 +1,17 @@
|
|||||||
package kr.co.vividnext.sodalive.live.room.visit
|
package kr.co.vividnext.sodalive.live.room.visit
|
||||||
|
|
||||||
|
import com.querydsl.core.types.Projections
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
|
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@@ -16,11 +21,15 @@ interface LiveRoomVisitRepository : JpaRepository<LiveRoomVisit, Long>, LiveRoom
|
|||||||
interface LiveRoomVisitQueryRepository {
|
interface LiveRoomVisitQueryRepository {
|
||||||
fun findByRoomIdAndMemberId(roomId: Long, memberId: Long): LiveRoomVisit?
|
fun findByRoomIdAndMemberId(roomId: Long, memberId: Long): LiveRoomVisit?
|
||||||
fun findFirstByMemberIdOrderByUpdatedAtDesc(memberId: Long): LiveRoomVisit?
|
fun findFirstByMemberIdOrderByUpdatedAtDesc(memberId: Long): LiveRoomVisit?
|
||||||
fun getRecentVisitRoomUsers(roomId: Long, member: Member): List<Member>
|
fun getRecentVisitRoomUsers(roomId: Long, member: Member, lang: Lang): List<GetRoomDetailUser>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class LiveRoomVisitQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveRoomVisitQueryRepository {
|
class LiveRoomVisitQueryRepositoryImpl(
|
||||||
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) : LiveRoomVisitQueryRepository {
|
||||||
override fun findByRoomIdAndMemberId(roomId: Long, memberId: Long): LiveRoomVisit? {
|
override fun findByRoomIdAndMemberId(roomId: Long, memberId: Long): LiveRoomVisit? {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(liveRoomVisit)
|
.selectFrom(liveRoomVisit)
|
||||||
@@ -44,7 +53,9 @@ class LiveRoomVisitQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
|
|||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRecentVisitRoomUsers(roomId: Long, member: Member): List<Member> {
|
override fun getRecentVisitRoomUsers(roomId: Long, member: Member, lang: Lang): List<GetRoomDetailUser> {
|
||||||
|
val visitor = QMember.member
|
||||||
|
val visitorName = CreatorNameJoin(visitor, lang, "recentLiveRoomVisitorName")
|
||||||
var where = liveRoomVisit.room.id.eq(roomId)
|
var where = liveRoomVisit.room.id.eq(roomId)
|
||||||
.and(liveRoomVisit.member.isActive.isTrue)
|
.and(liveRoomVisit.member.isActive.isTrue)
|
||||||
.and(liveRoomVisit.member.id.ne(member.id))
|
.and(liveRoomVisit.member.id.ne(member.id))
|
||||||
@@ -58,10 +69,18 @@ class LiveRoomVisitQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(QMember.member)
|
.select(
|
||||||
|
Projections.constructor(
|
||||||
|
GetRoomDetailUser::class.java,
|
||||||
|
visitor.id,
|
||||||
|
visitorName.displayName,
|
||||||
|
visitor.profileImage.coalesce("profile/default-profile.png").prepend("/").prepend(cloudFrontHost)
|
||||||
|
)
|
||||||
|
)
|
||||||
.from(liveRoomVisit)
|
.from(liveRoomVisit)
|
||||||
.innerJoin(liveRoomVisit.member, QMember.member)
|
.innerJoin(liveRoomVisit.member, visitor)
|
||||||
.innerJoin(liveRoomVisit.room, liveRoom)
|
.innerJoin(liveRoomVisit.room, liveRoom)
|
||||||
|
.leftJoin(visitorName.name).on(visitorName.condition)
|
||||||
.where(where)
|
.where(where)
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package kr.co.vividnext.sodalive.live.room.visit
|
package kr.co.vividnext.sodalive.live.room.visit
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.beans.factory.annotation.Value
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -12,9 +12,7 @@ import java.time.LocalDateTime
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
class LiveRoomVisitService(
|
class LiveRoomVisitService(
|
||||||
private val repository: LiveRoomVisitRepository,
|
private val repository: LiveRoomVisitRepository,
|
||||||
|
private val langContext: LangContext
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
|
||||||
private val cloudFrontHost: String
|
|
||||||
) {
|
) {
|
||||||
@Transactional
|
@Transactional
|
||||||
fun roomVisit(room: LiveRoom, member: Member) {
|
fun roomVisit(room: LiveRoom, member: Member) {
|
||||||
@@ -34,9 +32,6 @@ class LiveRoomVisitService(
|
|||||||
val roomVisit = repository.findFirstByMemberIdOrderByUpdatedAtDesc(member.id!!)
|
val roomVisit = repository.findFirstByMemberIdOrderByUpdatedAtDesc(member.id!!)
|
||||||
?: return emptyList()
|
?: return emptyList()
|
||||||
|
|
||||||
return repository.getRecentVisitRoomUsers(roomVisit.room!!.id!!, member)
|
return repository.getRecentVisitRoomUsers(roomVisit.room!!.id!!, member, langContext.lang)
|
||||||
.asSequence()
|
|
||||||
.map { GetRoomDetailUser(it, cloudFrontHost) }
|
|
||||||
.toList()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package kr.co.vividnext.sodalive.member
|
package kr.co.vividnext.sodalive.member
|
||||||
|
|
||||||
|
import com.querydsl.core.types.Projections
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.fcm.PushTokenInfo
|
import kr.co.vividnext.sodalive.fcm.PushTokenInfo
|
||||||
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
|
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
|
||||||
import kr.co.vividnext.sodalive.fcm.QPushTokenInfo
|
import kr.co.vividnext.sodalive.fcm.QPushTokenInfo
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.live.reservation.QLiveReservation.liveReservation
|
import kr.co.vividnext.sodalive.live.reservation.QLiveReservation.liveReservation
|
||||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
|
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.auth.QAuth.auth
|
import kr.co.vividnext.sodalive.member.auth.QAuth.auth
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
@@ -15,6 +18,8 @@ import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollow
|
|||||||
import kr.co.vividnext.sodalive.member.nickname.QNicknameChangeLog.nicknameChangeLog
|
import kr.co.vividnext.sodalive.member.nickname.QNicknameChangeLog.nicknameChangeLog
|
||||||
import kr.co.vividnext.sodalive.member.notification.QMemberNotification.memberNotification
|
import kr.co.vividnext.sodalive.member.notification.QMemberNotification.memberNotification
|
||||||
import kr.co.vividnext.sodalive.message.QMessage.message
|
import kr.co.vividnext.sodalive.message.QMessage.message
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.creatorNameContains
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.Lock
|
import org.springframework.data.jpa.repository.Lock
|
||||||
@@ -38,7 +43,7 @@ interface MemberRepository : JpaRepository<Member, Long>, MemberQueryRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface MemberQueryRepository {
|
interface MemberQueryRepository {
|
||||||
fun findByNicknameAndOtherCondition(nickname: String, member: Member): List<Member>
|
fun findByNicknameAndOtherCondition(nickname: String, member: Member, lang: Lang? = null): List<GetRoomDetailUser>
|
||||||
fun findCreatorByIdOrNull(memberId: Long): Member?
|
fun findCreatorByIdOrNull(memberId: Long): Member?
|
||||||
fun getAllRecipientPushTokens(isAuth: Boolean?): List<PushTokenInfo>
|
fun getAllRecipientPushTokens(isAuth: Boolean?): List<PushTokenInfo>
|
||||||
fun getCreateLiveRoomNotificationRecipientPushTokens(
|
fun getCreateLiveRoomNotificationRecipientPushTokens(
|
||||||
@@ -70,7 +75,7 @@ interface MemberQueryRepository {
|
|||||||
fun getPushTokenFromReservationList(roomId: Long): List<PushTokenInfo>
|
fun getPushTokenFromReservationList(roomId: Long): List<PushTokenInfo>
|
||||||
fun getAuditionNoticeRecipientPushTokens(isAuth: Boolean): List<PushTokenInfo>
|
fun getAuditionNoticeRecipientPushTokens(isAuth: Boolean): List<PushTokenInfo>
|
||||||
|
|
||||||
fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse
|
fun getMemberProfile(memberId: Long, myMemberId: Long, lang: Lang = Lang.KO): GetMemberProfileResponse
|
||||||
|
|
||||||
fun existsByNickname(nickname: String): Boolean
|
fun existsByNickname(nickname: String): Boolean
|
||||||
fun findNicknamesWithPrefix(prefix: String): List<String>
|
fun findNicknamesWithPrefix(prefix: String): List<String>
|
||||||
@@ -86,8 +91,10 @@ class MemberQueryRepositoryImpl(
|
|||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) : MemberQueryRepository {
|
) : MemberQueryRepository {
|
||||||
override fun findByNicknameAndOtherCondition(nickname: String, member: Member): List<Member> {
|
override fun findByNicknameAndOtherCondition(nickname: String, member: Member, lang: Lang?): List<GetRoomDetailUser> {
|
||||||
var where = QMember.member.nickname.containsIgnoreCase(nickname)
|
val creatorName = lang?.let { CreatorNameJoin(QMember.member, it, "memberSearchName") }
|
||||||
|
val displayName = creatorName?.displayName ?: QMember.member.nickname
|
||||||
|
var where = creatorNameContains(QMember.member, nickname)
|
||||||
.and(QMember.member.id.ne(member.id))
|
.and(QMember.member.id.ne(member.id))
|
||||||
.and(QMember.member.role.ne(MemberRole.ADMIN))
|
.and(QMember.member.role.ne(MemberRole.ADMIN))
|
||||||
.and(QMember.member.role.ne(MemberRole.AGENT))
|
.and(QMember.member.role.ne(MemberRole.AGENT))
|
||||||
@@ -97,10 +104,19 @@ class MemberQueryRepositoryImpl(
|
|||||||
where = where.and(QMember.member.role.ne(MemberRole.USER))
|
where = where.and(QMember.member.role.ne(MemberRole.USER))
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
val query = queryFactory
|
||||||
.selectFrom(QMember.member)
|
.select(
|
||||||
|
Projections.constructor(
|
||||||
|
GetRoomDetailUser::class.java,
|
||||||
|
QMember.member.id,
|
||||||
|
displayName,
|
||||||
|
QMember.member.profileImage.coalesce("profile/default-profile.png").prepend("/").prepend(cloudFrontHost)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(QMember.member)
|
||||||
.where(where)
|
.where(where)
|
||||||
.fetch()
|
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
return query.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findCreatorByIdOrNull(memberId: Long): Member? {
|
override fun findCreatorByIdOrNull(memberId: Long): Member? {
|
||||||
@@ -499,17 +515,19 @@ class MemberQueryRepositoryImpl(
|
|||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse {
|
override fun getMemberProfile(memberId: Long, myMemberId: Long, lang: Lang): GetMemberProfileResponse {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "profileMemberName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QGetMemberProfileResponse(
|
QGetMemberProfileResponse(
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
blockMember.id.isNotNull
|
blockMember.id.isNotNull
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(blockMember)
|
.leftJoin(blockMember)
|
||||||
.on(
|
.on(
|
||||||
member.id.eq(blockMember.blockedMember.id)
|
member.id.eq(blockMember.blockedMember.id)
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import kr.co.vividnext.sodalive.member.token.MemberTokenRepository
|
|||||||
import kr.co.vividnext.sodalive.point.MemberPointRepository
|
import kr.co.vividnext.sodalive.point.MemberPointRepository
|
||||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
import kr.co.vividnext.sodalive.utils.generatePassword
|
import kr.co.vividnext.sodalive.utils.generatePassword
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import kr.co.vividnext.sodalive.v2.home.following.port.out.HomeFollowingNewsInboxPort
|
import kr.co.vividnext.sodalive.v2.home.following.port.out.HomeFollowingNewsInboxPort
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.cache.CacheManager
|
import org.springframework.cache.CacheManager
|
||||||
@@ -105,6 +106,7 @@ class MemberService(
|
|||||||
|
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
private val countryContext: CountryContext,
|
private val countryContext: CountryContext,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
|
|
||||||
@@ -308,7 +310,8 @@ class MemberService(
|
|||||||
).sumOf { it.point }
|
).sumOf { it.point }
|
||||||
|
|
||||||
return MyPageResponse(
|
return MyPageResponse(
|
||||||
nickname = member.nickname,
|
nickname = localizedNameService.findLocalizedNames(listOf(member.id!!), langContext.lang)[member.id]
|
||||||
|
?: member.nickname,
|
||||||
profileUrl = if (member.profileImage != null) {
|
profileUrl = if (member.profileImage != null) {
|
||||||
"$cloudFrontHost/${member.profileImage}"
|
"$cloudFrontHost/${member.profileImage}"
|
||||||
} else {
|
} else {
|
||||||
@@ -542,8 +545,7 @@ class MemberService(
|
|||||||
|
|
||||||
fun getBlockedMemberList(memberId: Long, offset: Long, limit: Long): GetBlockedMemberListResponse {
|
fun getBlockedMemberList(memberId: Long, offset: Long, limit: Long): GetBlockedMemberListResponse {
|
||||||
val totalCount = blockMemberRepository.getBlockedMemberTotalCount(memberId)
|
val totalCount = blockMemberRepository.getBlockedMemberTotalCount(memberId)
|
||||||
val items = blockMemberRepository.getBlockedMemberList(offset = offset, limit = limit, memberId = memberId)
|
val items = blockMemberRepository.getBlockedMemberList(offset, limit, memberId, langContext.lang)
|
||||||
|
|
||||||
return GetBlockedMemberListResponse(totalCount = totalCount, items = items)
|
return GetBlockedMemberListResponse(totalCount = totalCount, items = items)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,14 +612,11 @@ class MemberService(
|
|||||||
throw SodaException(messageKey = "member.validation.nickname_min_length")
|
throw SodaException(messageKey = "member.validation.nickname_min_length")
|
||||||
}
|
}
|
||||||
|
|
||||||
return repository.findByNicknameAndOtherCondition(nickname, member)
|
return repository.findByNicknameAndOtherCondition(nickname, member, langContext.lang)
|
||||||
.filter {
|
.filter {
|
||||||
!blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.id!!) &&
|
!blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.id!!) &&
|
||||||
!blockMemberRepository.isBlocked(blockedMemberId = it.id!!, memberId = member.id!!)
|
!blockMemberRepository.isBlocked(blockedMemberId = it.id!!, memberId = member.id!!)
|
||||||
}
|
}
|
||||||
.map {
|
|
||||||
GetRoomDetailUser(it, cloudFrontHost)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -837,7 +836,7 @@ class MemberService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse {
|
fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse {
|
||||||
return repository.getMemberProfile(memberId, myMemberId)
|
return repository.getMemberProfile(memberId, myMemberId, langContext.lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatMessage(key: String, vararg args: Any): String {
|
private fun formatMessage(key: String, vararg args: Any): String {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.member.block
|
package kr.co.vividnext.sodalive.member.block
|
||||||
|
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
@@ -14,7 +16,7 @@ interface BlockMemberQueryRepository {
|
|||||||
fun getBlockAccount(blockedMemberId: Long, memberId: Long): BlockMember?
|
fun getBlockAccount(blockedMemberId: Long, memberId: Long): BlockMember?
|
||||||
fun isBlocked(blockedMemberId: Long, memberId: Long): Boolean
|
fun isBlocked(blockedMemberId: Long, memberId: Long): Boolean
|
||||||
fun getBlockedMemberIdList(memberId: Long): List<Long>
|
fun getBlockedMemberIdList(memberId: Long): List<Long>
|
||||||
fun getBlockedMemberList(offset: Long, limit: Long, memberId: Long): List<GetBlockedMemberListItem>
|
fun getBlockedMemberList(offset: Long, limit: Long, memberId: Long, lang: Lang = Lang.KO): List<GetBlockedMemberListItem>
|
||||||
fun getBlockedMemberTotalCount(memberId: Long): Int
|
fun getBlockedMemberTotalCount(memberId: Long): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,18 +63,20 @@ class BlockMemberQueryRepositoryImpl(
|
|||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBlockedMemberList(offset: Long, limit: Long, memberId: Long): List<GetBlockedMemberListItem> {
|
override fun getBlockedMemberList(offset: Long, limit: Long, memberId: Long, lang: Lang): List<GetBlockedMemberListItem> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "blockedMemberName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QGetBlockedMemberListItem(
|
QGetBlockedMemberListItem(
|
||||||
blockMember.blockedMember.id,
|
blockMember.blockedMember.id,
|
||||||
blockMember.blockedMember.nickname,
|
creatorName.displayName,
|
||||||
blockMember.blockedMember.profileImage.prepend("/").prepend(cloudFrontHost),
|
blockMember.blockedMember.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||||
blockMember.isActive
|
blockMember.isActive
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(blockMember)
|
.from(blockMember)
|
||||||
.innerJoin(blockMember.blockedMember, member)
|
.innerJoin(blockMember.blockedMember, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
blockMember.member.id.eq(memberId)
|
blockMember.member.id.eq(memberId)
|
||||||
.and(blockMember.isActive.isTrue)
|
.and(blockMember.isActive.isTrue)
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ 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.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
||||||
|
import kr.co.vividnext.sodalive.explorer.GetExplorerSectionCreatorResponse
|
||||||
import kr.co.vividnext.sodalive.explorer.QCreatorRanking.creatorRanking
|
import kr.co.vividnext.sodalive.explorer.QCreatorRanking.creatorRanking
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||||
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
||||||
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
|
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -36,7 +38,9 @@ class RankingRepository(
|
|||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val imageHost: String
|
private val imageHost: String
|
||||||
) {
|
) {
|
||||||
fun getCreatorRankings(memberId: Long? = null): List<Member> {
|
fun getCreatorRankings(memberId: Long? = null, lang: Lang = Lang.KO): List<GetExplorerSectionCreatorResponse> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "rankingCreatorName")
|
||||||
|
val displayName = creatorName.displayName
|
||||||
val blockMemberCondition = if (memberId != null) {
|
val blockMemberCondition = if (memberId != null) {
|
||||||
blockMember.isActive.isTrue
|
blockMember.isActive.isTrue
|
||||||
.and(
|
.and(
|
||||||
@@ -52,11 +56,12 @@ class RankingRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var select = queryFactory
|
var select = queryFactory
|
||||||
.select(member)
|
.select(member, displayName)
|
||||||
.from(creatorRanking)
|
.from(creatorRanking)
|
||||||
.innerJoin(creatorRanking.member, member)
|
.innerJoin(creatorRanking.member, member)
|
||||||
.leftJoin(member.tags, memberCreatorTag).fetchJoin()
|
.leftJoin(member.tags, memberCreatorTag).fetchJoin()
|
||||||
.leftJoin(memberCreatorTag.tag, creatorTag).fetchJoin()
|
.leftJoin(memberCreatorTag.tag, creatorTag).fetchJoin()
|
||||||
|
select.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
|
||||||
if (memberId != null) {
|
if (memberId != null) {
|
||||||
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
||||||
@@ -69,6 +74,9 @@ class RankingRepository(
|
|||||||
return select
|
return select
|
||||||
.orderBy(creatorRanking.ranking.asc())
|
.orderBy(creatorRanking.ranking.asc())
|
||||||
.fetch()
|
.fetch()
|
||||||
|
.map { row ->
|
||||||
|
row.get(member)!!.toExplorerSectionCreator(imageHost).copy(nickname = row.get(displayName)!!)
|
||||||
|
}
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +92,8 @@ class RankingRepository(
|
|||||||
theme: String = "",
|
theme: String = "",
|
||||||
locale: String? = null
|
locale: String? = null
|
||||||
): List<GetAudioContentRankingItem> {
|
): List<GetAudioContentRankingItem> {
|
||||||
|
val creatorName = locale?.let { CreatorNameJoin(member, Lang.fromCode(it), "rankingContentCreatorName") }
|
||||||
|
val displayName = creatorName?.displayName ?: member.nickname
|
||||||
val blockMemberCondition = if (memberId != null) {
|
val blockMemberCondition = if (memberId != null) {
|
||||||
blockMember.isActive.isTrue
|
blockMember.isActive.isTrue
|
||||||
.and(
|
.and(
|
||||||
@@ -148,7 +158,7 @@ class RankingRepository(
|
|||||||
audioContent.price,
|
audioContent.price,
|
||||||
audioContent.duration,
|
audioContent.duration,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
displayName,
|
||||||
audioContent.isPointAvailable,
|
audioContent.isPointAvailable,
|
||||||
creatorProfileImageUrl
|
creatorProfileImageUrl
|
||||||
)
|
)
|
||||||
@@ -199,6 +209,7 @@ class RankingRepository(
|
|||||||
select = select.leftJoin(contentTranslation)
|
select = select.leftJoin(contentTranslation)
|
||||||
.on(contentTranslation.contentId.eq(audioContent.id).and(contentTranslation.locale.eq(locale)))
|
.on(contentTranslation.contentId.eq(audioContent.id).and(contentTranslation.locale.eq(locale)))
|
||||||
}
|
}
|
||||||
|
if (creatorName != null) select.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
|
||||||
if (memberId != null) {
|
if (memberId != null) {
|
||||||
where = where.and(blockMember.id.isNull)
|
where = where.and(blockMember.id.isNull)
|
||||||
@@ -269,6 +280,7 @@ class RankingRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val results = select
|
val results = select
|
||||||
|
.groupBy(member.id, displayName)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.fetch()
|
.fetch()
|
||||||
@@ -283,7 +295,7 @@ class RankingRepository(
|
|||||||
val price = row.get(audioContent.price) ?: 0
|
val price = row.get(audioContent.price) ?: 0
|
||||||
val duration = row.get(audioContent.duration) ?: ""
|
val duration = row.get(audioContent.duration) ?: ""
|
||||||
val creatorId = row.get(member.id)!!
|
val creatorId = row.get(member.id)!!
|
||||||
val creatorNickname = row.get(member.nickname)!!
|
val creatorNickname = row.get(displayName)!!
|
||||||
val isPointAvailable = row.get(audioContent.isPointAvailable) ?: false
|
val isPointAvailable = row.get(audioContent.isPointAvailable) ?: false
|
||||||
val creatorProfileImageUrlValue = row.get(creatorProfileImageUrl)!!
|
val creatorProfileImageUrlValue = row.get(creatorProfileImageUrl)!!
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class RankingService(
|
|||||||
fun getCreatorRanking(memberId: Long?, rankingDate: String): GetExplorerSectionResponse {
|
fun getCreatorRanking(memberId: Long?, rankingDate: String): GetExplorerSectionResponse {
|
||||||
val creatorRankings = repository
|
val creatorRankings = repository
|
||||||
.getCreatorRankings(memberId = memberId)
|
.getCreatorRankings(memberId = memberId)
|
||||||
.map { it.toExplorerSectionCreator(imageHost) }
|
|
||||||
|
|
||||||
return GetExplorerSectionResponse(
|
return GetExplorerSectionResponse(
|
||||||
title = "인기 크리에이터",
|
title = "인기 크리에이터",
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentThe
|
|||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
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.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword
|
import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||||
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
||||||
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
|
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.creatorNameContains
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@@ -50,7 +53,7 @@ class SearchRepository(
|
|||||||
.and(member.role.eq(MemberRole.CREATOR))
|
.and(member.role.eq(MemberRole.CREATOR))
|
||||||
.and(creatorTag.isActive.isTrue.or(creatorTag.id.isNull))
|
.and(creatorTag.isActive.isTrue.or(creatorTag.id.isNull))
|
||||||
.and(
|
.and(
|
||||||
member.nickname.containsIgnoreCase(keyword)
|
creatorNameContains(member, keyword)
|
||||||
.or(member.introduce.containsIgnoreCase(keyword))
|
.or(member.introduce.containsIgnoreCase(keyword))
|
||||||
.or(creatorTag.tag.containsIgnoreCase(keyword))
|
.or(creatorTag.tag.containsIgnoreCase(keyword))
|
||||||
)
|
)
|
||||||
@@ -65,7 +68,8 @@ class SearchRepository(
|
|||||||
keyword: String,
|
keyword: String,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
lang: Lang = Lang.KO
|
||||||
): List<SearchResponseItem> {
|
): List<SearchResponseItem> {
|
||||||
val blockMemberCondition = blockMember.isActive.isTrue
|
val blockMemberCondition = blockMember.isActive.isTrue
|
||||||
.and(
|
.and(
|
||||||
@@ -77,16 +81,18 @@ class SearchRepository(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "searchCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QSearchResponseItem(
|
QSearchResponseItem(
|
||||||
member.id,
|
member.id,
|
||||||
member.profileImage.prepend("/").prepend(imageHost),
|
member.profileImage.prepend("/").prepend(imageHost),
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.nickname
|
creatorName.displayName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(memberCreatorTag).on(memberCreatorTag.member.id.eq(member.id))
|
.leftJoin(memberCreatorTag).on(memberCreatorTag.member.id.eq(member.id))
|
||||||
.leftJoin(creatorTag).on(memberCreatorTag.tag.id.eq(creatorTag.id))
|
.leftJoin(creatorTag).on(memberCreatorTag.tag.id.eq(creatorTag.id))
|
||||||
.leftJoin(blockMember).on(blockMemberCondition)
|
.leftJoin(blockMember).on(blockMemberCondition)
|
||||||
@@ -95,13 +101,13 @@ class SearchRepository(
|
|||||||
.and(member.role.eq(MemberRole.CREATOR))
|
.and(member.role.eq(MemberRole.CREATOR))
|
||||||
.and(creatorTag.isActive.isTrue.or(creatorTag.id.isNull))
|
.and(creatorTag.isActive.isTrue.or(creatorTag.id.isNull))
|
||||||
.and(
|
.and(
|
||||||
member.nickname.containsIgnoreCase(keyword)
|
creatorNameContains(member, keyword)
|
||||||
.or(member.introduce.containsIgnoreCase(keyword))
|
.or(member.introduce.containsIgnoreCase(keyword))
|
||||||
.or(creatorTag.tag.containsIgnoreCase(keyword))
|
.or(creatorTag.tag.containsIgnoreCase(keyword))
|
||||||
)
|
)
|
||||||
.and(blockMember.id.isNull)
|
.and(blockMember.id.isNull)
|
||||||
)
|
)
|
||||||
.groupBy(member.id)
|
.groupBy(member.id, creatorName.name.displayName)
|
||||||
.orderBy(member.id.desc())
|
.orderBy(member.id.desc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@@ -131,7 +137,7 @@ class SearchRepository(
|
|||||||
.and(audioContent.duration.isNotNull)
|
.and(audioContent.duration.isNotNull)
|
||||||
.and(
|
.and(
|
||||||
audioContent.title.containsIgnoreCase(keyword)
|
audioContent.title.containsIgnoreCase(keyword)
|
||||||
.or(member.nickname.containsIgnoreCase(keyword))
|
.or(creatorNameContains(member, keyword))
|
||||||
.or(audioContent.detail.containsIgnoreCase(keyword))
|
.or(audioContent.detail.containsIgnoreCase(keyword))
|
||||||
.or(audioContent.theme.theme.containsIgnoreCase(keyword))
|
.or(audioContent.theme.theme.containsIgnoreCase(keyword))
|
||||||
.or(hashTag.tag.containsIgnoreCase(keyword).and(audioContentHashTag.isActive.isTrue))
|
.or(hashTag.tag.containsIgnoreCase(keyword).and(audioContentHashTag.isActive.isTrue))
|
||||||
@@ -177,7 +183,8 @@ class SearchRepository(
|
|||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
lang: Lang = Lang.KO
|
||||||
): List<SearchResponseItem> {
|
): List<SearchResponseItem> {
|
||||||
val blockMemberCondition = blockMember.isActive.isTrue
|
val blockMemberCondition = blockMember.isActive.isTrue
|
||||||
.and(
|
.and(
|
||||||
@@ -196,7 +203,7 @@ class SearchRepository(
|
|||||||
.and(audioContent.duration.isNotNull)
|
.and(audioContent.duration.isNotNull)
|
||||||
.and(
|
.and(
|
||||||
audioContent.title.containsIgnoreCase(keyword)
|
audioContent.title.containsIgnoreCase(keyword)
|
||||||
.or(member.nickname.containsIgnoreCase(keyword))
|
.or(creatorNameContains(member, keyword))
|
||||||
.or(audioContent.detail.containsIgnoreCase(keyword))
|
.or(audioContent.detail.containsIgnoreCase(keyword))
|
||||||
.or(audioContent.theme.theme.containsIgnoreCase(keyword))
|
.or(audioContent.theme.theme.containsIgnoreCase(keyword))
|
||||||
.or(hashTag.tag.containsIgnoreCase(keyword).and(audioContentHashTag.isActive.isTrue))
|
.or(hashTag.tag.containsIgnoreCase(keyword).and(audioContentHashTag.isActive.isTrue))
|
||||||
@@ -222,23 +229,25 @@ class SearchRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "searchContentCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QSearchResponseItem(
|
QSearchResponseItem(
|
||||||
audioContent.id,
|
audioContent.id,
|
||||||
audioContent.coverImage.prepend("/").prepend(imageHost),
|
audioContent.coverImage.prepend("/").prepend(imageHost),
|
||||||
audioContent.title,
|
audioContent.title,
|
||||||
audioContent.member.nickname
|
creatorName.displayName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(audioContent)
|
.from(audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(audioContentTheme).on(audioContentTheme.id.eq(audioContent.theme.id))
|
.leftJoin(audioContentTheme).on(audioContentTheme.id.eq(audioContent.theme.id))
|
||||||
.leftJoin(audioContentHashTag).on(audioContentHashTag.audioContent.id.eq(audioContent.id))
|
.leftJoin(audioContentHashTag).on(audioContentHashTag.audioContent.id.eq(audioContent.id))
|
||||||
.leftJoin(hashTag).on(audioContentHashTag.hashTag.id.eq(hashTag.id))
|
.leftJoin(hashTag).on(audioContentHashTag.hashTag.id.eq(hashTag.id))
|
||||||
.leftJoin(blockMember).on(blockMemberCondition)
|
.leftJoin(blockMember).on(blockMemberCondition)
|
||||||
.where(where)
|
.where(where)
|
||||||
.groupBy(audioContent.id)
|
.groupBy(audioContent.id, member.id, creatorName.name.displayName)
|
||||||
.orderBy(audioContent.id.desc())
|
.orderBy(audioContent.id.desc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@@ -270,7 +279,7 @@ class SearchRepository(
|
|||||||
.and(audioContent.limited.isNull)
|
.and(audioContent.limited.isNull)
|
||||||
.and(
|
.and(
|
||||||
series.title.containsIgnoreCase(keyword)
|
series.title.containsIgnoreCase(keyword)
|
||||||
.or(member.nickname.containsIgnoreCase(keyword))
|
.or(creatorNameContains(member, keyword))
|
||||||
.or(series.introduction.containsIgnoreCase(keyword))
|
.or(series.introduction.containsIgnoreCase(keyword))
|
||||||
.or(seriesGenre.genre.containsIgnoreCase(keyword))
|
.or(seriesGenre.genre.containsIgnoreCase(keyword))
|
||||||
.or(hashTag.tag.containsIgnoreCase(keyword).and(seriesKeyword.isActive.isTrue))
|
.or(hashTag.tag.containsIgnoreCase(keyword).and(seriesKeyword.isActive.isTrue))
|
||||||
@@ -319,7 +328,8 @@ class SearchRepository(
|
|||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
lang: Lang = Lang.KO
|
||||||
): List<SearchResponseItem> {
|
): List<SearchResponseItem> {
|
||||||
val blockMemberCondition = blockMember.isActive.isTrue
|
val blockMemberCondition = blockMember.isActive.isTrue
|
||||||
.and(
|
.and(
|
||||||
@@ -340,7 +350,7 @@ class SearchRepository(
|
|||||||
.and(audioContent.limited.isNull)
|
.and(audioContent.limited.isNull)
|
||||||
.and(
|
.and(
|
||||||
series.title.containsIgnoreCase(keyword)
|
series.title.containsIgnoreCase(keyword)
|
||||||
.or(member.nickname.containsIgnoreCase(keyword))
|
.or(creatorNameContains(member, keyword))
|
||||||
.or(series.introduction.containsIgnoreCase(keyword))
|
.or(series.introduction.containsIgnoreCase(keyword))
|
||||||
.or(seriesGenre.genre.containsIgnoreCase(keyword))
|
.or(seriesGenre.genre.containsIgnoreCase(keyword))
|
||||||
.or(hashTag.tag.containsIgnoreCase(keyword).and(seriesKeyword.isActive.isTrue))
|
.or(hashTag.tag.containsIgnoreCase(keyword).and(seriesKeyword.isActive.isTrue))
|
||||||
@@ -367,25 +377,27 @@ class SearchRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "searchSeriesCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QSearchResponseItem(
|
QSearchResponseItem(
|
||||||
series.id,
|
series.id,
|
||||||
series.coverImage.prepend("/").prepend(imageHost),
|
series.coverImage.prepend("/").prepend(imageHost),
|
||||||
series.title,
|
series.title,
|
||||||
series.member.nickname
|
creatorName.displayName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(seriesContent)
|
.from(seriesContent)
|
||||||
.innerJoin(seriesContent.series, series)
|
.innerJoin(seriesContent.series, series)
|
||||||
.innerJoin(seriesContent.content, audioContent)
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
.innerJoin(series.member, member)
|
.innerJoin(series.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(seriesGenre).on(seriesGenre.id.eq(series.genre.id))
|
.leftJoin(seriesGenre).on(seriesGenre.id.eq(series.genre.id))
|
||||||
.leftJoin(seriesKeyword).on(seriesKeyword.series.id.eq(series.id))
|
.leftJoin(seriesKeyword).on(seriesKeyword.series.id.eq(series.id))
|
||||||
.leftJoin(hashTag).on(hashTag.id.eq(seriesKeyword.keyword.id))
|
.leftJoin(hashTag).on(hashTag.id.eq(seriesKeyword.keyword.id))
|
||||||
.leftJoin(blockMember).on(blockMemberCondition)
|
.leftJoin(blockMember).on(blockMemberCondition)
|
||||||
.where(where)
|
.where(where)
|
||||||
.groupBy(series.id, series.orders)
|
.groupBy(series.id, series.orders, member.id, creatorName.name.displayName)
|
||||||
.orderBy(series.orders.asc())
|
.orderBy(series.orders.asc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package kr.co.vividnext.sodalive.search
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.content.ContentType
|
import kr.co.vividnext.sodalive.content.ContentType
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class SearchService(private val repository: SearchRepository) {
|
class SearchService(private val repository: SearchRepository, private val langContext: LangContext) {
|
||||||
fun searchUnified(
|
fun searchUnified(
|
||||||
keyword: String,
|
keyword: String,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
@@ -16,7 +17,8 @@ class SearchService(private val repository: SearchRepository) {
|
|||||||
keyword = keyword,
|
keyword = keyword,
|
||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 3
|
limit = 3,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
it.type = SearchResponseType.CREATOR
|
it.type = SearchResponseType.CREATOR
|
||||||
@@ -29,7 +31,8 @@ class SearchService(private val repository: SearchRepository) {
|
|||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 3
|
limit = 3,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
it.type = SearchResponseType.CONTENT
|
it.type = SearchResponseType.CONTENT
|
||||||
@@ -42,7 +45,8 @@ class SearchService(private val repository: SearchRepository) {
|
|||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 3
|
limit = 3,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
it.type = SearchResponseType.SERIES
|
it.type = SearchResponseType.SERIES
|
||||||
@@ -67,7 +71,8 @@ class SearchService(private val repository: SearchRepository) {
|
|||||||
keyword = keyword,
|
keyword = keyword,
|
||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
it.type = SearchResponseType.CREATOR
|
it.type = SearchResponseType.CREATOR
|
||||||
@@ -98,7 +103,8 @@ class SearchService(private val repository: SearchRepository) {
|
|||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
it.type = SearchResponseType.CONTENT
|
it.type = SearchResponseType.CONTENT
|
||||||
@@ -129,7 +135,8 @@ class SearchService(private val repository: SearchRepository) {
|
|||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
it.type = SearchResponseType.SERIES
|
it.type = SearchResponseType.SERIES
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import kr.co.vividnext.sodalive.query.recommend.RecommendChannelQueryService
|
|||||||
import kr.co.vividnext.sodalive.rank.ContentRankingSortType
|
import kr.co.vividnext.sodalive.rank.ContentRankingSortType
|
||||||
import kr.co.vividnext.sodalive.rank.RankingRepository
|
import kr.co.vividnext.sodalive.rank.RankingRepository
|
||||||
import kr.co.vividnext.sodalive.rank.RankingService
|
import kr.co.vividnext.sodalive.rank.RankingService
|
||||||
|
import kr.co.vividnext.sodalive.support.originalLocalizedNameService
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -68,6 +69,7 @@ class HomeServiceTest {
|
|||||||
rankingRepository = rankingRepository,
|
rankingRepository = rankingRepository,
|
||||||
explorerQueryRepository = explorerQueryRepository,
|
explorerQueryRepository = explorerQueryRepository,
|
||||||
langContext = LangContext().apply { setLang(Lang.JA) },
|
langContext = LangContext().apply { setLang(Lang.JA) },
|
||||||
|
localizedNameService = originalLocalizedNameService(),
|
||||||
memberContentPreferenceService = Mockito.mock(
|
memberContentPreferenceService = Mockito.mock(
|
||||||
kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService::class.java
|
kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService::class.java
|
||||||
),
|
),
|
||||||
@@ -99,7 +101,7 @@ class HomeServiceTest {
|
|||||||
|
|
||||||
Mockito.doReturn(emptyList<Any>()).`when`(liveRoomService)
|
Mockito.doReturn(emptyList<Any>()).`when`(liveRoomService)
|
||||||
.getRoomList(null, LiveRoomStatus.NOW, Pageable.ofSize(10), null, timezone)
|
.getRoomList(null, LiveRoomStatus.NOW, Pageable.ofSize(10), null, timezone)
|
||||||
Mockito.`when`(rankingRepository.getCreatorRankings(null)).thenReturn(emptyList())
|
Mockito.`when`(rankingRepository.getCreatorRankings(null, Lang.JA)).thenReturn(emptyList())
|
||||||
Mockito.`when`(
|
Mockito.`when`(
|
||||||
contentThemeService.getActiveThemeOfContent(
|
contentThemeService.getActiveThemeOfContent(
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.can
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
import kr.co.vividnext.sodalive.common.CountryContext
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
@@ -22,7 +23,7 @@ class CanServiceTest {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
repository = mock(CanRepository::class.java)
|
repository = mock(CanRepository::class.java)
|
||||||
countryContext = mock(CountryContext::class.java)
|
countryContext = mock(CountryContext::class.java)
|
||||||
service = CanService(repository, countryContext)
|
service = CanService(repository, countryContext, LangContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+328
@@ -0,0 +1,328 @@
|
|||||||
|
package kr.co.vividnext.sodalive.content
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration
|
||||||
|
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationItem
|
||||||
|
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.order.Order
|
||||||
|
import kr.co.vividnext.sodalive.content.order.OrderType
|
||||||
|
import kr.co.vividnext.sodalive.content.series.ContentSeriesRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslation
|
||||||
|
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationPayload
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslation
|
||||||
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslationPayload
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
|
||||||
|
import kr.co.vividnext.sodalive.explorer.CreatorRanking
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.member.block.BlockMember
|
||||||
|
import kr.co.vividnext.sodalive.rank.RankingRepository
|
||||||
|
import org.junit.jupiter.api.Assertions.assertAll
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@DataJpaTest(
|
||||||
|
properties = [
|
||||||
|
"spring.cache.type=none",
|
||||||
|
"spring.datasource.url=jdbc:h2:mem:legacy-content-owner-name;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
||||||
|
@Import(QueryDslConfig::class, RankingRepository::class, AudioContentCurationQueryRepository::class)
|
||||||
|
class AudioContentRepositoryLocalizedNameTest @Autowired constructor(
|
||||||
|
private val repository: AudioContentRepository,
|
||||||
|
private val seriesRepository: ContentSeriesRepository,
|
||||||
|
private val rankingRepository: RankingRepository,
|
||||||
|
private val curationRepository: AudioContentCurationQueryRepository,
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
private val jdbcTemplate: JdbcTemplate
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
@DisplayName("JA 메인 콘텐츠 조회는 콘텐츠 ID가 아닌 실제 소유자 ID로 크리에이터 이름만 대체한다")
|
||||||
|
fun shouldLocalizeCurrentOwnerNameByCreatorIdWithoutChangingContentTitleOrUserName() {
|
||||||
|
val viewer = saveMember("owner-query-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("owner-original", MemberRole.CREATOR)
|
||||||
|
val user = saveMember("user-original", MemberRole.USER)
|
||||||
|
val creatorContent = saveContent(creator, "Original audio title").takeIf { it.id != creator.id }
|
||||||
|
?: saveContent(creator, "Original audio title")
|
||||||
|
val userContent = saveContent(user, "User audio title")
|
||||||
|
saveAlias(creator, "Japanese owner")
|
||||||
|
saveAlias(user, "Must not replace USER")
|
||||||
|
entityManager.clear()
|
||||||
|
assertNotEquals(creator.id, creatorContent.id)
|
||||||
|
|
||||||
|
val items = findLatest(viewer.id!!, "ja").associateBy { it.contentId }
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals("Japanese owner", items[creatorContent.id]!!.creatorNickname) },
|
||||||
|
{ assertEquals(creator.id, items[creatorContent.id]!!.creatorId) },
|
||||||
|
{ assertEquals("Original audio title", items[creatorContent.id]!!.title) },
|
||||||
|
{ assertEquals("user-original", items[userContent.id]!!.creatorNickname) },
|
||||||
|
{ assertEquals("User audio title", items[userContent.id]!!.title) },
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
"owner-original",
|
||||||
|
findLatest(viewer.id!!, "ko").first { it.contentId == creatorContent.id }.creatorNickname
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
"owner-original",
|
||||||
|
findLatest(viewer.id!!, "en").first { it.contentId == creatorContent.id }.creatorNickname
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("메인 콘텐츠 이름 대체는 공개 필터를 유지하고 해제 후 변경된 현재 원본으로 돌아간다")
|
||||||
|
fun shouldPreserveVisibilityFiltersAndFallbackToRenamedOriginalAfterSoftClear() {
|
||||||
|
val viewer = saveMember("visibility-query-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("before-rename", MemberRole.CREATOR)
|
||||||
|
val visible = saveContent(creator, "visible title")
|
||||||
|
saveContent(creator, "future title").releaseDate = LocalDateTime.now().plusDays(1)
|
||||||
|
saveContent(creator, "adult title").isAdult = true
|
||||||
|
saveContent(creator, "inactive title").isActive = false
|
||||||
|
saveContent(creator, "unfinished title").duration = null
|
||||||
|
val blockedCreator = saveMember("blocked-owner", MemberRole.CREATOR)
|
||||||
|
saveContent(blockedCreator, "blocked title")
|
||||||
|
entityManager.persist(
|
||||||
|
BlockMember(isActive = true).apply {
|
||||||
|
member = blockedCreator
|
||||||
|
blockedMember = viewer
|
||||||
|
}
|
||||||
|
)
|
||||||
|
saveAlias(creator, "Retained owner alias")
|
||||||
|
saveAlias(blockedCreator, "Blocked owner alias")
|
||||||
|
creator.nickname = "renamed-original"
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val activeItems = findLatest(viewer.id!!, "ja")
|
||||||
|
assertEquals(listOf(visible.id), activeItems.map { it.contentId })
|
||||||
|
assertEquals("Retained owner alias", activeItems.single().creatorNickname)
|
||||||
|
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update creator_localized_name set is_active = false where member_id = ? and lang = 'JA'",
|
||||||
|
creator.id!!
|
||||||
|
)
|
||||||
|
entityManager.clear()
|
||||||
|
assertEquals("renamed-original", findLatest(viewer.id!!, "ja").single().creatorNickname)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("테마 공유 조회는 요청한 KO·JA 별칭과 기존 제목을 반환한다")
|
||||||
|
fun shouldLocalizeThemeNamesByRequestedLanguageWithoutChangingTitle() {
|
||||||
|
val viewer = saveMember("raw-theme-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("raw-theme-original", MemberRole.CREATOR)
|
||||||
|
val content = saveContent(creator, "Unchanged theme title")
|
||||||
|
saveAlias(creator, "Japanese theme owner")
|
||||||
|
saveAlias(creator, "Korean theme owner", Lang.KO)
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val names = listOf(
|
||||||
|
Lang.KO to "Korean theme owner",
|
||||||
|
Lang.JA to "Japanese theme owner"
|
||||||
|
)
|
||||||
|
for ((lang, expected) in names) {
|
||||||
|
val item = repository.findByTheme(memberId = viewer.id!!, contentType = ContentType.ALL, lang = lang).single()
|
||||||
|
assertEquals(content.id, item.contentId)
|
||||||
|
assertEquals(expected, item.creatorNickname)
|
||||||
|
assertEquals("Unchanged theme title", item.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("큐레이션 조회는 활성 KO·JA 별칭이 있어도 원본 닉네임을 반환한다")
|
||||||
|
fun shouldKeepCurationNamesRawWithActiveAliases() {
|
||||||
|
val creator = saveMember("raw-curation-original", MemberRole.CREATOR)
|
||||||
|
val curation = AudioContentCuration(title = "curation", description = "description")
|
||||||
|
.also { entityManager.persist(it) }
|
||||||
|
val content = saveContent(creator, "Unchanged curation title").apply { this.curation = curation }
|
||||||
|
entityManager.persist(
|
||||||
|
AudioContentCurationItem().apply {
|
||||||
|
this.curation = curation
|
||||||
|
this.content = content
|
||||||
|
}
|
||||||
|
)
|
||||||
|
saveAlias(creator, "Japanese curation owner")
|
||||||
|
saveAlias(creator, "Korean curation owner", Lang.KO)
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val results = listOf(
|
||||||
|
repository.findContentByCurationId(curation.id!!, false, ContentType.ALL),
|
||||||
|
curationRepository.findByCurationId(curation.id!!, "https://cdn.test", contentType = ContentType.ALL)
|
||||||
|
)
|
||||||
|
for (items in results) {
|
||||||
|
assertEquals(content.id, items.single().contentId)
|
||||||
|
assertEquals("raw-curation-original", items.single().creatorNickname)
|
||||||
|
assertEquals("Unchanged curation title", items.single().title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("랭킹 공유 조회는 요청한 KO·JA 별칭과 제목 번역·원본 fallback을 반환한다")
|
||||||
|
fun shouldLocalizeRankingNamesAndTitlesByRequestedLocale() {
|
||||||
|
val viewer = saveMember("raw-rank-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("raw-rank-original", MemberRole.CREATOR).apply { profileImage = "profile.png" }
|
||||||
|
val content = saveContent(creator, "Original ranking title")
|
||||||
|
entityManager.persist(CreatorRanking(ranking = 1).apply { member = creator })
|
||||||
|
entityManager.persist(
|
||||||
|
Order(type = OrderType.KEEP).apply {
|
||||||
|
member = viewer
|
||||||
|
this.creator = creator
|
||||||
|
audioContent = content
|
||||||
|
}
|
||||||
|
)
|
||||||
|
entityManager.persist(
|
||||||
|
ContentTranslation(content.id!!, "ja", ContentTranslationPayload("Translated ranking title", "detail", ""))
|
||||||
|
)
|
||||||
|
saveAlias(creator, "Japanese ranking owner")
|
||||||
|
saveAlias(creator, "Korean ranking owner", Lang.KO)
|
||||||
|
// H2 JSON 컬럼에 문자열이 아닌 JSON 객체를 적재한다.
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update content_translation set rendered_payload = ? format json where content_id = ? and locale = 'ja'",
|
||||||
|
"""{"title":"Translated ranking title","detail":"detail","tags":""}""",
|
||||||
|
content.id!!
|
||||||
|
)
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val names = listOf(
|
||||||
|
Triple(Lang.KO, "Korean ranking owner", "Original ranking title"),
|
||||||
|
Triple(Lang.JA, "Japanese ranking owner", "Translated ranking title")
|
||||||
|
)
|
||||||
|
for ((lang, expected, expectedTitle) in names) {
|
||||||
|
assertEquals(expected, rankingRepository.getCreatorRankings(viewer.id, lang).single().nickname)
|
||||||
|
val item = rankingRepository.getAudioContentRanking(
|
||||||
|
memberId = viewer.id,
|
||||||
|
isAdult = false,
|
||||||
|
contentType = ContentType.ALL,
|
||||||
|
startDate = LocalDateTime.now().minusDays(1),
|
||||||
|
endDate = LocalDateTime.now().plusDays(1),
|
||||||
|
offset = 0,
|
||||||
|
limit = 10,
|
||||||
|
sortType = "판매량",
|
||||||
|
locale = lang.code
|
||||||
|
).single()
|
||||||
|
assertEquals(expected, item.creatorNickname)
|
||||||
|
assertEquals(expectedTitle, item.title)
|
||||||
|
}
|
||||||
|
assertEquals(
|
||||||
|
"Korean ranking owner",
|
||||||
|
rankingRepository.getCreatorRankings(viewer.id).single().nickname
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("오리지널 시리즈 공유 조회는 요청한 언어의 제목 번역·원본 fallback을 반환한다")
|
||||||
|
fun shouldLocalizeOriginalSeriesTitlesByRequestedLocale() {
|
||||||
|
val creator = saveMember("raw-series-original", MemberRole.CREATOR)
|
||||||
|
val content = saveContent(creator, "Series content")
|
||||||
|
val genre = SeriesGenre(genre = "genre").also { entityManager.persist(it) }
|
||||||
|
val series = Series(title = "Original series title", introduction = "introduction", isOriginal = true).apply {
|
||||||
|
member = creator
|
||||||
|
this.genre = genre
|
||||||
|
coverImage = "series.png"
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
entityManager.persist(
|
||||||
|
SeriesContent().apply {
|
||||||
|
this.series = series
|
||||||
|
this.content = content
|
||||||
|
}
|
||||||
|
)
|
||||||
|
entityManager.persist(
|
||||||
|
SeriesTranslation(series.id!!, "ja", SeriesTranslationPayload("Translated series title", "intro", emptyList()))
|
||||||
|
)
|
||||||
|
entityManager.flush()
|
||||||
|
// H2 JSON 컬럼에 문자열이 아닌 JSON 객체를 적재한다.
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update series_translation set rendered_payload = ? format json where series_id = ? and locale = 'ja'",
|
||||||
|
"""{"title":"Translated series title","introduction":"intro","keywords":[]}""",
|
||||||
|
series.id!!
|
||||||
|
)
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val titles = listOf(
|
||||||
|
"ja" to "Translated series title",
|
||||||
|
"ko" to "Original series title"
|
||||||
|
)
|
||||||
|
for ((locale, expectedTitle) in titles) {
|
||||||
|
val localized = seriesRepository.getOriginalAudioDramaList(
|
||||||
|
imageHost = "https://cdn.test",
|
||||||
|
isAdult = false,
|
||||||
|
contentType = ContentType.ALL,
|
||||||
|
locale = locale
|
||||||
|
).single()
|
||||||
|
assertEquals(expectedTitle, localized.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findLatest(viewerId: Long, locale: String): List<AudioContentMainItem> {
|
||||||
|
return repository.getLatestContentByTheme(
|
||||||
|
memberId = viewerId,
|
||||||
|
theme = emptyList(),
|
||||||
|
contentType = ContentType.ALL,
|
||||||
|
offset = 0,
|
||||||
|
limit = 20,
|
||||||
|
sortType = SortType.NEWEST,
|
||||||
|
isFree = false,
|
||||||
|
isAdult = false,
|
||||||
|
locale = locale
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||||
|
return Member(
|
||||||
|
email = "$nickname@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = nickname,
|
||||||
|
profileImage = "profile.png",
|
||||||
|
role = role
|
||||||
|
).also {
|
||||||
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveContent(creator: Member, title: String): AudioContent {
|
||||||
|
val theme = AudioContentTheme(theme = "theme-$title", image = "theme.png")
|
||||||
|
entityManager.persist(theme)
|
||||||
|
return AudioContent(
|
||||||
|
title = title,
|
||||||
|
detail = "detail",
|
||||||
|
languageCode = "ko",
|
||||||
|
releaseDate = LocalDateTime.now().minusDays(1)
|
||||||
|
).apply {
|
||||||
|
member = creator
|
||||||
|
this.theme = theme
|
||||||
|
isActive = true
|
||||||
|
coverImage = "audio.png"
|
||||||
|
duration = "00:10:00"
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAlias(member: Member, displayName: String, lang: Lang = Lang.JA) {
|
||||||
|
entityManager.flush()
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"values (?, ?, ?, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
member.id!!,
|
||||||
|
lang.name,
|
||||||
|
displayName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.member.Member
|
|||||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
|
import kr.co.vividnext.sodalive.support.originalLocalizedNameService
|
||||||
import kr.co.vividnext.sodalive.v2.home.following.application.HomeFollowingNewsPublishService
|
import kr.co.vividnext.sodalive.v2.home.following.application.HomeFollowingNewsPublishService
|
||||||
import kr.co.vividnext.sodalive.v2.recommendation.application.CreatorContentViewHistoryService
|
import kr.co.vividnext.sodalive.v2.recommendation.application.CreatorContentViewHistoryService
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
@@ -114,6 +115,7 @@ class AudioContentServiceTest {
|
|||||||
applicationEventPublisher = applicationEventPublisher,
|
applicationEventPublisher = applicationEventPublisher,
|
||||||
messageSource = SodaMessageSource(),
|
messageSource = SodaMessageSource(),
|
||||||
langContext = LangContext(),
|
langContext = LangContext(),
|
||||||
|
localizedNameService = originalLocalizedNameService(),
|
||||||
contentThemeTranslationRepository = contentThemeTranslationRepository,
|
contentThemeTranslationRepository = contentThemeTranslationRepository,
|
||||||
homeFollowingNewsPublishService = homeFollowingNewsPublishService,
|
homeFollowingNewsPublishService = homeFollowingNewsPublishService,
|
||||||
memberContentPreferenceService = memberContentPreferenceService,
|
memberContentPreferenceService = memberContentPreferenceService,
|
||||||
|
|||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
package kr.co.vividnext.sodalive.content.comment
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContentRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.order.OrderRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
|
import kr.co.vividnext.sodalive.fcm.FcmEvent
|
||||||
|
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.block.BlockMemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.support.LegacyCreatorNameTestConfiguration
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@DataJpaTest(properties = ["spring.cache.type=none"])
|
||||||
|
@Import(QueryDslConfig::class, LegacyCreatorNameTestConfiguration::class)
|
||||||
|
class AudioContentCommentNameMetadataTest @Autowired constructor(
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
private val repository: AudioContentCommentRepository,
|
||||||
|
private val blockRepository: BlockMemberRepository,
|
||||||
|
private val contentRepository: AudioContentRepository,
|
||||||
|
private val orderRepository: OrderRepository,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService
|
||||||
|
) {
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource("CREATOR, USER", "USER, CREATOR")
|
||||||
|
@DisplayName("답글 알림 제목은 부모 작성자 ID이고 snapshot 발신자 ID와 콘텐츠 제목 인자는 그대로다")
|
||||||
|
fun shouldKeepParentTitleOwnerSeparateFromActualCommenterAndResourceTitle(
|
||||||
|
parentRole: MemberRole,
|
||||||
|
commenterRole: MemberRole
|
||||||
|
) {
|
||||||
|
val parentWriter = Member(nickname = "parent-original", password = "password", role = parentRole)
|
||||||
|
val commenter = Member(nickname = "commenter-original", password = "password", role = commenterRole)
|
||||||
|
val contentOwner = Member(nickname = "content-owner-original", password = "password", role = MemberRole.CREATOR)
|
||||||
|
entityManager.persist(parentWriter)
|
||||||
|
entityManager.persist(commenter)
|
||||||
|
entityManager.persist(contentOwner)
|
||||||
|
val theme = AudioContentTheme(theme = "metadata-theme", image = "theme.png")
|
||||||
|
entityManager.persist(theme)
|
||||||
|
val content = AudioContent(title = "Unchanged content title", detail = "detail", languageCode = "ko").apply {
|
||||||
|
member = contentOwner
|
||||||
|
this.theme = theme
|
||||||
|
isActive = true
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
val parent = AudioContentComment(comment = "parent comment", languageCode = "ko").apply {
|
||||||
|
member = parentWriter
|
||||||
|
audioContent = content
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
entityManager.flush()
|
||||||
|
val publisher = Mockito.mock(ApplicationEventPublisher::class.java)
|
||||||
|
val service = AudioContentCommentService(
|
||||||
|
repository,
|
||||||
|
blockRepository,
|
||||||
|
contentRepository,
|
||||||
|
publisher,
|
||||||
|
orderRepository,
|
||||||
|
SodaMessageSource(),
|
||||||
|
LangContext(),
|
||||||
|
localizedNameService,
|
||||||
|
"https://cdn.test"
|
||||||
|
)
|
||||||
|
|
||||||
|
service.registerComment(commenter, "reply", content.id!!, parent.id!!, false, "ko")
|
||||||
|
|
||||||
|
val captor = ArgumentCaptor.forClass(FcmEvent::class.java)
|
||||||
|
Mockito.verify(publisher).publishEvent(captor.capture())
|
||||||
|
val event = captor.value
|
||||||
|
assertNotEquals(parentWriter.id, commenter.id)
|
||||||
|
assertEquals(parentWriter.id, event.titleMemberId)
|
||||||
|
assertEquals(commenter.id, event.senderMemberId)
|
||||||
|
assertEquals("parent-original", event.title)
|
||||||
|
assertEquals(listOf("Unchanged content title"), event.args)
|
||||||
|
assertEquals(emptyMap<Int, Long>(), event.nameArgumentMemberIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
+259
@@ -0,0 +1,259 @@
|
|||||||
|
package kr.co.vividnext.sodalive.explorer
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
|
||||||
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
||||||
|
import org.springframework.test.context.ContextConfiguration
|
||||||
|
import org.springframework.test.context.transaction.TestTransaction
|
||||||
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||||
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
|
||||||
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@SpringBootTest(
|
||||||
|
properties = [
|
||||||
|
"cloud.aws.cloud-front.host=https://cdn.test",
|
||||||
|
"spring.cache.type=none",
|
||||||
|
"spring.datasource.url=jdbc:h2:mem:legacy-creator-name-e2e;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||||
|
@Transactional
|
||||||
|
class ExplorerLocalizedNameEndToEndTest @Autowired constructor(
|
||||||
|
private val mockMvc: MockMvc,
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
private val jdbcTemplate: JdbcTemplate
|
||||||
|
) {
|
||||||
|
@MockBean
|
||||||
|
private lateinit var countryContext: CountryContext
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
Mockito.doReturn("US").`when`(countryContext).countryCode
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("레거시 프로필 이름은 현지화하되 팬톡 작성자·답글은 활성 JA·KO 별칭이 있어도 원본을 유지한다")
|
||||||
|
fun shouldLocalizeProfileButKeepOriginalFanTalkWriterAndReplyNamesDespiteActiveAliases() {
|
||||||
|
val viewer = saveMember("profile-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("profile-original", MemberRole.CREATOR).apply { isVisibleDonationRank = false }
|
||||||
|
val fan = saveMember("fan-original", MemberRole.USER)
|
||||||
|
val peer = saveMember("deleted_peer-original", MemberRole.CREATOR)
|
||||||
|
val fanTalk = saveCheers(fan, creator, "fan talk")
|
||||||
|
saveCheers(peer, creator, "creator fan talk")
|
||||||
|
saveCheers(creator, creator, "creator reply", parent = fanTalk)
|
||||||
|
saveAlias(creator, "Japanese profile")
|
||||||
|
saveAlias(peer, "Japanese peer")
|
||||||
|
saveAlias(fan, "Must not replace fan")
|
||||||
|
saveAlias(creator, "Korean profile", lang = "KO")
|
||||||
|
saveAlias(peer, "Korean peer", lang = "KO")
|
||||||
|
entityManager.clear()
|
||||||
|
commitFixture()
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/explorer/profile/${creator.id}")
|
||||||
|
.param("timezone", "Asia/Seoul")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.success").value(true))
|
||||||
|
.andExpect(jsonPath("$.data.creator.creatorId").value(creator.id!!))
|
||||||
|
.andExpect(jsonPath("$.data.creator.nickname").value("Japanese profile"))
|
||||||
|
.andExpect(jsonPath("$.data.cheers.totalCount").value(2))
|
||||||
|
.andExpect(jsonPath("$.data.cheers.cheers[0].memberId").value(peer.id!!))
|
||||||
|
.andExpect(jsonPath("$.data.cheers.cheers[0].nickname").value("peer-original"))
|
||||||
|
.andExpect(jsonPath("$.data.cheers.cheers[1].nickname").value("fan-original"))
|
||||||
|
.andExpect(jsonPath("$.data.cheers.cheers[1].replyList[0].memberId").value(creator.id!!))
|
||||||
|
.andExpect(jsonPath("$.data.cheers.cheers[1].replyList[0].nickname").value("profile-original"))
|
||||||
|
|
||||||
|
listOf("ko", "en").forEach { locale ->
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/explorer/profile/${creator.id}")
|
||||||
|
.param("timezone", "Asia/Seoul")
|
||||||
|
.header("Accept-Language", locale)
|
||||||
|
.with(user(MemberAdapter(viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(
|
||||||
|
jsonPath("$.data.creator.nickname").value(if (locale == "ko") "Korean profile" else "profile-original")
|
||||||
|
)
|
||||||
|
.andExpect(jsonPath("$.data.cheers.cheers[0].nickname").value("peer-original"))
|
||||||
|
.andExpect(jsonPath("$.data.cheers.cheers[1].nickname").value("fan-original"))
|
||||||
|
.andExpect(jsonPath("$.data.cheers.cheers[1].replyList[0].nickname").value("profile-original"))
|
||||||
|
}
|
||||||
|
assertEquals("profile-original", entityManager.find(Member::class.java, creator.id).nickname)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("JA 상세는 원본 변경 후에도 별칭을 유지하고 해제·미등록이면 현재 원본을 반환한다")
|
||||||
|
fun shouldKeepAliasAfterOriginalRenameAndFallbackAfterSoftClearOrMissingAlias() {
|
||||||
|
val viewer = saveMember("detail-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("before-rename", MemberRole.CREATOR)
|
||||||
|
val unregistered = saveMember("unregistered-original", MemberRole.CREATOR)
|
||||||
|
saveAlias(creator, "Retained profile alias")
|
||||||
|
creator.nickname = "renamed-original"
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
commitFixture()
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/explorer/profile/${creator.id}/detail")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.nickname").value("Retained profile alias"))
|
||||||
|
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update creator_localized_name set is_active = false where member_id = ? and lang = 'JA'",
|
||||||
|
creator.id!!
|
||||||
|
)
|
||||||
|
entityManager.clear()
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/explorer/profile/${creator.id}/detail")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.nickname").value("renamed-original"))
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/explorer/profile/${unregistered.id}/detail")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.nickname").value("unregistered-original"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("통합 검색은 creator 제목·닉네임만 JA 이름으로 바꾸고 콘텐츠·시리즈 제목과 소유자 ID를 구분한다")
|
||||||
|
fun shouldLocalizeCreatorSearchTitleAndAllOwnerNamesWithoutReplacingContentOrSeriesTitles() {
|
||||||
|
val viewer = saveMember("unified-viewer", MemberRole.USER)
|
||||||
|
saveMember("unused-member", MemberRole.USER)
|
||||||
|
val creator = saveMember("unified-original", MemberRole.CREATOR)
|
||||||
|
val theme = AudioContentTheme(theme = "unified-theme", image = "theme.png")
|
||||||
|
entityManager.persist(theme)
|
||||||
|
val content = AudioContent(
|
||||||
|
title = "Unchanged content title",
|
||||||
|
detail = "description",
|
||||||
|
languageCode = "ko",
|
||||||
|
releaseDate = LocalDateTime.now().minusDays(1)
|
||||||
|
).apply {
|
||||||
|
member = creator
|
||||||
|
this.theme = theme
|
||||||
|
isActive = true
|
||||||
|
coverImage = "audio.png"
|
||||||
|
duration = "00:10:00"
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
val genre = SeriesGenre(genre = "unified-genre", isAdult = false, isActive = true)
|
||||||
|
entityManager.persist(genre)
|
||||||
|
val series = Series(title = "Unchanged series title", introduction = "introduction").apply {
|
||||||
|
member = creator
|
||||||
|
this.genre = genre
|
||||||
|
coverImage = "series.png"
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
entityManager.persist(
|
||||||
|
SeriesContent().apply {
|
||||||
|
this.series = series
|
||||||
|
this.content = content
|
||||||
|
}
|
||||||
|
)
|
||||||
|
saveAlias(creator, "Japanese unified creator")
|
||||||
|
entityManager.clear()
|
||||||
|
assertNotEquals(creator.id, content.id)
|
||||||
|
assertNotEquals(creator.id, series.id)
|
||||||
|
commitFixture()
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/search")
|
||||||
|
.param("keyword", "unified-original")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.success").value(true))
|
||||||
|
.andExpect(jsonPath("$.data.creatorList[0].id").value(creator.id!!))
|
||||||
|
.andExpect(jsonPath("$.data.creatorList[0].title").value("Japanese unified creator"))
|
||||||
|
.andExpect(jsonPath("$.data.creatorList[0].nickname").value("Japanese unified creator"))
|
||||||
|
.andExpect(jsonPath("$.data.contentList[0].id").value(content.id!!))
|
||||||
|
.andExpect(jsonPath("$.data.contentList[0].title").value("Unchanged content title"))
|
||||||
|
.andExpect(jsonPath("$.data.contentList[0].nickname").value("Japanese unified creator"))
|
||||||
|
.andExpect(jsonPath("$.data.seriesList[0].id").value(series.id!!))
|
||||||
|
.andExpect(jsonPath("$.data.seriesList[0].title").value("Unchanged series title"))
|
||||||
|
.andExpect(jsonPath("$.data.seriesList[0].nickname").value("Japanese unified creator"))
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/search/creators")
|
||||||
|
.param("keyword", "Japanese unified")
|
||||||
|
.header("Accept-Language", "ko")
|
||||||
|
.with(user(MemberAdapter(viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.totalCount").value(1))
|
||||||
|
.andExpect(jsonPath("$.data.items[0].title").value("unified-original"))
|
||||||
|
.andExpect(jsonPath("$.data.items[0].nickname").value("unified-original"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun commitFixture() {
|
||||||
|
TestTransaction.flagForCommit()
|
||||||
|
TestTransaction.end()
|
||||||
|
TestTransaction.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||||
|
return Member(
|
||||||
|
email = "$nickname@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = nickname,
|
||||||
|
profileImage = "$nickname.png",
|
||||||
|
role = role
|
||||||
|
).also { entityManager.persist(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCheers(writer: Member, creator: Member, text: String, parent: CreatorCheers? = null): CreatorCheers {
|
||||||
|
return CreatorCheers(cheers = text, languageCode = "ko").apply {
|
||||||
|
member = writer
|
||||||
|
this.creator = creator
|
||||||
|
this.parent = parent
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAlias(member: Member, displayName: String, lang: String = "JA") {
|
||||||
|
entityManager.flush()
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"values (?, ?, ?, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
member.id!!,
|
||||||
|
lang,
|
||||||
|
displayName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
package kr.co.vividnext.sodalive.explorer
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
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 org.junit.jupiter.api.Assertions.assertAll
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@DataJpaTest(properties = ["spring.cache.type=none"])
|
||||||
|
@Import(QueryDslConfig::class)
|
||||||
|
class ExplorerQueryRepositoryLocalizedNameTest @Autowired constructor(
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
private val jdbcTemplate: JdbcTemplate,
|
||||||
|
queryFactory: JPAQueryFactory
|
||||||
|
) {
|
||||||
|
private val langContext = LangContext().apply { setLang(Lang.KO) }
|
||||||
|
private val repository = ExplorerQueryRepository(queryFactory, SodaMessageSource(), langContext, "https://cdn.test")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("KO 채널 검색도 원본 또는 활성 JA 이름을 찾고 self·비활성·USER를 제외한다")
|
||||||
|
fun shouldSearchOriginalOrActiveJaAliasInKoWithoutBypassingChannelFilters() {
|
||||||
|
val viewer = saveMember("Comet-viewer")
|
||||||
|
val original = saveMember("Comet-original")
|
||||||
|
val aliasOnly = saveMember("another-original")
|
||||||
|
val inactive = saveMember("Comet-inactive").apply { isActive = false }
|
||||||
|
val user = saveMember("Comet-user", MemberRole.USER)
|
||||||
|
listOf(viewer, original, aliasOnly, inactive, user).forEach { saveAlias(it, "Comet alias") }
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
setOf(original.id, aliasOnly.id),
|
||||||
|
repository.getSearchChannel("cOmEt", viewer.id!!).map { it.id }.toSet()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ assertEquals(2, repository.getSearchChannel("cOmEt", viewer.id!!).size) },
|
||||||
|
{ assertEquals(listOf(original.id), repository.getSearchChannel("Comet-original", viewer.id!!).map { it.id }) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("채널 검색은 해제한 JA와 EN 이름을 제외하고 동일 행 재활성화 후 별칭을 다시 찾는다")
|
||||||
|
fun shouldIgnoreSoftClearedAndNonJaChannelAliasesAndSearchReactivatedAlias() {
|
||||||
|
val viewer = saveMember("channel-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("channel-original")
|
||||||
|
saveAlias(creator, "ChannelAlias", isActive = false)
|
||||||
|
saveAlias(creator, "EnglishChannel", lang = "EN")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
assertEquals(emptyList<Long>(), repository.getSearchChannel("ChannelAlias", viewer.id!!).map { it.id })
|
||||||
|
assertEquals(emptyList<Long>(), repository.getSearchChannel("EnglishChannel", viewer.id!!).map { it.id })
|
||||||
|
assertEquals(listOf(creator.id), repository.getSearchChannel("channel-original", viewer.id!!).map { it.id })
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update creator_localized_name set is_active = true where member_id = ? and lang = 'JA'",
|
||||||
|
creator.id!!
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(listOf(creator.id), repository.getSearchChannel("ChannelAlias", viewer.id!!).map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole = MemberRole.CREATOR): Member {
|
||||||
|
return Member(email = "$nickname@test.com", password = "password", nickname = nickname, role = role).also {
|
||||||
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAlias(member: Member, displayName: String, isActive: Boolean = true, lang: String = "JA") {
|
||||||
|
entityManager.flush()
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"values (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
member.id!!,
|
||||||
|
lang,
|
||||||
|
displayName,
|
||||||
|
isActive
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,15 @@ import kr.co.vividnext.sodalive.explorer.profile.CreatorDonationRankingService
|
|||||||
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
|
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationService
|
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationService
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityService
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityService
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.MemberService
|
import kr.co.vividnext.sodalive.member.MemberService
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -42,7 +45,28 @@ class ExplorerServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createService(queryRepository: ExplorerQueryRepository): ExplorerService {
|
@Test
|
||||||
|
@DisplayName("팬톡 수정 응답은 별칭 대신 작성자 원본 이름을 반환하고 deleted_ 접두사만 제거한다")
|
||||||
|
fun shouldKeepOriginalWriterNameAndRemoveDeletedPrefixWhenModifyingCheers() {
|
||||||
|
val queryRepository = Mockito.mock(ExplorerQueryRepository::class.java)
|
||||||
|
val localizedNameService = Mockito.mock(CreatorLocalizedNameService::class.java)
|
||||||
|
val service = createService(queryRepository, localizedNameService)
|
||||||
|
val writer = createMember(id = 10L, nickname = "deleted_writer", role = MemberRole.CREATOR)
|
||||||
|
val cheers = createCheers(writer, createMember(id = 20L, nickname = "creator", role = MemberRole.CREATOR))
|
||||||
|
Mockito.doReturn(cheers).`when`(queryRepository).getCheers(101L)
|
||||||
|
Mockito.`when`(localizedNameService.findLocalizedNames(listOf(10L), Lang.JA)).thenReturn(mapOf(10L to "alias"))
|
||||||
|
|
||||||
|
val response = service.modifyCheers(PutWriteCheersRequest(cheersId = 101L, content = "modified"), writer)
|
||||||
|
|
||||||
|
assertEquals("writer", response.writerNickname)
|
||||||
|
assertEquals("modified", response.content)
|
||||||
|
Mockito.verifyNoInteractions(localizedNameService)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createService(
|
||||||
|
queryRepository: ExplorerQueryRepository,
|
||||||
|
localizedNameService: CreatorLocalizedNameService = Mockito.mock(CreatorLocalizedNameService::class.java)
|
||||||
|
): ExplorerService {
|
||||||
return ExplorerService(
|
return ExplorerService(
|
||||||
memberService = Mockito.mock(MemberService::class.java),
|
memberService = Mockito.mock(MemberService::class.java),
|
||||||
memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java),
|
memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java),
|
||||||
@@ -57,7 +81,8 @@ class ExplorerServiceTest {
|
|||||||
applicationEventPublisher = Mockito.mock(ApplicationEventPublisher::class.java),
|
applicationEventPublisher = Mockito.mock(ApplicationEventPublisher::class.java),
|
||||||
contentTranslationRepository = Mockito.mock(ContentTranslationRepository::class.java),
|
contentTranslationRepository = Mockito.mock(ContentTranslationRepository::class.java),
|
||||||
messageSource = Mockito.mock(SodaMessageSource::class.java),
|
messageSource = Mockito.mock(SodaMessageSource::class.java),
|
||||||
langContext = Mockito.mock(LangContext::class.java),
|
langContext = LangContext().apply { setLang(Lang.JA) },
|
||||||
|
localizedNameService = localizedNameService,
|
||||||
cloudFrontHost = "https://cdn.test"
|
cloudFrontHost = "https://cdn.test"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-9
@@ -72,9 +72,9 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor(
|
|||||||
|
|
||||||
// then: 비공개 타인 메시지는 제외되고, createdAt desc/id desc 정렬이 적용돼야 한다.
|
// then: 비공개 타인 메시지는 제외되고, createdAt desc/id desc 정렬이 적용돼야 한다.
|
||||||
assertEquals(3, list.size)
|
assertEquals(3, list.size)
|
||||||
assertEquals(secretMine.id, list[0].id)
|
assertEquals(secretMine.id, list[0].first.id)
|
||||||
assertEquals(publicTieSecond.id, list[1].id)
|
assertEquals(publicTieSecond.id, list[1].first.id)
|
||||||
assertEquals(publicTieFirst.id, list[2].id)
|
assertEquals(publicTieFirst.id, list[2].first.id)
|
||||||
assertEquals(3, totalCount)
|
assertEquals(3, totalCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,9 +127,9 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor(
|
|||||||
|
|
||||||
// then: 본인 조회는 비공개 메시지를 포함하고 최신순으로 반환해야 한다.
|
// then: 본인 조회는 비공개 메시지를 포함하고 최신순으로 반환해야 한다.
|
||||||
assertEquals(3, list.size)
|
assertEquals(3, list.size)
|
||||||
assertEquals(recentSecretOther.id, list[0].id)
|
assertEquals(recentSecretOther.id, list[0].first.id)
|
||||||
assertEquals(recentSecretMine.id, list[1].id)
|
assertEquals(recentSecretMine.id, list[1].first.id)
|
||||||
assertEquals(recentPublic.id, list[2].id)
|
assertEquals(recentPublic.id, list[2].first.id)
|
||||||
assertEquals(3, totalCount)
|
assertEquals(3, totalCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor(
|
|||||||
fun shouldFetchMemberWithDonationMessages() {
|
fun shouldFetchMemberWithDonationMessages() {
|
||||||
// given: 크리에이터/후원자와 조회 대상 메시지를 준비한다.
|
// given: 크리에이터/후원자와 조회 대상 메시지를 준비한다.
|
||||||
val creator = saveMember(nickname = "creator3", role = MemberRole.CREATOR)
|
val creator = saveMember(nickname = "creator3", role = MemberRole.CREATOR)
|
||||||
val viewer = saveMember(nickname = "viewer3", role = MemberRole.USER)
|
val viewer = saveMember(nickname = "deleted_viewer3", role = MemberRole.USER)
|
||||||
val message = saveMessage(member = viewer, creator = creator, can = 1, isSecret = false)
|
val message = saveMessage(member = viewer, creator = creator, can = 1, isSecret = false)
|
||||||
val monthStart = LocalDateTime.now().withDayOfMonth(1).toLocalDate().atStartOfDay()
|
val monthStart = LocalDateTime.now().withDayOfMonth(1).toLocalDate().atStartOfDay()
|
||||||
val nextMonthStart = monthStart.plusMonths(1)
|
val nextMonthStart = monthStart.plusMonths(1)
|
||||||
@@ -160,8 +160,9 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor(
|
|||||||
|
|
||||||
// then: 조회 결과의 후원자 정보는 세션 없이도 접근 가능해야 한다.
|
// then: 조회 결과의 후원자 정보는 세션 없이도 접근 가능해야 한다.
|
||||||
assertEquals(1, list.size)
|
assertEquals(1, list.size)
|
||||||
assertEquals(viewer.id, list[0].member!!.id)
|
assertEquals(viewer.id, list[0].first.member!!.id)
|
||||||
assertEquals("viewer3", list[0].member!!.nickname)
|
assertEquals("deleted_viewer3", list[0].first.member!!.nickname)
|
||||||
|
assertEquals("viewer3", list[0].second)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveMember(nickname: String, role: MemberRole): Member {
|
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||||
|
|||||||
+16
-9
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.explorer.profile.channelDonation
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -97,9 +98,10 @@ class ChannelDonationServiceTest {
|
|||||||
Mockito.eq(0L),
|
Mockito.eq(0L),
|
||||||
Mockito.eq(5L),
|
Mockito.eq(5L),
|
||||||
anyLocalDateTime(),
|
anyLocalDateTime(),
|
||||||
anyLocalDateTime()
|
anyLocalDateTime(),
|
||||||
|
Mockito.eq(Lang.KO) ?: Lang.KO
|
||||||
)
|
)
|
||||||
).thenReturn(listOf(message))
|
).thenReturn(listOf(message to viewer.nickname))
|
||||||
|
|
||||||
// when: 채널 후원 목록 조회를 실행한다.
|
// when: 채널 후원 목록 조회를 실행한다.
|
||||||
val result = service.getChannelDonationList(
|
val result = service.getChannelDonationList(
|
||||||
@@ -129,7 +131,8 @@ class ChannelDonationServiceTest {
|
|||||||
Mockito.eq(0L),
|
Mockito.eq(0L),
|
||||||
Mockito.eq(5L),
|
Mockito.eq(5L),
|
||||||
anyLocalDateTime(),
|
anyLocalDateTime(),
|
||||||
anyLocalDateTime()
|
anyLocalDateTime(),
|
||||||
|
Mockito.eq(Lang.KO) ?: Lang.KO
|
||||||
)
|
)
|
||||||
|
|
||||||
// then: 월 시작/다음 달 시작 범위를 사용해야 한다.
|
// then: 월 시작/다음 달 시작 범위를 사용해야 한다.
|
||||||
@@ -181,9 +184,10 @@ class ChannelDonationServiceTest {
|
|||||||
Mockito.eq(0L),
|
Mockito.eq(0L),
|
||||||
Mockito.eq(5L),
|
Mockito.eq(5L),
|
||||||
anyLocalDateTime(),
|
anyLocalDateTime(),
|
||||||
anyLocalDateTime()
|
anyLocalDateTime(),
|
||||||
|
Mockito.eq(Lang.KO) ?: Lang.KO
|
||||||
)
|
)
|
||||||
).thenReturn(listOf(message))
|
).thenReturn(listOf(message to "withdrawn"))
|
||||||
|
|
||||||
// when: 채널 후원 목록 조회를 실행한다.
|
// when: 채널 후원 목록 조회를 실행한다.
|
||||||
val result = service.getChannelDonationList(
|
val result = service.getChannelDonationList(
|
||||||
@@ -228,9 +232,10 @@ class ChannelDonationServiceTest {
|
|||||||
Mockito.eq(0L),
|
Mockito.eq(0L),
|
||||||
Mockito.eq(5L),
|
Mockito.eq(5L),
|
||||||
anyLocalDateTime(),
|
anyLocalDateTime(),
|
||||||
anyLocalDateTime()
|
anyLocalDateTime(),
|
||||||
|
Mockito.eq(Lang.KO) ?: Lang.KO
|
||||||
)
|
)
|
||||||
).thenReturn(listOf(message))
|
).thenReturn(listOf(message to viewer.nickname))
|
||||||
|
|
||||||
// when: 목록 조회를 실행한다.
|
// when: 목록 조회를 실행한다.
|
||||||
val result = service.getChannelDonationList(
|
val result = service.getChannelDonationList(
|
||||||
@@ -275,7 +280,8 @@ class ChannelDonationServiceTest {
|
|||||||
Mockito.eq(0L),
|
Mockito.eq(0L),
|
||||||
Mockito.eq(5L),
|
Mockito.eq(5L),
|
||||||
anyLocalDateTime(),
|
anyLocalDateTime(),
|
||||||
anyLocalDateTime()
|
anyLocalDateTime(),
|
||||||
|
Mockito.eq(Lang.KO) ?: Lang.KO
|
||||||
)
|
)
|
||||||
).thenReturn(emptyList())
|
).thenReturn(emptyList())
|
||||||
|
|
||||||
@@ -302,7 +308,8 @@ class ChannelDonationServiceTest {
|
|||||||
Mockito.eq(0L),
|
Mockito.eq(0L),
|
||||||
Mockito.eq(5L),
|
Mockito.eq(5L),
|
||||||
anyLocalDateTime(),
|
anyLocalDateTime(),
|
||||||
anyLocalDateTime()
|
anyLocalDateTime(),
|
||||||
|
Mockito.eq(Lang.KO) ?: Lang.KO
|
||||||
)
|
)
|
||||||
|
|
||||||
// then: 월 시작/다음 달 시작 범위를 사용해야 한다.
|
// then: 월 시작/다음 달 시작 범위를 사용해야 한다.
|
||||||
|
|||||||
+1
@@ -150,6 +150,7 @@ class CreatorCommunityServiceTest {
|
|||||||
assertEquals(FcmEventType.INDIVIDUAL, event.type)
|
assertEquals(FcmEventType.INDIVIDUAL, event.type)
|
||||||
assertEquals(PushNotificationCategory.COMMUNITY, event.category)
|
assertEquals(PushNotificationCategory.COMMUNITY, event.category)
|
||||||
assertEquals("creator.community.fcm.new_comment", event.messageKey)
|
assertEquals("creator.community.fcm.new_comment", event.messageKey)
|
||||||
|
assertEquals(commenter.id, event.titleMemberId)
|
||||||
assertEquals(commenter.id, event.senderMemberId)
|
assertEquals(commenter.id, event.senderMemberId)
|
||||||
assertEquals(listOf(creator.id!!), event.recipients)
|
assertEquals(listOf(creator.id!!), event.recipients)
|
||||||
assertEquals(FcmDeepLinkValue.COMMUNITY, event.deepLinkValue)
|
assertEquals(FcmDeepLinkValue.COMMUNITY, event.deepLinkValue)
|
||||||
|
|||||||
+2
-1
@@ -94,7 +94,8 @@ class LiveRecommendRepositoryTest @Autowired constructor(
|
|||||||
memberId = viewer.id!!,
|
memberId = viewer.id!!,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 20,
|
limit = 20,
|
||||||
isBlocked = { false }
|
isBlocked = { false },
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(2, result.size)
|
assertEquals(2, result.size)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.live.recommend
|
package kr.co.vividnext.sodalive.live.recommend
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.i18n.Lang
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.auth.Auth
|
import kr.co.vividnext.sodalive.member.auth.Auth
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
@@ -28,7 +29,8 @@ class LiveRecommendServiceTest {
|
|||||||
repository = repository,
|
repository = repository,
|
||||||
blockMemberRepository = blockMemberRepository,
|
blockMemberRepository = blockMemberRepository,
|
||||||
memberContentPreferenceService = memberContentPreferenceService,
|
memberContentPreferenceService = memberContentPreferenceService,
|
||||||
liveRecommendCacheService = liveRecommendCacheService
|
liveRecommendCacheService = liveRecommendCacheService,
|
||||||
|
langContext = LangContext()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+523
@@ -0,0 +1,523 @@
|
|||||||
|
package kr.co.vividnext.sodalive.live.room
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanRepository
|
||||||
|
import kr.co.vividnext.sodalive.fcm.FcmService
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.live.recommend.LiveRecommendService
|
||||||
|
import kr.co.vividnext.sodalive.live.reservation.LiveReservation
|
||||||
|
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
|
||||||
|
import kr.co.vividnext.sodalive.live.reservation.LiveReservationService
|
||||||
|
import kr.co.vividnext.sodalive.live.reservation.MakeLiveReservationRequest
|
||||||
|
import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfo
|
||||||
|
import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository
|
||||||
|
import kr.co.vividnext.sodalive.live.room.info.LiveRoomMemberRole
|
||||||
|
import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisitService
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.member.following.CreatorFollowing
|
||||||
|
import kr.co.vividnext.sodalive.member.following.CreatorFollowingRepository
|
||||||
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||||
|
import org.hibernate.SessionFactory
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Assertions.assertAll
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
|
import org.springframework.cache.CacheManager
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest
|
||||||
|
import org.springframework.test.annotation.DirtiesContext
|
||||||
|
import org.springframework.test.context.ContextConfiguration
|
||||||
|
import org.springframework.transaction.annotation.Propagation
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.EntityManagerFactory
|
||||||
|
|
||||||
|
@SpringBootTest(
|
||||||
|
properties = [
|
||||||
|
"spring.cache.type=none",
|
||||||
|
"spring.jpa.properties.hibernate.generate_statistics=true",
|
||||||
|
"jwt.secret=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZg==",
|
||||||
|
"agora.app-id=0123456789abcdef0123456789abcdef",
|
||||||
|
"agora.app-certificate=0123456789abcdef0123456789abcdef"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||||
|
@Transactional
|
||||||
|
class LiveRoomLocalizedNameIntegrationTest @Autowired constructor(
|
||||||
|
private val service: LiveRoomService,
|
||||||
|
private val reservationService: LiveReservationService,
|
||||||
|
private val reservationRepository: LiveReservationRepository,
|
||||||
|
private val visitService: LiveRoomVisitService,
|
||||||
|
private val recommendService: LiveRecommendService,
|
||||||
|
private val followingRepository: CreatorFollowingRepository,
|
||||||
|
private val useCanRepository: UseCanRepository,
|
||||||
|
private val roomRepository: LiveRoomRepository,
|
||||||
|
private val roomInfoRepository: LiveRoomInfoRedisRepository,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val jdbcTemplate: JdbcTemplate,
|
||||||
|
private val cacheManager: CacheManager,
|
||||||
|
private val entityManagerFactory: EntityManagerFactory,
|
||||||
|
private val langContext: LangContext
|
||||||
|
) {
|
||||||
|
@MockBean
|
||||||
|
private lateinit var fcmService: FcmService
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var s3Uploader: S3Uploader
|
||||||
|
|
||||||
|
private val roomIds = mutableListOf<Long>()
|
||||||
|
private val finishedLiveCacheKeys = mutableListOf<String>()
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUpRequest() {
|
||||||
|
RequestContextHolder.setRequestAttributes(ServletRequestAttributes(MockHttpServletRequest()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun resetRequestAndRemoveRoomInfo() {
|
||||||
|
roomIds.forEach { roomInfoRepository.deleteById(it) }
|
||||||
|
finishedLiveCacheKeys.forEach { cacheManager.getCache("cache_ttl_10_minutes")?.evict(it) }
|
||||||
|
RequestContextHolder.resetRequestAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("라이브 info는 모든 멤버 목록을 요청 언어로 복사하고 역할과 공유 Redis 원본을 보존한다")
|
||||||
|
fun shouldRenderRoomInfoMemberNamesWithoutMutatingSharedRedisMembership() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
langContext.setLang(Lang.JA)
|
||||||
|
val japanese = service.getRoomInfo(fixture.roomId, fixture.viewer)
|
||||||
|
langContext.setLang(Lang.KO)
|
||||||
|
val korean = service.getRoomInfo(fixture.roomId, fixture.viewer)
|
||||||
|
val stored = roomInfoRepository.findById(fixture.roomId).orElseThrow()
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals("日本-host", japanese.creatorNickname) },
|
||||||
|
{ assertEquals("日本-host", japanese.speakerList.single().nickname) },
|
||||||
|
{ assertEquals("日本-manager", japanese.managerList.single().nickname) },
|
||||||
|
{ assertEquals(listOf("viewer", "fallback"), japanese.listenerList.map { it.nickname }) },
|
||||||
|
{ assertEquals(LiveRoomMemberRole.SPEAKER, japanese.speakerList.single().role) },
|
||||||
|
{ assertEquals(LiveRoomMemberRole.MANAGER, japanese.managerList.single().role) },
|
||||||
|
{ assertEquals("host", korean.creatorNickname) },
|
||||||
|
{ assertEquals("host", korean.speakerList.single().nickname) },
|
||||||
|
{ assertEquals("manager", korean.managerList.single().nickname) },
|
||||||
|
{ assertEquals("host", stored.speakerList.single().nickname) },
|
||||||
|
{ assertEquals("manager", stored.managerList.single().nickname) },
|
||||||
|
{ assertEquals("host", memberRepository.findById(fixture.host.id!!).orElseThrow().nickname) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("라이브 상세와 멤버 프로필의 현재 이름은 JA 별칭이며 USER와 비활성 별칭은 원본이다")
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
|
||||||
|
fun shouldRenderCurrentRoomDetailAndMemberProfileNames() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
langContext.setLang(Lang.JA)
|
||||||
|
|
||||||
|
val detail = service.getRoomDetail(fixture.roomId, fixture.viewer, "UTC")
|
||||||
|
val hostProfile = service.getUserProfile(fixture.roomId, fixture.host.id!!, fixture.viewer)
|
||||||
|
val viewerProfile = service.getUserProfile(fixture.roomId, fixture.viewer.id!!, fixture.viewer)
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals("日本-host", detail.manager!!.nickname) },
|
||||||
|
{ assertEquals("日本-host", detail.participatingUsers.single { it.id == fixture.host.id }.nickname) },
|
||||||
|
{ assertEquals("日本-manager", detail.participatingUsers.single { it.id == fixture.manager.id }.nickname) },
|
||||||
|
{ assertEquals("viewer", detail.participatingUsers.single { it.id == fixture.viewer.id }.nickname) },
|
||||||
|
{ assertEquals("fallback", detail.participatingUsers.single { it.id == fixture.fallback.id }.nickname) },
|
||||||
|
{ assertEquals("日本-host", hostProfile.nickname) },
|
||||||
|
{ assertEquals("viewer", viewerProfile.nickname) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("현재 후원 룰렛 메시지 이름은 요청별로 렌더링하며 비밀 필터와 본문 UUID 및 Redis 원본은 보존한다")
|
||||||
|
fun shouldRenderDonationAndRouletteNamesOnCopiesAfterSecretFiltering() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
val roomInfo = roomInfoRepository.findById(fixture.roomId).orElseThrow()
|
||||||
|
roomInfo.addDonationMessage(fixture.manager.id!!, "manager", false, 10, "manager 후원 본문")
|
||||||
|
roomInfo.addRouletteMessage(fixture.manager.id!!, "manager", "manager 룰렛 결과")
|
||||||
|
roomInfo.addDonationMessage(fixture.viewer.id!!, "viewer", false, 10, "viewer 본문")
|
||||||
|
roomInfo.addDonationMessage(fixture.manager.id!!, "manager", true, 10, "비밀 본문")
|
||||||
|
roomInfoRepository.save(roomInfo)
|
||||||
|
langContext.setLang(Lang.JA)
|
||||||
|
val japanese = service.getDonationMessageList(fixture.roomId, fixture.viewer)
|
||||||
|
langContext.setLang(Lang.KO)
|
||||||
|
val korean = service.getDonationMessageList(fixture.roomId, fixture.viewer)
|
||||||
|
val stored = roomInfoRepository.findById(fixture.roomId).orElseThrow().donationMessageList
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals(listOf("日本-manager", "日本-manager", "viewer"), japanese.map { it.nickname }) },
|
||||||
|
{ assertEquals(listOf("manager", "manager", "viewer"), korean.map { it.nickname }) },
|
||||||
|
{ assertEquals(listOf("manager 후원 본문", "manager 룰렛 결과", "viewer 본문"), japanese.map { it.donationMessage }) },
|
||||||
|
{ assertEquals(korean.map { it.uuid }, japanese.map { it.uuid }) },
|
||||||
|
{ assertEquals(listOf("manager", "manager", "viewer", "manager"), stored.map { it.nickname }) },
|
||||||
|
{ assertEquals(4, stored.size) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("최근 종료 라이브의 현재 이름은 같은 회원의 KO 조회 캐시 이후에도 JA 요청을 따른다")
|
||||||
|
fun shouldRenderJapaneseFinishedLiveNameAfterKoreanCacheRead() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
val room = roomRepository.findById(fixture.roomId).orElseThrow()
|
||||||
|
room.isActive = false
|
||||||
|
fixture.host.profileImage = "profile/host.png"
|
||||||
|
roomRepository.flush()
|
||||||
|
val cacheKey = "getLatestFinishedLive:${fixture.viewer.id}"
|
||||||
|
finishedLiveCacheKeys.add(cacheKey)
|
||||||
|
val cache = requireNotNull(cacheManager.getCache("cache_ttl_10_minutes"))
|
||||||
|
cache.evict(cacheKey)
|
||||||
|
val statistics = entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
statistics.clear()
|
||||||
|
langContext.setLang(Lang.KO)
|
||||||
|
val korean = service.getLatestFinishedLive(fixture.viewer).single { it.memberId == fixture.host.id }
|
||||||
|
assertEquals(1L, currentNameQueryCount())
|
||||||
|
val roomListQuery = statistics.queries.single { it.contains("from LiveRoom") }
|
||||||
|
val roomListExecutions = statistics.getQueryStatistics(roomListQuery).executionCount
|
||||||
|
val cached = cache.get(cacheKey)?.get()
|
||||||
|
assertNotNull(cached)
|
||||||
|
assertEquals(1L, roomListExecutions)
|
||||||
|
assertEquals("host", (cached as List<*>).filterIsInstance<GetLatestFinishedLiveResponse>().single().nickname)
|
||||||
|
langContext.setLang(Lang.JA)
|
||||||
|
val nameQueryCountBeforeHit = currentNameQueryCount()
|
||||||
|
val japanese = service.getLatestFinishedLive(fixture.viewer).single { it.memberId == fixture.host.id }
|
||||||
|
assertEquals(1L, currentNameQueryCount() - nameQueryCountBeforeHit)
|
||||||
|
assertEquals(roomListExecutions, statistics.getQueryStatistics(roomListQuery).executionCount)
|
||||||
|
assertEquals(cached, cache.get(cacheKey)?.get())
|
||||||
|
fixture.host.nickname = "updated-host"
|
||||||
|
memberRepository.flush()
|
||||||
|
jdbcTemplate.update("UPDATE creator_localized_name SET is_active = false WHERE member_id = ?", fixture.host.id!!)
|
||||||
|
val nameQueryCountBeforeFallback = currentNameQueryCount()
|
||||||
|
val fallback = service.getLatestFinishedLive(fixture.viewer).single { it.memberId == fixture.host.id }
|
||||||
|
assertEquals(1L, currentNameQueryCount() - nameQueryCountBeforeFallback)
|
||||||
|
|
||||||
|
assertEquals("host", korean.nickname)
|
||||||
|
assertEquals("日本-host", japanese.nickname)
|
||||||
|
assertEquals("updated-host", fallback.nickname)
|
||||||
|
assertEquals("host", korean.nickname)
|
||||||
|
assertEquals(roomListExecutions, statistics.getQueryStatistics(roomListQuery).executionCount)
|
||||||
|
assertEquals(cached, cache.get(cacheKey)?.get())
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"INSERT INTO creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"VALUES (?, 'EN', 'English host', true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
fixture.host.id!!
|
||||||
|
)
|
||||||
|
langContext.setLang(Lang.EN)
|
||||||
|
val nameQueryCountBeforeEnglishHit = currentNameQueryCount()
|
||||||
|
val english = service.getLatestFinishedLive(fixture.viewer).single { it.memberId == fixture.host.id }
|
||||||
|
assertEquals("English host", english.nickname)
|
||||||
|
assertEquals(1L, currentNameQueryCount() - nameQueryCountBeforeEnglishHit)
|
||||||
|
assertEquals(roomListExecutions, statistics.getQueryStatistics(roomListQuery).executionCount)
|
||||||
|
assertEquals(cached, cache.get(cacheKey)?.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("언어 중립 live feed와 종료 캐시는 별칭 해제 후 변경된 현재 원본으로 fallback한다")
|
||||||
|
fun shouldFallBackToCurrentOriginalWithoutRewritingNeutralRedisNames() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
val info = roomInfoRepository.findById(fixture.roomId).orElseThrow()
|
||||||
|
info.addDonationMessage(fixture.host.id!!, "host", false, 10, "host 본문")
|
||||||
|
roomInfoRepository.save(info)
|
||||||
|
fixture.host.nickname = "updated-host"
|
||||||
|
memberRepository.flush()
|
||||||
|
jdbcTemplate.update("UPDATE creator_localized_name SET is_active = false WHERE member_id = ?", fixture.host.id!!)
|
||||||
|
langContext.setLang(Lang.JA)
|
||||||
|
|
||||||
|
val response = service.getRoomInfo(fixture.roomId, fixture.viewer)
|
||||||
|
val messages = service.getDonationMessageList(fixture.roomId, fixture.viewer)
|
||||||
|
val stored = roomInfoRepository.findById(fixture.roomId).orElseThrow()
|
||||||
|
|
||||||
|
assertEquals("updated-host", response.creatorNickname)
|
||||||
|
assertEquals("updated-host", response.speakerList.single().nickname)
|
||||||
|
assertEquals("updated-host", messages.single().nickname)
|
||||||
|
assertEquals("host", stored.speakerList.single().nickname)
|
||||||
|
assertEquals("host", stored.donationMessageList.single().nickname)
|
||||||
|
assertEquals("host 본문", messages.single().donationMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("예약 생성·목록·상세와 최근 방문자 조회는 실제 크리에이터 소유 ID로 JA 이름을 표시한다")
|
||||||
|
fun shouldLocalizeReservationAndRecentVisitorNames() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
langContext.setLang(Lang.JA)
|
||||||
|
val reservation = reservationService.makeReservation(
|
||||||
|
MakeLiveReservationRequest(fixture.roomId, "web", "UTC"),
|
||||||
|
fixture.viewer.id!!
|
||||||
|
)
|
||||||
|
val statistics = entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
statistics.clear()
|
||||||
|
val listed = reservationService.getReservationList(fixture.viewer.id!!, true, "UTC").single()
|
||||||
|
val detail = reservationService.getReservation(reservation.reservationId, fixture.viewer.id!!, "UTC")
|
||||||
|
assertEquals(2L, statistics.queryExecutionCount, statistics.queries.joinToString("\n"))
|
||||||
|
val room = roomRepository.findById(fixture.roomId).orElseThrow()
|
||||||
|
visitService.roomVisit(room, fixture.viewer)
|
||||||
|
visitService.roomVisit(room, fixture.manager)
|
||||||
|
statistics.clear()
|
||||||
|
val visitor = visitService.getRecentVisitRoomUsers(fixture.viewer).single()
|
||||||
|
assertEquals(2L, statistics.queryExecutionCount, statistics.queries.joinToString("\n"))
|
||||||
|
|
||||||
|
assertEquals("日本-host", reservation.nickname)
|
||||||
|
assertEquals("日本-host", listed.masterNickname)
|
||||||
|
assertEquals("日本-host", detail.masterNickname)
|
||||||
|
assertEquals(fixture.manager.id, visitor.id)
|
||||||
|
assertEquals("日本-manager", visitor.nickname)
|
||||||
|
langContext.setLang(Lang.KO)
|
||||||
|
assertEquals("manager", visitService.getRecentVisitRoomUsers(fixture.viewer).single().nickname)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("추천과 팔로잉의 on-air 및 off-air 분기는 요청별 이름을 표시하고 count와 순서를 보존한다")
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
|
||||||
|
fun shouldLocalizeRecommendedAndFollowingChannels() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
followingRepository.saveAndFlush(
|
||||||
|
CreatorFollowing().apply {
|
||||||
|
member = fixture.viewer
|
||||||
|
creator = fixture.host
|
||||||
|
}
|
||||||
|
)
|
||||||
|
followingRepository.saveAndFlush(
|
||||||
|
CreatorFollowing().apply {
|
||||||
|
member = fixture.viewer
|
||||||
|
creator = fixture.manager
|
||||||
|
}
|
||||||
|
)
|
||||||
|
langContext.setLang(Lang.JA)
|
||||||
|
|
||||||
|
val statistics = entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
statistics.clear()
|
||||||
|
val recommended = recommendService.getRecommendChannelList(null).associateBy { it.creatorId }
|
||||||
|
val aliasQueries = statistics.queries.filter {
|
||||||
|
Regex("""(?is)^select\s+\w+\.memberId,\s*\w+\.displayName\s+from\s+CreatorLocalizedNameEntity\b""")
|
||||||
|
.containsMatchIn(it)
|
||||||
|
}
|
||||||
|
val aliasQueryCount = aliasQueries.sumOf { statistics.getQueryStatistics(it).executionCount }
|
||||||
|
val queryCount = statistics.queryExecutionCount
|
||||||
|
val following = recommendService.getFollowingChannelList(fixture.viewer).associateBy { it.creatorId }
|
||||||
|
val all = recommendService.getFollowingAllChannelList(fixture.viewer, PageRequest.of(0, 20))
|
||||||
|
|
||||||
|
assertEquals("日本-host", recommended.getValue(fixture.host.id!!).nickname)
|
||||||
|
assertEquals("日本-manager", recommended.getValue(fixture.manager.id!!).nickname)
|
||||||
|
assertEquals("fallback", recommended.getValue(fixture.fallback.id!!).nickname)
|
||||||
|
assertEquals("日本-host", following.getValue(fixture.host.id!!).nickname)
|
||||||
|
assertEquals("日本-manager", following.getValue(fixture.manager.id!!).nickname)
|
||||||
|
assertEquals(2, all.totalCount)
|
||||||
|
assertEquals(setOf("日本-host", "日本-manager"), all.items.map { it.nickname }.toSet())
|
||||||
|
langContext.setLang(Lang.KO)
|
||||||
|
assertEquals(
|
||||||
|
setOf("host", "manager"),
|
||||||
|
recommendService.getFollowingAllChannelList(fixture.viewer, PageRequest.of(0, 20)).items.map { it.nickname }.toSet()
|
||||||
|
)
|
||||||
|
assertEquals(0L, aliasQueryCount, "Standalone alias SELECTs (total queries=$queryCount): $aliasQueries")
|
||||||
|
assertNoStandaloneNameQueries()
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"INSERT INTO creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"VALUES (?, 'EN', 'English host', true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
fixture.host.id!!
|
||||||
|
)
|
||||||
|
langContext.setLang(Lang.EN)
|
||||||
|
assertEquals(
|
||||||
|
setOf("English host", "manager"),
|
||||||
|
recommendService.getFollowingAllChannelList(fixture.viewer, PageRequest.of(0, 20)).items.map { it.nickname }.toSet()
|
||||||
|
)
|
||||||
|
assertNoStandaloneNameQueries()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("후원과 하트 집계는 donor ID로 이름만 바꾸고 USER·금액·비밀 합계와 공개 DTO 필드를 보존한다")
|
||||||
|
fun shouldLocalizeDonationAndHeartDonorNamesWithoutChangingAmounts() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
val room = roomRepository.findById(fixture.roomId).orElseThrow()
|
||||||
|
listOf(CanUsage.DONATION, CanUsage.HEART).forEach { usage ->
|
||||||
|
useCanRepository.saveAndFlush(
|
||||||
|
UseCan(usage, 10, 2).apply {
|
||||||
|
member = fixture.manager
|
||||||
|
this.room = room
|
||||||
|
}
|
||||||
|
)
|
||||||
|
useCanRepository.saveAndFlush(
|
||||||
|
UseCan(usage, 5, 1).apply {
|
||||||
|
member = fixture.viewer
|
||||||
|
this.room = room
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
langContext.setLang(Lang.JA)
|
||||||
|
|
||||||
|
val statistics = entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
statistics.clear()
|
||||||
|
val donations = service.getDonationStatus(fixture.roomId, fixture.host.id!!)
|
||||||
|
val hearts = service.getHeartList(fixture.roomId)
|
||||||
|
assertEquals(3L, statistics.queryExecutionCount, statistics.queries.joinToString("\n"))
|
||||||
|
|
||||||
|
assertEquals(listOf("日本-manager", "viewer"), donations.donationList.map { it.nickname })
|
||||||
|
assertEquals(listOf("日本-manager", "viewer"), hearts.heartList.map { it.nickname })
|
||||||
|
assertEquals(18, donations.totalCan)
|
||||||
|
assertEquals(0, donations.totalSecretCan)
|
||||||
|
assertEquals(18, hearts.totalHeart)
|
||||||
|
assertEquals(2, hearts.totalCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("진행·날짜별 예약·예정 라이브 목록은 EN 이름을 조인하고 USER·미등록·비활성 소유자를 유지한다")
|
||||||
|
fun shouldJoinEnglishNamesInEveryDatabaseRoomListWithoutMutatingOwners() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
val owners = listOf(fixture.host, fixture.manager, fixture.viewer, fixture.fallback)
|
||||||
|
listOf(fixture.host, fixture.viewer, fixture.fallback).forEach {
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"INSERT INTO creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"VALUES (?, 'EN', ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
it.id!!,
|
||||||
|
"EN-${it.nickname}",
|
||||||
|
it != fixture.fallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val rooms = listOf(roomRepository.findById(fixture.roomId).orElseThrow()) + owners.drop(1).map { owner ->
|
||||||
|
roomRepository.saveAndFlush(
|
||||||
|
LiveRoom("${owner.nickname} room", "notice", LocalDateTime.now(), 10, "cover.png", isAdult = false).apply {
|
||||||
|
member = owner
|
||||||
|
channelName = "live_${owner.id}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val expected = mapOf(
|
||||||
|
fixture.host.id to "EN-host",
|
||||||
|
fixture.manager.id to "manager",
|
||||||
|
fixture.viewer.id to "viewer",
|
||||||
|
fixture.fallback.id to "fallback"
|
||||||
|
)
|
||||||
|
val statistics = entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
statistics.clear()
|
||||||
|
|
||||||
|
val current = roomRepository.getLiveRoomListNow(0, 20, "UTC", null, false, true, null, Lang.EN)
|
||||||
|
assertEquals(expected, current.associate { it.room.member!!.id to it.creatorNickname })
|
||||||
|
assertEquals(1L, statistics.queryExecutionCount)
|
||||||
|
|
||||||
|
owners.forEach { owner ->
|
||||||
|
reservationRepository.saveAndFlush(
|
||||||
|
LiveReservation().apply {
|
||||||
|
member = owner
|
||||||
|
room = rooms.first()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
statistics.clear()
|
||||||
|
val bookers = reservationRepository.getReservationList(fixture.roomId, Lang.EN)
|
||||||
|
assertEquals(expected, bookers.associate { it.id to it.nickname })
|
||||||
|
assertEquals(1L, statistics.queryExecutionCount)
|
||||||
|
statistics.clear()
|
||||||
|
val reservation = reservationRepository.getReservationListByMemberId(fixture.viewer.id!!, true, Lang.EN).single()
|
||||||
|
val reserved = reservationRepository.getReservationByReservationAndMemberId(reservation.id, fixture.viewer.id!!, Lang.EN)
|
||||||
|
assertEquals("EN-host", reservation.masterNickname)
|
||||||
|
assertEquals("EN-host", reserved!!.masterNickname)
|
||||||
|
assertEquals(2L, statistics.queryExecutionCount)
|
||||||
|
|
||||||
|
val date = LocalDateTime.now().plusDays(1).withHour(12).withMinute(0)
|
||||||
|
rooms.forEach {
|
||||||
|
it.channelName = null
|
||||||
|
it.beginDateTime = date
|
||||||
|
}
|
||||||
|
roomRepository.flush()
|
||||||
|
statistics.clear()
|
||||||
|
val dated = roomRepository.getLiveRoomListReservationWithDate(
|
||||||
|
date.toLocalDate().atStartOfDay(),
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
Lang.EN
|
||||||
|
)
|
||||||
|
val upcoming = roomRepository.getLiveRoomListReservationWithoutDate("UTC", null, false, true, null, Lang.EN)
|
||||||
|
|
||||||
|
assertEquals(expected, dated.associate { it.room.member!!.id to it.creatorNickname })
|
||||||
|
assertEquals(expected, upcoming.associate { it.room.member!!.id to it.creatorNickname })
|
||||||
|
assertEquals(2L, statistics.queryExecutionCount)
|
||||||
|
assertEquals(listOf("host", "manager", "viewer", "fallback"), owners.map { it.nickname })
|
||||||
|
assertNoStandaloneNameQueries()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertNoStandaloneNameQueries() {
|
||||||
|
val statistics = entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
val nameQueries = statistics.queries.filter {
|
||||||
|
Regex("""(?is)\bfrom\s+CreatorLocalizedNameEntity\b""").containsMatchIn(it) ||
|
||||||
|
Regex("""(?is)^select\s+\w+\.id,\s*coalesce\([^)]*\)\s+from\s+Member\b""").containsMatchIn(it)
|
||||||
|
}
|
||||||
|
assertEquals(0L, nameQueries.sumOf { statistics.getQueryStatistics(it).executionCount }, nameQueries.joinToString("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun currentNameQueryCount(): Long {
|
||||||
|
val statistics = entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
return statistics.queries.filter { it.contains("from Member") && it.contains("CreatorLocalizedNameEntity") }
|
||||||
|
.sumOf { statistics.getQueryStatistics(it).executionCount }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFixture(): Fixture {
|
||||||
|
val host = savedMember("host", MemberRole.CREATOR)
|
||||||
|
val manager = savedMember("manager", MemberRole.CREATOR)
|
||||||
|
val viewer = savedMember("viewer", MemberRole.USER)
|
||||||
|
val fallback = savedMember("fallback", MemberRole.CREATOR, activeAlias = false)
|
||||||
|
val room = roomRepository.saveAndFlush(
|
||||||
|
LiveRoom(
|
||||||
|
title = "room title",
|
||||||
|
notice = "notice",
|
||||||
|
beginDateTime = LocalDateTime.now().minusMinutes(1),
|
||||||
|
numberOfPeople = 10,
|
||||||
|
coverImage = "cover.png",
|
||||||
|
isAdult = false
|
||||||
|
).apply {
|
||||||
|
member = host
|
||||||
|
channelName = "localized_live_${host.id}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
roomIds.add(room.id!!)
|
||||||
|
roomInfoRepository.save(
|
||||||
|
LiveRoomInfo(room.id!!).apply {
|
||||||
|
addSpeaker(host, "https://cdn.test")
|
||||||
|
addManager(manager, "https://cdn.test")
|
||||||
|
addListener(viewer, "https://cdn.test")
|
||||||
|
addListener(fallback, "https://cdn.test")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return Fixture(room.id!!, host, manager, viewer, fallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun savedMember(nickname: String, role: MemberRole, activeAlias: Boolean = true): Member {
|
||||||
|
val member = memberRepository.saveAndFlush(
|
||||||
|
Member(
|
||||||
|
email = "$nickname@localized-live.test",
|
||||||
|
password = "pw",
|
||||||
|
nickname = nickname,
|
||||||
|
profileImage = "profile/$nickname.png",
|
||||||
|
role = role,
|
||||||
|
isVisibleDonationRank = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"INSERT INTO creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"VALUES (?, 'JA', ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
member.id!!,
|
||||||
|
"日本-$nickname",
|
||||||
|
activeAlias
|
||||||
|
)
|
||||||
|
return member
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Fixture(val roomId: Long, val host: Member, val manager: Member, val viewer: Member, val fallback: Member)
|
||||||
|
}
|
||||||
+15
-6
@@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.content.ContentType
|
|||||||
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.CreatorDonationRankingService
|
import kr.co.vividnext.sodalive.explorer.profile.CreatorDonationRankingService
|
||||||
import kr.co.vividnext.sodalive.fcm.PushTokenRepository
|
import kr.co.vividnext.sodalive.fcm.PushTokenRepository
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
|
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
|
||||||
@@ -30,6 +31,7 @@ import kr.co.vividnext.sodalive.member.MemberRepository
|
|||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
|
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
@@ -106,6 +108,7 @@ class LiveRoomServiceAdultVisibilityPolicyTest {
|
|||||||
menuService = menuService,
|
menuService = menuService,
|
||||||
messageSource = messageSource,
|
messageSource = messageSource,
|
||||||
langContext = langContext,
|
langContext = langContext,
|
||||||
|
localizedNameService = mock<CreatorLocalizedNameService>(),
|
||||||
repository = repository,
|
repository = repository,
|
||||||
rouletteRepository = rouletteRepository,
|
rouletteRepository = rouletteRepository,
|
||||||
roomInfoRepository = roomInfoRepository,
|
roomInfoRepository = roomInfoRepository,
|
||||||
@@ -152,7 +155,8 @@ class LiveRoomServiceAdultVisibilityPolicyTest {
|
|||||||
memberId = 100L,
|
memberId = 100L,
|
||||||
isCreator = false,
|
isCreator = false,
|
||||||
isAdult = true,
|
isAdult = true,
|
||||||
effectiveGender = Gender.NONE
|
effectiveGender = Gender.NONE,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
).thenReturn(emptyList())
|
).thenReturn(emptyList())
|
||||||
|
|
||||||
@@ -172,7 +176,8 @@ class LiveRoomServiceAdultVisibilityPolicyTest {
|
|||||||
memberId = 100L,
|
memberId = 100L,
|
||||||
isCreator = false,
|
isCreator = false,
|
||||||
isAdult = true,
|
isAdult = true,
|
||||||
effectiveGender = Gender.NONE
|
effectiveGender = Gender.NONE,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +192,8 @@ class LiveRoomServiceAdultVisibilityPolicyTest {
|
|||||||
memberId = null,
|
memberId = null,
|
||||||
isCreator = false,
|
isCreator = false,
|
||||||
isAdult = true,
|
isAdult = true,
|
||||||
effectiveGender = null
|
effectiveGender = null,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
).thenReturn(emptyList())
|
).thenReturn(emptyList())
|
||||||
|
|
||||||
@@ -207,7 +213,8 @@ class LiveRoomServiceAdultVisibilityPolicyTest {
|
|||||||
memberId = null,
|
memberId = null,
|
||||||
isCreator = false,
|
isCreator = false,
|
||||||
isAdult = true,
|
isAdult = true,
|
||||||
effectiveGender = null
|
effectiveGender = null,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +229,8 @@ class LiveRoomServiceAdultVisibilityPolicyTest {
|
|||||||
memberId = 200L,
|
memberId = 200L,
|
||||||
isCreator = false,
|
isCreator = false,
|
||||||
isAdult = false,
|
isAdult = false,
|
||||||
effectiveGender = Gender.NONE
|
effectiveGender = Gender.NONE,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
).thenReturn(emptyList())
|
).thenReturn(emptyList())
|
||||||
|
|
||||||
@@ -240,7 +248,8 @@ class LiveRoomServiceAdultVisibilityPolicyTest {
|
|||||||
memberId = 200L,
|
memberId = 200L,
|
||||||
isCreator = false,
|
isCreator = false,
|
||||||
isAdult = false,
|
isAdult = false,
|
||||||
effectiveGender = Gender.NONE
|
effectiveGender = Gender.NONE,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import kr.co.vividnext.sodalive.member.MemberRole
|
|||||||
import kr.co.vividnext.sodalive.member.auth.Auth
|
import kr.co.vividnext.sodalive.member.auth.Auth
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
@@ -70,6 +71,7 @@ class LiveRoomServiceTest {
|
|||||||
menuService = mock<LiveRoomMenuService>(),
|
menuService = mock<LiveRoomMenuService>(),
|
||||||
messageSource = mock<SodaMessageSource>(),
|
messageSource = mock<SodaMessageSource>(),
|
||||||
langContext = LangContext(),
|
langContext = LangContext(),
|
||||||
|
localizedNameService = mock<CreatorLocalizedNameService>(),
|
||||||
repository = repository,
|
repository = repository,
|
||||||
rouletteRepository = rouletteRepository,
|
rouletteRepository = rouletteRepository,
|
||||||
roomInfoRepository = roomInfoRepository,
|
roomInfoRepository = roomInfoRepository,
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package kr.co.vividnext.sodalive.member
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import org.junit.jupiter.api.Assertions.assertAll
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@DataJpaTest(properties = ["spring.cache.type=none"])
|
||||||
|
@Import(QueryDslConfig::class)
|
||||||
|
class MemberRepositoryLocalizedNameTest @Autowired constructor(
|
||||||
|
private val repository: MemberRepository,
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
private val jdbcTemplate: JdbcTemplate
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
@DisplayName("크리에이터 요청의 회원 검색은 USER 원본 매칭과 CREATOR 별칭을 함께 유지한다")
|
||||||
|
fun shouldRetainUserOriginalMatchesAndAddOnlyCreatorAliasesForCreatorRequester() {
|
||||||
|
val requester = saveMember("needle-requester", MemberRole.CREATOR)
|
||||||
|
val creator = saveMember("creator-original", MemberRole.CREATOR)
|
||||||
|
val user = saveMember("needle-user", MemberRole.USER)
|
||||||
|
val aiUser = saveMember("ai-user-original", MemberRole.USER).apply { memberKind = MemberKind.AI_CHARACTER }
|
||||||
|
val admin = saveMember("needle-admin", MemberRole.ADMIN)
|
||||||
|
val agent = saveMember("needle-agent", MemberRole.AGENT)
|
||||||
|
val bot = saveMember("needle-bot", MemberRole.BOT)
|
||||||
|
listOf(requester, creator, user, aiUser, admin, agent, bot).forEach { saveAlias(it, "needle-alias") }
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val matches = repository.findByNicknameAndOtherCondition("NeEdLe", requester)
|
||||||
|
|
||||||
|
assertEquals(setOf(creator.id, user.id), matches.map { it.id }.toSet())
|
||||||
|
assertEquals(2, matches.size)
|
||||||
|
assertEquals(listOf(creator.id), repository.findByNicknameAndOtherCondition("creator-original", requester).map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("USER 요청의 회원 검색은 기존 USER·self·관리 역할 제외 조건을 별칭에도 적용한다")
|
||||||
|
fun shouldPreserveUserRequesterRoleAndSelfExclusionsForAliases() {
|
||||||
|
val requester = saveMember("needle-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("creator-original", MemberRole.CREATOR)
|
||||||
|
val user = saveMember("needle-user", MemberRole.USER)
|
||||||
|
val admin = saveMember("needle-admin", MemberRole.ADMIN)
|
||||||
|
val agent = saveMember("needle-agent", MemberRole.AGENT)
|
||||||
|
val bot = saveMember("needle-bot", MemberRole.BOT)
|
||||||
|
listOf(requester, creator, user, admin, agent, bot).forEach { saveAlias(it, "needle-alias") }
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
assertEquals(listOf(creator.id), repository.findByNicknameAndOtherCondition("needle", requester).map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("비활성 JA와 다른 언어 이름은 회원 별칭 검색에서 제외하고 현재 원본은 유지한다")
|
||||||
|
fun shouldExcludeInactiveAndNonJaAliasesWhileRetainingCurrentOriginalNickname() {
|
||||||
|
val requester = saveMember("member-clear-viewer", MemberRole.CREATOR)
|
||||||
|
val creator = saveMember("before-rename", MemberRole.CREATOR)
|
||||||
|
saveAlias(creator, "RetainedAlias", isActive = false)
|
||||||
|
saveAlias(creator, "EnglishAlias", lang = "EN")
|
||||||
|
creator.nickname = "after-rename"
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
assertEquals(emptyList<Long>(), repository.findByNicknameAndOtherCondition("RetainedAlias", requester).map { it.id })
|
||||||
|
assertEquals(emptyList<Long>(), repository.findByNicknameAndOtherCondition("EnglishAlias", requester).map { it.id })
|
||||||
|
assertEquals(listOf(creator.id), repository.findByNicknameAndOtherCondition("after-rename", requester).map { it.id })
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update creator_localized_name set is_active = true where member_id = ? and lang = 'JA'",
|
||||||
|
creator.id!!
|
||||||
|
)
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
listOf(creator.id),
|
||||||
|
repository.findByNicknameAndOtherCondition("RetainedAlias", requester).map { it.id }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
listOf(creator.id),
|
||||||
|
repository.findByNicknameAndOtherCondition("after-rename", requester).map { it.id }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||||
|
return Member(email = "$nickname@test.com", password = "password", nickname = nickname, role = role).also {
|
||||||
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAlias(member: Member, displayName: String, isActive: Boolean = true, lang: String = "JA") {
|
||||||
|
entityManager.flush()
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"values (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
member.id!!,
|
||||||
|
lang,
|
||||||
|
displayName,
|
||||||
|
isActive
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.member.block.BlockMember
|
|||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
import kr.co.vividnext.sodalive.member.block.MemberBlockRequest
|
import kr.co.vividnext.sodalive.member.block.MemberBlockRequest
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
|
import kr.co.vividnext.sodalive.support.originalLocalizedNameService
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -62,6 +63,7 @@ class MemberServiceCacheEvictionTest {
|
|||||||
authenticationManagerBuilder = mock(),
|
authenticationManagerBuilder = mock(),
|
||||||
messageSource = SodaMessageSource(),
|
messageSource = SodaMessageSource(),
|
||||||
langContext = LangContext(),
|
langContext = LangContext(),
|
||||||
|
localizedNameService = originalLocalizedNameService(),
|
||||||
countryContext = CountryContext(),
|
countryContext = CountryContext(),
|
||||||
memberContentPreferenceService = mock<MemberContentPreferenceService>(),
|
memberContentPreferenceService = mock<MemberContentPreferenceService>(),
|
||||||
objectMapper = ObjectMapper(),
|
objectMapper = ObjectMapper(),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.member.stipulation.Stipulation
|
|||||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgreeRepository
|
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgreeRepository
|
||||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationIds
|
import kr.co.vividnext.sodalive.member.stipulation.StipulationIds
|
||||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationRepository
|
import kr.co.vividnext.sodalive.member.stipulation.StipulationRepository
|
||||||
|
import kr.co.vividnext.sodalive.support.originalLocalizedNameService
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
@@ -71,6 +72,7 @@ class MemberServiceContentPreferenceTest {
|
|||||||
authenticationManagerBuilder = mock(),
|
authenticationManagerBuilder = mock(),
|
||||||
messageSource = SodaMessageSource(),
|
messageSource = SodaMessageSource(),
|
||||||
langContext = LangContext(),
|
langContext = LangContext(),
|
||||||
|
localizedNameService = originalLocalizedNameService(),
|
||||||
countryContext = CountryContext(),
|
countryContext = CountryContext(),
|
||||||
memberContentPreferenceService = memberContentPreferenceService,
|
memberContentPreferenceService = memberContentPreferenceService,
|
||||||
objectMapper = ObjectMapper(),
|
objectMapper = ObjectMapper(),
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ class OsivLazyLoadingRegressionTest @Autowired constructor(
|
|||||||
|
|
||||||
val creators = rankingRepository.getCreatorRankings(memberId = null)
|
val creators = rankingRepository.getCreatorRankings(memberId = null)
|
||||||
|
|
||||||
val response = creators.single().toExplorerSectionCreator("https://cdn.test")
|
val response = creators.single()
|
||||||
assertEquals("#creator-tag", response.tags)
|
assertEquals("#creator-tag", response.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.ContentType
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.member.block.BlockMember
|
||||||
|
import kr.co.vividnext.sodalive.member.tag.CreatorTag
|
||||||
|
import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag
|
||||||
|
import org.hibernate.SessionFactory
|
||||||
|
import org.junit.jupiter.api.Assertions.assertAll
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@DataJpaTest(properties = ["spring.cache.type=none", "spring.jpa.properties.hibernate.generate_statistics=true"])
|
||||||
|
@Import(QueryDslConfig::class)
|
||||||
|
class SearchRepositoryLocalizedNameTest @Autowired constructor(
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
private val jdbcTemplate: JdbcTemplate,
|
||||||
|
queryFactory: JPAQueryFactory
|
||||||
|
) {
|
||||||
|
private val repository = SearchRepository(queryFactory, "https://cdn.test")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("크리에이터 검색은 원본과 활성 JA 별칭을 함께 찾고 중복 별칭도 회원별로 한 번씩 센다")
|
||||||
|
fun shouldSearchOriginalOrActiveJaAliasWithDistinctOwnersAndMatchingCount() {
|
||||||
|
val viewer = saveMember("search-viewer", MemberRole.USER)
|
||||||
|
val original = saveMember("Orion-original")
|
||||||
|
val aliasOnly = saveMember("second-original")
|
||||||
|
saveAlias(original, "Orion Japanese")
|
||||||
|
saveAlias(aliasOnly, "Orion Japanese")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val items = repository.searchCreatorList("oRiOn", viewer.id!!, 0, 20)
|
||||||
|
val page = repository.searchCreatorList("oRiOn", viewer.id!!, 1, 1)
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals(2, repository.searchCreatorTotalCount("oRiOn", viewer.id!!)) },
|
||||||
|
{ assertEquals(listOf(aliasOnly.id, original.id), items.map { it.id }) },
|
||||||
|
{ assertEquals(listOf(original.id), page.map { it.id }) },
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
listOf(original.id),
|
||||||
|
repository.searchCreatorList("Orion-original", viewer.id!!, 0, 20).map { it.id }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("별칭 검색은 회원 활성·역할·태그 활성·양방향 차단 필터를 우회하지 않는다")
|
||||||
|
fun shouldPreserveCreatorVisibilityTagAndBidirectionalBlockFiltersForAliasMatches() {
|
||||||
|
val viewer = saveMember("filter-viewer", MemberRole.USER)
|
||||||
|
val visible = saveMember("visible-original")
|
||||||
|
val inactive = saveMember("inactive-original").apply { isActive = false }
|
||||||
|
val user = saveMember("user-original", MemberRole.USER)
|
||||||
|
val blockedByViewer = saveMember("blocked-original")
|
||||||
|
val blockingViewer = saveMember("blocking-original")
|
||||||
|
val inactiveTagOwner = saveMember("inactive-tag-original")
|
||||||
|
saveBlock(viewer, blockedByViewer)
|
||||||
|
saveBlock(blockingViewer, viewer)
|
||||||
|
val inactiveTag = CreatorTag(tag = "inactive-tag", isActive = false)
|
||||||
|
entityManager.persist(inactiveTag)
|
||||||
|
entityManager.persist(MemberCreatorTag(member = inactiveTagOwner, tag = inactiveTag))
|
||||||
|
listOf(visible, inactive, user, blockedByViewer, blockingViewer, inactiveTagOwner).forEach {
|
||||||
|
saveAlias(it, "AliasVisibility")
|
||||||
|
}
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals(1, repository.searchCreatorTotalCount("AliasVisibility", viewer.id!!)) },
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
listOf(visible.id),
|
||||||
|
repository.searchCreatorList("AliasVisibility", viewer.id!!, 0, 20).map { it.id }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("비활성 JA와 다른 언어 별칭은 제외하고 원본 검색과 같은 행 재활성화는 유지한다")
|
||||||
|
fun shouldExcludeSoftClearedAndNonJaAliasesWithoutLosingOriginalSearch() {
|
||||||
|
val viewer = saveMember("clear-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("current-original")
|
||||||
|
val content = saveContent(creator, "clear content title")
|
||||||
|
val series = saveSeries(creator, "clear series title", content)
|
||||||
|
saveAlias(creator, "ClearedAlias", isActive = false)
|
||||||
|
saveAlias(creator, "EnglishAlias", lang = "EN")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
assertEquals(0, repository.searchCreatorTotalCount("ClearedAlias", viewer.id!!))
|
||||||
|
assertEquals(emptyList<Long>(), repository.searchCreatorList("ClearedAlias", viewer.id!!, 0, 20).map { it.id })
|
||||||
|
assertEquals(0, repository.searchCreatorTotalCount("EnglishAlias", viewer.id!!))
|
||||||
|
assertEquals(1, repository.searchCreatorTotalCount("current-original", viewer.id!!))
|
||||||
|
assertEquals(0, repository.searchContentTotalCount("ClearedAlias", viewer.id!!, false, ContentType.ALL))
|
||||||
|
assertEquals(0, repository.searchSeriesTotalCount("ClearedAlias", viewer.id!!, false, ContentType.ALL))
|
||||||
|
assertEquals(1, repository.searchContentTotalCount("current-original", viewer.id!!, false, ContentType.ALL))
|
||||||
|
assertEquals(1, repository.searchSeriesTotalCount("current-original", viewer.id!!, false, ContentType.ALL))
|
||||||
|
|
||||||
|
repeat(2) {
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update creator_localized_name set is_active = true where member_id = ? and lang = 'JA'",
|
||||||
|
creator.id!!
|
||||||
|
)
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals(1, repository.searchCreatorTotalCount("ClearedAlias", viewer.id!!)) },
|
||||||
|
{ assertEquals(1, repository.searchContentTotalCount("ClearedAlias", viewer.id!!, false, ContentType.ALL)) },
|
||||||
|
{ assertEquals(1, repository.searchSeriesTotalCount("ClearedAlias", viewer.id!!, false, ContentType.ALL)) },
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
listOf(content.id),
|
||||||
|
repository.searchContentList("ClearedAlias", viewer.id!!, false, ContentType.ALL, 0, 20).map { it.id }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
listOf(series.id),
|
||||||
|
repository.searchSeriesList("ClearedAlias", viewer.id!!, false, ContentType.ALL, 0, 20).map { it.id }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assertEquals(
|
||||||
|
listOf(creator.id),
|
||||||
|
repository.searchCreatorList("ClearedAlias", viewer.id!!, 0, 20).map { it.id }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update creator_localized_name set is_active = false where member_id = ? and lang = 'JA'",
|
||||||
|
creator.id!!
|
||||||
|
)
|
||||||
|
assertEquals(0, repository.searchCreatorTotalCount("ClearedAlias", viewer.id!!))
|
||||||
|
assertEquals(0, repository.searchContentTotalCount("ClearedAlias", viewer.id!!, false, ContentType.ALL))
|
||||||
|
assertEquals(0, repository.searchSeriesTotalCount("ClearedAlias", viewer.id!!, false, ContentType.ALL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("콘텐츠 검색은 소유자 별칭을 찾되 성인·활성·완성·한정·차단 필터와 콘텐츠 제목을 보존한다")
|
||||||
|
fun shouldSearchContentOwnerAliasWithMatchingCountAndExistingContentFilters() {
|
||||||
|
val viewer = saveMember("content-search-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("audio-owner-original")
|
||||||
|
val visible = saveContent(creator, "Unchanged audio title")
|
||||||
|
saveContent(creator, "adult audio").isAdult = true
|
||||||
|
saveContent(creator, "inactive audio").isActive = false
|
||||||
|
saveContent(creator, "unfinished audio").duration = null
|
||||||
|
saveContent(creator, "limited audio", limited = 5)
|
||||||
|
val blocked = saveMember("blocked-audio-owner")
|
||||||
|
saveContent(blocked, "blocked audio")
|
||||||
|
saveBlock(viewer, blocked)
|
||||||
|
saveAlias(creator, "AudioAlias")
|
||||||
|
saveAlias(creator, "English Audio Owner", lang = "EN")
|
||||||
|
saveAlias(blocked, "AudioAlias")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val statistics = entityManager.entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
statistics.clear()
|
||||||
|
val items = repository.searchContentList("AudioAlias", viewer.id!!, false, ContentType.ALL, 0, 20, Lang.JA)
|
||||||
|
val aliasQueries = statistics.queries.filter {
|
||||||
|
Regex("""(?is)^select\s+\w+\.memberId,\s*\w+\.displayName\s+from\s+CreatorLocalizedNameEntity\b""")
|
||||||
|
.containsMatchIn(it)
|
||||||
|
}
|
||||||
|
val aliasQueryCount = aliasQueries.sumOf { statistics.getQueryStatistics(it).executionCount }
|
||||||
|
val queryCount = statistics.queryExecutionCount
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals(1, repository.searchContentTotalCount("AudioAlias", viewer.id!!, false, ContentType.ALL)) },
|
||||||
|
{ assertEquals(listOf(visible.id), items.map { it.id }) },
|
||||||
|
{ assertEquals(listOf("Unchanged audio title"), items.map { it.title }) },
|
||||||
|
{ assertEquals(listOf("AudioAlias"), items.map { it.nickname }) },
|
||||||
|
{ assertEquals(1, repository.searchContentTotalCount("audio-owner-original", viewer.id!!, false, ContentType.ALL)) },
|
||||||
|
{ assertEquals(1, repository.searchContentTotalCount("Unchanged audio title", viewer.id!!, false, ContentType.ALL)) }
|
||||||
|
)
|
||||||
|
assertEquals(0L, aliasQueryCount, "Standalone alias SELECTs (total queries=$queryCount): $aliasQueries")
|
||||||
|
val englishItems = repository.searchContentList("AudioAlias", viewer.id!!, false, ContentType.ALL, 0, 20, Lang.EN)
|
||||||
|
assertEquals(listOf("English Audio Owner"), englishItems.map { it.nickname })
|
||||||
|
assertEquals(0, repository.searchContentTotalCount("English Audio Owner", viewer.id!!, false, ContentType.ALL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 검색은 시리즈 소유자 별칭을 찾고 콘텐츠 조인 중복과 기존 공개 필터를 보존한다")
|
||||||
|
fun shouldSearchSeriesOwnerAliasWithoutDuplicatingJoinedContentsOrChangingTitles() {
|
||||||
|
val viewer = saveMember("series-search-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("series-owner-original")
|
||||||
|
val audioOwner = saveMember("different-audio-owner")
|
||||||
|
val visible = saveSeries(creator, "Unchanged series title", saveContent(audioOwner, "first episode"))
|
||||||
|
saveSeriesContent(visible, saveContent(audioOwner, "second episode"))
|
||||||
|
saveSeries(creator, "adult series", saveContent(creator, "public episode")).isAdult = true
|
||||||
|
saveSeries(creator, "adult episode series", saveContent(creator, "adult episode").apply { isAdult = true })
|
||||||
|
saveSeries(creator, "inactive series", saveContent(creator, "active episode")).isActive = false
|
||||||
|
saveSeries(creator, "unfinished series", saveContent(creator, "unfinished episode").apply { duration = null })
|
||||||
|
val blocked = saveMember("blocked-series-owner")
|
||||||
|
saveSeries(blocked, "blocked series", saveContent(blocked, "blocked episode"))
|
||||||
|
saveBlock(blocked, viewer)
|
||||||
|
saveAlias(creator, "SeriesAlias")
|
||||||
|
saveAlias(audioOwner, "EpisodeOwnerAlias")
|
||||||
|
saveAlias(blocked, "SeriesAlias")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val items = repository.searchSeriesList("SeriesAlias", viewer.id!!, false, ContentType.ALL, 0, 20)
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals(1, repository.searchSeriesTotalCount("SeriesAlias", viewer.id!!, false, ContentType.ALL)) },
|
||||||
|
{ assertEquals(listOf(visible.id), items.map { it.id }) },
|
||||||
|
{ assertEquals(listOf("Unchanged series title"), items.map { it.title }) },
|
||||||
|
{ assertEquals(0, repository.searchSeriesTotalCount("EpisodeOwnerAlias", viewer.id!!, false, ContentType.ALL)) },
|
||||||
|
{ assertEquals(1, repository.searchSeriesTotalCount("series-owner-original", viewer.id!!, false, ContentType.ALL)) },
|
||||||
|
{ assertEquals(1, repository.searchSeriesTotalCount("Unchanged series title", viewer.id!!, false, ContentType.ALL)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole = MemberRole.CREATOR): Member {
|
||||||
|
return Member(
|
||||||
|
email = "$nickname@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = nickname,
|
||||||
|
profileImage = "$nickname.png",
|
||||||
|
role = role
|
||||||
|
).also { entityManager.persist(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAlias(member: Member, displayName: String, isActive: Boolean = true, lang: String = "JA") {
|
||||||
|
entityManager.flush()
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"values (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
member.id!!,
|
||||||
|
lang,
|
||||||
|
displayName,
|
||||||
|
isActive
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveBlock(member: Member, blockedMember: Member) {
|
||||||
|
entityManager.persist(
|
||||||
|
BlockMember(isActive = true).apply {
|
||||||
|
this.member = member
|
||||||
|
this.blockedMember = blockedMember
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveContent(creator: Member, title: String, limited: Int? = null): AudioContent {
|
||||||
|
val theme = AudioContentTheme(theme = "theme-$title", image = "theme.png")
|
||||||
|
entityManager.persist(theme)
|
||||||
|
return AudioContent(
|
||||||
|
title = title,
|
||||||
|
detail = "description",
|
||||||
|
languageCode = "ko",
|
||||||
|
releaseDate = LocalDateTime.now().minusDays(1),
|
||||||
|
limited = limited
|
||||||
|
).apply {
|
||||||
|
member = creator
|
||||||
|
this.theme = theme
|
||||||
|
isActive = true
|
||||||
|
coverImage = "audio.png"
|
||||||
|
duration = "00:10:00"
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveSeries(creator: Member, title: String, content: AudioContent): Series {
|
||||||
|
val genre = SeriesGenre(genre = "genre-$title", isAdult = false, isActive = true)
|
||||||
|
entityManager.persist(genre)
|
||||||
|
return Series(title = title, introduction = "introduction", languageCode = "ko").apply {
|
||||||
|
member = creator
|
||||||
|
this.genre = genre
|
||||||
|
coverImage = "series.png"
|
||||||
|
entityManager.persist(this)
|
||||||
|
saveSeriesContent(this, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveSeriesContent(series: Series, content: AudioContent) {
|
||||||
|
entityManager.persist(
|
||||||
|
SeriesContent().apply {
|
||||||
|
this.series = series
|
||||||
|
this.content = content
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.search
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.content.ContentType
|
import kr.co.vividnext.sodalive.content.ContentType
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
@@ -9,7 +10,7 @@ import org.mockito.Mockito
|
|||||||
|
|
||||||
class SearchServiceTest {
|
class SearchServiceTest {
|
||||||
private val repository: SearchRepository = Mockito.mock(SearchRepository::class.java)
|
private val repository: SearchRepository = Mockito.mock(SearchRepository::class.java)
|
||||||
private val service = SearchService(repository)
|
private val service = SearchService(repository, LangContext())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("콘텐츠 검색은 전달받은 isAdult 값을 그대로 사용한다")
|
@DisplayName("콘텐츠 검색은 전달받은 isAdult 값을 그대로 사용한다")
|
||||||
|
|||||||
+1
@@ -132,6 +132,7 @@ class LegacyCommunityPostCharacterizationTest {
|
|||||||
assertEquals(FcmEventType.CHANGE_NOTICE, event.type)
|
assertEquals(FcmEventType.CHANGE_NOTICE, event.type)
|
||||||
assertEquals(PushNotificationCategory.COMMUNITY, event.category)
|
assertEquals(PushNotificationCategory.COMMUNITY, event.category)
|
||||||
assertEquals("creator.community.fcm.new_post", event.messageKey)
|
assertEquals("creator.community.fcm.new_post", event.messageKey)
|
||||||
|
assertEquals(creator.id, event.titleMemberId)
|
||||||
assertEquals(creator.id, event.creatorId)
|
assertEquals(creator.id, event.creatorId)
|
||||||
assertEquals(FcmDeepLinkValue.COMMUNITY, event.deepLinkValue)
|
assertEquals(FcmDeepLinkValue.COMMUNITY, event.deepLinkValue)
|
||||||
assertEquals(creator.id, event.deepLinkId)
|
assertEquals(creator.id, event.deepLinkId)
|
||||||
|
|||||||
+12
-1
@@ -22,6 +22,7 @@ import kr.co.vividnext.sodalive.member.Member
|
|||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.MemberService
|
import kr.co.vividnext.sodalive.member.MemberService
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertNull
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
@@ -42,6 +43,7 @@ class LegacyFanTalkReplyCharacterizationTest {
|
|||||||
private lateinit var cheersRepository: CreatorCheersRepository
|
private lateinit var cheersRepository: CreatorCheersRepository
|
||||||
private lateinit var applicationEventPublisher: ApplicationEventPublisher
|
private lateinit var applicationEventPublisher: ApplicationEventPublisher
|
||||||
private lateinit var messageSource: SodaMessageSource
|
private lateinit var messageSource: SodaMessageSource
|
||||||
|
private lateinit var localizedNameService: CreatorLocalizedNameService
|
||||||
private lateinit var service: ExplorerService
|
private lateinit var service: ExplorerService
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -51,6 +53,7 @@ class LegacyFanTalkReplyCharacterizationTest {
|
|||||||
cheersRepository = Mockito.mock(CreatorCheersRepository::class.java)
|
cheersRepository = Mockito.mock(CreatorCheersRepository::class.java)
|
||||||
applicationEventPublisher = Mockito.mock(ApplicationEventPublisher::class.java)
|
applicationEventPublisher = Mockito.mock(ApplicationEventPublisher::class.java)
|
||||||
messageSource = Mockito.mock(SodaMessageSource::class.java)
|
messageSource = Mockito.mock(SodaMessageSource::class.java)
|
||||||
|
localizedNameService = Mockito.mock(CreatorLocalizedNameService::class.java)
|
||||||
service = ExplorerService(
|
service = ExplorerService(
|
||||||
memberService = memberService,
|
memberService = memberService,
|
||||||
memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java),
|
memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java),
|
||||||
@@ -66,6 +69,7 @@ class LegacyFanTalkReplyCharacterizationTest {
|
|||||||
contentTranslationRepository = Mockito.mock(ContentTranslationRepository::class.java),
|
contentTranslationRepository = Mockito.mock(ContentTranslationRepository::class.java),
|
||||||
messageSource = messageSource,
|
messageSource = messageSource,
|
||||||
langContext = LangContext().apply { setLang(Lang.KO) },
|
langContext = LangContext().apply { setLang(Lang.KO) },
|
||||||
|
localizedNameService = localizedNameService,
|
||||||
cloudFrontHost = "https://cdn.test"
|
cloudFrontHost = "https://cdn.test"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -177,6 +181,8 @@ class LegacyFanTalkReplyCharacterizationTest {
|
|||||||
val root = createCheers(id = 300L, content = "root", member = creator, creator = creator)
|
val root = createCheers(id = 300L, content = "root", member = creator, creator = creator)
|
||||||
Mockito.`when`(queryRepository.getMember(creator.id!!)).thenReturn(creator)
|
Mockito.`when`(queryRepository.getMember(creator.id!!)).thenReturn(creator)
|
||||||
Mockito.`when`(queryRepository.getCreatorCheers(root.id!!)).thenReturn(root)
|
Mockito.`when`(queryRepository.getCreatorCheers(root.id!!)).thenReturn(root)
|
||||||
|
Mockito.`when`(localizedNameService.findLocalizedNames(listOf(creator.id!!), Lang.KO))
|
||||||
|
.thenReturn(mapOf(creator.id!! to "Korean creator alias"))
|
||||||
var nextId = 301L
|
var nextId = 301L
|
||||||
Mockito.`when`(cheersRepository.save(Mockito.any(CreatorCheers::class.java))).thenAnswer { invocation ->
|
Mockito.`when`(cheersRepository.save(Mockito.any(CreatorCheers::class.java))).thenAnswer { invocation ->
|
||||||
invocation.getArgument<CreatorCheers>(0).also { cheers ->
|
invocation.getArgument<CreatorCheers>(0).also { cheers ->
|
||||||
@@ -185,7 +191,7 @@ class LegacyFanTalkReplyCharacterizationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
service.writeCheers(
|
val response = service.writeCheers(
|
||||||
PostWriteCheersRequest(root.id, creator.id!!, "first reply", "ko"),
|
PostWriteCheersRequest(root.id, creator.id!!, "first reply", "ko"),
|
||||||
creator
|
creator
|
||||||
)
|
)
|
||||||
@@ -198,6 +204,8 @@ class LegacyFanTalkReplyCharacterizationTest {
|
|||||||
Mockito.verify(cheersRepository, Mockito.times(2)).save(cheersCaptor.capture())
|
Mockito.verify(cheersRepository, Mockito.times(2)).save(cheersCaptor.capture())
|
||||||
assertEquals(listOf("first reply", "second reply"), cheersCaptor.allValues.map { it.cheers })
|
assertEquals(listOf("first reply", "second reply"), cheersCaptor.allValues.map { it.cheers })
|
||||||
assertTrue(cheersCaptor.allValues.all { it.parent === root })
|
assertTrue(cheersCaptor.allValues.all { it.parent === root })
|
||||||
|
assertEquals("duplicate-creator", response.writerNickname)
|
||||||
|
Mockito.verifyNoInteractions(localizedNameService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -222,6 +230,8 @@ class LegacyFanTalkReplyCharacterizationTest {
|
|||||||
Mockito.`when`(queryRepository.getMember(creator.id!!)).thenReturn(creator)
|
Mockito.`when`(queryRepository.getMember(creator.id!!)).thenReturn(creator)
|
||||||
Mockito.`when`(memberService.isBlocked(writer.id!!, creator.id!!)).thenReturn(true)
|
Mockito.`when`(memberService.isBlocked(writer.id!!, creator.id!!)).thenReturn(true)
|
||||||
Mockito.`when`(messageSource.getMessage("explorer.creator.blocked_cheers", Lang.KO)).thenReturn("blocked %s")
|
Mockito.`when`(messageSource.getMessage("explorer.creator.blocked_cheers", Lang.KO)).thenReturn("blocked %s")
|
||||||
|
Mockito.`when`(localizedNameService.findLocalizedNames(listOf(creator.id!!), Lang.KO))
|
||||||
|
.thenReturn(mapOf(creator.id!! to "Korean blocked alias"))
|
||||||
|
|
||||||
val exception = assertThrows(SodaException::class.java) {
|
val exception = assertThrows(SodaException::class.java) {
|
||||||
service.writeCheers(PostWriteCheersRequest(creatorId = creator.id!!, content = "reply"), writer)
|
service.writeCheers(PostWriteCheersRequest(creatorId = creator.id!!, content = "reply"), writer)
|
||||||
@@ -229,6 +239,7 @@ class LegacyFanTalkReplyCharacterizationTest {
|
|||||||
|
|
||||||
assertNull(exception.messageKey)
|
assertNull(exception.messageKey)
|
||||||
assertEquals("blocked blocked-creator", exception.message)
|
assertEquals("blocked blocked-creator", exception.message)
|
||||||
|
Mockito.verifyNoInteractions(localizedNameService)
|
||||||
Mockito.verifyNoInteractions(cheersRepository, applicationEventPublisher)
|
Mockito.verifyNoInteractions(cheersRepository, applicationEventPublisher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+467
@@ -0,0 +1,467 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creatorname.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.audition.Audition
|
||||||
|
import kr.co.vividnext.sodalive.audition.AuditionApplicant
|
||||||
|
import kr.co.vividnext.sodalive.audition.AuditionRole
|
||||||
|
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantService
|
||||||
|
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantSortType
|
||||||
|
import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront
|
||||||
|
import kr.co.vividnext.sodalive.can.CanService
|
||||||
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanCalculate
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
|
||||||
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContentService
|
||||||
|
import kr.co.vividnext.sodalive.content.order.Order
|
||||||
|
import kr.co.vividnext.sodalive.content.order.OrderService
|
||||||
|
import kr.co.vividnext.sodalive.content.order.OrderType
|
||||||
|
import kr.co.vividnext.sodalive.content.playlist.AudioContentPlaylist
|
||||||
|
import kr.co.vividnext.sodalive.content.playlist.AudioContentPlaylistRedisRepository
|
||||||
|
import kr.co.vividnext.sodalive.content.playlist.AudioContentPlaylistService
|
||||||
|
import kr.co.vividnext.sodalive.content.playlist.PlaylistContentIdAndOrder
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
|
import kr.co.vividnext.sodalive.explorer.ExplorerService
|
||||||
|
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingListResponse
|
||||||
|
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingResponse
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationMessage
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationService
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityService
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.CreatorCommunityComment
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberService
|
||||||
|
import kr.co.vividnext.sodalive.member.block.BlockMember
|
||||||
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import org.springframework.test.context.ContextConfiguration
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@SpringBootTest(
|
||||||
|
properties = [
|
||||||
|
"spring.cache.type=none",
|
||||||
|
"cloud.aws.cloud-front.host=https://cdn.test",
|
||||||
|
"spring.datasource.url=jdbc:h2:mem:legacy-current-name-reads;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||||
|
@Transactional
|
||||||
|
class LegacyCurrentNameReadIntegrationTest @Autowired constructor(
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
private val jdbcTemplate: JdbcTemplate,
|
||||||
|
private val memberService: MemberService,
|
||||||
|
private val orderService: OrderService,
|
||||||
|
private val playlistService: AudioContentPlaylistService,
|
||||||
|
private val playlistRepository: AudioContentPlaylistRedisRepository,
|
||||||
|
private val communityService: CreatorCommunityService,
|
||||||
|
private val donationService: ChannelDonationService,
|
||||||
|
private val explorerService: ExplorerService,
|
||||||
|
private val canService: CanService,
|
||||||
|
private val contentService: AudioContentService,
|
||||||
|
private val applicantService: AuditionApplicantService,
|
||||||
|
private val redisTemplate: RedisTemplate<String, Any>
|
||||||
|
) {
|
||||||
|
@MockBean
|
||||||
|
private lateinit var langContext: LangContext
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var countryContext: CountryContext
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var cloudFront: AudioContentCloudFront
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
jdbcTemplate.execute(
|
||||||
|
"CREATE ALIAS IF NOT EXISTS DATE_FORMAT FOR 'kr.co.vividnext.sodalive.support.H2MysqlDateFunctions.dateFormat'"
|
||||||
|
)
|
||||||
|
jdbcTemplate.execute(
|
||||||
|
"CREATE ALIAS IF NOT EXISTS CONVERT_TZ FOR 'kr.co.vividnext.sodalive.support.H2MysqlDateFunctions.convertTz'"
|
||||||
|
)
|
||||||
|
Mockito.doReturn(Lang.JA).`when`(langContext).lang
|
||||||
|
Mockito.doReturn("US").`when`(countryContext).countryCode
|
||||||
|
Mockito.doReturn("https://cdn.test/signed-audio").`when`(cloudFront)
|
||||||
|
.generateSignedURL(Mockito.anyString(), Mockito.anyLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("다른 회원 프로필과 차단 목록은 JA 이름을 표시하고 편집용 원본 프로필과 USER는 보존한다")
|
||||||
|
fun shouldLocalizeMemberDisplayAndBlockedListWithoutChangingRawEditorOrUserName() {
|
||||||
|
val viewer = saveMember("blocked-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("blocked-creator", MemberRole.CREATOR)
|
||||||
|
val user = saveMember("blocked-user", MemberRole.USER)
|
||||||
|
listOf(creator, user).forEach { target ->
|
||||||
|
entityManager.persist(
|
||||||
|
BlockMember().apply {
|
||||||
|
member = viewer
|
||||||
|
blockedMember = target
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
saveAlias(creator, "Japanese blocked creator")
|
||||||
|
saveAlias(user, "Must not replace blocked USER")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
assertEquals("Japanese blocked creator", memberService.getMemberProfile(creator.id!!, viewer.id!!).nickname)
|
||||||
|
assertEquals("blocked-creator", memberService.getMember(creator.id!!, "web").nickname)
|
||||||
|
assertEquals("Japanese blocked creator", memberService.getMyPage(creator, "web").nickname)
|
||||||
|
val blocked = memberService.getBlockedMemberList(viewer.id!!, 0, 20)
|
||||||
|
assertEquals(2, blocked.totalCount)
|
||||||
|
assertEquals(
|
||||||
|
mapOf(creator.id to "Japanese blocked creator", user.id to "blocked-user"),
|
||||||
|
blocked.items.associate { it.memberId to it.nickname }
|
||||||
|
)
|
||||||
|
assertEquals("blocked-creator", entityManager.find(Member::class.java, creator.id).nickname)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("주문과 Redis 플레이리스트 상세는 콘텐츠 ID와 다른 소유자 이름을 현재 언어로 표시한다")
|
||||||
|
fun shouldLocalizeOrderAndPlaylistOwnersWithoutChangingTitlesOrStoredPlaylist() {
|
||||||
|
val viewer = saveMember("playlist-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("playlist-creator", MemberRole.CREATOR)
|
||||||
|
val content = saveContent(creator, "Unchanged playlist title")
|
||||||
|
entityManager.persist(
|
||||||
|
Order(type = OrderType.KEEP).apply {
|
||||||
|
member = viewer
|
||||||
|
this.creator = creator
|
||||||
|
audioContent = content
|
||||||
|
}
|
||||||
|
)
|
||||||
|
saveAlias(creator, "Japanese playlist creator")
|
||||||
|
entityManager.clear()
|
||||||
|
assertNotEquals(creator.id, content.id)
|
||||||
|
val playlist = AudioContentPlaylist(
|
||||||
|
id = 100000L + viewer.id!!,
|
||||||
|
memberId = viewer.id!!,
|
||||||
|
title = "playlist metadata",
|
||||||
|
contentIdAndOrderList = listOf(PlaylistContentIdAndOrder(content.id!!, 1))
|
||||||
|
)
|
||||||
|
playlistRepository.save(playlist)
|
||||||
|
try {
|
||||||
|
val order = orderService.getAudioContentOrderList(viewer, 0, 20)
|
||||||
|
assertEquals("Japanese playlist creator", order.items.single().creatorNickname)
|
||||||
|
assertEquals("Unchanged playlist title", order.items.single().title)
|
||||||
|
val detail = playlistService.getPlaylistDetail(playlist.id, viewer)
|
||||||
|
assertEquals("Japanese playlist creator", detail.contentList.single().creatorNickname)
|
||||||
|
assertEquals("Unchanged playlist title", detail.contentList.single().title)
|
||||||
|
assertEquals(playlist, playlistRepository.findById(playlist.id).orElseThrow())
|
||||||
|
Mockito.doReturn(Lang.KO).`when`(langContext).lang
|
||||||
|
val korean = playlistService.getPlaylistDetail(playlist.id, viewer)
|
||||||
|
assertEquals("playlist-creator", korean.contentList.single().creatorNickname)
|
||||||
|
assertEquals("Japanese playlist creator", detail.contentList.single().creatorNickname)
|
||||||
|
} finally {
|
||||||
|
playlistRepository.delete(playlist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("한정 콘텐츠 상세의 구매자 목록은 콘텐츠 소유자가 아닌 각 구매자 CREATOR 이름만 대체한다")
|
||||||
|
fun shouldLocalizeMixedBuyerNamesByBuyerIdWithoutAddingPublicOwnerFields() {
|
||||||
|
val owner = saveMember("buyer-content-owner", MemberRole.CREATOR)
|
||||||
|
val creatorBuyer = saveMember("buyer-creator", MemberRole.CREATOR)
|
||||||
|
val userBuyer = saveMember("buyer-user", MemberRole.USER)
|
||||||
|
val content = saveContent(owner, "Unchanged limited content title", limited = 5)
|
||||||
|
listOf(creatorBuyer, userBuyer).forEach { buyer ->
|
||||||
|
entityManager.persist(
|
||||||
|
Order(type = OrderType.KEEP).apply {
|
||||||
|
member = buyer
|
||||||
|
creator = owner
|
||||||
|
audioContent = content
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
saveAlias(owner, "Japanese content owner")
|
||||||
|
saveAlias(creatorBuyer, "Japanese buyer")
|
||||||
|
saveAlias(userBuyer, "Must not replace buyer USER")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val detail = contentService.getDetail(content.id!!, owner, false, "Asia/Seoul")
|
||||||
|
|
||||||
|
assertEquals("Japanese content owner", detail.creator.nickname)
|
||||||
|
assertEquals(setOf("Japanese buyer", "buyer-user"), detail.buyerList.map { it.nickname }.toSet())
|
||||||
|
assertEquals("Unchanged limited content title", detail.title)
|
||||||
|
assertEquals("https://cdn.test/signed-audio", detail.contentUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("레거시 커뮤니티 게시글·댓글·답글·구매 응답은 활성 JA·KO 별칭이 있어도 원본 이름을 유지한다")
|
||||||
|
fun shouldKeepOriginalCommunityNamesDespiteActiveJaAndKoAliases() {
|
||||||
|
val viewer = saveMember("community-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("community-owner", MemberRole.CREATOR)
|
||||||
|
val peer = saveMember("deleted_community-peer", MemberRole.CREATOR)
|
||||||
|
val post = savePost(creator)
|
||||||
|
val parent = CreatorCommunityComment(comment = "unchanged creator comment").apply {
|
||||||
|
member = peer
|
||||||
|
creatorCommunity = post
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
val reply = CreatorCommunityComment(comment = "unchanged USER reply").apply {
|
||||||
|
member = viewer
|
||||||
|
creatorCommunity = post
|
||||||
|
this.parent = parent
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
val creatorReply = CreatorCommunityComment(comment = "unchanged creator reply").apply {
|
||||||
|
member = creator
|
||||||
|
creatorCommunity = post
|
||||||
|
this.parent = parent
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
saveReceipt(viewer, CanUsage.PAID_COMMUNITY_POST, creator).communityPost = post
|
||||||
|
saveAlias(creator, "Japanese community owner")
|
||||||
|
saveAlias(peer, "Japanese community writer")
|
||||||
|
saveAlias(viewer, "Must not replace community USER")
|
||||||
|
listOf(creator, peer).forEach { writer ->
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"values (?, 'KO', ?, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
writer.id!!,
|
||||||
|
"Korean ${writer.nickname}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
listOf(Lang.JA, Lang.KO).forEach { lang ->
|
||||||
|
Mockito.doReturn(lang).`when`(langContext).lang
|
||||||
|
val posts = communityService.getCommunityPostList(creator.id!!, viewer.id!!, "Asia/Seoul", 0, 20, false)
|
||||||
|
assertEquals("community-owner", posts.single().creatorNickname)
|
||||||
|
assertEquals("community-peer", posts.single().firstComment!!.nickname)
|
||||||
|
assertEquals("unchanged post content", posts.single().content)
|
||||||
|
val detail = communityService.getCommunityPostDetail(post.id!!, viewer.id!!, "Asia/Seoul", false)
|
||||||
|
assertEquals("community-owner", detail.creatorNickname)
|
||||||
|
val comments = communityService.getCommunityPostCommentList(post.id!!, viewer.id!!, "Asia/Seoul", 0, 20, false)
|
||||||
|
assertEquals("community-peer", comments.items.single().nickname)
|
||||||
|
val replies = communityService.getCommentReplyList(parent.id!!, viewer.id!!, "Asia/Seoul", 0, 20, false)
|
||||||
|
assertEquals(
|
||||||
|
mapOf(reply.id to "community-viewer", creatorReply.id to "community-owner"),
|
||||||
|
replies.items.associate { it.id to it.nickname }
|
||||||
|
)
|
||||||
|
assertEquals("unchanged USER reply", replies.items.single { it.id == reply.id }.comment)
|
||||||
|
val purchased = communityService.purchasePost(post.id!!, viewer.id!!, "Asia/Seoul", false, "web")
|
||||||
|
assertEquals("community-owner", purchased.creatorNickname)
|
||||||
|
assertEquals("community-peer", purchased.firstComment!!.nickname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("채널 후원 목록의 이름 소유자는 채널이 아니라 후원자이며 USER는 원본이다")
|
||||||
|
fun shouldLocalizeCreatorDonorsIndependentlyOfTheChannelOwner() {
|
||||||
|
val viewer = saveMember("donation-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("donation-channel", MemberRole.CREATOR)
|
||||||
|
val donor = saveMember("donation-creator-donor", MemberRole.CREATOR)
|
||||||
|
listOf(viewer, donor).forEach { writer ->
|
||||||
|
entityManager.persist(
|
||||||
|
ChannelDonationMessage(can = 100, isSecret = false).apply {
|
||||||
|
member = writer
|
||||||
|
this.creator = creator
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
saveAlias(creator, "Wrong channel owner name")
|
||||||
|
saveAlias(donor, "Japanese donor")
|
||||||
|
saveAlias(viewer, "Must not replace donor USER")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val response = donationService.getChannelDonationList(creator.id!!, viewer, 0, 20)
|
||||||
|
assertEquals(2, response.totalCount)
|
||||||
|
assertEquals(
|
||||||
|
mapOf(donor.id to "Japanese donor", viewer.id to "donation-viewer"),
|
||||||
|
response.items.associate { it.memberId to it.nickname }
|
||||||
|
)
|
||||||
|
assertEquals(listOf(100, 100), response.items.map { it.can })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("receipt는 각 recipient·audio·community 소유자의 현재 이름을 쓰고 리소스 제목과 금액을 보존한다")
|
||||||
|
fun shouldLocalizeReceiptOwnerNamesWithoutReplacingResourceTitlesOrAmounts() {
|
||||||
|
val viewer = saveMember("receipt-viewer", MemberRole.USER)
|
||||||
|
val owner = saveMember("receipt-owner", MemberRole.CREATOR)
|
||||||
|
val recipient = saveMember("receipt-recipient", MemberRole.CREATOR)
|
||||||
|
val content = saveContent(owner, "Unchanged receipt content title")
|
||||||
|
val post = savePost(owner)
|
||||||
|
saveReceipt(viewer, CanUsage.DONATION, owner).audioContent = content
|
||||||
|
saveReceipt(viewer, CanUsage.PAID_COMMUNITY_POST, owner).communityPost = post
|
||||||
|
saveReceipt(viewer, CanUsage.CHANNEL_DONATION, recipient)
|
||||||
|
saveReceipt(viewer, CanUsage.CHANNEL_DONATION, viewer)
|
||||||
|
saveReceipt(viewer, CanUsage.ORDER_CONTENT, owner).audioContent = content
|
||||||
|
saveAlias(owner, "Japanese receipt owner")
|
||||||
|
saveAlias(recipient, "Japanese recipient")
|
||||||
|
saveAlias(viewer, "Must not replace receipt USER")
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val rows = canService.getCanUseStatus(viewer, PageRequest.of(0, 20), "UTC", "web")
|
||||||
|
assertEquals(
|
||||||
|
setOf(
|
||||||
|
"[콘텐츠 후원] Japanese receipt owner",
|
||||||
|
"[게시글 보기] Japanese receipt owner",
|
||||||
|
"[채널 후원] Japanese recipient",
|
||||||
|
"[채널 후원] receipt-viewer",
|
||||||
|
"[콘텐츠 구매] Unchanged receipt content title"
|
||||||
|
),
|
||||||
|
rows.map { it.title }.toSet()
|
||||||
|
)
|
||||||
|
assertEquals(5, rows.size)
|
||||||
|
assertEquals(listOf(100, 100, 100, 100, 100), rows.map { it.can })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("오디션 지원자는 applicant ID가 아닌 member ID로 이름을 표시하고 USER·count·정렬을 보존한다")
|
||||||
|
fun shouldLocalizeAuditionApplicantsByMemberIdAndKeepUserNamesAndCount() {
|
||||||
|
saveMember("applicant-unused-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("applicant-creator", MemberRole.CREATOR)
|
||||||
|
val user = saveMember("applicant-user", MemberRole.USER)
|
||||||
|
val audition = Audition(title = "Unchanged audition title", information = "information")
|
||||||
|
entityManager.persist(audition)
|
||||||
|
val role = AuditionRole(name = "Unchanged role name", information = "information").apply {
|
||||||
|
this.audition = audition
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
val creatorApplicant = AuditionApplicant(phoneNumber = "01000000000", voicePath = "voice.m4a").apply {
|
||||||
|
member = creator
|
||||||
|
this.role = role
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
val userApplicant = AuditionApplicant(phoneNumber = "01000000001", voicePath = "voice.m4a").apply {
|
||||||
|
member = user
|
||||||
|
this.role = role
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
saveAlias(creator, "Japanese applicant")
|
||||||
|
saveAlias(user, "Must not replace applicant USER")
|
||||||
|
entityManager.clear()
|
||||||
|
assertNotEquals(creator.id, creatorApplicant.id)
|
||||||
|
|
||||||
|
val response = applicantService.getAuditionApplicantList(role.id!!, AuditionApplicantSortType.NEWEST, 0, 20)
|
||||||
|
|
||||||
|
assertEquals(2, response.totalCount)
|
||||||
|
assertEquals(listOf(userApplicant.id, creatorApplicant.id), response.items.map { it.applicantId })
|
||||||
|
assertEquals(listOf("applicant-user", "Japanese applicant"), response.items.map { it.nickname })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("중립 후원랭킹 cache hit는 요청별 현재 이름을 복사하고 원본 변경·해제 후 현재 원본으로 돌아간다")
|
||||||
|
fun shouldRenderCachedDonorNamesAfterReadAndKeepCachedRowsImmutable() {
|
||||||
|
val viewer = saveMember("cached-viewer", MemberRole.USER)
|
||||||
|
val channel = saveMember("cached-channel", MemberRole.CREATOR)
|
||||||
|
val donor = saveMember("cached-donor-original", MemberRole.CREATOR)
|
||||||
|
saveAlias(donor, "Japanese cached donor")
|
||||||
|
entityManager.clear()
|
||||||
|
val key = "creator_donation_ranking_v2:${channel.id}:CUMULATIVE:0:20:false"
|
||||||
|
val totalKey = "creator_donation_ranking_member_total_v2:${channel.id}:CUMULATIVE"
|
||||||
|
val cached = MemberDonationRankingListResponse(
|
||||||
|
listOf(MemberDonationRankingResponse(donor.id!!, "stale cached original", "image.png", 0))
|
||||||
|
)
|
||||||
|
redisTemplate.opsForValue().set(key, cached)
|
||||||
|
redisTemplate.opsForValue().set(totalKey, 1)
|
||||||
|
try {
|
||||||
|
val japanese = explorerService.getCreatorProfileDonationRanking(
|
||||||
|
channel.id!!,
|
||||||
|
DonationRankingPeriod.CUMULATIVE,
|
||||||
|
PageRequest.of(0, 20),
|
||||||
|
viewer
|
||||||
|
)
|
||||||
|
assertEquals("Japanese cached donor", japanese.userDonationRanking.single().nickname)
|
||||||
|
jdbcTemplate.update("update member set nickname = ? where id = ?", "renamed donor", donor.id!!)
|
||||||
|
jdbcTemplate.update("update creator_localized_name set is_active = false where member_id = ?", donor.id!!)
|
||||||
|
entityManager.clear()
|
||||||
|
val cleared = explorerService.getCreatorProfileDonationRanking(
|
||||||
|
channel.id!!,
|
||||||
|
DonationRankingPeriod.CUMULATIVE,
|
||||||
|
PageRequest.of(0, 20),
|
||||||
|
viewer
|
||||||
|
)
|
||||||
|
assertEquals("renamed donor", cleared.userDonationRanking.single().nickname)
|
||||||
|
assertEquals("Japanese cached donor", japanese.userDonationRanking.single().nickname)
|
||||||
|
assertEquals(cached, redisTemplate.opsForValue().get(key))
|
||||||
|
} finally {
|
||||||
|
redisTemplate.delete(listOf(key, totalKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMember(nickname: String, role: MemberRole): Member =
|
||||||
|
Member(
|
||||||
|
email = "$nickname@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = nickname,
|
||||||
|
profileImage = "profile.png",
|
||||||
|
role = role
|
||||||
|
).also { entityManager.persist(it) }
|
||||||
|
|
||||||
|
private fun saveContent(owner: Member, title: String, limited: Int? = null): AudioContent {
|
||||||
|
val theme = AudioContentTheme(theme = "theme-$title", image = "theme.png")
|
||||||
|
entityManager.persist(theme)
|
||||||
|
return AudioContent(
|
||||||
|
title = title,
|
||||||
|
detail = "detail",
|
||||||
|
languageCode = "ko",
|
||||||
|
releaseDate = LocalDateTime.now().minusDays(1),
|
||||||
|
limited = limited,
|
||||||
|
remaining = limited
|
||||||
|
)
|
||||||
|
.apply {
|
||||||
|
member = owner
|
||||||
|
this.theme = theme
|
||||||
|
isActive = true
|
||||||
|
content = "output/audio.m4a"
|
||||||
|
duration = "00:10:00"
|
||||||
|
coverImage = "content.png"
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun savePost(owner: Member): CreatorCommunity =
|
||||||
|
CreatorCommunity(
|
||||||
|
content = "unchanged post content",
|
||||||
|
price = 0,
|
||||||
|
isCommentAvailable = true,
|
||||||
|
isAdult = false,
|
||||||
|
languageCode = "ko"
|
||||||
|
).apply {
|
||||||
|
member = owner
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveReceipt(viewer: Member, usage: CanUsage, recipient: Member): UseCan {
|
||||||
|
entityManager.flush()
|
||||||
|
val receipt = UseCan(canUsage = usage, can = 100, rewardCan = 0).apply {
|
||||||
|
member = viewer
|
||||||
|
entityManager.persist(this)
|
||||||
|
}
|
||||||
|
entityManager.persist(
|
||||||
|
UseCanCalculate(100, PaymentGateway.PG, UseCanCalculateStatus.RECEIVED).apply {
|
||||||
|
useCan = receipt
|
||||||
|
recipientCreatorId = recipient.id!!
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return receipt
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAlias(member: Member, name: String) {
|
||||||
|
entityManager.flush()
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"values (?, 'JA', ?, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
member.id!!,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user