test #350

Merged
klaus merged 2 commits from test into main 2025-10-17 05:46:36 +00:00
7 changed files with 137 additions and 13 deletions

View File

@@ -193,6 +193,11 @@ interface AudioContentQueryRepository {
offset: Long = 0, offset: Long = 0,
limit: Long = 20 limit: Long = 20
): List<GetAudioContentMainItem> ): List<GetAudioContentMainItem>
fun findLatestContentByCreatorId(
creatorId: Long,
isAdult: Boolean = false
): AudioContent?
} }
@Repository @Repository
@@ -1416,4 +1421,27 @@ class AudioContentQueryRepositoryImpl(
.limit(limit) .limit(limit)
.fetch() .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()
}
} }

View File

@@ -386,7 +386,7 @@ class AudioContentService(
// Check if the time difference is greater than 30 seconds (30000 milliseconds) // Check if the time difference is greater than 30 seconds (30000 milliseconds)
return date2.time - date1.time return date2.time - date1.time
} catch (e: Exception) { } catch (_: Exception) {
// Handle invalid time formats or parsing errors // Handle invalid time formats or parsing errors
return 0 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( fun getAudioContentList(
creatorId: Long, creatorId: Long,
sortType: SortType, sortType: SortType,

View File

@@ -187,6 +187,7 @@ class OrderQueryRepositoryImpl(
return queryFactory.select(order.id) return queryFactory.select(order.id)
.from(order) .from(order)
.where(where) .where(where)
.distinct()
.fetch() .fetch()
.size .size
} }

View File

@@ -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.can.use.UseCanCalculateStatus
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent 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.QCreatorRanking.creatorRanking
import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListDto import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListDto
import kr.co.vividnext.sodalive.explorer.follower.QGetFollowerListDto import kr.co.vividnext.sodalive.explorer.follower.QGetFollowerListDto
@@ -39,6 +40,7 @@ import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Locale
@Repository @Repository
class ExplorerQueryRepository( class ExplorerQueryRepository(
@@ -353,7 +355,6 @@ class ExplorerQueryRepository(
creatorId: Long, creatorId: Long,
userMember: Member, userMember: Member,
timezone: String, timezone: String,
limit: Int,
offset: Long = 0 offset: Long = 0
): List<LiveRoomResponse> { ): List<LiveRoomResponse> {
var where = liveRoom.member.id.eq(creatorId) var where = liveRoom.member.id.eq(creatorId)
@@ -392,6 +393,14 @@ class ExplorerQueryRepository(
val beginDateTime = it.beginDateTime val beginDateTime = it.beginDateTime
.atZone(ZoneId.of("UTC")) .atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of(timezone)) .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 isPaid = if (it.channelName != null) {
val useCan = queryFactory val useCan = queryFactory
@@ -415,9 +424,8 @@ class ExplorerQueryRepository(
title = it.title, title = it.title,
content = it.notice, content = it.notice,
isPaid = isPaid, isPaid = isPaid,
beginDateTime = beginDateTime.format( beginDateTime = beginDateTime,
DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a") beginDateTimeUtc = beginDateTimeUtc,
),
isAdult = it.isAdult, isAdult = it.isAdult,
price = it.price, price = it.price,
channelName = it.channelName, channelName = it.channelName,
@@ -653,6 +661,28 @@ class ExplorerQueryRepository(
.fetchFirst() .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 { fun getVisibleDonationRank(creatorId: Long): Boolean {
return queryFactory return queryFactory
.select(member.isVisibleDonationRank) .select(member.isVisibleDonationRank)

View File

@@ -76,7 +76,7 @@ class ExplorerService(
) )
} }
fun getExplorer(member: Member, growthRankingCreatorsLimit: Long = 20): GetExplorerResponse { fun getExplorer(member: Member): GetExplorerResponse {
val sections = mutableListOf<GetExplorerSectionResponse>() val sections = mutableListOf<GetExplorerSectionResponse>()
// 인기 크리에이터 // 인기 크리에이터
@@ -209,8 +209,7 @@ class ExplorerService(
queryRepository.getLiveRoomList( queryRepository.getLiveRoomList(
creatorId, creatorId,
userMember = member, userMember = member,
timezone = timezone, timezone = timezone
limit = 3
) )
} else { } else {
listOf() listOf()
@@ -231,6 +230,27 @@ class ExplorerService(
listOf() 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) { val notice = if (isCreator) {
queryRepository.getNoticeString(creatorId) queryRepository.getNoticeString(creatorId)
@@ -311,6 +331,9 @@ class ExplorerService(
similarCreatorList = similarCreatorList, similarCreatorList = similarCreatorList,
liveRoomList = liveRoomList, liveRoomList = liveRoomList,
contentList = contentList, contentList = contentList,
latestContent = latestContent,
totalContentCount = totalContentCount,
ownedContentCount = ownedContentCount,
notice = notice, notice = notice,
communityPostList = communityPostList, communityPostList = communityPostList,
cheers = cheers, cheers = cheers,

View File

@@ -10,6 +10,9 @@ data class GetCreatorProfileResponse(
val similarCreatorList: List<SimilarCreatorResponse>, val similarCreatorList: List<SimilarCreatorResponse>,
val liveRoomList: List<LiveRoomResponse>, val liveRoomList: List<LiveRoomResponse>,
val contentList: List<GetAudioContentListItem>, val contentList: List<GetAudioContentListItem>,
val latestContent: GetAudioContentListItem?,
val totalContentCount: Long,
val ownedContentCount: Long,
val notice: String, val notice: String,
val communityPostList: List<GetCommunityPostListResponse>, val communityPostList: List<GetCommunityPostListResponse>,
val cheers: GetCheersResponse, val cheers: GetCheersResponse,

View File

@@ -1,16 +1,12 @@
package kr.co.vividnext.sodalive.explorer package kr.co.vividnext.sodalive.explorer
data class GetLiveRoomAllResponse(
val totalCount: Int,
val liveRoomList: List<LiveRoomResponse>
)
data class LiveRoomResponse( data class LiveRoomResponse(
val roomId: Long, val roomId: Long,
val title: String, val title: String,
val content: String, val content: String,
val isPaid: Boolean, val isPaid: Boolean,
val beginDateTime: String, val beginDateTime: String,
val beginDateTimeUtc: String,
val coverImageUrl: String, val coverImageUrl: String,
val isAdult: Boolean, val isAdult: Boolean,
val price: Int, val price: Int,