feat(explorer): 크리에이터 프로필에 최신/총/소장 콘텐츠 정보 추가
- ExplorerService.getCreatorProfile에서 다음 정보 계산/반환 - 최신 오디오 콘텐츠 1개(`latestContent`) - 전체 콘텐츠 수(`totalContentCount`) - 조회 유저의 소장 콘텐츠 수(`ownedContentCount`) - ExplorerQueryRepository.getOwnedContentCount 추가 - 활성 KEEP 또는 유효한 RENTAL 주문 기준으로 distinct 카운트 - GetCreatorProfileResponse 스키마 확장 - `latestContent`, `totalContentCount`, `ownedContentCount` 필드 추가 - AudioContentService.getLatestCreatorAudioContent 사용해 최신 콘텐츠 조회 로직 보강 - 성인 콘텐츠 노출 여부 및 구매/대여 상태 반영 - OrderRepository의 주문 타입 조회 로직을 활용해 보유/대여 상태 표시 API 응답 필드가 추가되어 프로필 화면 구성 정보를 보강합니다. (호환성 유지)
This commit is contained in:
@@ -193,6 +193,11 @@ interface AudioContentQueryRepository {
|
||||
offset: Long = 0,
|
||||
limit: Long = 20
|
||||
): List<GetAudioContentMainItem>
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -187,6 +187,7 @@ class OrderQueryRepositoryImpl(
|
||||
return queryFactory.select(order.id)
|
||||
.from(order)
|
||||
.where(where)
|
||||
.distinct()
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -76,7 +76,7 @@ class ExplorerService(
|
||||
)
|
||||
}
|
||||
|
||||
fun getExplorer(member: Member, growthRankingCreatorsLimit: Long = 20): GetExplorerResponse {
|
||||
fun getExplorer(member: Member): GetExplorerResponse {
|
||||
val sections = mutableListOf<GetExplorerSectionResponse>()
|
||||
|
||||
// 인기 크리에이터
|
||||
@@ -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,
|
||||
|
@@ -10,6 +10,9 @@ data class GetCreatorProfileResponse(
|
||||
val similarCreatorList: List<SimilarCreatorResponse>,
|
||||
val liveRoomList: List<LiveRoomResponse>,
|
||||
val contentList: List<GetAudioContentListItem>,
|
||||
val latestContent: GetAudioContentListItem?,
|
||||
val totalContentCount: Long,
|
||||
val ownedContentCount: Long,
|
||||
val notice: String,
|
||||
val communityPostList: List<GetCommunityPostListResponse>,
|
||||
val cheers: GetCheersResponse,
|
||||
|
Reference in New Issue
Block a user