From 8c4b59973516762dc5b680a296d117c4bc3d11f5 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 30 Jan 2026 16:41:43 +0900 Subject: [PATCH 01/10] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=20=EB=B0=A9?= =?UTF-8?q?=20=ED=83=9C=EA=B7=B8=20=EC=96=B8=EC=96=B4=20=EC=9A=B0=EC=84=A0?= =?UTF-8?q?=20=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, From 461ee435e05b851e85d9f6d703f9632de88e4963 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 30 Jan 2026 17:17:50 +0900 Subject: [PATCH 02/10] =?UTF-8?q?=EC=B5=9C=EC=8B=A0=20=EC=BD=98=ED=85=90?= =?UTF-8?q?=EC=B8=A0=20=EC=A1=B0=ED=9A=8C=EC=97=90=EC=84=9C=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=EB=93=A3=EA=B8=B0=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt index b5476b55..f0b9714e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt @@ -317,8 +317,9 @@ class HomeService( val themeList = if (theme.isBlank()) { contentThemeService.getActiveThemeOfContent( isAdult = isAdult, - isFree = true, - contentType = contentType + isFree = false, + contentType = contentType, + excludeThemes = listOf("다시듣기") ) } else { listOf(theme) From 6b0ceffe0682a0f5a21917f5bdbacea149e0d34b Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Feb 2026 11:24:24 +0900 Subject: [PATCH 03/10] =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=ED=86=B5=EA=B3=84?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=EC=97=90=20LINE=20=EA=B0=80=EC=9E=85?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/AdminMemberStatisticsRepository.kt | 32 +++++++++++++++++++ .../member/AdminMemberStatisticsService.kt | 11 +++++++ .../member/GetMemberStatisticsResponse.kt | 2 ++ 3 files changed, 45 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/AdminMemberStatisticsRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/AdminMemberStatisticsRepository.kt index 332c48f8..66650292 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/AdminMemberStatisticsRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/AdminMemberStatisticsRepository.kt @@ -68,6 +68,19 @@ class AdminMemberStatisticsRepository(private val queryFactory: JPAQueryFactory) .size } + fun getTotalSignUpLineCount(startDate: LocalDateTime, endDate: LocalDateTime): Int { + return queryFactory + .select(member.id) + .from(member) + .where( + member.createdAt.goe(startDate), + member.createdAt.loe(endDate), + member.provider.eq(MemberProvider.LINE) + ) + .fetch() + .size + } + fun getTotalAuthCount(startDate: LocalDateTime, endDate: LocalDateTime): Int { return queryFactory .select(auth.id) @@ -189,6 +202,25 @@ class AdminMemberStatisticsRepository(private val queryFactory: JPAQueryFactory) .fetch() } + fun getSignUpLineCountInRange(startDate: LocalDateTime, endDate: LocalDateTime): List { + return queryFactory + .select( + QDateAndMemberCount( + getFormattedDate(member.createdAt), + member.id.countDistinct().castToNum(Int::class.java) + ) + ) + .from(member) + .where( + member.createdAt.goe(startDate), + member.createdAt.loe(endDate), + member.provider.eq(MemberProvider.LINE) + ) + .groupBy(getFormattedDate(member.createdAt)) + .orderBy(getFormattedDate(member.createdAt).desc()) + .fetch() + } + fun getAuthCountInRange(startDate: LocalDateTime, endDate: LocalDateTime): List { return queryFactory .select( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/AdminMemberStatisticsService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/AdminMemberStatisticsService.kt index e4a197da..67906d64 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/AdminMemberStatisticsService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/AdminMemberStatisticsService.kt @@ -58,6 +58,10 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics startDate = startDateTime, endDate = endDateTime ) + val totalSignUpLineCount = repository.getTotalSignUpLineCount( + startDate = startDateTime, + endDate = endDateTime + ) val totalAuthCount = repository.getTotalAuthCount(startDate = startDateTime, endDate = endDateTime) val totalSignOutCount = repository.getTotalSignOutCount(startDate = startDateTime, endDate = endDateTime) val totalPaymentMemberCount = repository.getPaymentMemberCount(startDate = startDateTime, endDate = endDateTime) @@ -92,6 +96,11 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics endDate = endDateTime ).associateBy({ it.date }, { it.memberCount }) + val signUpLineCountInRange = repository.getSignUpLineCountInRange( + startDate = startDateTime, + endDate = endDateTime + ).associateBy({ it.date }, { it.memberCount }) + val authCountInRange = repository.getAuthCountInRange( startDate = startDateTime, endDate = endDateTime @@ -121,6 +130,7 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics signUpEmailCount = signUpEmailCountInRange[date] ?: 0, signUpKakaoCount = signUpKakaoCountInRange[date] ?: 0, signUpGoogleCount = signUpGoogleCountInRange[date] ?: 0, + signUpLineCount = signUpLineCountInRange[date] ?: 0, signOutCount = signOutCountInRange[date] ?: 0, paymentMemberCount = paymentMemberCountInRangeMap[date] ?: 0 ) @@ -134,6 +144,7 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics totalSignUpEmailCount = totalSignUpEmailCount, totalSignUpKakaoCount = totalSignUpKakaoCount, totalSignUpGoogleCount = totalSignUpGoogleCount, + totalSignUpLineCount = totalSignUpLineCount, totalSignOutCount = totalSignOutCount, totalPaymentMemberCount = totalPaymentMemberCount, items = items diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/GetMemberStatisticsResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/GetMemberStatisticsResponse.kt index d2a6faac..1558d86f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/GetMemberStatisticsResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/statistics/member/GetMemberStatisticsResponse.kt @@ -7,6 +7,7 @@ data class GetMemberStatisticsResponse( val totalSignUpEmailCount: Int, val totalSignUpKakaoCount: Int, val totalSignUpGoogleCount: Int, + val totalSignUpLineCount: Int, val totalSignOutCount: Int, val totalPaymentMemberCount: Int, val items: List @@ -19,6 +20,7 @@ data class GetMemberStatisticsItem( val signUpEmailCount: Int, val signUpKakaoCount: Int, val signUpGoogleCount: Int, + val signUpLineCount: Int, val signOutCount: Int, val paymentMemberCount: Int ) From 96513eef6a70341a81160c3b0d4b04500a74aab1 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Feb 2026 14:44:07 +0900 Subject: [PATCH 04/10] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=A3=B8=20?= =?UTF-8?q?=EC=84=B1=EB=B3=84=20=EC=A0=9C=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 라이브룸 생성/수정 요청에 genderRestriction 필드 추가 라이브룸 상세 응답에 genderRestriction 필드 추가 --- .../kr/co/vividnext/sodalive/fcm/FcmEvent.kt | 10 +- .../sodalive/i18n/SodaMessageSource.kt | 5 + .../live/room/CreateLiveRoomRequest.kt | 3 +- .../live/room/EditLiveRoomInfoRequest.kt | 3 +- .../vividnext/sodalive/live/room/LiveRoom.kt | 8 +- .../sodalive/live/room/LiveRoomRepository.kt | 95 +++++++++++++++++-- .../sodalive/live/room/LiveRoomService.kt | 62 ++++++++---- .../live/room/detail/GetRoomDetailResponse.kt | 2 + .../kr/co/vividnext/sodalive/member/Member.kt | 17 ++++ .../sodalive/member/MemberRepository.kt | 52 +++++++++- 10 files changed, 219 insertions(+), 38 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmEvent.kt b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmEvent.kt index d054ed3e..bbf0b7f2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmEvent.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmEvent.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.fcm import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository import kr.co.vividnext.sodalive.i18n.Lang import kr.co.vividnext.sodalive.i18n.SodaMessageSource +import kr.co.vividnext.sodalive.live.room.GenderRestriction import kr.co.vividnext.sodalive.member.MemberRepository import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Component @@ -33,7 +34,8 @@ class FcmEvent( val auditionId: Long? = null, val commentParentId: Long? = null, val myMemberId: Long? = null, - val isAvailableJoinCreator: Boolean? = null + val isAvailableJoinCreator: Boolean? = null, + val genderRestriction: GenderRestriction? = null ) @Component @@ -69,7 +71,8 @@ class FcmSendListener( val pushTokens = memberRepository.getCreateLiveRoomNotificationRecipientPushTokens( creatorId = fcmEvent.creatorId!!, isAuth = fcmEvent.isAuth ?: false, - isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false + isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false, + genderRestriction = fcmEvent.genderRestriction ) sendPush(pushTokens, fcmEvent, roomId = fcmEvent.roomId) } @@ -79,7 +82,8 @@ class FcmSendListener( creatorId = fcmEvent.creatorId!!, roomId = fcmEvent.roomId!!, isAuth = fcmEvent.isAuth ?: false, - isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false + isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false, + genderRestriction = fcmEvent.genderRestriction ) sendPush(pushTokens, fcmEvent, roomId = fcmEvent.roomId) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt index a95615d7..7f8e3c5e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt @@ -1448,6 +1448,11 @@ class SodaMessageSource { ) private val liveRoomMessages = mapOf( + "live.room.gender_restricted" to mapOf( + Lang.KO to "입장 가능한 성별이 아닙니다.", + Lang.EN to "Your gender is not allowed to enter this room.", + Lang.JA to "入場可能な性別ではありません。" + ), "live.room.max_reservations" to mapOf( Lang.KO to "예약 라이브는 최대 3개까지 가능합니다.", Lang.EN to "You can reserve up to 3 live sessions.", diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateLiveRoomRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateLiveRoomRequest.kt index 08e1387a..3d8f56c8 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateLiveRoomRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateLiveRoomRequest.kt @@ -15,5 +15,6 @@ data class CreateLiveRoomRequest( val menuPanId: Long = 0, val menuPan: String = "", val isActiveMenuPan: Boolean = false, - val isAvailableJoinCreator: Boolean = true + val isAvailableJoinCreator: Boolean = true, + val genderRestriction: GenderRestriction = GenderRestriction.ALL ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/EditLiveRoomInfoRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/EditLiveRoomInfoRequest.kt index b6de7ad5..bf880b4d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/EditLiveRoomInfoRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/EditLiveRoomInfoRequest.kt @@ -9,5 +9,6 @@ data class EditLiveRoomInfoRequest( val menuPanId: Long = 0, val menuPan: String = "", val isActiveMenuPan: Boolean? = null, - val isAdult: Boolean? = null + val isAdult: Boolean? = null, + val genderRestriction: GenderRestriction? = null ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt index b65b6d00..2ca56c9c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt @@ -32,7 +32,9 @@ data class LiveRoom( @Enumerated(value = EnumType.STRING) val type: LiveRoomType = LiveRoomType.OPEN, @Column(nullable = true) - var password: String? = null + var password: String? = null, + @Enumerated(value = EnumType.STRING) + var genderRestriction: GenderRestriction = GenderRestriction.ALL ) : BaseEntity() { @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) @@ -67,3 +69,7 @@ enum class LiveRoomType { enum class LiveRoomStatus { NOW, RESERVATION } + +enum class GenderRestriction { + ALL, MALE_ONLY, FEMALE_ONLY +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt index da71f490..435e568e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt @@ -14,8 +14,10 @@ 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 org.springframework.beans.factory.annotation.Value import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -32,7 +34,8 @@ interface LiveRoomQueryRepository { timezone: String, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List fun getLiveRoomListReservationWithDate( @@ -41,14 +44,16 @@ interface LiveRoomQueryRepository { limit: Long, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List fun getLiveRoomListReservationWithoutDate( timezone: String, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List fun getLiveRoom(id: Long): LiveRoom? @@ -76,7 +81,8 @@ class LiveRoomQueryRepositoryImpl( timezone: String, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List { var where = liveRoom.channelName.isNotNull .and(liveRoom.channelName.isNotEmpty) @@ -94,10 +100,33 @@ class LiveRoomQueryRepositoryImpl( ) } - return queryFactory + if (effectiveGender != null && effectiveGender != Gender.NONE) { + where = when (effectiveGender) { + Gender.MALE -> where.and( + liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) + ) + Gender.FEMALE -> where.and( + liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) + ) + Gender.NONE -> where + } + } + + var select = queryFactory .selectFrom(liveRoom) .innerJoin(liveRoom.member, member) .leftJoin(quarterLiveRankings).on(liveRoom.id.eq(quarterLiveRankings.roomId)) + + if (memberId != null) { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.blockedMember.id.eq(memberId)) + .and(blockMember.isActive.isTrue) + + select = select.leftJoin(blockMember).on(blockMemberCondition) + where = where.and(blockMember.id.isNull) + } + + return select .where(where) .offset(offset) .limit(limit) @@ -116,7 +145,8 @@ class LiveRoomQueryRepositoryImpl( limit: Long, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List { var where = liveRoom.beginDateTime.goe(date) .and(liveRoom.beginDateTime.lt(date.plusDays(1))) @@ -138,9 +168,32 @@ class LiveRoomQueryRepositoryImpl( ) } - return queryFactory + if (effectiveGender != null && effectiveGender != Gender.NONE) { + where = when (effectiveGender) { + Gender.MALE -> where.and( + liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) + ) + Gender.FEMALE -> where.and( + liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) + ) + Gender.NONE -> where + } + } + + var select = queryFactory .selectFrom(liveRoom) .innerJoin(liveRoom.member, member) + + if (memberId != null) { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.blockedMember.id.eq(memberId)) + .and(blockMember.isActive.isTrue) + + select = select.leftJoin(blockMember).on(blockMemberCondition) + where = where.and(blockMember.id.isNull) + } + + return select .where(where) .offset(offset) .limit(limit) @@ -152,7 +205,8 @@ class LiveRoomQueryRepositoryImpl( timezone: String, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List { var where = liveRoom.beginDateTime.gt( LocalDateTime.now() @@ -178,6 +232,18 @@ class LiveRoomQueryRepositoryImpl( ) } + if (effectiveGender != null && effectiveGender != Gender.NONE) { + where = when (effectiveGender) { + Gender.MALE -> where.and( + liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) + ) + Gender.FEMALE -> where.and( + liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) + ) + Gender.NONE -> where + } + } + val orderBy = if (memberId != null) { listOf( CaseBuilder() @@ -190,10 +256,21 @@ class LiveRoomQueryRepositoryImpl( listOf(liveRoom.beginDateTime.asc()) } - return queryFactory + var select = queryFactory .selectFrom(liveRoom) .innerJoin(liveRoom.member, member) .limit(10) + + if (memberId != null) { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.blockedMember.id.eq(memberId)) + .and(blockMember.isActive.isTrue) + + select = select.leftJoin(blockMember).on(blockMemberCondition) + where = where.and(blockMember.id.isNull) + } + + return select .where(where) .orderBy(*orderBy.toTypedArray()) .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 d7178263..6938f1e4 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 @@ -193,13 +193,21 @@ class LiveRoomService( member: Member?, timezone: String ): List { + val effectiveGender = member?.let { + if (it.auth != null) { + if (it.auth!!.gender == 1) Gender.MALE else Gender.FEMALE + } else { + it.gender + } + } val roomList = if (status == LiveRoomStatus.NOW) { getLiveRoomListNow( pageable, timezone, memberId = member?.id, isCreator = member?.role == MemberRole.CREATOR, - isAdult = member?.auth != null && isAdultContentVisible + isAdult = member?.auth != null && isAdultContentVisible, + effectiveGender = effectiveGender ) } else if (dateString != null) { getLiveRoomListReservationWithDate( @@ -208,14 +216,16 @@ class LiveRoomService( timezone, memberId = member?.id, isCreator = member?.role == MemberRole.CREATOR, - isAdult = member?.auth != null && isAdultContentVisible + isAdult = member?.auth != null && isAdultContentVisible, + effectiveGender = effectiveGender ) } else { getLiveRoomListReservationWithoutDate( timezone, isCreator = member?.role == MemberRole.CREATOR, memberId = member?.id, - isAdult = member?.auth != null && isAdultContentVisible + isAdult = member?.auth != null && isAdultContentVisible, + effectiveGender = effectiveGender ) } @@ -223,13 +233,6 @@ class LiveRoomService( val languageTagByMemberId = buildLanguageTagMap(creatorIds) return roomList - .filter { - if (member?.id != null) { - !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) - } else { - true - } - } .map { val roomInfo = roomInfoRepository.findByIdOrNull(it.id!!) @@ -296,7 +299,8 @@ class LiveRoomService( timezone: String, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List { return repository.getLiveRoomListNow( offset = pageable.offset, @@ -304,7 +308,8 @@ class LiveRoomService( timezone = timezone, memberId = memberId, isCreator = isCreator, - isAdult = isAdult + isAdult = isAdult, + effectiveGender = effectiveGender ) } @@ -314,7 +319,8 @@ class LiveRoomService( timezone: String, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List { val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") val date = LocalDate.parse(dateString, dateTimeFormatter).atStartOfDay() @@ -328,7 +334,8 @@ class LiveRoomService( limit = pageable.pageSize.toLong(), memberId = memberId, isCreator = isCreator, - isAdult = isAdult + isAdult = isAdult, + effectiveGender = effectiveGender ) } @@ -336,9 +343,10 @@ class LiveRoomService( timezone: String, memberId: Long?, isCreator: Boolean, - isAdult: Boolean + isAdult: Boolean, + effectiveGender: Gender? ): List { - return repository.getLiveRoomListReservationWithoutDate(timezone, memberId, isCreator, isAdult) + return repository.getLiveRoomListReservationWithoutDate(timezone, memberId, isCreator, isAdult, effectiveGender) } @Transactional @@ -400,7 +408,8 @@ class LiveRoomService( }, type = request.type, password = request.password, - isAvailableJoinCreator = request.isAvailableJoinCreator + isAvailableJoinCreator = request.isAvailableJoinCreator, + genderRestriction = request.genderRestriction ) room.member = member @@ -479,7 +488,8 @@ class LiveRoomService( isAuth = createdRoom.isAdult, isAvailableJoinCreator = createdRoom.isAvailableJoinCreator, roomId = createdRoom.id, - creatorId = createdRoom.member!!.id + creatorId = createdRoom.member!!.id, + genderRestriction = createdRoom.genderRestriction ) ) @@ -494,6 +504,10 @@ class LiveRoomService( throw SodaException(messageKey = "live.room.adult_verification_required") } + if (!member.canEnter(room.genderRestriction) && room.member!!.id!! != member.id!!) { + throw SodaException(messageKey = "live.room.gender_restricted") + } + val beginDateTime = room.beginDateTime .atZone(ZoneId.of("UTC")) .withZoneSameInstant(ZoneId.of(timezone)) @@ -521,6 +535,7 @@ class LiveRoomService( isPaid = false, isAdult = room.isAdult, isPrivateRoom = room.type == LiveRoomType.PRIVATE, + genderRestriction = room.genderRestriction, password = room.password ) response.manager = GetRoomDetailManager(room.member!!, cloudFrontHost = cloudFrontHost) @@ -640,7 +655,8 @@ class LiveRoomService( isAuth = room.isAdult, isAvailableJoinCreator = room.isAvailableJoinCreator, roomId = room.id, - creatorId = room.member!!.id + creatorId = room.member!!.id, + genderRestriction = room.genderRestriction ) ) } @@ -739,6 +755,10 @@ class LiveRoomService( ) } + if (room.member!!.id!! != member.id!! && !member.canEnter(room.genderRestriction)) { + throw SodaException(messageKey = "live.room.gender_restricted") + } + val lock = getOrCreateLock(memberId = member.id!!) lock.write { var roomInfo = roomInfoRepository.findByIdOrNull(request.roomId) @@ -853,6 +873,10 @@ class LiveRoomService( room.isAdult = request.isAdult } + if (request.genderRestriction != null) { + room.genderRestriction = request.genderRestriction + } + if (request.isActiveMenuPan != null) { if (request.isActiveMenuPan) { if (request.menuPanId > 0) { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/detail/GetRoomDetailResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/detail/GetRoomDetailResponse.kt index 1ca4ba46..a97b0460 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/detail/GetRoomDetailResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/detail/GetRoomDetailResponse.kt @@ -1,5 +1,6 @@ package kr.co.vividnext.sodalive.live.room.detail +import kr.co.vividnext.sodalive.live.room.GenderRestriction import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.MemberRole @@ -11,6 +12,7 @@ data class GetRoomDetailResponse( var isPaid: Boolean, val isAdult: Boolean, val isPrivateRoom: Boolean, + val genderRestriction: GenderRestriction, val password: String?, val tags: List, val channelName: String?, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt index 0a588b94..2eaa42f3 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.member import kr.co.vividnext.sodalive.common.BaseEntity import kr.co.vividnext.sodalive.explorer.GetExplorerSectionCreatorResponse +import kr.co.vividnext.sodalive.live.room.GenderRestriction import kr.co.vividnext.sodalive.member.auth.Auth import kr.co.vividnext.sodalive.member.following.CreatorFollowing import kr.co.vividnext.sodalive.member.notification.MemberNotification @@ -148,6 +149,22 @@ data class Member( follow = follow ) } + + fun canEnter(restriction: GenderRestriction): Boolean { + val effectiveGender = if (auth != null) { + if (auth!!.gender == 1) Gender.MALE else Gender.FEMALE + } else { + gender + } + + if (effectiveGender == Gender.NONE) return true + + return when (restriction) { + GenderRestriction.ALL -> true + GenderRestriction.MALE_ONLY -> effectiveGender == Gender.MALE + GenderRestriction.FEMALE_ONLY -> effectiveGender == Gender.FEMALE + } + } } enum class Gender { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt index 7b09a29e..a83afc6e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt @@ -5,6 +5,7 @@ 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.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.member.QMember.member import kr.co.vividnext.sodalive.member.auth.QAuth.auth @@ -35,14 +36,16 @@ interface MemberQueryRepository { fun getCreateLiveRoomNotificationRecipientPushTokens( creatorId: Long, isAuth: Boolean, - isAvailableJoinCreator: Boolean + isAvailableJoinCreator: Boolean, + genderRestriction: GenderRestriction? = null ): List fun getStartLiveRoomNotificationRecipientPushTokens( creatorId: Long, roomId: Long, isAuth: Boolean, - isAvailableJoinCreator: Boolean + isAvailableJoinCreator: Boolean, + genderRestriction: GenderRestriction? = null ): List fun getUploadContentNotificationRecipientPushTokens( @@ -132,7 +135,8 @@ class MemberQueryRepositoryImpl( override fun getCreateLiveRoomNotificationRecipientPushTokens( creatorId: Long, isAuth: Boolean, - isAvailableJoinCreator: Boolean + isAvailableJoinCreator: Boolean, + genderRestriction: GenderRestriction? ): List { val member = QMember.member val creator = QMember.member @@ -158,6 +162,10 @@ class MemberQueryRepositoryImpl( where = where.and(creatorFollowing.member.role.ne(MemberRole.CREATOR)) } + if (genderRestriction != null && genderRestriction != GenderRestriction.ALL) { + where = where.and(getGenderCondition(genderRestriction)) + } + return queryFactory .select( QPushTokenInfo( @@ -180,7 +188,8 @@ class MemberQueryRepositoryImpl( creatorId: Long, roomId: Long, isAuth: Boolean, - isAvailableJoinCreator: Boolean + isAvailableJoinCreator: Boolean, + genderRestriction: GenderRestriction? ): List { val member = QMember.member val creator = QMember.member @@ -206,6 +215,10 @@ class MemberQueryRepositoryImpl( where = where.and(creatorFollowing.member.role.ne(MemberRole.CREATOR)) } + if (genderRestriction != null && genderRestriction != GenderRestriction.ALL) { + where = where.and(getGenderCondition(genderRestriction)) + } + val followingMemberPushToken = queryFactory .select( QPushTokenInfo( @@ -237,6 +250,10 @@ class MemberQueryRepositoryImpl( where = where.and(auth.isNotNull) } + if (genderRestriction != null && genderRestriction != GenderRestriction.ALL) { + where = where.and(getGenderCondition(genderRestriction, liveReservation.member)) + } + val reservationMemberPushToken = queryFactory .select( QPushTokenInfo( @@ -256,6 +273,33 @@ class MemberQueryRepositoryImpl( return (followingMemberPushToken + reservationMemberPushToken).distinctBy { it.token } } + private fun getGenderCondition( + genderRestriction: GenderRestriction, + qMember: QMember = member + ) = when (genderRestriction) { + GenderRestriction.MALE_ONLY -> { + auth.isNotNull.and(auth.gender.eq(1)) + .or( + auth.isNull.and( + qMember.gender.eq(Gender.MALE) + .or(qMember.gender.eq(Gender.NONE)) + ) + ) + } + + GenderRestriction.FEMALE_ONLY -> { + auth.isNotNull.and(auth.gender.eq(0)) + .or( + auth.isNull.and( + qMember.gender.eq(Gender.FEMALE) + .or(qMember.gender.eq(Gender.NONE)) + ) + ) + } + + else -> null + } + override fun getUploadContentNotificationRecipientPushTokens( creatorId: Long, isAuth: Boolean From 04a4b362da27dca3c0f47f6c8cf06b169fbf5243 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Feb 2026 17:22:09 +0900 Subject: [PATCH 05/10] =?UTF-8?q?=EB=B3=B8=EC=9D=B8=20=EB=B0=A9=20?= =?UTF-8?q?=EC=84=B1=EB=B3=84=20=EC=A0=9C=ED=95=9C=20=EC=98=88=EC=99=B8=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/live/room/LiveRoomRepository.kt | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt index 435e568e..9fbac274 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt @@ -101,14 +101,15 @@ class LiveRoomQueryRepositoryImpl( } if (effectiveGender != null && effectiveGender != Gender.NONE) { - where = when (effectiveGender) { - Gender.MALE -> where.and( - liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) - ) - Gender.FEMALE -> where.and( - liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) - ) - Gender.NONE -> where + val genderCondition = when (effectiveGender) { + Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) + Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) + Gender.NONE -> liveRoom.genderRestriction.isNotNull + } + where = if (memberId != null) { + where.and(genderCondition.or(liveRoom.member.id.eq(memberId))) + } else { + where.and(genderCondition) } } @@ -169,14 +170,15 @@ class LiveRoomQueryRepositoryImpl( } if (effectiveGender != null && effectiveGender != Gender.NONE) { - where = when (effectiveGender) { - Gender.MALE -> where.and( - liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) - ) - Gender.FEMALE -> where.and( - liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) - ) - Gender.NONE -> where + val genderCondition = when (effectiveGender) { + Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) + Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) + Gender.NONE -> liveRoom.genderRestriction.isNotNull + } + where = if (memberId != null) { + where.and(genderCondition.or(liveRoom.member.id.eq(memberId))) + } else { + where.and(genderCondition) } } @@ -233,14 +235,15 @@ class LiveRoomQueryRepositoryImpl( } if (effectiveGender != null && effectiveGender != Gender.NONE) { - where = when (effectiveGender) { - Gender.MALE -> where.and( - liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) - ) - Gender.FEMALE -> where.and( - liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) - ) - Gender.NONE -> where + val genderCondition = when (effectiveGender) { + Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) + Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) + Gender.NONE -> liveRoom.genderRestriction.isNotNull + } + where = if (memberId != null) { + where.and(genderCondition.or(liveRoom.member.id.eq(memberId))) + } else { + where.and(genderCondition) } } From ac5741b9af65350671546ea7bd534a46e9d4560a Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Feb 2026 17:51:05 +0900 Subject: [PATCH 06/10] =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=94=84=EB=A1=9C=ED=95=84=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=20=EC=84=B1=EB=B3=84=20=EC=A0=9C=ED=95=9C=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../explorer/ExplorerQueryRepository.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt index d11e69d6..cce60c55 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt @@ -22,11 +22,13 @@ import kr.co.vividnext.sodalive.explorer.profile.TimeDifferenceResult 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.room.GenderRestriction 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.visit.QLiveRoomVisit.liveRoomVisit +import kr.co.vividnext.sodalive.member.Gender import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.QMember @@ -342,6 +344,21 @@ class ExplorerQueryRepository( .and(liveRoom.cancel.id.isNull) .and(liveRoom.isActive.isTrue) + val effectiveGender = if (userMember.auth != null) { + if (userMember.auth!!.gender == 1) Gender.MALE else Gender.FEMALE + } else { + userMember.gender + } + + if (effectiveGender != Gender.NONE) { + val genderCondition = when (effectiveGender) { + Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY) + Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY) + Gender.NONE -> liveRoom.genderRestriction.isNotNull + } + where = where.and(genderCondition.or(liveRoom.member.id.eq(userMember.id))) + } + if (userMember.auth == null) { where = where.and(liveRoom.isAdult.isFalse) } From 5eca3f770c802b9c7000a0ba78268ccad77a06fd Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 2 Feb 2026 18:08:09 +0900 Subject: [PATCH 07/10] =?UTF-8?q?=EC=B5=9C=EA=B7=BC=20=EB=B0=A9=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=84=B1=EB=B3=84=20=EC=A0=9C=ED=95=9C=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vividnext/sodalive/live/room/GetRecentRoomInfoResponse.kt | 3 ++- .../kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetRecentRoomInfoResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetRecentRoomInfoResponse.kt index 4a076d23..87fa0a6a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetRecentRoomInfoResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetRecentRoomInfoResponse.kt @@ -5,5 +5,6 @@ data class GetRecentRoomInfoResponse( val notice: String, var coverImageUrl: String, val coverImagePath: String, - val numberOfPeople: Int + val numberOfPeople: Int, + val genderRestriction: GenderRestriction ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt index 9fbac274..c64de8ab 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt @@ -311,7 +311,8 @@ class LiveRoomQueryRepositoryImpl( liveRoom.notice, liveRoom.coverImage.prepend("/").prepend(cloudFrontHost), liveRoom.coverImage, - liveRoom.numberOfPeople + liveRoom.numberOfPeople, + liveRoom.genderRestriction ) ) .from(liveRoom) From f1f80ae386a13b4217f742256f83ad2594ae8b89 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 3 Feb 2026 15:48:42 +0900 Subject: [PATCH 08/10] =?UTF-8?q?=ED=9B=84=EC=9B=90=EB=9E=AD=ED=82=B9=20?= =?UTF-8?q?=EA=B8=B0=EA=B0=84=20=EC=84=A0=ED=83=9D=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로필 업데이트에 후원랭킹 기간 선택을 추가하고 프로필 후원랭킹 조회가 선택한 기간을 사용한다 --- .../sodalive/explorer/ExplorerService.kt | 16 +++++-- .../CreatorDonationRankingQueryRepository.kt | 35 +++++++++++++- .../profile/CreatorDonationRankingService.kt | 47 ++++++++++++++++--- .../kr/co/vividnext/sodalive/member/Member.kt | 7 +++ .../sodalive/member/MemberService.kt | 4 ++ .../sodalive/member/ProfileUpdateRequest.kt | 1 + 6 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt index bd003ee8..6d0a4b62 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt @@ -24,6 +24,7 @@ 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.room.detail.GetRoomDetailUser +import kr.co.vividnext.sodalive.member.DonationRankingPeriod import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.MemberService @@ -215,6 +216,7 @@ class ExplorerService( val notificationUserIds = queryRepository.getNotificationUserIds(creatorId) val creatorFollowing = queryRepository.getCreatorFollowing(creatorId = creatorId, memberId = member.id!!) val notificationRecipientCount = notificationUserIds.size + val donationRankingPeriod = creatorAccount.donationRankingPeriod ?: DonationRankingPeriod.CUMULATIVE // 후원랭킹 val memberDonationRanking = if ( @@ -223,7 +225,8 @@ class ExplorerService( donationRankingService.getMemberDonationRanking( creatorId = creatorId, limit = 10, - withDonationCan = creatorId == member.id!! + withDonationCan = creatorId == member.id!!, + period = donationRankingPeriod ) } else { listOf() @@ -399,16 +402,23 @@ class ExplorerService( pageable: Pageable, member: Member ): GetDonationAllResponse { + val creatorAccount = queryRepository.getMember(creatorId) + ?: throw SodaException(messageKey = "member.validation.user_not_found") + val donationRankingPeriod = creatorAccount.donationRankingPeriod ?: DonationRankingPeriod.CUMULATIVE val currentDate = LocalDate.now().atTime(0, 0, 0) val firstDayOfLastWeek = currentDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).minusDays(7) val firstDayOfMonth = currentDate.with(TemporalAdjusters.firstDayOfMonth()) - val donationMemberTotal = donationRankingService.getMemberDonationRankingTotal(creatorId) + val donationMemberTotal = donationRankingService.getMemberDonationRankingTotal( + creatorId, + donationRankingPeriod + ) val donationRanking = donationRankingService.getMemberDonationRanking( creatorId = creatorId, offset = pageable.offset, limit = pageable.pageSize.toLong(), - withDonationCan = creatorId == member.id!! + withDonationCan = creatorId == member.id!!, + period = donationRankingPeriod ) return GetDonationAllResponse( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingQueryRepository.kt index 7ab5c2fa..51e02323 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingQueryRepository.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.explorer.profile import com.fasterxml.jackson.annotation.JsonProperty +import com.querydsl.core.BooleanBuilder import com.querydsl.core.annotations.QueryProjection import com.querydsl.jpa.impl.JPAQueryFactory import kr.co.vividnext.sodalive.can.use.CanUsage @@ -8,13 +9,17 @@ import kr.co.vividnext.sodalive.can.use.QUseCan.useCan import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate import kr.co.vividnext.sodalive.member.QMember.member import org.springframework.stereotype.Repository +import java.time.LocalDateTime +import java.time.ZoneId @Repository class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFactory) { fun getMemberDonationRanking( creatorId: Long, offset: Long, - limit: Long + limit: Long, + startDate: LocalDateTime? = null, + endDate: LocalDateTime? = null ): List { val donationCan = useCan.rewardCan.add(useCan.can).sum() return queryFactory @@ -38,6 +43,7 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa .or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE)) .or(useCan.canUsage.eq(CanUsage.LIVE)) ) + .and(buildDateRangeCondition(startDate, endDate)) ) .offset(offset) .limit(limit) @@ -46,7 +52,11 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa .fetch() } - fun getMemberDonationRankingTotal(creatorId: Long): Int { + fun getMemberDonationRankingTotal( + creatorId: Long, + startDate: LocalDateTime? = null, + endDate: LocalDateTime? = null + ): Int { return queryFactory .select(member.id) .from(useCanCalculate) @@ -61,11 +71,32 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa .or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE)) .or(useCan.canUsage.eq(CanUsage.LIVE)) ) + .and(buildDateRangeCondition(startDate, endDate)) ) .groupBy(member.id) .fetch() .size } + + private fun buildDateRangeCondition( + startDate: LocalDateTime?, + endDate: LocalDateTime? + ): BooleanBuilder { + val condition = BooleanBuilder() + if (startDate != null && endDate != null) { + val startUtc = startDate + .atZone(ZoneId.of("Asia/Seoul")) + .withZoneSameInstant(ZoneId.of("UTC")) + .toLocalDateTime() + val endUtc = endDate + .atZone(ZoneId.of("Asia/Seoul")) + .withZoneSameInstant(ZoneId.of("UTC")) + .toLocalDateTime() + condition.and(useCanCalculate.createdAt.goe(startUtc)) + condition.and(useCanCalculate.createdAt.lt(endUtc)) + } + return condition + } } data class DonationRankingProjection @QueryProjection constructor( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingService.kt index bb8bf903..5505321b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingService.kt @@ -2,11 +2,13 @@ package kr.co.vividnext.sodalive.explorer.profile import kr.co.vividnext.sodalive.explorer.MemberDonationRankingListResponse import kr.co.vividnext.sodalive.explorer.MemberDonationRankingResponse +import kr.co.vividnext.sodalive.member.DonationRankingPeriod import org.springframework.beans.factory.annotation.Value import org.springframework.data.redis.core.RedisTemplate import org.springframework.stereotype.Service import java.time.DayOfWeek import java.time.Duration +import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime import java.time.temporal.ChronoUnit @@ -20,14 +22,22 @@ class CreatorDonationRankingService( @Value("\${cloud.aws.cloud-front.host}") private val imageHost: String ) { - fun getMemberDonationRankingTotal(creatorId: Long): Int { - val cacheKey = "creator_donation_ranking_member_total_v2:$creatorId" + fun getMemberDonationRankingTotal( + creatorId: Long, + period: DonationRankingPeriod = DonationRankingPeriod.CUMULATIVE + ): Int { + val cacheKey = "creator_donation_ranking_member_total_v2:$creatorId:$period" val cachedTotal = redisTemplate.opsForValue().get(cacheKey) as? Int if (cachedTotal != null) { return cachedTotal } - val total = repository.getMemberDonationRankingTotal(creatorId) + val weeklyDateRange = getWeeklyDateRange(period) + val total = if (weeklyDateRange == null) { + repository.getMemberDonationRankingTotal(creatorId) + } else { + repository.getMemberDonationRankingTotal(creatorId, weeklyDateRange.first, weeklyDateRange.second) + } val now = LocalDateTime.now() val nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).with(LocalTime.MIN) @@ -46,15 +56,27 @@ class CreatorDonationRankingService( creatorId: Long, offset: Long = 0, limit: Long = 10, - withDonationCan: Boolean + withDonationCan: Boolean, + period: DonationRankingPeriod = DonationRankingPeriod.CUMULATIVE ): List { - val cacheKey = "creator_donation_ranking_v2:$creatorId:$offset:$limit:$withDonationCan" + val cacheKey = "creator_donation_ranking_v2:$creatorId:$period:$offset:$limit:$withDonationCan" val cachedData = redisTemplate.opsForValue().get(cacheKey) as? MemberDonationRankingListResponse if (cachedData != null) { return cachedData.rankings } - val memberDonationRanking = repository.getMemberDonationRanking(creatorId, offset, limit) + val weeklyDateRange = getWeeklyDateRange(period) + val memberDonationRanking = if (weeklyDateRange == null) { + repository.getMemberDonationRanking(creatorId, offset, limit) + } else { + repository.getMemberDonationRanking( + creatorId, + offset, + limit, + weeklyDateRange.first, + weeklyDateRange.second + ) + } val result = memberDonationRanking.map { MemberDonationRankingResponse( @@ -77,4 +99,17 @@ class CreatorDonationRankingService( return result } + + private fun getWeeklyDateRange(period: DonationRankingPeriod): Pair? { + if (period != DonationRankingPeriod.WEEKLY) { + return null + } + + val currentDate = LocalDate.now() + val lastWeekMonday = currentDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).minusWeeks(1) + val startDate = lastWeekMonday.atStartOfDay() + val endDate = startDate.plusDays(7) + + return startDate to endDate + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt index 2eaa42f3..75ff126f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt @@ -47,6 +47,9 @@ data class Member( var isVisibleDonationRank: Boolean = true, + @Enumerated(value = EnumType.STRING) + var donationRankingPeriod: DonationRankingPeriod? = DonationRankingPeriod.CUMULATIVE, + var isActive: Boolean = true, var container: String = "web", @@ -178,3 +181,7 @@ enum class MemberRole { enum class MemberProvider { EMAIL, KAKAO, GOOGLE, APPLE, LINE } + +enum class DonationRankingPeriod { + WEEKLY, CUMULATIVE +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt index 02684ef2..e92fdc3a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt @@ -726,6 +726,10 @@ class MemberService( member.isVisibleDonationRank = profileUpdateRequest.isVisibleDonationRank } + if (profileUpdateRequest.donationRankingPeriod != null) { + member.donationRankingPeriod = profileUpdateRequest.donationRankingPeriod + } + return ProfileResponse(member, cloudFrontHost, profileUpdateRequest.container) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileUpdateRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileUpdateRequest.kt index 806f00d8..4f5d97de 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileUpdateRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/ProfileUpdateRequest.kt @@ -14,5 +14,6 @@ data class ProfileUpdateRequest( val websiteUrl: String? = null, val blogUrl: String? = null, val isVisibleDonationRank: Boolean? = null, + val donationRankingPeriod: DonationRankingPeriod? = null, val container: String ) From 3cabc9de95791181c8409655414499a92ec81f29 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 3 Feb 2026 16:05:26 +0900 Subject: [PATCH 09/10] =?UTF-8?q?=ED=9B=84=EC=9B=90=EB=9E=AD=ED=82=B9=20?= =?UTF-8?q?=EA=B8=B0=EA=B0=84=20=EC=84=A0=ED=83=9D=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 크리에이터 본인 조회 시 후원랭킹 기간을 선택하도록 period 파라미터를 제공한다. --- .../sodalive/explorer/ExplorerController.kt | 4 +++- .../vividnext/sodalive/explorer/ExplorerService.kt | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerController.kt index 21a4c8e1..46fdeae2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerController.kt @@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest import kr.co.vividnext.sodalive.i18n.LangContext import kr.co.vividnext.sodalive.i18n.SodaMessageSource +import kr.co.vividnext.sodalive.member.DonationRankingPeriod import kr.co.vividnext.sodalive.member.Member import org.springframework.data.domain.Pageable import org.springframework.security.access.prepost.PreAuthorize @@ -75,11 +76,12 @@ class ExplorerController( @GetMapping("/profile/{id}/donation-rank") fun getCreatorProfileDonationRanking( @PathVariable("id") creatorId: Long, + @RequestParam("period", required = false) period: DonationRankingPeriod? = null, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, pageable: Pageable ) = run { if (member == null) throw SodaException(messageKey = "common.error.bad_credentials") - ApiResponse.ok(service.getCreatorProfileDonationRanking(creatorId, pageable, member)) + ApiResponse.ok(service.getCreatorProfileDonationRanking(creatorId, period, pageable, member)) } @PostMapping("/profile/cheers") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt index 6d0a4b62..69a8d013 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt @@ -399,26 +399,33 @@ class ExplorerService( fun getCreatorProfileDonationRanking( creatorId: Long, + period: DonationRankingPeriod?, pageable: Pageable, member: Member ): GetDonationAllResponse { val creatorAccount = queryRepository.getMember(creatorId) ?: throw SodaException(messageKey = "member.validation.user_not_found") val donationRankingPeriod = creatorAccount.donationRankingPeriod ?: DonationRankingPeriod.CUMULATIVE + val isCreatorSelf = creatorId == member.id!! + val effectivePeriod = if (isCreatorSelf && period != null) { + period + } else { + donationRankingPeriod + } val currentDate = LocalDate.now().atTime(0, 0, 0) val firstDayOfLastWeek = currentDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).minusDays(7) val firstDayOfMonth = currentDate.with(TemporalAdjusters.firstDayOfMonth()) val donationMemberTotal = donationRankingService.getMemberDonationRankingTotal( creatorId, - donationRankingPeriod + effectivePeriod ) val donationRanking = donationRankingService.getMemberDonationRanking( creatorId = creatorId, offset = pageable.offset, limit = pageable.pageSize.toLong(), - withDonationCan = creatorId == member.id!!, - period = donationRankingPeriod + withDonationCan = isCreatorSelf, + period = effectivePeriod ) return GetDonationAllResponse( From e0024a52abd6bd922a5959f42ce4cb738f919221 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 3 Feb 2026 17:27:49 +0900 Subject: [PATCH 10/10] =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=9B=84=EC=9B=90=EB=9E=AD=ED=82=B9=20=EA=B8=B0?= =?UTF-8?q?=EA=B0=84=20=EC=9D=91=EB=8B=B5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vividnext/sodalive/explorer/ExplorerService.kt | 13 +++++++++---- .../explorer/MemberDonationRankingResponse.kt | 2 ++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt index 69a8d013..d0366c4d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt @@ -429,7 +429,7 @@ class ExplorerService( ) return GetDonationAllResponse( - accumulatedCansToday = if (creatorId == member.id!!) { + accumulatedCansToday = if (isCreatorSelf) { queryRepository.getDonationCoinsDateRange( creatorId, currentDate, @@ -438,7 +438,7 @@ class ExplorerService( } else { 0 }, - accumulatedCansLastWeek = if (creatorId == member.id!!) { + accumulatedCansLastWeek = if (isCreatorSelf) { queryRepository.getDonationCoinsDateRange( creatorId, firstDayOfLastWeek, @@ -447,7 +447,7 @@ class ExplorerService( } else { 0 }, - accumulatedCansThisMonth = if (creatorId == member.id!!) { + accumulatedCansThisMonth = if (isCreatorSelf) { queryRepository.getDonationCoinsDateRange( creatorId, firstDayOfMonth, @@ -456,11 +456,16 @@ class ExplorerService( } else { 0 }, - isVisibleDonationRank = if (creatorId == member.id!!) { + isVisibleDonationRank = if (isCreatorSelf) { queryRepository.getVisibleDonationRank(creatorId) } else { false }, + donationRankingPeriod = if (isCreatorSelf) { + donationRankingPeriod + } else { + null + }, totalCount = donationMemberTotal, userDonationRanking = donationRanking ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/MemberDonationRankingResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/MemberDonationRankingResponse.kt index 53e7dc69..40d28e7e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/MemberDonationRankingResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/MemberDonationRankingResponse.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.explorer import com.fasterxml.jackson.annotation.JsonProperty +import kr.co.vividnext.sodalive.member.DonationRankingPeriod import java.io.Serializable data class GetDonationAllResponse( @@ -8,6 +9,7 @@ data class GetDonationAllResponse( val accumulatedCansLastWeek: Int, val accumulatedCansThisMonth: Int, val isVisibleDonationRank: Boolean, + val donationRankingPeriod: DonationRankingPeriod?, val totalCount: Int, val userDonationRanking: List ) : Serializable