From 8c4b59973516762dc5b680a296d117c4bc3d11f5 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 30 Jan 2026 16:41:43 +0900 Subject: [PATCH] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=20=EB=B0=A9=20?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=EC=96=B8=EC=96=B4=20=EC=9A=B0=EC=84=A0=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/fcm/PushTokenRepository.kt | 9 +++ .../sodalive/live/room/LiveRoomService.kt | 79 +++++++++++++++++-- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/PushTokenRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/PushTokenRepository.kt index 6047a0fb..637cb922 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/PushTokenRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/PushTokenRepository.kt @@ -9,6 +9,7 @@ interface PushTokenRepository : JpaRepository, PushTokenQueryRe interface PushTokenQueryRepository { fun findByToken(token: String): PushToken? fun findByMemberId(memberId: Long): List + fun findByMemberIds(memberIds: List): List } class PushTokenQueryRepositoryImpl( @@ -27,4 +28,12 @@ class PushTokenQueryRepositoryImpl( .where(pushToken.member.id.eq(memberId)) .fetch() } + + override fun findByMemberIds(memberIds: List): List { + if (memberIds.isEmpty()) return emptyList() + return queryFactory + .selectFrom(pushToken) + .where(pushToken.member.id.`in`(memberIds)) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt index fa5b2816..d7178263 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt @@ -21,6 +21,7 @@ import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository import kr.co.vividnext.sodalive.extensions.convertLocalDateTime 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.i18n.LangContext import kr.co.vividnext.sodalive.i18n.SodaMessageSource import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository @@ -95,6 +96,7 @@ class LiveRoomService( private val roomVisitService: LiveRoomVisitService, private val canPaymentService: CanPaymentService, private val chargeRepository: ChargeRepository, + private val pushTokenRepository: PushTokenRepository, private val memberRepository: MemberRepository, private val tagRepository: LiveTagRepository, private val canRepository: CanRepository, @@ -126,6 +128,62 @@ class LiveRoomService( } } + private fun applyLanguageTagToRoomTags( + memberId: Long?, + tags: List, + languageTagByMemberId: Map? = null + ): List { + val randomizedTags = tags.shuffled() + val languageTag = getCreatorLanguageTag(memberId, languageTagByMemberId) ?: return randomizedTags + val filteredTags = randomizedTags.filterNot { it == languageTag } + return listOf(languageTag) + filteredTags + } + + private fun getCreatorLanguageTag( + memberId: Long?, + languageTagByMemberId: Map? = null + ): String? { + if (memberId == null) return null + if (languageTagByMemberId != null && languageTagByMemberId.containsKey(memberId)) { + return languageTagByMemberId[memberId] + } + + val tokens = pushTokenRepository.findByMemberId(memberId) + val languageCode = tokens + .filterNot { it.languageCode.isNullOrBlank() } + .maxByOrNull { it.updatedAt ?: LocalDateTime.MIN } + ?.languageCode + + val languageTag = when (languageCode?.lowercase()?.take(2)) { + "ko" -> "한국어" + "ja" -> "일본어" + "en" -> "영어" + else -> null + } + return languageTag + } + + private fun buildLanguageTagMap(memberIds: List): Map { + val tokens = pushTokenRepository.findByMemberIds(memberIds) + if (tokens.isEmpty()) return emptyMap() + + val latestTokenByMemberId = tokens + .filter { it.member?.id != null } + .groupBy { it.member!!.id!! } + .mapValues { (_, memberTokens) -> + memberTokens.maxByOrNull { it.updatedAt ?: LocalDateTime.MIN } + } + + return latestTokenByMemberId.mapValues { (_, token) -> + when (token?.languageCode?.lowercase()?.take(2)) { + "ko" -> "한국어" + "ja" -> "일본어" + "en" -> "영어" + else -> null + } + } + } + @Transactional(readOnly = true) fun getRoomList( dateString: String?, @@ -161,6 +219,9 @@ class LiveRoomService( ) } + val creatorIds = roomList.mapNotNull { it.member?.id }.distinct() + val languageTagByMemberId = buildLanguageTagMap(creatorIds) + return roomList .filter { if (member?.id != null) { @@ -193,6 +254,11 @@ class LiveRoomService( val beginDateTimeUtc = it.beginDateTime .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + val tags = it.tags + .filter { tag -> tag.tag.isActive } + .map { tag -> tag.tag.tag } + .let { list -> applyLanguageTagToRoomTags(it.member?.id, list, languageTagByMemberId) } + GetRoomListResponse( roomId = it.id!!, title = it.title, @@ -213,11 +279,7 @@ class LiveRoomService( }, creatorNickname = it.member!!.nickname, creatorId = it.member!!.id!!, - tags = it.tags - .asSequence() - .filter { tag -> tag.tag.isActive } - .map { tag -> tag.tag.tag } - .toList(), + tags = tags, coverImageUrl = if (it.coverImage!!.startsWith("https://")) { it.coverImage!! } else { @@ -440,12 +502,17 @@ class LiveRoomService( val beginDateTimeUtc = room.beginDateTime .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + val languageTagByMemberId = buildLanguageTagMap(listOfNotNull(room.member?.id)) val response = GetRoomDetailResponse( roomId = roomId, title = room.title, notice = room.notice, price = room.price, - tags = room.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(), + tags = room.tags.asSequence() + .filter { it.tag.isActive } + .map { it.tag.tag } + .toList() + .let { tags -> applyLanguageTagToRoomTags(room.member?.id, tags, languageTagByMemberId) }, numberOfParticipantsTotal = room.numberOfPeople, numberOfParticipants = 0, channelName = room.channelName,