From 59700493eb2c24c15647b98e04218cee254629b8 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 14 Oct 2025 15:35:15 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(explorer):=20=ED=81=AC=EB=A6=AC?= =?UTF-8?q?=EC=97=90=EC=9D=B4=ED=84=B0=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=EC=97=90=20=EC=B5=9C=EC=8B=A0/=EC=B4=9D/=EC=86=8C=EC=9E=A5=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=A0=95=EB=B3=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ExplorerService.getCreatorProfile에서 다음 정보 계산/반환 - 최신 오디오 콘텐츠 1개(`latestContent`) - 전체 콘텐츠 수(`totalContentCount`) - 조회 유저의 소장 콘텐츠 수(`ownedContentCount`) - ExplorerQueryRepository.getOwnedContentCount 추가 - 활성 KEEP 또는 유효한 RENTAL 주문 기준으로 distinct 카운트 - GetCreatorProfileResponse 스키마 확장 - `latestContent`, `totalContentCount`, `ownedContentCount` 필드 추가 - AudioContentService.getLatestCreatorAudioContent 사용해 최신 콘텐츠 조회 로직 보강 - 성인 콘텐츠 노출 여부 및 구매/대여 상태 반영 - OrderRepository의 주문 타입 조회 로직을 활용해 보유/대여 상태 표시 API 응답 필드가 추가되어 프로필 화면 구성 정보를 보강합니다. (호환성 유지) --- .../content/AudioContentRepository.kt | 28 ++++++++++++ .../sodalive/content/AudioContentService.kt | 45 ++++++++++++++++++- .../sodalive/content/order/OrderRepository.kt | 1 + .../explorer/ExplorerQueryRepository.kt | 23 ++++++++++ .../sodalive/explorer/ExplorerService.kt | 26 ++++++++++- .../explorer/GetCreatorProfileResponse.kt | 3 ++ 6 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index 40038d3..0d31cee 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -193,6 +193,11 @@ interface AudioContentQueryRepository { offset: Long = 0, limit: Long = 20 ): List + + fun findLatestContentByCreatorId( + creatorId: Long, + isAdult: Boolean = false + ): AudioContent? } @Repository @@ -1416,4 +1421,27 @@ class AudioContentQueryRepositoryImpl( .limit(limit) .fetch() } + + override fun findLatestContentByCreatorId( + creatorId: Long, + isAdult: Boolean + ): AudioContent? { + var where = audioContent.member.id.eq(creatorId) + .and( + audioContent.isActive.isTrue + .and(audioContent.duration.isNotNull) + .or(audioContent.releaseDate.isNotNull.and(audioContent.duration.isNotNull)) + ) + + if (!isAdult) { + where = where.and(audioContent.isAdult.isFalse) + } + + return queryFactory + .selectFrom(audioContent) + .where(where) + .orderBy(audioContent.releaseDate.desc()) + .limit(1) + .fetchFirst() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index aa5aeb3..3a9e3a3 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -386,7 +386,7 @@ class AudioContentService( // Check if the time difference is greater than 30 seconds (30000 milliseconds) return date2.time - date1.time - } catch (e: Exception) { + } catch (_: Exception) { // Handle invalid time formats or parsing errors return 0 } @@ -749,6 +749,49 @@ class AudioContentService( ) } + fun getLatestCreatorAudioContent( + creatorId: Long, + member: Member, + isAdultContentVisible: Boolean + ): GetAudioContentListItem? { + val isAdult = member.auth != null && isAdultContentVisible + + val audioContent = repository.findLatestContentByCreatorId(creatorId, isAdult) ?: return null + + val commentCount = commentRepository + .totalCountCommentByContentId( + audioContent.id!!, + memberId = member.id!!, + isContentCreator = creatorId == member.id!! + ) + + val likeCount = audioContentLikeRepository + .totalCountAudioContentLike(audioContent.id!!) + + val (isExistsAudioContent, orderType) = orderRepository.isExistOrderedAndOrderType( + memberId = member.id!!, + contentId = audioContent.id!! + ) + + return GetAudioContentListItem( + contentId = audioContent.id!!, + coverImageUrl = "$coverImageHost/${audioContent.coverImage}", + title = audioContent.title, + price = audioContent.price, + themeStr = audioContent.theme!!.theme, + duration = audioContent.duration, + likeCount = likeCount, + commentCount = commentCount, + isPin = false, + isAdult = audioContent.isAdult, + isScheduledToOpen = audioContent.releaseDate != null && audioContent.releaseDate!! >= LocalDateTime.now(), + isRented = isExistsAudioContent && orderType == OrderType.RENTAL, + isOwned = isExistsAudioContent && orderType == OrderType.KEEP, + isSoldOut = audioContent.remaining != null && audioContent.remaining!! <= 0, + isPointAvailable = audioContent.isPointAvailable + ) + } + fun getAudioContentList( creatorId: Long, sortType: SortType, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt index a05ea4e..faa7522 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt @@ -187,6 +187,7 @@ class OrderQueryRepositoryImpl( return queryFactory.select(order.id) .from(order) .where(where) + .distinct() .fetch() .size } 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 a8a55aa..e321409 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt @@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.content.QAudioContent.audioContent +import kr.co.vividnext.sodalive.content.order.QOrder.order import kr.co.vividnext.sodalive.explorer.QCreatorRanking.creatorRanking import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListDto import kr.co.vividnext.sodalive.explorer.follower.QGetFollowerListDto @@ -653,6 +654,28 @@ class ExplorerQueryRepository( .fetchFirst() } + fun getOwnedContentCount(creatorId: Long, memberId: Long): Long { + // 활성 주문 + 대여의 경우 유효기간 내 주문만 포함, 동일 콘텐츠 중복 구매는 1개로 카운트 + return queryFactory + .select(audioContent.id) + .from(order) + .innerJoin(order.audioContent, audioContent) + .where( + order.isActive.isTrue, + order.member.id.eq(memberId), + audioContent.member.id.eq(creatorId), + order.type.eq(kr.co.vividnext.sodalive.content.order.OrderType.KEEP) + .or( + order.type.eq(kr.co.vividnext.sodalive.content.order.OrderType.RENTAL) + .and(order.endDate.after(LocalDateTime.now())) + ) + ) + .distinct() + .fetch() + .size + .toLong() + } + fun getVisibleDonationRank(creatorId: Long): Boolean { return queryFactory .select(member.isVisibleDonationRank) 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 ea3192c..aff691a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt @@ -76,7 +76,7 @@ class ExplorerService( ) } - fun getExplorer(member: Member, growthRankingCreatorsLimit: Long = 20): GetExplorerResponse { + fun getExplorer(member: Member): GetExplorerResponse { val sections = mutableListOf() // 인기 크리에이터 @@ -231,6 +231,27 @@ class ExplorerService( listOf() } + // 크리에이터의 최신 오디오 콘텐츠 1개 + val latestContent = if (isCreator) { + audioContentService.getLatestCreatorAudioContent(creatorId, member, isAdultContentVisible) + } else { + null + } + + // 크리에이터의 전체 콘텐츠 개수 + val totalContentCount = if (isCreator) { + queryRepository.getContentCount(creatorId) ?: 0 + } else { + 0 + } + + // 조회하는 유저가 소장 중인 크리에이터의 콘텐츠 개수 + val ownedContentCount = if (isCreator) { + queryRepository.getOwnedContentCount(creatorId, member.id!!) + } else { + 0 + } + // 공지사항 val notice = if (isCreator) { queryRepository.getNoticeString(creatorId) @@ -311,6 +332,9 @@ class ExplorerService( similarCreatorList = similarCreatorList, liveRoomList = liveRoomList, contentList = contentList, + latestContent = latestContent, + totalContentCount = totalContentCount, + ownedContentCount = ownedContentCount, notice = notice, communityPostList = communityPostList, cheers = cheers, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/GetCreatorProfileResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/GetCreatorProfileResponse.kt index 5e80a77..083d3e2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/GetCreatorProfileResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/GetCreatorProfileResponse.kt @@ -10,6 +10,9 @@ data class GetCreatorProfileResponse( val similarCreatorList: List, val liveRoomList: List, val contentList: List, + val latestContent: GetAudioContentListItem?, + val totalContentCount: Long, + val ownedContentCount: Long, val notice: String, val communityPostList: List, val cheers: GetCheersResponse, -- 2.49.1 From 12cdd25be7342427b2cfc382bb2b34cff9b7606f Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 16 Oct 2025 15:05:23 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat(creator-profile-live):=20LiveRoomRespo?= =?UTF-8?q?nse=EC=97=90=20utc=20=EA=B8=B0=EB=B0=98=EC=9D=98=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=20=EC=8B=9C=EC=9E=91=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/explorer/ExplorerQueryRepository.kt | 15 +++++++++++---- .../sodalive/explorer/ExplorerService.kt | 3 +-- .../sodalive/explorer/LiveRoomResponse.kt | 6 +----- 3 files changed, 13 insertions(+), 11 deletions(-) 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 e321409..4c1bacd 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt @@ -40,6 +40,7 @@ import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter +import java.util.Locale @Repository class ExplorerQueryRepository( @@ -354,7 +355,6 @@ class ExplorerQueryRepository( creatorId: Long, userMember: Member, timezone: String, - limit: Int, offset: Long = 0 ): List { var where = liveRoom.member.id.eq(creatorId) @@ -393,6 +393,14 @@ class ExplorerQueryRepository( val beginDateTime = it.beginDateTime .atZone(ZoneId.of("UTC")) .withZoneSameInstant(ZoneId.of(timezone)) + .format( + DateTimeFormatter + .ofPattern("yyyy년 MM월 dd일 (E) a hh시 mm분") + .withLocale(Locale.KOREAN) + ) + + val beginDateTimeUtc = it.beginDateTime + .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) val isPaid = if (it.channelName != null) { val useCan = queryFactory @@ -416,9 +424,8 @@ class ExplorerQueryRepository( title = it.title, content = it.notice, isPaid = isPaid, - beginDateTime = beginDateTime.format( - DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a") - ), + beginDateTime = beginDateTime, + beginDateTimeUtc = beginDateTimeUtc, isAdult = it.isAdult, price = it.price, channelName = it.channelName, 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 aff691a..7536269 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerService.kt @@ -209,8 +209,7 @@ class ExplorerService( queryRepository.getLiveRoomList( creatorId, userMember = member, - timezone = timezone, - limit = 3 + timezone = timezone ) } else { listOf() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/LiveRoomResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/LiveRoomResponse.kt index d21bd6b..fb04ba5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/LiveRoomResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/LiveRoomResponse.kt @@ -1,16 +1,12 @@ package kr.co.vividnext.sodalive.explorer -data class GetLiveRoomAllResponse( - val totalCount: Int, - val liveRoomList: List -) - data class LiveRoomResponse( val roomId: Long, val title: String, val content: String, val isPaid: Boolean, val beginDateTime: String, + val beginDateTimeUtc: String, val coverImageUrl: String, val isAdult: Boolean, val price: Int, -- 2.49.1