From 0f8fcbcaedc51eb1558b4d82b49a475c47af259f Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 7 Feb 2025 02:58:13 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20=ED=83=AD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AudioContentCurationQueryRepository.kt | 18 ++ .../tab/GetRecommendSeriesListResponse.kt | 11 ++ .../main/tab/RecommendSeriesRepository.kt | 43 ++++ .../AudioContentMainTabSeriesController.kt | 22 +++ .../AudioContentMainTabSeriesService.kt | 149 ++++++++++++++ .../series/GetContentMainTabSeriesResponse.kt | 23 +++ .../tab/series/GetSeriesCurationResponse.kt | 8 + .../content/series/ContentSeriesRepository.kt | 66 +++++++ .../content/series/ContentSeriesService.kt | 22 +++ .../series/GetSeriesGenreListResponse.kt | 8 + .../sodalive/rank/RankingRepository.kt | 186 +++++++++++++++++- .../vividnext/sodalive/rank/RankingService.kt | 45 +++++ 12 files changed, 595 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetRecommendSeriesListResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetContentMainTabSeriesResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetSeriesCurationResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesGenreListResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt index 916d860..84b63ed 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt @@ -6,6 +6,8 @@ import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.SortType import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem +import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration +import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme import kr.co.vividnext.sodalive.member.QMember.member import org.springframework.stereotype.Repository @@ -87,4 +89,20 @@ class AudioContentCurationQueryRepository(private val queryFactory: JPAQueryFact .orderBy(orderBy) .fetch() } + + fun findByContentMainTabId(tabId: Long, isAdult: Boolean): List { + var where = audioContentCuration.isActive.isTrue + .and(audioContentMainTab.id.eq(tabId)) + + if (!isAdult) { + where = where.and(audioContentCuration.isAdult.isFalse) + } + + return queryFactory + .selectFrom(audioContentCuration) + .innerJoin(audioContentCuration.tab, audioContentMainTab) + .where(where) + .orderBy(audioContentCuration.orders.asc()) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetRecommendSeriesListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetRecommendSeriesListResponse.kt new file mode 100644 index 0000000..a84a20d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetRecommendSeriesListResponse.kt @@ -0,0 +1,11 @@ +package kr.co.vividnext.sodalive.content.main.tab + +import com.querydsl.core.annotations.QueryProjection + +data class GetRecommendSeriesListResponse @QueryProjection constructor( + val seriesId: Long, + val title: String, + val imageUrl: String, + val creatorId: Long, + val creatorNickname: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt new file mode 100644 index 0000000..4375d96 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt @@ -0,0 +1,43 @@ +package kr.co.vividnext.sodalive.content.main.tab + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.main.tab.QRecommendSeries.recommendSeries +import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series +import kr.co.vividnext.sodalive.member.QMember.member +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Repository + +@Repository +class RecommendSeriesRepository( + private val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) { + fun getNewSeriesList(isAdult: Boolean): List { + var where = recommendSeries.isActive.isTrue + .and(recommendSeries.isFree.isFalse) + .and(series.isActive.isTrue) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select( + QGetRecommendSeriesListResponse( + series.id, + series.title, + recommendSeries.imagePath.prepend("/").prepend(imageHost), + member.id, + member.nickname + ) + ) + .from(recommendSeries) + .innerJoin(recommendSeries.series, series) + .innerJoin(series.member, member) + .where(where) + .orderBy(recommendSeries.orders.asc()) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt new file mode 100644 index 0000000..1e27aa6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.content.main.tab.series + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v2/audio-content/main/series") +class AudioContentMainTabSeriesController(private val service: AudioContentMainTabSeriesService) { + @GetMapping + fun fetchContentMainSeries( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.fetchData(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt new file mode 100644 index 0000000..681dd5f --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt @@ -0,0 +1,149 @@ +package kr.co.vividnext.sodalive.content.main.tab.series + +import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository +import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository +import kr.co.vividnext.sodalive.content.series.ContentSeriesService +import kr.co.vividnext.sodalive.event.EventService +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.rank.RankingService +import org.springframework.stereotype.Service +import java.time.DayOfWeek +import java.time.LocalDateTime +import java.time.temporal.TemporalAdjusters + +@Service +class AudioContentMainTabSeriesService( + private val bannerService: AudioContentBannerService, + private val seriesService: ContentSeriesService, + private val rankingService: RankingService, + private val recommendSeriesRepository: RecommendSeriesRepository, + private val eventService: EventService, + private val curationRepository: AudioContentCurationQueryRepository +) { + fun fetchData(member: Member): GetContentMainTabSeriesResponse { + val isAdult = member.auth != null + + // 메인 배너 (시리즈) + val contentBannerList = bannerService.getBannerList( + tabId = 2, + memberId = member.id!!, + isAdult = isAdult + ) + + val originalAudioDrama = seriesService.getOriginalAudioDramaList( + isAdult = isAdult, + offset = 0, + limit = 20 + ) + + // 일간 랭킹 + val currentDateTime = LocalDateTime.now() + val dailyRankingStartDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusDays(2) + val dailyRankingEndDate = dailyRankingStartDate + .plusDays(1) + + val rankSeriesList = rankingService.getSeriesRanking( + memberId = member.id!!, + isAdult = isAdult, + startDate = dailyRankingStartDate, + endDate = dailyRankingEndDate + ) + + // 시리즈 장르 + val genreList = seriesService.getGenreList(isAdult = isAdult) + + // 장르별 추천 시리즈 + val recommendSeriesList = if (genreList.isNotEmpty()) { + rankingService.getSeriesAllRankingByGenre( + memberId = member.id!!, + isAdult = isAdult, + genreId = genreList[0].id + ) + } else { + emptyList() + } + + // 새로운 시리즈 + val newSeriesList = recommendSeriesRepository.getNewSeriesList(isAdult = isAdult) + + // 완결 시리즈 월간 랭킹 + val monthlyRankingStartDate = currentDateTime + .withDayOfMonth(1) + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusDays(1) + val monthlyRankingEndDate = monthlyRankingStartDate + .plusMonths(1) + + val rankCompleteSeriesList = rankingService.getCompleteSeriesRanking( + memberId = member.id!!, + isAdult = isAdult, + startDate = monthlyRankingStartDate, + endDate = monthlyRankingEndDate + ) + + val startDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + val endDate = startDate + .plusDays(6) + + val seriesRankCreatorList = rankingService.fetchCreatorBySeriesRevenueRankTop20( + memberId = member.id!!, + startDate = startDate.minusDays(1), + endDate = endDate + ) + + val salesRankContentList = if (seriesRankCreatorList.isNotEmpty()) { + rankingService.fetchCreatorSeriesBySales( + creatorId = seriesRankCreatorList[0].creatorId, + isAdult = isAdult + ) + } else { + emptyList() + } + + // 이벤트 배너 + val eventBannerList = eventService.getEventList(isAdult = isAdult) + + // 큐레이션 + val curationList = curationRepository.findByContentMainTabId(tabId = 2, isAdult = isAdult) + .map { + GetSeriesCurationResponse( + title = it.title, + items = seriesService.fetchSeriesByCurationId( + curationId = it.id!!, + memberId = member.id!!, + isAdult = isAdult + ) + ) + } + + return GetContentMainTabSeriesResponse( + contentBannerList = contentBannerList, + originalAudioDrama = if (originalAudioDrama.size >= 3) { + originalAudioDrama + } else { + emptyList() + }, + rankSeriesList = rankSeriesList, + genreList = genreList, + recommendSeriesList = recommendSeriesList, + newSeriesList = newSeriesList, + rankCompleteSeriesList = rankCompleteSeriesList, + seriesRankCreatorList = seriesRankCreatorList, + salesRankContentList = salesRankContentList, + eventBannerList = eventBannerList, + curationList = curationList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetContentMainTabSeriesResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetContentMainTabSeriesResponse.kt new file mode 100644 index 0000000..d7155b0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetContentMainTabSeriesResponse.kt @@ -0,0 +1,23 @@ +package kr.co.vividnext.sodalive.content.main.tab.series + +import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse +import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.content.main.tab.GetRecommendSeriesListResponse +import kr.co.vividnext.sodalive.content.series.GetSeriesGenreListResponse +import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse +import kr.co.vividnext.sodalive.event.GetEventResponse + +data class GetContentMainTabSeriesResponse( + val tabId: Long = 2, + val contentBannerList: List, + val originalAudioDrama: List, + val rankSeriesList: List, + val genreList: List, + val recommendSeriesList: List, + val newSeriesList: List, + val rankCompleteSeriesList: List, + val seriesRankCreatorList: List, + val salesRankContentList: List, + val eventBannerList: GetEventResponse, + val curationList: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetSeriesCurationResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetSeriesCurationResponse.kt new file mode 100644 index 0000000..bb24415 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetSeriesCurationResponse.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.content.main.tab.series + +import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse + +data class GetSeriesCurationResponse( + val title: String, + val items: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt index 10b8f25..e87f059 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt @@ -2,15 +2,21 @@ package kr.co.vividnext.sodalive.content.series import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.hashtag.QHashTag.hashTag +import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration +import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCurationItem.audioContentCurationItem import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentMinMaxPriceResponse import kr.co.vividnext.sodalive.content.series.content.QGetSeriesContentMinMaxPriceResponse import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent import kr.co.vividnext.sodalive.creator.admin.content.series.Series import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword +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.data.jpa.repository.JpaRepository interface ContentSeriesRepository : JpaRepository, ContentSeriesQueryRepository @@ -29,6 +35,9 @@ interface ContentSeriesQueryRepository { fun getKeywordList(seriesId: Long): List fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List + fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long = 0, limit: Long = 20): List + fun getGenreList(isAdult: Boolean): List + fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List } class ContentSeriesQueryRepositoryImpl( @@ -133,4 +142,61 @@ class ContentSeriesQueryRepositoryImpl( .limit(limit) .fetch() } + + override fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long, limit: Long): List { + var where = series.isOriginal.isTrue + .and(series.isActive.isTrue) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .selectFrom(series) + .where(where) + .orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc()) + .offset(offset) + .limit(limit) + .fetch() + } + + override fun getGenreList(isAdult: Boolean): List { + var where = seriesGenre.isActive.isTrue + + if (!isAdult) { + where = where.and(seriesGenre.isAdult.isFalse) + } + + return queryFactory + .select(QGetSeriesGenreListResponse(seriesGenre.id, seriesGenre.genre)) + .from(seriesGenre) + .where(where) + .orderBy(seriesGenre.orders.asc()) + .fetch() + } + + override fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + var where = series.isActive.isTrue + .and(member.isActive.isTrue) + .and(member.role.eq(MemberRole.CREATOR)) + .and(blockMember.id.isNull) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(audioContentCurationItem) + .innerJoin(audioContentCurationItem.curation, audioContentCuration) + .innerJoin(audioContentCurationItem.series, series) + .innerJoin(series.member, member) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt index 2a60ae3..1e95021 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt @@ -30,6 +30,19 @@ class ContentSeriesService( @Value("\${cloud.aws.cloud-front.host}") private val coverImageHost: String ) { + fun getOriginalAudioDramaList( + isAdult: Boolean, + offset: Long = 0, + limit: Long = 20 + ): List { + val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, offset, limit) + return seriesToSeriesListItem(originalAudioDramaList, isAdult) + } + + fun getGenreList(isAdult: Boolean): List { + return repository.getGenreList(isAdult = isAdult) + } + fun getSeriesList( creatorId: Long, member: Member, @@ -170,6 +183,15 @@ class ContentSeriesService( return seriesToSeriesListItem(seriesList = seriesList, isAdult = member.auth != null) } + fun fetchSeriesByCurationId( + curationId: Long, + memberId: Long, + isAdult: Boolean + ): List { + val seriesList = repository.findByCurationId(curationId = curationId, memberId = memberId, isAdult = isAdult) + return seriesToSeriesListItem(seriesList, isAdult) + } + private fun seriesToSeriesListItem( seriesList: List, isAdult: Boolean diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesGenreListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesGenreListResponse.kt new file mode 100644 index 0000000..30aa078 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesGenreListResponse.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.content.series + +import com.querydsl.core.annotations.QueryProjection + +data class GetSeriesGenreListResponse @QueryProjection constructor( + val id: Long, + val genre: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt index 67d17a4..235b034 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt @@ -1,6 +1,8 @@ package kr.co.vividnext.sodalive.rank +import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike @@ -13,6 +15,7 @@ import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentThe import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent import kr.co.vividnext.sodalive.creator.admin.content.series.Series +import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState import kr.co.vividnext.sodalive.explorer.QCreatorRanking.creatorRanking import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.MemberRole @@ -187,16 +190,107 @@ class RankingRepository( .innerJoin(seriesContent.series, series) .innerJoin(seriesContent.content, audioContent) .innerJoin(series.member, member) - .innerJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) .leftJoin(blockMember).on(blockMemberCondition) .where(where) .groupBy(series.id) - .orderBy(order.can.sum().desc(), series.createdAt.asc()) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) .offset(0) .limit(10) .fetch() } + fun getCompleteSeriesRanking( + memberId: Long, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime, + offset: Long, + limit: Long + ): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + var where = series.isActive.isTrue + .and(series.state.eq(SeriesState.COMPLETE)) + .and(audioContent.isActive.isTrue) + .and(member.isActive.isTrue) + .and(member.isNotNull) + .and(member.role.eq(MemberRole.CREATOR)) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + .and(order.isActive.isTrue) + .and(order.createdAt.goe(startDate)) + .and(order.createdAt.lt(endDate)) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .groupBy(series.id) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) + .offset(offset) + .limit(limit) + .fetch() + } + + fun getSeriesAllRankingByGenre(memberId: Long, isAdult: Boolean, genreId: Long): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + var where = series.isActive.isTrue + .and(seriesGenre.id.eq(genreId)) + .and(audioContent.isActive.isTrue) + .and(member.isActive.isTrue) + .and(member.isNotNull) + .and(member.role.eq(MemberRole.CREATOR)) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + .and(order.isActive.isTrue) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(series.genre, seriesGenre) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .groupBy(series.id) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) + .offset(0) + .limit(20) + .fetch() + } + fun fetchCreatorByContentRevenueRankTop20( memberId: Long, startDate: LocalDateTime, @@ -232,11 +326,11 @@ class RankingRepository( .leftJoin(blockMember).on(blockMemberCondition) .where(where) .groupBy(member.id) - .having( - audioContent.id.count().goe(4) - .and(order.can.sum().gt(0)) + .having(audioContent.id.count().goe(4)) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() ) - .orderBy(order.can.sum().desc()) .offset(0) .limit(20) .fetch() @@ -315,4 +409,84 @@ class RankingRepository( .limit(2) .fetch() } + + fun fetchCreatorBySeriesRevenueRankTop20( + memberId: Long, + startDate: LocalDateTime, + endDate: LocalDateTime + ): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + val ordersCondition = order.audioContent.id.eq(audioContent.id) + .and(order.isActive.isTrue) + .and(order.createdAt.goe(startDate)) + .and(order.createdAt.lt(startDate)) + + val where = member.isActive.isTrue + .and(member.role.eq(MemberRole.CREATOR)) + .and(series.isActive.isTrue) + .and(audioContent.isActive.isTrue) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + + return queryFactory + .select( + QContentCreatorResponse( + member.id, + member.nickname, + member.profileImage.prepend("/").prepend(imageHost) + ) + ) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .leftJoin(order).on(ordersCondition) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .groupBy(member.id) + .having(series.id.countDistinct().goe(3)) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) + .offset(0) + .limit(20) + .fetch() + } + + fun fetchCreatorSeriesBySales(creatorId: Long, isAdult: Boolean): List { + var where = member.isActive.isTrue + .and(member.role.eq(MemberRole.CREATOR)) + .and(series.isActive.isTrue) + .and(audioContent.isActive.isTrue) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(order.isActive.isTrue) + .and(member.id.eq(creatorId)) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .where(where) + .groupBy(series.id) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) + .offset(0) + .limit(10) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt index 662a668..508da30 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt @@ -67,6 +67,38 @@ class RankingService( return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) } + fun getSeriesAllRankingByGenre( + memberId: Long, + isAdult: Boolean, + genreId: Long + ): List { + val seriesList = repository.getSeriesAllRankingByGenre( + memberId = memberId, + isAdult = isAdult, + genreId = genreId + ) + return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) + } + + fun getCompleteSeriesRanking( + memberId: Long, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime, + offset: Long = 0, + limit: Long = 10 + ): List { + val seriesList = repository.getCompleteSeriesRanking( + memberId = memberId, + isAdult = isAdult, + startDate = startDate, + endDate = endDate, + offset = offset, + limit = limit + ) + return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) + } + private fun seriesToSeriesListItem( seriesList: List, isAdult: Boolean @@ -148,4 +180,17 @@ class RankingService( fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List { return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult) } + + fun fetchCreatorBySeriesRevenueRankTop20( + memberId: Long, + startDate: LocalDateTime, + endDate: LocalDateTime + ): List { + return repository.fetchCreatorBySeriesRevenueRankTop20(memberId, startDate, endDate) + } + + fun fetchCreatorSeriesBySales(creatorId: Long, isAdult: Boolean): List { + val seriesList = repository.fetchCreatorSeriesBySales(creatorId = creatorId, isAdult = isAdult) + return seriesToSeriesListItem(seriesList, isAdult) + } }