feat(creatorname): 레거시 조회와 검색에 언어별 이름을 적용한다

This commit is contained in:
2026-10-16 14:04:17 +09:00
parent 995b41e89d
commit eef2e2bf55
66 changed files with 3022 additions and 373 deletions
@@ -342,6 +342,7 @@ class AdminLiveService(
type = FcmEventType.CANCEL_LIVE,
category = PushNotificationCategory.LIVE,
title = room.member!!.nickname,
titleMemberId = room.member!!.id,
messageKey = "live.room.fcm.message.canceled",
senderMemberId = room.member!!.id,
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.RankingRepository
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.data.domain.Pageable
import org.springframework.stereotype.Service
@@ -49,6 +50,7 @@ class HomeService(
private val explorerQueryRepository: ExplorerQueryRepository,
private val langContext: LangContext,
private val localizedNameService: CreatorLocalizedNameService,
private val memberContentPreferenceService: MemberContentPreferenceService,
@Value("\${cloud.aws.cloud-front.host}")
@@ -87,7 +89,7 @@ class HomeService(
timezone = timezone
)
val creatorRankingMembers = rankingRepository.getCreatorRankings(memberId = memberId)
val creatorRankingMembers = rankingRepository.getCreatorRankings(memberId = memberId, lang = langContext.lang)
val creatorRankingIds = creatorRankingMembers.mapNotNull { it.id }
val followedCreatorIds = if (memberId != null) {
explorerQueryRepository.getFollowedCreatorIds(creatorRankingIds, memberId)
@@ -99,7 +101,7 @@ class HomeService(
val creatorId = creator.id!!
val follow = memberId != null && followedCreatorIds.contains(creatorId)
creator.toExplorerSectionCreator(imageHost, follow)
creator.copy(follow = follow)
}
val latestContentThemeList = contentThemeService.getActiveThemeOfContent(
@@ -171,6 +173,10 @@ class HomeService(
isAdult = isAdult,
contentType = resolvedContentType
)
val recommendedNames = localizedNameService.findCurrentNames(
recommendChannelList.map { it.channelId },
langContext.lang
)
val freeContentList = getRandomizedContentList(
memberId = memberId,
@@ -212,7 +218,9 @@ class HomeService(
dayOfWeekSeriesList = translatedDayOfWeekSeriesList,
popularCharacters = translatedPopularCharacters,
contentRanking = contentRanking,
recommendChannelList = recommendChannelList,
recommendChannelList = recommendChannelList.map {
it.copy(creatorNickname = recommendedNames[it.channelId] ?: it.creatorNickname)
},
freeContentList = freeContentList,
pointAvailableContentList = pointAvailableContentList,
recommendContentList = getRecommendContentListByPreference(
@@ -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.QAuditionRole.auditionRole
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.v2.creatorname.adapter.out.persistence.CreatorNameJoin
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.jpa.repository.JpaRepository
@@ -18,7 +20,8 @@ interface AuditionApplicantQueryRepository {
auditionRoleId: Long,
sortType: AuditionApplicantSortType,
offset: Long,
limit: Long
limit: Long,
lang: Lang = Lang.KO
): List<GetAuditionRoleApplicantItem>
fun findActiveApplicantByMemberIdAndRoleId(memberId: Long, roleId: Long): AuditionApplicant?
@@ -64,8 +67,10 @@ class AuditionApplicantQueryRepositoryImpl(
auditionRoleId: Long,
sortType: AuditionApplicantSortType,
offset: Long,
limit: Long
limit: Long,
lang: Lang
): List<GetAuditionRoleApplicantItem> {
val creatorName = CreatorNameJoin(member, lang, "applicantMemberName")
val orderBy = if (sortType == AuditionApplicantSortType.LIKES) {
auditionVote.id.count().desc()
} else {
@@ -77,7 +82,7 @@ class AuditionApplicantQueryRepositoryImpl(
QGetAuditionRoleApplicantItem(
auditionApplicant.id,
member.id,
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(cloudFrontHost),
auditionApplicant.voicePath.prepend("/").prepend(cloudFrontHost),
auditionVote.id.count()
@@ -85,6 +90,7 @@ class AuditionApplicantQueryRepositoryImpl(
)
.from(auditionApplicant)
.innerJoin(auditionApplicant.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.innerJoin(auditionApplicant.role, auditionRole)
.leftJoin(auditionVote).on(auditionApplicant.id.eq(auditionVote.applicant.id))
.where(
@@ -92,7 +98,7 @@ class AuditionApplicantQueryRepositoryImpl(
auditionApplicant.isActive.isTrue,
member.isActive.isTrue
)
.groupBy(auditionApplicant.id)
.groupBy(auditionApplicant.id, member.id, creatorName.name.displayName)
.orderBy(orderBy)
.offset(offset)
.limit(limit)
@@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.audition.AuditionApplicant
import kr.co.vividnext.sodalive.audition.role.AuditionRoleRepository
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
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.utils.generateFileName
import org.springframework.beans.factory.annotation.Value
@@ -21,6 +22,7 @@ class AuditionApplicantService(
private val s3Uploader: S3Uploader,
private val objectMapper: ObjectMapper,
private val langContext: LangContext,
@Value("\${cloud.aws.s3.bucket}")
private val bucket: String
@@ -36,7 +38,8 @@ class AuditionApplicantService(
auditionRoleId = auditionRoleId,
sortType = sortType,
offset = offset,
limit = limit
limit = limit,
lang = langContext.lang
)
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.content.QAudioContent.audioContent
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.member.Member
import kr.co.vividnext.sodalive.member.QMember
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.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@@ -32,7 +34,7 @@ interface CanRepository : JpaRepository<Can, Long>, CanQueryRepository
interface CanQueryRepository {
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 isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
fun getCanUsedForLiveRoomNotRefund(memberId: Long, roomId: Long, canUsage: CanUsage = CanUsage.LIVE): UseCan?
@@ -66,11 +68,15 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
.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 qAudioContentMember = QMember("audioContentMember")
val qCommunityPostMember = QMember("communityPostMember")
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) {
"aos" -> useCanCalculate.paymentGateway.`in`(
@@ -96,23 +102,26 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
useCan.can,
useCan.rewardCan,
useCan.createdAt,
qRoomMember.nickname,
roomName.displayName,
liveRoom.title,
qAudioContentMember.nickname,
audioName.displayName,
audioContent.title,
qCommunityPostMember.nickname,
communityName.displayName,
audition.title,
chatCharacter.name,
qRecipientMember.nickname
recipientName.displayName
)
)
.from(useCan)
.leftJoin(useCan.room, liveRoom)
.leftJoin(liveRoom.member, qRoomMember)
.leftJoin(roomName.name).on(roomName.condition)
.leftJoin(useCan.audioContent, audioContent)
.leftJoin(audioContent.member, qAudioContentMember)
.leftJoin(audioName.name).on(audioName.condition)
.leftJoin(useCan.communityPost, creatorCommunity)
.leftJoin(creatorCommunity.member, qCommunityPostMember)
.leftJoin(communityName.name).on(communityName.condition)
.leftJoin(useCan.auditionApplicant, auditionApplicant)
.leftJoin(auditionApplicant.role, auditionRole)
.leftJoin(auditionRole.audition, audition)
@@ -120,6 +129,7 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
.leftJoin(characterImage.chatCharacter, chatCharacter)
.innerJoin(useCan.useCanCalculates, useCanCalculate)
.leftJoin(qRecipientMember).on(useCanCalculate.recipientCreatorId.eq(qRecipientMember.id))
.leftJoin(recipientName.name).on(recipientName.condition)
.where(
useCan.member.id.eq(member.id)
.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.use.CanUsage
import kr.co.vividnext.sodalive.common.CountryContext
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
@@ -14,7 +15,8 @@ import java.time.format.DateTimeFormatter
@Transactional(readOnly = true)
class CanService(
private val repository: CanRepository,
private val countryContext: CountryContext
private val countryContext: CountryContext,
private val langContext: LangContext
) {
fun getCans(forcedCurrency: String? = null): List<CanResponse> {
val currency = forcedCurrency ?: when (countryContext.countryCode) {
@@ -43,7 +45,7 @@ class CanService(
ZoneId.of("UTC")
}
return repository.getCanUseStatus(member, pageable, container)
return repository.getCanUseStatus(member, pageable, container, langContext.lang)
.map {
val title: String = when (it.canUsage) {
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.SodaException
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.contentpreference.MemberContentPreferenceService
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.lang.Nullable
import org.springframework.security.access.prepost.PreAuthorize
@@ -29,7 +31,9 @@ import java.time.temporal.TemporalAdjusters
@RequestMapping("/audio-content")
class AudioContentController(
private val service: AudioContentService,
private val memberContentPreferenceService: MemberContentPreferenceService
private val memberContentPreferenceService: MemberContentPreferenceService,
private val localizedNameService: CreatorLocalizedNameService,
private val langContext: LangContext
) {
@PostMapping
@PreAuthorize("hasRole('CREATOR')")
@@ -214,7 +218,10 @@ class AudioContentController(
offset = pageable.offset,
limit = pageable.pageSize.toLong(),
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.QSeriesContent.seriesContent
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.QMember.member
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.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@@ -85,7 +87,8 @@ interface AudioContentQueryRepository {
contentType: ContentType,
offset: Long = 0,
limit: Long = 20,
isFree: Boolean = false
isFree: Boolean = false,
lang: Lang = Lang.KO
): List<GetAudioContentMainItem>
fun findAlarmContentByTheme(
@@ -167,7 +170,7 @@ interface AudioContentQueryRepository {
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 findPreviousContent(seriesId: Long, title: String, isAdult: Boolean): OtherContentResponse?
fun findNextContent(seriesId: Long, title: String, isAdult: Boolean): OtherContentResponse?
@@ -467,8 +470,10 @@ class AudioContentQueryRepositoryImpl(
contentType: ContentType,
offset: Long,
limit: Long,
isFree: Boolean
isFree: Boolean,
lang: Lang
): List<GetAudioContentMainItem> {
val creatorName = CreatorNameJoin(member, lang, "themeCreatorName")
val blockMemberCondition = blockMember.isActive.isTrue
.and(
blockMember.member.id.eq(member.id)
@@ -535,7 +540,7 @@ class AudioContentQueryRepositoryImpl(
where = where.and(audioContent.price.loe(0))
}
return queryFactory
val query = queryFactory
.select(
QGetAudioContentMainItem(
audioContent.id,
@@ -543,7 +548,7 @@ class AudioContentQueryRepositoryImpl(
audioContent.title,
member.id,
member.profileImage.prepend("/").prepend(imageHost),
member.nickname,
creatorName.displayName,
audioContent.price,
audioContent.duration,
audioContent.isPointAvailable
@@ -557,7 +562,8 @@ class AudioContentQueryRepositoryImpl(
.offset(offset)
.limit(limit)
.orderBy(*orderBy.toTypedArray())
.fetch()
query.leftJoin(creatorName.name).on(creatorName.condition)
return query.fetch()
}
override fun findAlarmContentByTheme(
@@ -1210,7 +1216,8 @@ class AudioContentQueryRepositoryImpl(
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
.select(
QAudioContentPlaylistContent(
@@ -1219,12 +1226,13 @@ class AudioContentQueryRepositoryImpl(
audioContentTheme.theme,
audioContent.coverImage.prepend("/").prepend(imageHost),
audioContent.duration,
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(imageHost)
)
)
.from(audioContent)
.innerJoin(audioContent.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.innerJoin(audioContent.theme, audioContentTheme)
.where(audioContent.id.`in`(contentIdList))
.fetch()
@@ -1466,6 +1474,7 @@ class AudioContentQueryRepositoryImpl(
}
val coverImageUrl = audioContent.coverImage.prepend("/").prepend(imageHost)
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "latestCreatorName")
var select = queryFactory
.select(
@@ -1474,11 +1483,12 @@ class AudioContentQueryRepositoryImpl(
audioContent.title,
contentTranslation.renderedPayload,
coverImageUrl,
member.nickname,
creatorName.displayName,
audioContent.isPointAvailable
)
.from(audioContent)
.innerJoin(audioContent.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.innerJoin(audioContent.theme, audioContentTheme)
.leftJoin(contentTranslation)
.on(contentTranslation.contentId.eq(audioContent.id).and(contentTranslation.locale.eq(locale)))
@@ -1523,7 +1533,7 @@ class AudioContentQueryRepositoryImpl(
val payload = row.get(contentTranslation.renderedPayload)
val translatedTitle = payload?.title
val imageUrl = row.get(coverImageUrl)!!
val creatorNickname = row.get(member.nickname)!!
val creatorNickname = row.get(creatorName.displayName)!!
val isPointAvailableValue = row.get(audioContent.isPointAvailable) ?: false
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.contentpreference.MemberContentPreferenceService
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.recommendation.application.CreatorContentViewHistoryService
import org.slf4j.LoggerFactory
@@ -87,6 +88,7 @@ class AudioContentService(
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val localizedNameService: CreatorLocalizedNameService,
private val contentThemeTranslationRepository: ContentThemeTranslationRepository,
private val homeFollowingNewsPublishService: HomeFollowingNewsPublishService,
@@ -512,6 +514,7 @@ class AudioContentService(
type = FcmEventType.UPLOAD_CONTENT,
category = PushNotificationCategory.CONTENT,
title = audioContent.member!!.nickname,
titleMemberId = audioContent.member!!.id,
messageKey = "content.notification.uploaded_message",
senderMemberId = audioContent.member!!.id,
args = listOf(audioContent.title),
@@ -542,6 +545,7 @@ class AudioContentService(
type = FcmEventType.UPLOAD_CONTENT,
category = PushNotificationCategory.CONTENT,
title = audioContent.member!!.nickname,
titleMemberId = audioContent.member!!.id,
messageKey = "content.notification.uploaded_message",
senderMemberId = audioContent.member!!.id,
args = listOf(audioContent.title),
@@ -702,7 +706,8 @@ class AudioContentService(
isContentCreator = creatorId == member.id!!,
timezone = timezone,
offset = 0,
limit = 1
limit = 1,
lang = langContext.lang
)
} else {
listOf()
@@ -844,7 +849,7 @@ class AudioContentService(
audioContent.limited != null &&
audioContent.remaining != null
) {
orderRepository.findBuyerListByContentId(id)
orderRepository.findBuyerListByContentId(id, langContext.lang)
} else {
listOf()
}
@@ -920,6 +925,10 @@ class AudioContentService(
)
}
val names = localizedNameService.findLocalizedNames(
listOf(creatorId),
langContext.lang
)
return GetAudioContentDetailResponse(
contentId = audioContent.id!!,
title = audioContent.title,
@@ -955,7 +964,7 @@ class AudioContentService(
isAvailablePin = isAvailablePin,
creator = AudioContentCreator(
creatorId = creatorId,
nickname = creator.nickname,
nickname = names[creatorId] ?: creator.nickname,
profileImageUrl = if (creator.profileImage != null) {
"$coverImageHost/${creator.profileImage}"
} else {
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.content.comment
import kr.co.vividnext.sodalive.common.ApiResponse
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.useraction.ActionType
import kr.co.vividnext.sodalive.useraction.UserActionService
@@ -18,7 +19,8 @@ import org.springframework.web.bind.annotation.RestController
@RestController
class AudioContentCommentController(
private val service: AudioContentCommentService,
private val userActionService: UserActionService
private val userActionService: UserActionService,
private val langContext: LangContext
) {
@PostMapping("/audio-content/comment")
fun registerComment(
@@ -79,7 +81,8 @@ class AudioContentCommentController(
audioContentId = audioContentId,
memberId = member.id!!,
timezone = timezone,
pageable = pageable
pageable = pageable,
lang = langContext.lang
)
)
}
@@ -98,7 +101,8 @@ class AudioContentCommentController(
commentId = commentId,
memberId = member.id!!,
timezone = timezone,
pageable = pageable
pageable = pageable,
lang = langContext.lang
)
)
}
@@ -1,16 +1,18 @@
package kr.co.vividnext.sodalive.content.comment
import com.querydsl.core.types.dsl.CaseBuilder
import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.JPAExpressions
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
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.QPushToken.pushToken
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.block.QBlockMember.blockMember
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@@ -26,7 +28,8 @@ interface AudioContentCommentQueryRepository {
isContentCreator: Boolean,
timezone: String,
offset: Long,
limit: Int
limit: Int,
lang: Lang? = null
): List<GetAudioContentCommentListItem>
fun totalCountCommentByContentId(contentId: Long, memberId: Long, isContentCreator: Boolean): Int
@@ -37,7 +40,8 @@ interface AudioContentCommentQueryRepository {
memberId: Long,
timezone: String,
offset: Long,
limit: Int
limit: Int,
lang: Lang? = null
): List<GetAudioContentCommentListItem>
fun findPushTokenByContentIdAndCommentParentIdMyMemberId(
@@ -58,8 +62,13 @@ class AudioContentCommentQueryRepositoryImpl(
isContentCreator: Boolean,
timezone: String,
offset: Long,
limit: Int
limit: Int,
lang: Lang?
): 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)
.and(audioContentComment.isActive.isTrue)
.and(audioContentComment.parent.isNull)
@@ -85,12 +94,12 @@ class AudioContentCommentQueryRepositoryImpl(
"%Y.%m.%d %W %h:%i %p"
)
return queryFactory
val query = queryFactory
.select(
QGetAudioContentCommentListItem(
audioContentComment.id,
audioContentComment.member.id,
audioContentComment.member.nickname,
displayName,
audioContentComment.member.profileImage.prepend("/").prepend(cloudFrontHost),
audioContentComment.comment,
audioContentComment.languageCode,
@@ -107,10 +116,10 @@ class AudioContentCommentQueryRepositoryImpl(
.offset(offset)
.limit(limit.toLong())
.orderBy(audioContentComment.createdAt.desc())
.fetch()
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
return query.fetch()
.map {
it.copy(
nickname = it.nickname.removeDeletedNicknamePrefix(),
replyCount = commentReplyCountByAudioContentCommentId(it.id, memberId)
)
}
@@ -160,8 +169,13 @@ class AudioContentCommentQueryRepositoryImpl(
memberId: Long,
timezone: String,
offset: Long,
limit: Int
limit: Int,
lang: Lang?
): 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(
"DATE_FORMAT({0}, {1})",
Expressions.dateTimeTemplate(
@@ -174,12 +188,12 @@ class AudioContentCommentQueryRepositoryImpl(
"%Y.%m.%d %W %h:%i %p"
)
return queryFactory
val query = queryFactory
.select(
QGetAudioContentCommentListItem(
audioContentComment.id,
audioContentComment.member.id,
audioContentComment.member.nickname,
displayName,
audioContentComment.member.profileImage.prepend("/").prepend(cloudFrontHost),
audioContentComment.comment,
audioContentComment.languageCode,
@@ -201,10 +215,8 @@ class AudioContentCommentQueryRepositoryImpl(
.offset(offset)
.limit(limit.toLong())
.orderBy(audioContentComment.createdAt.desc())
.fetch()
.map {
it.copy(nickname = it.nickname.removeDeletedNicknamePrefix())
}
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
return query.fetch()
}
override fun findPushTokenByContentIdAndCommentParentIdMyMemberId(
@@ -9,10 +9,12 @@ import kr.co.vividnext.sodalive.fcm.FcmDeepLinkValue
import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType
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.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
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.context.ApplicationEventPublisher
import org.springframework.data.domain.Pageable
@@ -30,6 +32,7 @@ class AudioContentCommentService(
private val orderRepository: OrderRepository,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val localizedNameService: CreatorLocalizedNameService,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
@@ -49,7 +52,9 @@ class AudioContentCommentService(
val creator = audioContent.member!!
val isBlocked = isBlockedBetweenMembers(memberId = member.id!!, creatorId = creator.id!!)
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(
@@ -86,6 +91,7 @@ class AudioContentCommentService(
} else {
member.nickname
},
titleMemberId = parent?.member?.id ?: member.id,
messageKey = if (parent != null) {
"content.comment.notification.reply"
} else {
@@ -140,7 +146,8 @@ class AudioContentCommentService(
audioContentId: Long,
memberId: Long,
timezone: String,
pageable: Pageable
pageable: Pageable,
lang: Lang? = null
): GetAudioContentCommentListResponse {
val audioContent = audioContentRepository.findByIdOrNull(id = audioContentId)
?: return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
@@ -157,7 +164,8 @@ class AudioContentCommentService(
isContentCreator = isContentCreator,
timezone = timezone,
offset = pageable.offset,
limit = pageable.pageSize
limit = pageable.pageSize,
lang = lang
)
val totalCount = repository.totalCountCommentByContentId(
contentId = audioContentId,
@@ -172,7 +180,8 @@ class AudioContentCommentService(
commentId: Long,
memberId: Long,
timezone: String,
pageable: Pageable
pageable: Pageable,
lang: Lang? = null
): GetAudioContentCommentListResponse {
val parentComment = repository.findByIdOrNull(id = commentId)
?: return GetAudioContentCommentListResponse(totalCount = 0, items = listOf())
@@ -187,7 +196,8 @@ class AudioContentCommentService(
memberId = memberId,
timezone = timezone,
offset = pageable.offset,
limit = pageable.pageSize
limit = pageable.pageSize,
lang = lang
)
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.playlist.ContentIdAndEndDateData
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.v2.creatorname.adapter.out.persistence.CreatorNameJoin
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@@ -29,7 +31,8 @@ interface OrderQueryRepository {
coverImageHost: String,
memberId: Long,
offset: Long = 0,
limit: Long = 10
limit: Long = 10,
lang: Lang = Lang.KO
): List<GetAudioContentOrderListItem>
fun totalAudioContentOrderListCount(memberId: Long, dateTime: LocalDateTime): Int
@@ -38,12 +41,13 @@ interface OrderQueryRepository {
coverImageHost: String,
memberId: Long,
offset: Long = 0,
limit: Long = 20
limit: Long = 20,
lang: Lang = Lang.KO
): List<GetAudioContentMainItem>
fun findOrderedContent(contentIdList: List<Long>, memberId: Long): List<Long>
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?
}
@@ -134,8 +138,10 @@ class OrderQueryRepositoryImpl(
coverImageHost: String,
memberId: Long,
offset: Long,
limit: Long
limit: Long,
lang: Lang
): List<GetAudioContentOrderListItem> {
val creatorName = CreatorNameJoin(member, lang, "orderCreatorName")
val where = order.member.id.eq(memberId)
.and(order.isActive.isTrue)
.and(
@@ -153,7 +159,7 @@ class OrderQueryRepositoryImpl(
audioContent.id,
audioContent.coverImage.prepend("/").prepend(coverImageHost),
audioContent.member.id,
audioContent.member.nickname,
creatorName.displayName,
audioContent.title,
audioContent.theme.theme,
audioContent.duration,
@@ -165,6 +171,8 @@ class OrderQueryRepositoryImpl(
)
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(where)
.offset(offset)
.limit(limit)
@@ -197,8 +205,10 @@ class OrderQueryRepositoryImpl(
coverImageHost: String,
memberId: Long,
offset: Long,
limit: Long
limit: Long,
lang: Lang
): List<GetAudioContentMainItem> {
val creatorName = CreatorNameJoin(member, lang, "mainOrderCreatorName")
return queryFactory
.select(
QGetAudioContentMainItem(
@@ -209,7 +219,7 @@ class OrderQueryRepositoryImpl(
member.profileImage
.prepend("/")
.prepend(coverImageHost),
member.nickname,
creatorName.displayName,
audioContent.price,
audioContent.duration,
audioContent.isPointAvailable
@@ -218,6 +228,7 @@ class OrderQueryRepositoryImpl(
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(
order.member.id.eq(memberId)
.and(order.isActive.isTrue)
@@ -266,16 +277,18 @@ class OrderQueryRepositoryImpl(
.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
.select(
QContentBuyer(
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(imageHost)
)
)
.from(order)
.innerJoin(order.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(
order.isActive.isTrue,
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.like.AudioContentLikeRepository
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.point.PointUsageService
import org.springframework.beans.factory.annotation.Value
@@ -26,6 +27,7 @@ class OrderService(
private val audioContentCommentQueryRepository: AudioContentCommentRepository,
private val audioContentLikeQueryRepository: AudioContentLikeRepository,
private val limitedEditionOrderRepository: LimitedEditionOrderRepository,
private val langContext: LangContext,
@Value("\${cloud.aws.cloud-front.host}")
private val audioContentCoverImageHost: String
@@ -123,7 +125,8 @@ class OrderService(
coverImageHost = audioContentCoverImageHost,
memberId = member.id!!,
offset = offset,
limit = limit
limit = limit,
lang = langContext.lang
)
.map {
val commentCount = audioContentCommentQueryRepository
@@ -154,7 +157,8 @@ class OrderService(
coverImageHost = audioContentCoverImageHost,
memberId = memberId,
offset = 0,
limit = 20
limit = 20,
lang = langContext.lang
)
}
}
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.content.playlist
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.AudioContentRepository
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.member.Member
import org.springframework.data.repository.findByIdOrNull
@@ -16,7 +17,8 @@ class AudioContentPlaylistService(
private val idGenerator: RedisIdGenerator,
private val orderRepository: OrderRepository,
private val audioContentRepository: AudioContentRepository,
private val redisRepository: AudioContentPlaylistRedisRepository
private val redisRepository: AudioContentPlaylistRedisRepository,
private val langContext: LangContext
) {
fun createPlaylist(request: CreatePlaylistRequest, member: Member) {
if (request.contentIdAndOrderList.size >= 30) {
@@ -173,7 +175,8 @@ class AudioContentPlaylistService(
}
val contentList = audioContentRepository.fetchContentForPlaylist(
contentIdList = contentIdAndOrderList.map { it.contentId }
contentIdList = contentIdAndOrderList.map { it.contentId },
lang = langContext.lang
)
val orderMap = contentIdAndOrderList.sortedBy { it.order }
@@ -112,7 +112,8 @@ class AudioContentThemeService(
isAdult = memberContentPreferenceService.isAdultVisibleForQuery(member, isAdultContentVisible),
contentType = contentType,
offset = offset,
limit = limit
limit = limit,
lang = langContext.lang
)
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.QLiveRoom.liveRoom
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.member.Gender
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.tag.QCreatorTag.creatorTag
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.stereotype.Repository
import java.math.BigDecimal
@@ -122,13 +125,16 @@ class ExplorerQueryRepository(
creatorId: Long,
limit: Long,
offset: Long = 0,
withDonationCan: Boolean
withDonationCan: Boolean,
lang: Lang? = null
): List<MemberDonationRankingResponse> {
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()
return queryFactory
.select(member, donation)
val query = queryFactory
.select(member, donation, displayName)
.from(useCanCalculate)
.innerJoin(useCanCalculate.useCan, useCan)
.innerJoin(useCan.member, member)
@@ -144,16 +150,17 @@ class ExplorerQueryRepository(
.or(useCan.canUsage.eq(CanUsage.LIVE))
)
)
.groupBy(useCan.member.id)
.groupBy(useCan.member.id, member.id, displayName)
.orderBy(donation.desc(), member.id.desc())
.fetch()
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
return query.fetch()
.asSequence()
.map {
val account = it.get(member)!!
val donationCan = it.get(donation)!!
MemberDonationRankingResponse(
account.id!!,
account.nickname,
it.get(displayName)!!,
if (account.profileImage != null) {
"$cloudFrontHost/${account.profileImage}"
} else {
@@ -165,9 +172,12 @@ class ExplorerQueryRepository(
.toList()
}
fun getNewCreators(): List<Member> {
fun getNewCreators(lang: Lang = Lang.KO): List<GetExplorerSectionCreatorResponse> {
val creatorName = CreatorNameJoin(member, lang, "newExplorerCreatorName")
return queryFactory
.selectFrom(member)
.select(member, creatorName.displayName)
.from(member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(
member.role.eq(MemberRole.CREATOR)
.and(member.createdAt.goe(LocalDateTime.now().minusDays(30)))
@@ -176,9 +186,13 @@ class ExplorerQueryRepository(
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
.limit(20)
.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 offset = (today.dayOfMonth - 1) % 26
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()
return queryFactory
.selectFrom(member)
.select(member, creatorName.displayName)
.from(member)
.leftJoin(creatorName.name).on(creatorName.condition)
.innerJoin(member.auth, auth)
.where(
member.role.eq(MemberRole.CREATOR)
@@ -204,17 +220,30 @@ class ExplorerQueryRepository(
.offset(0)
.limit(10)
.fetch()
.map { row ->
row.get(member)!!.toExplorerSectionCreator(cloudFrontHost).copy(nickname = row.get(creatorName.displayName)!!)
}
}
fun getSearchChannel(channel: String, memberId: Long): List<Member> {
return queryFactory.selectFrom(member)
fun getSearchChannel(channel: String, memberId: Long, lang: Lang? = null): List<GetRoomDetailUser> {
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(
member.nickname.containsIgnoreCase(channel)
creatorNameContains(member, channel)
.and(member.isActive.isTrue)
.and(member.id.ne(memberId))
.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? {
@@ -224,7 +253,8 @@ class ExplorerQueryRepository(
.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
.selectFrom(member)
.where(member.id.eq(creatorId))
@@ -242,14 +272,15 @@ class ExplorerQueryRepository(
.and(member.tags.size().gt(0))
.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 similarCreators = queryFactory
.select(member, cnt)
.select(member, cnt, creatorName.displayName)
.from(memberCreatorTag)
.innerJoin(memberCreatorTag.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.innerJoin(memberCreatorTag.tag, creatorTag)
.leftJoin(member.auth, auth)
.where(
@@ -265,7 +296,7 @@ class ExplorerQueryRepository(
)
)
)
.groupBy(memberCreatorTag.member.id)
.groupBy(memberCreatorTag.member.id, member.id, creatorName.name.displayName)
.orderBy(cnt.desc())
.limit(3)
.fetch()
@@ -273,7 +304,7 @@ class ExplorerQueryRepository(
val account = it.get(member)!!
SimilarCreatorResponse(
account.id!!,
account.nickname,
it.get(creatorName.displayName)!!,
if (account.profileImage != null) {
"$cloudFrontHost/${account.profileImage}"
} else {
@@ -298,29 +329,32 @@ class ExplorerQueryRepository(
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
} else {
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
.select(member)
.select(member, creatorName.displayName)
.from(memberCreatorTag)
.join(memberCreatorTag.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.innerJoin(memberCreatorTag.tag, creatorTag)
.leftJoin(member.auth, auth)
.groupBy(memberCreatorTag.member.id)
.groupBy(memberCreatorTag.member.id, member.id, creatorName.name.displayName)
.where(where)
.limit(limit)
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
.fetch()
.map {
.map { row ->
val it = row.get(member)!!
SimilarCreatorResponse(
it.id!!,
it.nickname,
row.get(creatorName.displayName)!!,
if (it.profileImage != null) {
"$cloudFrontHost/${it.profileImage}"
} else {
@@ -474,7 +508,13 @@ class ExplorerQueryRepository(
.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
.getMessage("explorer.date.cheers.format", langContext.lang)
?: messageSource.getMessage("explorer.date.cheers.format", Lang.KO).orEmpty()
@@ -671,8 +711,10 @@ class ExplorerQueryRepository(
fun getFollowerList(
creatorId: Long,
offset: Long,
limit: Long
limit: Long,
lang: Lang = Lang.KO
): List<GetFollowerListDto> {
val creatorName = CreatorNameJoin(member, lang, "followerMemberName")
val blockMemberCondition = blockMember.member.id.eq(creatorId)
.and(blockMember.isActive.isTrue)
.and(blockMember.blockedMember.id.eq(member.id))
@@ -682,12 +724,13 @@ class ExplorerQueryRepository(
QGetFollowerListDto(
member.id,
member.profileImage.prepend("/").prepend(cloudFrontHost),
member.nickname,
creatorName.displayName,
member.role
)
)
.from(creatorFollowing)
.innerJoin(creatorFollowing.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.leftJoin(blockMember).on(blockMemberCondition)
.where(
member.isActive.isTrue
@@ -805,12 +848,17 @@ class ExplorerQueryRepository(
.fetchFirst()
}
fun getCreatorRankings(): List<Member> {
fun getCreatorRankings(lang: Lang = Lang.KO): List<GetExplorerSectionCreatorResponse> {
val creatorName = CreatorNameJoin(member, lang, "explorerRankCreatorName")
return queryFactory
.select(member)
.select(member, creatorName.displayName)
.from(creatorRanking)
.innerJoin(creatorRanking.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.orderBy(creatorRanking.ranking.asc())
.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.contentpreference.MemberContentPreferenceService
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.context.ApplicationEventPublisher
import org.springframework.data.domain.Pageable
@@ -65,15 +66,15 @@ class ExplorerService(
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val localizedNameService: CreatorLocalizedNameService,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
) {
fun getCreatorRank(memberId: Long): GetExplorerSectionResponse {
val creatorRankings = queryRepository
.getCreatorRankings()
.getCreatorRankings(langContext.lang)
.filter { !isBlockedBetweenMembers(memberId = memberId, otherMemberId = it.id!!) }
.map { it.toExplorerSectionCreator(cloudFrontHost) }
val currentDateTime = LocalDateTime.now()
val lastMonday = currentDateTime
@@ -107,9 +108,8 @@ class ExplorerService(
// 인기 크리에이터
val creatorRankings = queryRepository
.getCreatorRankings()
.getCreatorRankings(langContext.lang)
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
.map { it.toExplorerSectionCreator(cloudFrontHost) }
val currentDateTime = LocalDateTime.now()
val lastMonday = currentDateTime
@@ -140,9 +140,8 @@ class ExplorerService(
// 새로 시작 (newCreators)
val newCreators = queryRepository
.getNewCreators()
.getNewCreators(langContext.lang)
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
.map { it.toExplorerSectionCreator(cloudFrontHost) }
val newCreatorsSection = GetExplorerSectionResponse(
title = messageSource.getMessage("explorer.section.new_creators.title", langContext.lang).orEmpty(),
@@ -159,9 +158,8 @@ class ExplorerService(
.orEmpty(),
color = "39abde",
creators = queryRepository
.findCreatorByGender(1)
.findCreatorByGender(1, langContext.lang)
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
.map { it.toExplorerSectionCreator(cloudFrontHost) }
)
val femaleCreatorSection = GetExplorerSectionResponse(
@@ -170,9 +168,8 @@ class ExplorerService(
.orEmpty(),
color = "ffa517",
creators = queryRepository
.findCreatorByGender(0)
.findCreatorByGender(0, langContext.lang)
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
.map { it.toExplorerSectionCreator(cloudFrontHost) }
)
val randomInt = Random.nextInt(0, 2)
@@ -192,22 +189,23 @@ class ExplorerService(
throw SodaException(messageKey = "explorer.search.channel.min_length")
}
return queryRepository.getSearchChannel(channel, member.id!!)
return queryRepository.getSearchChannel(channel, member.id!!, langContext.lang)
.asSequence()
.filter { !isBlockedBetweenMembers(memberId = member.id!!, otherMemberId = it.id!!) }
.map { GetRoomDetailUser(it, cloudFrontHost) }
.toList()
}
fun getCreatorDetail(creatorId: Long, memberId: Long): GetCreatorDetailResponse {
val creatorAccount = queryRepository.getMember(creatorId)
?: 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)) {
val messageTemplate = messageSource
.getMessage("explorer.creator.blocked_access", langContext.lang)
.orEmpty()
throw SodaException(message = String.format(messageTemplate, creatorAccount.nickname))
throw SodaException(message = String.format(messageTemplate, creatorName))
}
if (creatorAccount.role != MemberRole.CREATOR) {
@@ -239,7 +237,7 @@ class ExplorerService(
}
return GetCreatorDetailResponse(
nickname = creatorAccount.nickname,
nickname = creatorName,
profileImageUrl = if (creatorAccount.profileImage != null) {
"$cloudFrontHost/${creatorAccount.profileImage}"
} else {
@@ -266,6 +264,8 @@ class ExplorerService(
// 크리에이터(유저) 정보
val creatorAccount = queryRepository.getMember(creatorId)
?: 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)
@@ -274,7 +274,7 @@ class ExplorerService(
val messageTemplate = messageSource
.getMessage("explorer.creator.blocked_access", langContext.lang)
.orEmpty()
throw SodaException(message = String.format(messageTemplate, creatorAccount.nickname))
throw SodaException(message = String.format(messageTemplate, creatorName))
}
val isCreator = creatorAccount.role == MemberRole.CREATOR
@@ -300,7 +300,7 @@ class ExplorerService(
// 추천 크리에이터
val similarCreatorList = if (isCreator) {
queryRepository.getSimilarCreatorList(creatorId)
queryRepository.getSimilarCreatorList(creatorId, langContext.lang)
} else {
listOf()
}
@@ -437,7 +437,7 @@ class ExplorerService(
} else {
"$cloudFrontHost/profile/default-profile.png"
},
nickname = creatorAccount.nickname,
nickname = creatorName,
tags = creatorAccount.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(),
introduce = creatorAccount.introduce,
instagramUrl = creatorAccount.instagramUrl,
@@ -450,9 +450,9 @@ class ExplorerService(
isNotification = creatorFollowing?.isFollow ?: false,
notificationRecipientCount = notificationRecipientCount
),
userDonationRanking = memberDonationRanking,
userDonationRanking = renderDonationRanking(memberDonationRanking),
similarCreatorList = similarCreatorList,
liveRoomList = liveRoomList,
liveRoomList = liveRoomList.map { it.copy(managerNickname = creatorName) },
contentList = translatedContentList,
latestContent = latestContent,
totalContentCount = totalContentCount,
@@ -537,7 +537,7 @@ class ExplorerService(
null
},
totalCount = donationMemberTotal,
userDonationRanking = donationRanking
userDonationRanking = renderDonationRanking(donationRanking)
)
}
@@ -548,7 +548,12 @@ class ExplorerService(
): GetFollowerListResponse {
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 {
val creatorFollowing = if (it.role == MemberRole.CREATOR) {
queryRepository.getCreatorFollowing(
@@ -684,6 +689,7 @@ class ExplorerService(
type = FcmEventType.CHANGE_NOTICE,
category = PushNotificationCategory.COMMUNITY,
title = member.nickname,
titleMemberId = member.id,
messageKey = "explorer.notice.fcm.message",
senderMemberId = 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 {
return memberService.isBlocked(blockedMemberId = memberId, memberId = otherMemberId) ||
memberService.isBlocked(blockedMemberId = otherMemberId, memberId = memberId)
@@ -1,7 +1,11 @@
package kr.co.vividnext.sodalive.explorer.profile.channelDonation
import com.querydsl.core.types.dsl.CaseBuilder
import com.querydsl.jpa.impl.JPAQueryFactory
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.stereotype.Repository
import java.time.LocalDateTime
@@ -18,8 +22,9 @@ interface ChannelDonationMessageQueryRepository {
offset: Long,
limit: Long,
startDateTime: LocalDateTime,
endDateTime: LocalDateTime
): List<ChannelDonationMessage>
endDateTime: LocalDateTime,
lang: Lang = Lang.KO
): List<Pair<ChannelDonationMessage, String>>
fun getChannelDonationMessageTotalCount(
creatorId: Long,
@@ -40,8 +45,13 @@ class ChannelDonationMessageQueryRepositoryImpl(
offset: Long,
limit: Long,
startDateTime: LocalDateTime,
endDateTime: LocalDateTime
): List<ChannelDonationMessage> {
endDateTime: LocalDateTime,
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(
creatorId = creatorId,
memberId = memberId,
@@ -51,8 +61,10 @@ class ChannelDonationMessageQueryRepositoryImpl(
)
return queryFactory
.selectFrom(channelDonationMessage)
.leftJoin(channelDonationMessage.member).fetchJoin()
.select(channelDonationMessage, displayName)
.from(channelDonationMessage)
.leftJoin(channelDonationMessage.member, member).fetchJoin()
.leftJoin(creatorName.name).on(creatorName.condition)
.where(where)
.offset(offset)
.limit(limit)
@@ -61,6 +73,7 @@ class ChannelDonationMessageQueryRepositoryImpl(
channelDonationMessage.id.desc()
)
.fetch()
.map { row -> row.get(channelDonationMessage)!! to row.get(displayName)!! }
}
override fun getChannelDonationMessageTotalCount(
@@ -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.use.CanUsage
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
@@ -99,12 +98,13 @@ class ChannelDonationService(
offset = offset,
limit = limit,
startDateTime = startDateTime,
endDateTime = endDateTime
).map {
endDateTime = endDateTime,
lang = langContext.lang
).map { (it, nickname) ->
GetChannelDonationListItem(
id = it.id!!,
memberId = it.member!!.id!!,
nickname = it.member!!.nickname.removeDeletedNicknamePrefix(),
nickname = nickname,
profileUrl = if (it.member!!.profileImage != null) {
"$cloudFrontHost/${it.member!!.profileImage}"
} else {
@@ -140,6 +140,7 @@ class CreatorCommunityService(
type = FcmEventType.CHANGE_NOTICE,
category = PushNotificationCategory.COMMUNITY,
title = member.nickname,
titleMemberId = member.id,
messageKey = "creator.community.fcm.new_post",
senderMemberId = member.id,
creatorId = member.id!!,
@@ -533,6 +534,7 @@ class CreatorCommunityService(
type = FcmEventType.INDIVIDUAL,
category = PushNotificationCategory.COMMUNITY,
title = member.nickname,
titleMemberId = member.id,
messageKey = "creator.community.fcm.new_comment",
senderMemberId = member.id,
recipients = listOf(creatorId),
@@ -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.block.QBlockMember.blockMember
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.stereotype.Repository
import java.time.LocalDateTime
@@ -70,8 +71,10 @@ class LiveRecommendRepository(
fun getOnAirRecommendChannelList(
isBlocked: (Long) -> Boolean,
isCreator: Boolean,
isAdult: Boolean
isAdult: Boolean,
lang: Lang
): List<GetRecommendChannelResponse> {
val creatorName = CreatorNameJoin(member, lang, "onAirRecommendedCreatorName")
var where = member.role.eq(MemberRole.CREATOR)
.and(member.isActive.isTrue)
@@ -88,20 +91,21 @@ class LiveRecommendRepository(
Projections.constructor(
GetRecommendChannelResponse::class.java,
member.id,
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(cloudFrontHost),
Expressions.asBoolean(true)
)
)
.from(liveRoom)
.rightJoin(liveRoom.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(
where
.and(liveRoom.isActive.isTrue)
.and(liveRoom.channelName.isNotNull)
.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())
.limit(20)
.fetch()
@@ -113,8 +117,10 @@ class LiveRecommendRepository(
fun getRecommendChannelList(
withOutCreatorList: List<Long>,
limit: Long,
isBlocked: (Long) -> Boolean
isBlocked: (Long) -> Boolean,
lang: Lang
): List<GetRecommendChannelResponse> {
val creatorName = CreatorNameJoin(member, lang, "recommendedCreatorName")
val where = member.role.eq(MemberRole.CREATOR)
.and(member.isActive.isTrue)
@@ -123,12 +129,13 @@ class LiveRecommendRepository(
Projections.constructor(
GetRecommendChannelResponse::class.java,
member.id,
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(cloudFrontHost),
Expressions.asBoolean(false)
)
)
.from(member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(where.and(member.id.notIn(withOutCreatorList)))
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
.limit(limit)
@@ -140,8 +147,10 @@ class LiveRecommendRepository(
memberId: Long,
isBlocked: (Long) -> Boolean,
isCreator: Boolean,
isAdult: Boolean
isAdult: Boolean,
lang: Lang
): List<GetRecommendChannelResponse> {
val creatorName = CreatorNameJoin(member, lang, "onAirFollowingCreatorName")
var where = member.role.eq(MemberRole.CREATOR)
.and(member.isActive.isTrue)
@@ -158,7 +167,7 @@ class LiveRecommendRepository(
Projections.constructor(
GetRecommendChannelResponse::class.java,
member.id,
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(cloudFrontHost),
Expressions.asBoolean(true)
)
@@ -166,6 +175,7 @@ class LiveRecommendRepository(
.from(liveRoom, creatorFollowing)
.rightJoin(liveRoom.member, member)
.innerJoin(creatorFollowing.creator, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(
where
.and(liveRoom.member.id.eq(creatorFollowing.creator.id))
@@ -175,7 +185,7 @@ class LiveRecommendRepository(
.and(liveRoom.channelName.isNotNull)
.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())
.limit(20)
.fetch()
@@ -188,8 +198,10 @@ class LiveRecommendRepository(
memberId: Long,
withOutCreatorList: List<Long>,
limit: Long,
isBlocked: (Long) -> Boolean
isBlocked: (Long) -> Boolean,
lang: Lang
): List<GetRecommendChannelResponse> {
val creatorName = CreatorNameJoin(member, lang, "followingCreatorName")
val where = member.role.eq(MemberRole.CREATOR)
.and(member.isActive.isTrue)
@@ -198,13 +210,14 @@ class LiveRecommendRepository(
Projections.constructor(
GetRecommendChannelResponse::class.java,
member.id,
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(cloudFrontHost),
Expressions.asBoolean(false)
)
)
.from(creatorFollowing)
.innerJoin(creatorFollowing.creator, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(
where
.and(member.id.notIn(withOutCreatorList))
@@ -244,8 +257,10 @@ class LiveRecommendRepository(
memberId: Long,
offset: Long,
limit: Long,
isBlocked: (Long) -> Boolean
isBlocked: (Long) -> Boolean,
lang: Lang
): List<GetCreatorFollowingAllListItem> {
val creatorName = CreatorNameJoin(member, lang, "allFollowingCreatorName")
val where = member.role.eq(MemberRole.CREATOR)
.and(member.isActive.isTrue)
.and(creatorFollowing.isActive.isTrue)
@@ -256,7 +271,7 @@ class LiveRecommendRepository(
.select(
QGetCreatorFollowingAllListItem(
member.id,
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(cloudFrontHost),
Expressions.asBoolean(true),
creatorFollowing.isNotify
@@ -264,10 +279,12 @@ class LiveRecommendRepository(
)
.from(creatorFollowing)
.innerJoin(creatorFollowing.creator, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(where)
.groupBy(
member.id,
member.nickname,
creatorName.name.displayName,
member.profileImage,
creatorFollowing.isNotify
)
@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.live.recommend
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.MemberRole
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
@@ -13,7 +14,8 @@ class LiveRecommendService(
private val repository: LiveRecommendRepository,
private val blockMemberRepository: BlockMemberRepository,
private val memberContentPreferenceService: MemberContentPreferenceService,
private val liveRecommendCacheService: LiveRecommendCacheService
private val liveRecommendCacheService: LiveRecommendCacheService,
private val langContext: LangContext
) {
fun getRecommendLive(member: Member?, lang: Lang): List<GetRecommendLiveResponse> {
val isAdult = if (member != null) {
@@ -45,7 +47,8 @@ class LiveRecommendService(
}
},
isCreator = member?.role == MemberRole.CREATOR,
isAdult = isAdult
isAdult = isAdult,
lang = langContext.lang
)
if (onAirChannelList.size >= 20) {
@@ -63,7 +66,8 @@ class LiveRecommendService(
} else {
false
}
}
},
lang = langContext.lang
)
return onAirChannelList + notOnAirCreatorList
@@ -76,7 +80,8 @@ class LiveRecommendService(
memberId = member.id!!,
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) },
isCreator = member.role == MemberRole.CREATOR,
isAdult = isAdult
isAdult = isAdult,
lang = langContext.lang
)
if (onAirFollowingChannelList.size >= 20) {
@@ -89,7 +94,8 @@ class LiveRecommendService(
memberId = member.id!!,
withOutCreatorList = onAirCreatorIdList,
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
@@ -105,7 +111,8 @@ class LiveRecommendService(
memberId = member.id!!,
offset = pageable.offset,
limit = pageable.pageSize.toLong(),
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) }
isBlocked = { isBlockedBetweenMembers(memberId = member.id!!, creatorId = it) },
lang = langContext.lang
)
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
)
@@ -1,10 +1,17 @@
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 kr.co.vividnext.sodalive.i18n.Lang
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.detail.GetRoomDetailUser
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.v2.creatorname.adapter.out.persistence.CreatorNameJoin
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@@ -12,29 +19,45 @@ import org.springframework.stereotype.Repository
interface LiveReservationRepository : JpaRepository<LiveReservation, Long>, LiveReservationQueryRepository
interface LiveReservationQueryRepository {
fun getReservationList(roomId: Long): List<LiveReservation>
fun getReservationList(roomId: Long, lang: Lang): List<GetRoomDetailUser>
fun cancelReservation(roomId: Long)
fun getReservationBookerList(roomId: Long): List<Member>
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(
reservationId: Long,
memberId: Long
): LiveReservation?
memberId: Long,
lang: Lang
): LiveReservationListRow?
fun getReservationCount(memberId: Long): Int
}
@Repository
class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveReservationQueryRepository {
override fun getReservationList(roomId: Long): List<LiveReservation> {
class LiveReservationQueryRepositoryImpl(
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
.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.member, member)
.leftJoin(bookerName.name).on(bookerName.condition)
.where(
liveRoom.id.eq(roomId)
.and(liveReservation.isActive.isTrue)
@@ -76,11 +99,8 @@ class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFacto
.fetchFirst() != null
}
override fun getReservationListByMemberId(memberId: Long, active: Boolean): List<LiveReservation> {
return queryFactory
.selectFrom(liveReservation)
.innerJoin(liveReservation.member, member)
.innerJoin(liveReservation.room, liveRoom)
override fun getReservationListByMemberId(memberId: Long, active: Boolean, lang: Lang): List<LiveReservationListRow> {
return reservationRows(lang)
.where(
liveRoom.isActive.isTrue
.and(liveReservation.isActive.eq(active))
@@ -89,11 +109,12 @@ class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFacto
.fetch()
}
override fun getReservationByReservationAndMemberId(reservationId: Long, memberId: Long): LiveReservation? {
return queryFactory
.selectFrom(liveReservation)
.innerJoin(liveReservation.member, member)
.innerJoin(liveReservation.room, liveRoom)
override fun getReservationByReservationAndMemberId(
reservationId: Long,
memberId: Long,
lang: Lang
): LiveReservationListRow? {
return reservationRows(lang)
.where(
liveReservation.id.eq(reservationId)
.and(member.id.eq(memberId))
@@ -101,6 +122,29 @@ class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFacto
.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 {
return queryFactory
.selectFrom(liveReservation)
@@ -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.LiveRoomType
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.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
@@ -25,6 +26,7 @@ class LiveReservationService(
private val liveReservationCancelRepository: LiveReservationCancelRepository,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val localizedNameService: CreatorLocalizedNameService,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
@@ -78,10 +80,11 @@ class LiveReservationService(
"live.reservation.datetime_format",
langContext.lang
).orEmpty()
val names = localizedNameService.findLocalizedNames(listOf(room.member!!.id!!), langContext.lang)
return MakeLiveReservationResponse(
reservationId = reservation.id!!,
nickname = room.member!!.nickname,
nickname = names[room.member!!.id] ?: room.member!!.nickname,
title = room.title,
beginDateString = beginDateTime.format(DateTimeFormatter.ofPattern(reservationDateFormat)),
beginDateTimeUtc = room.beginDateTime
@@ -98,34 +101,35 @@ class LiveReservationService(
)
}
@Transactional(readOnly = true)
fun getReservationList(memberId: Long, active: Boolean, timezone: String): List<GetLiveReservationResponse> {
val detailDateFormat = messageSource.getMessage(
"live.room.datetime_format_detail",
langContext.lang
).orEmpty()
return repository
.getReservationListByMemberId(memberId, active)
val reservations = repository.getReservationListByMemberId(memberId, active, langContext.lang)
return reservations
.asSequence()
.map {
val beginDateTime = it.room!!.beginDateTime
val beginDateTime = it.beginDateTime
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of(timezone))
GetLiveReservationResponse(
reservationId = it.id!!,
roomId = it.room!!.id!!,
title = it.room!!.title,
coverImageUrl = if (it.room!!.coverImage != null) {
"$cloudFrontHost/${it.room!!.coverImage}"
reservationId = it.id,
roomId = it.roomId,
title = it.title,
coverImageUrl = if (it.coverImage != null) {
"$cloudFrontHost/${it.coverImage}"
} else {
"$cloudFrontHost/profile/default-profile.png"
},
price = it.room!!.price,
masterNickname = it.room!!.member!!.nickname,
price = it.price,
masterNickname = it.masterNickname,
beginDateTime = beginDateTime.format(
DateTimeFormatter.ofPattern(detailDateFormat)
),
beginDateTimeUtc = it.room!!.beginDateTime
beginDateTimeUtc = it.beginDateTime
.atZone(ZoneId.of("UTC"))
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
cancelable = beginDateTime.minusHours(4).isAfter(
@@ -138,33 +142,34 @@ class LiveReservationService(
.toList()
}
@Transactional(readOnly = true)
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")
val detailDateFormat = messageSource.getMessage(
"live.room.datetime_format_detail",
langContext.lang
).orEmpty()
val beginDateTime = reservation.room!!.beginDateTime
val beginDateTime = reservation.beginDateTime
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of(timezone))
return GetLiveReservationResponse(
reservationId = reservation.id!!,
roomId = reservation.room!!.id!!,
title = reservation.room!!.title,
coverImageUrl = if (reservation.room!!.coverImage != null) {
"$cloudFrontHost/${reservation.room!!.coverImage}"
reservationId = reservation.id,
roomId = reservation.roomId,
title = reservation.title,
coverImageUrl = if (reservation.coverImage != null) {
"$cloudFrontHost/${reservation.coverImage}"
} else {
"$cloudFrontHost/profile/default-profile.png"
},
price = reservation.room!!.price,
masterNickname = reservation.room!!.member!!.nickname,
price = reservation.price,
masterNickname = reservation.masterNickname,
beginDateTime = beginDateTime.format(
DateTimeFormatter.ofPattern(detailDateFormat)
),
beginDateTimeUtc = reservation.room!!.beginDateTime
beginDateTimeUtc = reservation.beginDateTime
.atZone(ZoneId.of("UTC"))
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
cancelable = beginDateTime.minusHours(4).isAfter(
@@ -1,5 +1,7 @@
package kr.co.vividnext.sodalive.live.room
import kr.co.vividnext.sodalive.member.Member
data class GetLiveRoomUserProfileResponse(
val userId: Long,
val nickname: String,
@@ -19,3 +21,5 @@ data class GetLiveRoomUserProfileResponse(
val isFollowing: 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.QUseCan.useCan
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.QQuarterLiveRankings.quarterLiveRankings
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.like.GetLiveRoomHeartListItem
import kr.co.vividnext.sodalive.live.room.like.QGetLiveRoomHeartListItem
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember.member
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.cache.annotation.Cacheable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@@ -35,8 +37,9 @@ interface LiveRoomQueryRepository {
memberId: Long?,
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom>
effectiveGender: Gender?,
lang: Lang
): List<LiveRoomListRow>
fun getLiveRoomListReservationWithDate(
date: LocalDateTime,
@@ -45,30 +48,40 @@ interface LiveRoomQueryRepository {
memberId: Long?,
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom>
effectiveGender: Gender?,
lang: Lang
): List<LiveRoomListRow>
fun getLiveRoomListReservationWithoutDate(
timezone: String,
memberId: Long?,
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom>
effectiveGender: Gender?,
lang: Lang
): List<LiveRoomListRow>
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 getRecentRoomInfo(memberId: Long): GetRecentRoomInfoResponse?
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 getActiveRoomIdList(memberId: Long): Int
fun getTotalHeartCount(roomId: Long): Int?
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>
}
@Repository
class LiveRoomQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory,
@@ -82,8 +95,10 @@ class LiveRoomQueryRepositoryImpl(
memberId: Long?,
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom> {
effectiveGender: Gender?,
lang: Lang
): List<LiveRoomListRow> {
val creatorName = CreatorNameJoin(member, lang, "liveRoomNowCreatorName")
var where = liveRoom.channelName.isNotNull
.and(liveRoom.channelName.isNotEmpty)
.and(liveRoom.isActive.isTrue)
@@ -116,8 +131,10 @@ class LiveRoomQueryRepositoryImpl(
}
var select = queryFactory
.selectFrom(liveRoom)
.innerJoin(liveRoom.member, member)
.select(liveRoom, creatorName.displayName)
.from(liveRoom)
.innerJoin(liveRoom.member, member).fetchJoin()
.leftJoin(creatorName.name).on(creatorName.condition)
.leftJoin(quarterLiveRankings).on(liveRoom.id.eq(quarterLiveRankings.roomId))
if (hasMemberId) {
@@ -146,6 +163,7 @@ class LiveRoomQueryRepositoryImpl(
liveRoom.member.createdAt.desc()
)
.fetch()
.map { LiveRoomListRow(it.get(liveRoom)!!, it.get(creatorName.displayName)!!) }
}
override fun getLiveRoomListReservationWithDate(
@@ -155,8 +173,10 @@ class LiveRoomQueryRepositoryImpl(
memberId: Long?,
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom> {
effectiveGender: Gender?,
lang: Lang
): List<LiveRoomListRow> {
val creatorName = CreatorNameJoin(member, lang, "liveRoomReservationDateCreatorName")
var where = liveRoom.beginDateTime.goe(date)
.and(liveRoom.beginDateTime.lt(date.plusDays(1)))
.and(
@@ -192,8 +212,10 @@ class LiveRoomQueryRepositoryImpl(
}
var select = queryFactory
.selectFrom(liveRoom)
.innerJoin(liveRoom.member, member)
.select(liveRoom, creatorName.displayName)
.from(liveRoom)
.innerJoin(liveRoom.member, member).fetchJoin()
.leftJoin(creatorName.name).on(creatorName.condition)
if (memberId != null) {
val blockMemberCondition = blockMember.isActive.isTrue
@@ -216,6 +238,7 @@ class LiveRoomQueryRepositoryImpl(
.limit(limit)
.orderBy(liveRoom.beginDateTime.asc())
.fetch()
.map { LiveRoomListRow(it.get(liveRoom)!!, it.get(creatorName.displayName)!!) }
}
override fun getLiveRoomListReservationWithoutDate(
@@ -223,8 +246,10 @@ class LiveRoomQueryRepositoryImpl(
memberId: Long?,
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom> {
effectiveGender: Gender?,
lang: Lang
): List<LiveRoomListRow> {
val creatorName = CreatorNameJoin(member, lang, "liveRoomReservationCreatorName")
var where = liveRoom.beginDateTime.gt(
LocalDateTime.now()
.atZone(ZoneId.of(timezone))
@@ -276,8 +301,10 @@ class LiveRoomQueryRepositoryImpl(
}
var select = queryFactory
.selectFrom(liveRoom)
.innerJoin(liveRoom.member, member)
.select(liveRoom, creatorName.displayName)
.from(liveRoom)
.innerJoin(liveRoom.member, member).fetchJoin()
.leftJoin(creatorName.name).on(creatorName.condition)
.limit(10)
if (memberId != null) {
@@ -299,6 +326,7 @@ class LiveRoomQueryRepositoryImpl(
.where(where)
.orderBy(*orderBy.toTypedArray())
.fetch()
.map { LiveRoomListRow(it.get(liveRoom)!!, it.get(creatorName.displayName)!!) }
}
override fun getLiveRoom(id: Long): LiveRoom? {
@@ -312,6 +340,29 @@ class LiveRoomQueryRepositoryImpl(
.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? {
return queryFactory
.selectFrom(liveRoom)
@@ -359,7 +410,13 @@ class LiveRoomQueryRepositoryImpl(
.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)
.and(useCan.canUsage.eq(CanUsage.DONATION).or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE)))
.and(useCan.isRefund.isFalse)
@@ -380,7 +437,7 @@ class LiveRoomQueryRepositoryImpl(
.coalesce("profile/default-profile.png")
.prepend("/")
.prepend(cloudFrontHost),
member.nickname,
donorName.displayName,
member.id.coalesce(0),
Expressions.asNumber(
CaseBuilder()
@@ -406,7 +463,8 @@ class LiveRoomQueryRepositoryImpl(
.from(useCan)
.innerJoin(useCan.room, liveRoom)
.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)
.orderBy(sortExpression.sum().desc())
.fetch()
@@ -461,26 +519,29 @@ class LiveRoomQueryRepositoryImpl(
.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)
.and(useCan.canUsage.eq(CanUsage.HEART))
.and(useCan.isRefund.isFalse)
return queryFactory
.select(
QGetLiveRoomHeartListItem(
Projections.constructor(
GetLiveRoomHeartListItem::class.java,
member.profileImage
.coalesce("profile/default-profile.png")
.prepend("/")
.prepend(cloudFrontHost),
member.nickname,
donorName.displayName,
useCan.can.add(useCan.rewardCan).sum()
)
)
.from(useCan)
.innerJoin(useCan.room, liveRoom)
.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)
.orderBy(useCan.can.add(useCan.rewardCan).sum().desc())
.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.PushTokenRepository
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.SodaMessageSource
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.ViewerContentPreference
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.cache.annotation.Cacheable
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.findByIdOrNull
@@ -89,6 +90,7 @@ class LiveRoomService(
private val menuService: LiveRoomMenuService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val localizedNameService: CreatorLocalizedNameService,
private val repository: LiveRoomRepository,
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)
return roomList
.map {
val roomInfo = roomInfoRepository.findByIdOrNull(it.id!!)
.map { (room, creatorNickname) ->
val roomInfo = roomInfoRepository.findByIdOrNull(room.id!!)
val reservations = it.reservations
val reservations = room.reservations
.filter { reservation ->
if (member?.id != null) {
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"))
.withZoneSameInstant(ZoneId.of(timezone))
.format(
@@ -272,42 +274,42 @@ class LiveRoomService(
.withLocale(langContext.lang.locale)
)
val beginDateTimeUtc = it.beginDateTime
val beginDateTimeUtc = room.beginDateTime
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
val tags = it.tags
val tags = room.tags
.filter { tag -> tag.tag.isActive }
.map { tag -> tag.tag.tag }
.let { list -> applyLanguageTagToRoomTags(it.member?.id, list, languageTagByMemberId) }
.let { list -> applyLanguageTagToRoomTags(room.member?.id, list, languageTagByMemberId) }
GetRoomListResponse(
roomId = it.id!!,
title = it.title,
content = it.notice,
roomId = room.id!!,
title = room.title,
content = room.notice,
beginDateTime = beginDateTime,
beginDateTimeUtc = beginDateTimeUtc,
numberOfParticipate = (roomInfo?.listenerCount ?: 0) +
(roomInfo?.speakerCount ?: 0) +
(roomInfo?.managerCount ?: 0),
numberOfPeople = it.numberOfPeople,
isAdult = it.isAdult,
price = it.price,
channelName = it.channelName,
creatorProfileImage = if (it.member!!.profileImage != null) {
"$cloudFrontHost/${it.member!!.profileImage}"
numberOfPeople = room.numberOfPeople,
isAdult = room.isAdult,
price = room.price,
channelName = room.channelName,
creatorProfileImage = if (room.member!!.profileImage != null) {
"$cloudFrontHost/${room.member!!.profileImage}"
} else {
"$cloudFrontHost/profile/default-profile.png"
},
creatorNickname = it.member!!.nickname,
creatorId = it.member!!.id!!,
creatorNickname = creatorNickname,
creatorId = room.member!!.id!!,
tags = tags,
coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
it.coverImage!!
coverImageUrl = if (room.coverImage!!.startsWith("https://")) {
room.coverImage!!
} else {
"$cloudFrontHost/${it.coverImage!!}"
"$cloudFrontHost/${room.coverImage!!}"
},
isReservation = reservations.isNotEmpty(),
isPrivateRoom = it.type == LiveRoomType.PRIVATE
isPrivateRoom = room.type == LiveRoomType.PRIVATE
)
}
}
@@ -319,7 +321,7 @@ class LiveRoomService(
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom> {
): List<LiveRoomListRow> {
return repository.getLiveRoomListNow(
offset = pageable.offset,
limit = pageable.pageSize.toLong(),
@@ -327,7 +329,8 @@ class LiveRoomService(
memberId = memberId,
isCreator = isCreator,
isAdult = isAdult,
effectiveGender = effectiveGender
effectiveGender = effectiveGender,
lang = langContext.lang
)
}
@@ -339,7 +342,7 @@ class LiveRoomService(
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom> {
): List<LiveRoomListRow> {
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val date = LocalDate.parse(dateString, dateTimeFormatter).atStartOfDay()
.atZone(ZoneId.of(timezone))
@@ -353,7 +356,8 @@ class LiveRoomService(
memberId = memberId,
isCreator = isCreator,
isAdult = isAdult,
effectiveGender = effectiveGender
effectiveGender = effectiveGender,
lang = langContext.lang
)
}
@@ -363,8 +367,15 @@ class LiveRoomService(
isCreator: Boolean,
isAdult: Boolean,
effectiveGender: Gender?
): List<LiveRoom> {
return repository.getLiveRoomListReservationWithoutDate(timezone, memberId, isCreator, isAdult, effectiveGender)
): List<LiveRoomListRow> {
return repository.getLiveRoomListReservationWithoutDate(
timezone,
memberId,
isCreator,
isAdult,
effectiveGender,
langContext.lang
)
}
@Transactional
@@ -504,6 +515,7 @@ class LiveRoomService(
type = FcmEventType.CREATE_LIVE,
category = PushNotificationCategory.LIVE,
title = createdRoom.member!!.nickname,
titleMemberId = createdRoom.member!!.id,
messageKey = if (createdRoom.channelName != null) {
"live.room.fcm.message.started"
} else {
@@ -525,7 +537,7 @@ class LiveRoomService(
}
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")
val creatorId = room.member!!.id!!
@@ -573,7 +585,7 @@ class LiveRoomService(
genderRestriction = room.genderRestriction,
password = room.password
)
response.manager = GetRoomDetailManager(room.member!!, cloudFrontHost = cloudFrontHost)
response.manager = GetRoomDetailManager(room.member!!, cloudFrontHost, creatorNickname)
if (!room.channelName.isNullOrBlank()) {
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
}
} else {
val reservationList = reservationRepository.getReservationList(roomId)
val reservationList = reservationRepository.getReservationList(roomId, langContext.lang)
response.isPaid = reservationList.any { it.id == member.id }
response.participatingUsers = reservationList
.asSequence()
.map {
if (it.member!!.id!! == member.id!!) {
response.isPaid = true
}
GetRoomDetailUser(it.member!!, cloudFrontHost)
}
.toList()
response.numberOfParticipants = reservationList.size
}
@@ -686,6 +691,7 @@ class LiveRoomService(
type = FcmEventType.START_LIVE,
category = PushNotificationCategory.LIVE,
title = room.member!!.nickname,
titleMemberId = room.member!!.id,
messageKey = "live.room.fcm.message.started",
senderMemberId = room.member!!.id,
args = listOf(room.title),
@@ -761,6 +767,7 @@ class LiveRoomService(
type = FcmEventType.CANCEL_LIVE,
category = PushNotificationCategory.LIVE,
title = room.member!!.nickname,
titleMemberId = room.member!!.id,
messageKey = "live.room.fcm.message.canceled",
senderMemberId = room.member!!.id,
args = listOf(room.title),
@@ -792,8 +799,9 @@ class LiveRoomService(
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = room.member!!.id!!)
if (isBlocked) {
val names = localizedNameService.findLocalizedNames(listOf(room.member!!.id!!), langContext.lang)
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!!)
if (kickOutCount >= 2) {
val names = localizedNameService.findLocalizedNames(listOf(room.member!!.id!!), langContext.lang)
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()
?.take(2)
val names = findCurrentNames(
listOf(creatorId) + (roomInfo.speakerList + roomInfo.listenerList + roomInfo.managerList).map { it.id },
langContext.lang
)
return GetRoomInfoResponse(
roomId = roomId,
title = room.title,
@@ -1079,7 +1095,7 @@ class LiveRoomService(
rtmToken = rtmToken,
v2vWorkerToken = v2vWorkerToken,
creatorId = room.member!!.id!!,
creatorNickname = room.member!!.nickname,
creatorNickname = names[creatorId] ?: room.member!!.nickname,
creatorProfileUrl = if (room.member!!.profileImage != null) {
"$cloudFrontHost/${room.member!!.profileImage}"
} else {
@@ -1090,9 +1106,9 @@ class LiveRoomService(
isCaptureRecordingAvailable = room.isCaptureRecordingAvailable,
participantsCount = roomInfo.listenerCount + roomInfo.speakerCount + roomInfo.managerCount,
totalAvailableParticipantsCount = room.numberOfPeople,
speakerList = roomInfo.speakerList,
listenerList = roomInfo.listenerList,
managerList = roomInfo.managerList,
speakerList = roomInfo.speakerList.map { it.copy(nickname = names[it.id] ?: it.nickname).apply { role = it.role } },
listenerList = roomInfo.listenerList.map { it.copy(nickname = names[it.id] ?: it.nickname).apply { role = it.role } },
managerList = roomInfo.managerList.map { it.copy(nickname = names[it.id] ?: it.nickname).apply { role = it.role } },
donationRankingTop3UserIds = donationRankingTop3UserIds,
menuPan = menuPan?.menu ?: "",
creatorLanguageCode = creatorLanguageCode,
@@ -1129,12 +1145,14 @@ class LiveRoomService(
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
?: throw SodaException(messageKey = "live.room.info_not_found")
return if (liveRoomCreatorId != member.id!!) {
val messages = if (liveRoomCreatorId != member.id!!) {
roomInfo.donationMessageList
.filter { !it.isSecret || it.memberId == member.id!! }
} else {
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) {
@@ -1161,7 +1179,7 @@ class LiveRoomService(
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
?: 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")
val isFollowing = if (user.role == MemberRole.CREATOR) {
@@ -1194,7 +1212,7 @@ class LiveRoomService(
return GetLiveRoomUserProfileResponse(
userId = user.id!!,
nickname = user.nickname,
nickname = nickname,
profileUrl = if (user.profileImage != null) {
"$cloudFrontHost/${user.profileImage}"
} else {
@@ -1439,7 +1457,8 @@ class LiveRoomService(
val donationList = repository.getDonationList(
roomId = room.id!!,
isLiveCreator = isLiveCreator,
memberId = memberId
memberId = memberId,
lang = langContext.lang
).filter { it.secretCan > 0 || it.can > 0 }
val totalCan = donationList.sumOf { it.can }
@@ -1492,6 +1511,10 @@ class LiveRoomService(
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 {
if (member == null) {
return ViewerContentPreference(
@@ -1531,7 +1554,7 @@ class LiveRoomService(
}
fun getHeartList(roomId: Long): GetLiveRoomHeartListResponse {
val heartList = repository.getHeartList(roomId = roomId)
val heartList = repository.getHeartList(roomId = roomId, lang = langContext.lang)
return GetLiveRoomHeartListResponse(
heartList = heartList,
@@ -1541,11 +1564,10 @@ class LiveRoomService(
}
@Transactional(readOnly = true)
@Cacheable(
cacheNames = ["cache_ttl_10_minutes"],
key = "'getLatestFinishedLive:' + (#member?.id ?: 'guest')"
)
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 isCreator: Boolean
) {
constructor(member: Member, cloudFrontHost: String) : this(
constructor(member: Member, cloudFrontHost: String, nickname: String) : this(
id = member.id!!,
nickname = member.nickname,
nickname = nickname,
introduce = member.introduce,
youtubeUrl = member.youtubeUrl,
instagramUrl = member.instagramUrl,
@@ -1,14 +1,12 @@
package kr.co.vividnext.sodalive.live.room.like
import com.querydsl.core.annotations.QueryProjection
data class GetLiveRoomHeartListResponse(
val heartList: List<GetLiveRoomHeartListItem>,
val totalCount: Int,
val totalHeart: Int
)
data class GetLiveRoomHeartListItem @QueryProjection constructor(
data class GetLiveRoomHeartListItem(
val profileImage: String,
val nickname: String,
val heart: Int
@@ -1,12 +1,17 @@
package kr.co.vividnext.sodalive.live.room.visit
import com.querydsl.core.types.Projections
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.detail.GetRoomDetailUser
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember
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.stereotype.Repository
@@ -16,11 +21,15 @@ interface LiveRoomVisitRepository : JpaRepository<LiveRoomVisit, Long>, LiveRoom
interface LiveRoomVisitQueryRepository {
fun findByRoomIdAndMemberId(roomId: Long, 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
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? {
return queryFactory
.selectFrom(liveRoomVisit)
@@ -44,7 +53,9 @@ class LiveRoomVisitQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
.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)
.and(liveRoomVisit.member.isActive.isTrue)
.and(liveRoomVisit.member.id.ne(member.id))
@@ -58,10 +69,18 @@ class LiveRoomVisitQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
}
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)
.innerJoin(liveRoomVisit.member, QMember.member)
.innerJoin(liveRoomVisit.member, visitor)
.innerJoin(liveRoomVisit.room, liveRoom)
.leftJoin(visitorName.name).on(visitorName.condition)
.where(where)
.fetch()
}
@@ -1,9 +1,9 @@
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.detail.GetRoomDetailUser
import kr.co.vividnext.sodalive.member.Member
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@@ -12,9 +12,7 @@ import java.time.LocalDateTime
@Transactional(readOnly = true)
class LiveRoomVisitService(
private val repository: LiveRoomVisitRepository,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
private val langContext: LangContext
) {
@Transactional
fun roomVisit(room: LiveRoom, member: Member) {
@@ -34,9 +32,6 @@ class LiveRoomVisitService(
val roomVisit = repository.findFirstByMemberIdOrderByUpdatedAtDesc(member.id!!)
?: return emptyList()
return repository.getRecentVisitRoomUsers(roomVisit.room!!.id!!, member)
.asSequence()
.map { GetRoomDetailUser(it, cloudFrontHost) }
.toList()
return repository.getRecentVisitRoomUsers(roomVisit.room!!.id!!, member, langContext.lang)
}
}
@@ -1,12 +1,15 @@
package kr.co.vividnext.sodalive.member
import com.querydsl.core.types.Projections
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.fcm.PushTokenInfo
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
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.room.GenderRestriction
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.auth.QAuth.auth
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.notification.QMemberNotification.memberNotification
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.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Lock
@@ -38,7 +43,7 @@ interface MemberRepository : JpaRepository<Member, Long>, 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 getAllRecipientPushTokens(isAuth: Boolean?): List<PushTokenInfo>
fun getCreateLiveRoomNotificationRecipientPushTokens(
@@ -70,7 +75,7 @@ interface MemberQueryRepository {
fun getPushTokenFromReservationList(roomId: Long): 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 findNicknamesWithPrefix(prefix: String): List<String>
@@ -86,8 +91,10 @@ class MemberQueryRepositoryImpl(
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
) : MemberQueryRepository {
override fun findByNicknameAndOtherCondition(nickname: String, member: Member): List<Member> {
var where = QMember.member.nickname.containsIgnoreCase(nickname)
override fun findByNicknameAndOtherCondition(nickname: String, member: Member, lang: Lang?): List<GetRoomDetailUser> {
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.role.ne(MemberRole.ADMIN))
.and(QMember.member.role.ne(MemberRole.AGENT))
@@ -97,10 +104,19 @@ class MemberQueryRepositoryImpl(
where = where.and(QMember.member.role.ne(MemberRole.USER))
}
return queryFactory
.selectFrom(QMember.member)
val query = queryFactory
.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)
.fetch()
if (creatorName != null) query.leftJoin(creatorName.name).on(creatorName.condition)
return query.fetch()
}
override fun findCreatorByIdOrNull(memberId: Long): Member? {
@@ -499,17 +515,19 @@ class MemberQueryRepositoryImpl(
.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
.select(
QGetMemberProfileResponse(
member.id,
member.nickname,
creatorName.displayName,
member.profileImage.prepend("/").prepend(cloudFrontHost),
blockMember.id.isNotNull
)
)
.from(member)
.leftJoin(creatorName.name).on(creatorName.condition)
.leftJoin(blockMember)
.on(
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.utils.generateFileName
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 org.springframework.beans.factory.annotation.Value
import org.springframework.cache.CacheManager
@@ -105,6 +106,7 @@ class MemberService(
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
private val localizedNameService: CreatorLocalizedNameService,
private val countryContext: CountryContext,
private val memberContentPreferenceService: MemberContentPreferenceService,
@@ -308,7 +310,8 @@ class MemberService(
).sumOf { it.point }
return MyPageResponse(
nickname = member.nickname,
nickname = localizedNameService.findLocalizedNames(listOf(member.id!!), langContext.lang)[member.id]
?: member.nickname,
profileUrl = if (member.profileImage != null) {
"$cloudFrontHost/${member.profileImage}"
} else {
@@ -542,8 +545,7 @@ class MemberService(
fun getBlockedMemberList(memberId: Long, offset: Long, limit: Long): GetBlockedMemberListResponse {
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)
}
@@ -610,14 +612,11 @@ class MemberService(
throw SodaException(messageKey = "member.validation.nickname_min_length")
}
return repository.findByNicknameAndOtherCondition(nickname, member)
return repository.findByNicknameAndOtherCondition(nickname, member, langContext.lang)
.filter {
!blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.id!!) &&
!blockMemberRepository.isBlocked(blockedMemberId = it.id!!, memberId = member.id!!)
}
.map {
GetRoomDetailUser(it, cloudFrontHost)
}
}
@Transactional
@@ -837,7 +836,7 @@ class MemberService(
}
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 {
@@ -1,8 +1,10 @@
package kr.co.vividnext.sodalive.member.block
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.block.QBlockMember.blockMember
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.stereotype.Repository
@@ -14,7 +16,7 @@ interface BlockMemberQueryRepository {
fun getBlockAccount(blockedMemberId: Long, memberId: Long): BlockMember?
fun isBlocked(blockedMemberId: Long, memberId: Long): Boolean
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
}
@@ -61,18 +63,20 @@ class BlockMemberQueryRepositoryImpl(
.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
.select(
QGetBlockedMemberListItem(
blockMember.blockedMember.id,
blockMember.blockedMember.nickname,
creatorName.displayName,
blockMember.blockedMember.profileImage.prepend("/").prepend(cloudFrontHost),
blockMember.isActive
)
)
.from(blockMember)
.innerJoin(blockMember.blockedMember, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.where(
blockMember.member.id.eq(memberId)
.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.Series
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.member.Member
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember.member
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.QMemberCreatorTag.memberCreatorTag
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@@ -36,7 +38,9 @@ class RankingRepository(
@Value("\${cloud.aws.cloud-front.host}")
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) {
blockMember.isActive.isTrue
.and(
@@ -52,11 +56,12 @@ class RankingRepository(
}
var select = queryFactory
.select(member)
.select(member, displayName)
.from(creatorRanking)
.innerJoin(creatorRanking.member, member)
.leftJoin(member.tags, memberCreatorTag).fetchJoin()
.leftJoin(memberCreatorTag.tag, creatorTag).fetchJoin()
select.leftJoin(creatorName.name).on(creatorName.condition)
if (memberId != null) {
select = select.leftJoin(blockMember).on(blockMemberCondition)
@@ -69,6 +74,9 @@ class RankingRepository(
return select
.orderBy(creatorRanking.ranking.asc())
.fetch()
.map { row ->
row.get(member)!!.toExplorerSectionCreator(imageHost).copy(nickname = row.get(displayName)!!)
}
.distinctBy { it.id }
}
@@ -84,6 +92,8 @@ class RankingRepository(
theme: String = "",
locale: String? = null
): List<GetAudioContentRankingItem> {
val creatorName = locale?.let { CreatorNameJoin(member, Lang.fromCode(it), "rankingContentCreatorName") }
val displayName = creatorName?.displayName ?: member.nickname
val blockMemberCondition = if (memberId != null) {
blockMember.isActive.isTrue
.and(
@@ -148,7 +158,7 @@ class RankingRepository(
audioContent.price,
audioContent.duration,
member.id,
member.nickname,
displayName,
audioContent.isPointAvailable,
creatorProfileImageUrl
)
@@ -199,6 +209,7 @@ class RankingRepository(
select = select.leftJoin(contentTranslation)
.on(contentTranslation.contentId.eq(audioContent.id).and(contentTranslation.locale.eq(locale)))
}
if (creatorName != null) select.leftJoin(creatorName.name).on(creatorName.condition)
if (memberId != null) {
where = where.and(blockMember.id.isNull)
@@ -269,6 +280,7 @@ class RankingRepository(
}
val results = select
.groupBy(member.id, displayName)
.offset(offset)
.limit(limit)
.fetch()
@@ -283,7 +295,7 @@ class RankingRepository(
val price = row.get(audioContent.price) ?: 0
val duration = row.get(audioContent.duration) ?: ""
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 creatorProfileImageUrlValue = row.get(creatorProfileImageUrl)!!
@@ -26,7 +26,6 @@ class RankingService(
fun getCreatorRanking(memberId: Long?, rankingDate: String): GetExplorerSectionResponse {
val creatorRankings = repository
.getCreatorRankings(memberId = memberId)
.map { it.toExplorerSectionCreator(imageHost) }
return GetExplorerSectionResponse(
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.QSeriesContent.seriesContent
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.QMember.member
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.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.stereotype.Repository
@@ -50,7 +53,7 @@ class SearchRepository(
.and(member.role.eq(MemberRole.CREATOR))
.and(creatorTag.isActive.isTrue.or(creatorTag.id.isNull))
.and(
member.nickname.containsIgnoreCase(keyword)
creatorNameContains(member, keyword)
.or(member.introduce.containsIgnoreCase(keyword))
.or(creatorTag.tag.containsIgnoreCase(keyword))
)
@@ -65,7 +68,8 @@ class SearchRepository(
keyword: String,
memberId: Long,
offset: Long,
limit: Long
limit: Long,
lang: Lang = Lang.KO
): List<SearchResponseItem> {
val blockMemberCondition = blockMember.isActive.isTrue
.and(
@@ -77,16 +81,18 @@ class SearchRepository(
)
)
val creatorName = CreatorNameJoin(member, lang, "searchCreatorName")
return queryFactory
.select(
QSearchResponseItem(
member.id,
member.profileImage.prepend("/").prepend(imageHost),
member.nickname,
member.nickname
creatorName.displayName,
creatorName.displayName
)
)
.from(member)
.leftJoin(creatorName.name).on(creatorName.condition)
.leftJoin(memberCreatorTag).on(memberCreatorTag.member.id.eq(member.id))
.leftJoin(creatorTag).on(memberCreatorTag.tag.id.eq(creatorTag.id))
.leftJoin(blockMember).on(blockMemberCondition)
@@ -95,13 +101,13 @@ class SearchRepository(
.and(member.role.eq(MemberRole.CREATOR))
.and(creatorTag.isActive.isTrue.or(creatorTag.id.isNull))
.and(
member.nickname.containsIgnoreCase(keyword)
creatorNameContains(member, keyword)
.or(member.introduce.containsIgnoreCase(keyword))
.or(creatorTag.tag.containsIgnoreCase(keyword))
)
.and(blockMember.id.isNull)
)
.groupBy(member.id)
.groupBy(member.id, creatorName.name.displayName)
.orderBy(member.id.desc())
.offset(offset)
.limit(limit)
@@ -131,7 +137,7 @@ class SearchRepository(
.and(audioContent.duration.isNotNull)
.and(
audioContent.title.containsIgnoreCase(keyword)
.or(member.nickname.containsIgnoreCase(keyword))
.or(creatorNameContains(member, keyword))
.or(audioContent.detail.containsIgnoreCase(keyword))
.or(audioContent.theme.theme.containsIgnoreCase(keyword))
.or(hashTag.tag.containsIgnoreCase(keyword).and(audioContentHashTag.isActive.isTrue))
@@ -177,7 +183,8 @@ class SearchRepository(
isAdult: Boolean,
contentType: ContentType,
offset: Long,
limit: Long
limit: Long,
lang: Lang = Lang.KO
): List<SearchResponseItem> {
val blockMemberCondition = blockMember.isActive.isTrue
.and(
@@ -196,7 +203,7 @@ class SearchRepository(
.and(audioContent.duration.isNotNull)
.and(
audioContent.title.containsIgnoreCase(keyword)
.or(member.nickname.containsIgnoreCase(keyword))
.or(creatorNameContains(member, keyword))
.or(audioContent.detail.containsIgnoreCase(keyword))
.or(audioContent.theme.theme.containsIgnoreCase(keyword))
.or(hashTag.tag.containsIgnoreCase(keyword).and(audioContentHashTag.isActive.isTrue))
@@ -222,23 +229,25 @@ class SearchRepository(
}
}
val creatorName = CreatorNameJoin(member, lang, "searchContentCreatorName")
return queryFactory
.select(
QSearchResponseItem(
audioContent.id,
audioContent.coverImage.prepend("/").prepend(imageHost),
audioContent.title,
audioContent.member.nickname
creatorName.displayName
)
)
.from(audioContent)
.innerJoin(audioContent.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.leftJoin(audioContentTheme).on(audioContentTheme.id.eq(audioContent.theme.id))
.leftJoin(audioContentHashTag).on(audioContentHashTag.audioContent.id.eq(audioContent.id))
.leftJoin(hashTag).on(audioContentHashTag.hashTag.id.eq(hashTag.id))
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.groupBy(audioContent.id)
.groupBy(audioContent.id, member.id, creatorName.name.displayName)
.orderBy(audioContent.id.desc())
.offset(offset)
.limit(limit)
@@ -270,7 +279,7 @@ class SearchRepository(
.and(audioContent.limited.isNull)
.and(
series.title.containsIgnoreCase(keyword)
.or(member.nickname.containsIgnoreCase(keyword))
.or(creatorNameContains(member, keyword))
.or(series.introduction.containsIgnoreCase(keyword))
.or(seriesGenre.genre.containsIgnoreCase(keyword))
.or(hashTag.tag.containsIgnoreCase(keyword).and(seriesKeyword.isActive.isTrue))
@@ -319,7 +328,8 @@ class SearchRepository(
isAdult: Boolean,
contentType: ContentType,
offset: Long,
limit: Long
limit: Long,
lang: Lang = Lang.KO
): List<SearchResponseItem> {
val blockMemberCondition = blockMember.isActive.isTrue
.and(
@@ -340,7 +350,7 @@ class SearchRepository(
.and(audioContent.limited.isNull)
.and(
series.title.containsIgnoreCase(keyword)
.or(member.nickname.containsIgnoreCase(keyword))
.or(creatorNameContains(member, keyword))
.or(series.introduction.containsIgnoreCase(keyword))
.or(seriesGenre.genre.containsIgnoreCase(keyword))
.or(hashTag.tag.containsIgnoreCase(keyword).and(seriesKeyword.isActive.isTrue))
@@ -367,25 +377,27 @@ class SearchRepository(
}
}
val creatorName = CreatorNameJoin(member, lang, "searchSeriesCreatorName")
return queryFactory
.select(
QSearchResponseItem(
series.id,
series.coverImage.prepend("/").prepend(imageHost),
series.title,
series.member.nickname
creatorName.displayName
)
)
.from(seriesContent)
.innerJoin(seriesContent.series, series)
.innerJoin(seriesContent.content, audioContent)
.innerJoin(series.member, member)
.leftJoin(creatorName.name).on(creatorName.condition)
.leftJoin(seriesGenre).on(seriesGenre.id.eq(series.genre.id))
.leftJoin(seriesKeyword).on(seriesKeyword.series.id.eq(series.id))
.leftJoin(hashTag).on(hashTag.id.eq(seriesKeyword.keyword.id))
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.groupBy(series.id, series.orders)
.groupBy(series.id, series.orders, member.id, creatorName.name.displayName)
.orderBy(series.orders.asc())
.offset(offset)
.limit(limit)
@@ -1,11 +1,12 @@
package kr.co.vividnext.sodalive.search
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.member.Member
import org.springframework.stereotype.Service
@Service
class SearchService(private val repository: SearchRepository) {
class SearchService(private val repository: SearchRepository, private val langContext: LangContext) {
fun searchUnified(
keyword: String,
isAdult: Boolean,
@@ -16,7 +17,8 @@ class SearchService(private val repository: SearchRepository) {
keyword = keyword,
memberId = member.id!!,
offset = 0,
limit = 3
limit = 3,
lang = langContext.lang
)
.map {
it.type = SearchResponseType.CREATOR
@@ -29,7 +31,8 @@ class SearchService(private val repository: SearchRepository) {
isAdult = isAdult,
contentType = contentType,
offset = 0,
limit = 3
limit = 3,
lang = langContext.lang
)
.map {
it.type = SearchResponseType.CONTENT
@@ -42,7 +45,8 @@ class SearchService(private val repository: SearchRepository) {
isAdult = isAdult,
contentType = contentType,
offset = 0,
limit = 3
limit = 3,
lang = langContext.lang
)
.map {
it.type = SearchResponseType.SERIES
@@ -67,7 +71,8 @@ class SearchService(private val repository: SearchRepository) {
keyword = keyword,
memberId = member.id!!,
offset = offset,
limit = limit
limit = limit,
lang = langContext.lang
)
.map {
it.type = SearchResponseType.CREATOR
@@ -98,7 +103,8 @@ class SearchService(private val repository: SearchRepository) {
isAdult = isAdult,
contentType = contentType,
offset = offset,
limit = limit
limit = limit,
lang = langContext.lang
)
.map {
it.type = SearchResponseType.CONTENT
@@ -129,7 +135,8 @@ class SearchService(private val repository: SearchRepository) {
isAdult = isAdult,
contentType = contentType,
offset = offset,
limit = limit
limit = limit,
lang = langContext.lang
)
.map {
it.type = SearchResponseType.SERIES