| @@ -15,6 +15,7 @@ import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner | |||||||
| import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner | import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner | ||||||
| import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration | import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration | ||||||
| import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration | 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.main.tab.QAudioContentMainTab.audioContentMainTab | import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab | ||||||
| import kr.co.vividnext.sodalive.content.order.QOrder.order | import kr.co.vividnext.sodalive.content.order.QOrder.order | ||||||
| import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent | import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent | ||||||
| @@ -72,6 +73,14 @@ interface AudioContentQueryRepository { | |||||||
|         isFree: Boolean = false |         isFree: Boolean = false | ||||||
|     ): List<GetAudioContentMainItem> |     ): List<GetAudioContentMainItem> | ||||||
|  |  | ||||||
|  |     fun findAlarmContentByTheme( | ||||||
|  |         memberId: Long, | ||||||
|  |         theme: List<String>, | ||||||
|  |         isAdult: Boolean = false, | ||||||
|  |         offset: Long = 0, | ||||||
|  |         limit: Long = 20 | ||||||
|  |     ): List<GetAudioContentMainItem> | ||||||
|  |  | ||||||
|     fun totalCountByTheme( |     fun totalCountByTheme( | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         theme: String = "", |         theme: String = "", | ||||||
| @@ -80,6 +89,7 @@ interface AudioContentQueryRepository { | |||||||
|     ): Int |     ): Int | ||||||
|  |  | ||||||
|     fun findByThemeFor2Weeks( |     fun findByThemeFor2Weeks( | ||||||
|  |         isFree: Boolean = false, | ||||||
|         cloudfrontHost: String, |         cloudfrontHost: String, | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         theme: String = "", |         theme: String = "", | ||||||
| @@ -90,6 +100,7 @@ interface AudioContentQueryRepository { | |||||||
|     ): List<GetAudioContentMainItem> |     ): List<GetAudioContentMainItem> | ||||||
|  |  | ||||||
|     fun totalCountNewContentFor2Weeks( |     fun totalCountNewContentFor2Weeks( | ||||||
|  |         isFree: Boolean = false, | ||||||
|         theme: String, |         theme: String, | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
| @@ -109,6 +120,14 @@ interface AudioContentQueryRepository { | |||||||
|         contentType: ContentType |         contentType: ContentType | ||||||
|     ): List<GetAudioContentMainItem> |     ): List<GetAudioContentMainItem> | ||||||
|  |  | ||||||
|  |     fun findAudioContentByCurationIdV2( | ||||||
|  |         curationId: Long, | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         offset: Long = 0, | ||||||
|  |         limit: Long = 20 | ||||||
|  |     ): List<GetAudioContentMainItem> | ||||||
|  |  | ||||||
|     fun getAudioContentRanking( |     fun getAudioContentRanking( | ||||||
|         cloudfrontHost: String, |         cloudfrontHost: String, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
| @@ -413,6 +432,60 @@ class AudioContentQueryRepositoryImpl( | |||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun findAlarmContentByTheme( | ||||||
|  |         memberId: Long, | ||||||
|  |         theme: List<String>, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): List<GetAudioContentMainItem> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         val orderBy = listOf(audioContent.releaseDate.desc(), audioContent.id.desc()) | ||||||
|  |  | ||||||
|  |         var where = audioContent.isActive.isTrue | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and( | ||||||
|  |                 audioContent.releaseDate.isNull | ||||||
|  |                     .or(audioContent.releaseDate.loe(LocalDateTime.now())) | ||||||
|  |                     .or(audioContent.member.id.eq(memberId)) | ||||||
|  |             ) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(audioContent.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (theme.isNotEmpty()) { | ||||||
|  |             where = where.and(audioContentTheme.theme.`in`(theme)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetAudioContentMainItem( | ||||||
|  |                     audioContent.id, | ||||||
|  |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|  |                     audioContent.title, | ||||||
|  |                     member.id, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost), | ||||||
|  |                     member.nickname, | ||||||
|  |                     audioContent.price, | ||||||
|  |                     audioContent.duration | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(audioContent) | ||||||
|  |             .innerJoin(audioContent.member, member) | ||||||
|  |             .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .offset(offset) | ||||||
|  |             .limit(limit) | ||||||
|  |             .orderBy(*orderBy.toTypedArray()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override fun totalCountByTheme(memberId: Long, theme: String, isAdult: Boolean, contentType: ContentType): Int { |     override fun totalCountByTheme(memberId: Long, theme: String, isAdult: Boolean, contentType: ContentType): Int { | ||||||
|         var where = audioContent.isActive.isTrue |         var where = audioContent.isActive.isTrue | ||||||
|             .and(audioContent.duration.isNotNull) |             .and(audioContent.duration.isNotNull) | ||||||
| @@ -447,6 +520,7 @@ class AudioContentQueryRepositoryImpl( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun totalCountNewContentFor2Weeks( |     override fun totalCountNewContentFor2Weeks( | ||||||
|  |         isFree: Boolean, | ||||||
|         theme: String, |         theme: String, | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
| @@ -475,6 +549,10 @@ class AudioContentQueryRepositoryImpl( | |||||||
|             where = where.and(audioContentTheme.theme.eq(theme)) |             where = where.and(audioContentTheme.theme.eq(theme)) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (isFree) { | ||||||
|  |             where = where.and(audioContent.price.loe(0)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return queryFactory |         return queryFactory | ||||||
|             .select(audioContent.id) |             .select(audioContent.id) | ||||||
|             .from(audioContent) |             .from(audioContent) | ||||||
| @@ -486,6 +564,7 @@ class AudioContentQueryRepositoryImpl( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun findByThemeFor2Weeks( |     override fun findByThemeFor2Weeks( | ||||||
|  |         isFree: Boolean, | ||||||
|         cloudfrontHost: String, |         cloudfrontHost: String, | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         theme: String, |         theme: String, | ||||||
| @@ -517,6 +596,10 @@ class AudioContentQueryRepositoryImpl( | |||||||
|             where = where.and(audioContentTheme.theme.eq(theme)) |             where = where.and(audioContentTheme.theme.eq(theme)) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (isFree) { | ||||||
|  |             where = where.and(audioContent.price.loe(0)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return queryFactory |         return queryFactory | ||||||
|             .select( |             .select( | ||||||
|                 QGetAudioContentMainItem( |                 QGetAudioContentMainItem( | ||||||
| @@ -648,6 +731,54 @@ class AudioContentQueryRepositoryImpl( | |||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun findAudioContentByCurationIdV2( | ||||||
|  |         curationId: Long, | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): List<GetAudioContentMainItem> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         var where = audioContentCuration.isActive.isTrue | ||||||
|  |             .and(audioContentCurationItem.isActive.isTrue) | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(audioContent.member.isNotNull) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContent.member.isActive.isTrue) | ||||||
|  |             .and(audioContentCuration.id.eq(curationId)) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(audioContent.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetAudioContentMainItem( | ||||||
|  |                     audioContent.id, | ||||||
|  |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|  |                     audioContent.title, | ||||||
|  |                     member.id, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost), | ||||||
|  |                     member.nickname, | ||||||
|  |                     audioContent.price, | ||||||
|  |                     audioContent.duration | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(audioContentCurationItem) | ||||||
|  |             .innerJoin(audioContentCurationItem.content, audioContent) | ||||||
|  |             .innerJoin(audioContentCurationItem.curation, audioContentCuration) | ||||||
|  |             .innerJoin(audioContent.member, member) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .offset(offset) | ||||||
|  |             .limit(limit) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override fun getAudioContentRanking( |     override fun getAudioContentRanking( | ||||||
|         cloudfrontHost: String, |         cloudfrontHost: String, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
| @@ -679,7 +810,8 @@ class AudioContentQueryRepositoryImpl( | |||||||
|                     audioContent.price, |                     audioContent.price, | ||||||
|                     audioContent.duration, |                     audioContent.duration, | ||||||
|                     member.id, |                     member.id, | ||||||
|                     member.nickname |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -92,6 +92,7 @@ class AudioContentMainController( | |||||||
|  |  | ||||||
|     @GetMapping("/new/all") |     @GetMapping("/new/all") | ||||||
|     fun getNewContentAllByTheme( |     fun getNewContentAllByTheme( | ||||||
|  |         @RequestParam("isFree", required = false) isFree: Boolean? = null, | ||||||
|         @RequestParam("theme") theme: String, |         @RequestParam("theme") theme: String, | ||||||
|         @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, |         @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, | ||||||
|         @RequestParam("contentType", required = false) contentType: ContentType? = null, |         @RequestParam("contentType", required = false) contentType: ContentType? = null, | ||||||
| @@ -102,6 +103,7 @@ class AudioContentMainController( | |||||||
|  |  | ||||||
|         ApiResponse.ok( |         ApiResponse.ok( | ||||||
|             service.getNewContentFor2WeeksByTheme( |             service.getNewContentFor2WeeksByTheme( | ||||||
|  |                 isFree = isFree ?: false, | ||||||
|                 theme = theme, |                 theme = theme, | ||||||
|                 isAdultContentVisible = isAdultContentVisible ?: true, |                 isAdultContentVisible = isAdultContentVisible ?: true, | ||||||
|                 contentType = contentType ?: ContentType.ALL, |                 contentType = contentType ?: ContentType.ALL, | ||||||
|   | |||||||
| @@ -50,6 +50,7 @@ class AudioContentMainService( | |||||||
|  |  | ||||||
|     @Transactional(readOnly = true) |     @Transactional(readOnly = true) | ||||||
|     fun getNewContentFor2WeeksByTheme( |     fun getNewContentFor2WeeksByTheme( | ||||||
|  |         isFree: Boolean, | ||||||
|         theme: String, |         theme: String, | ||||||
|         isAdultContentVisible: Boolean, |         isAdultContentVisible: Boolean, | ||||||
|         contentType: ContentType, |         contentType: ContentType, | ||||||
| @@ -57,12 +58,14 @@ class AudioContentMainService( | |||||||
|         pageable: Pageable |         pageable: Pageable | ||||||
|     ): GetNewContentAllResponse { |     ): GetNewContentAllResponse { | ||||||
|         val totalCount = repository.totalCountNewContentFor2Weeks( |         val totalCount = repository.totalCountNewContentFor2Weeks( | ||||||
|  |             isFree, | ||||||
|             theme, |             theme, | ||||||
|             memberId = member.id!!, |             memberId = member.id!!, | ||||||
|             isAdult = member.auth != null && isAdultContentVisible, |             isAdult = member.auth != null && isAdultContentVisible, | ||||||
|             contentType = contentType |             contentType = contentType | ||||||
|         ) |         ) | ||||||
|         val items = repository.findByThemeFor2Weeks( |         val items = repository.findByThemeFor2Weeks( | ||||||
|  |             isFree, | ||||||
|             cloudfrontHost = imageHost, |             cloudfrontHost = imageHost, | ||||||
|             memberId = member.id!!, |             memberId = member.id!!, | ||||||
|             theme = theme, |             theme = theme, | ||||||
|   | |||||||
| @@ -17,5 +17,6 @@ data class GetAudioContentRankingItem @QueryProjection constructor( | |||||||
|     @JsonProperty("price") val price: Int, |     @JsonProperty("price") val price: Int, | ||||||
|     @JsonProperty("duration") val duration: String, |     @JsonProperty("duration") val duration: String, | ||||||
|     @JsonProperty("creatorId") val creatorId: Long, |     @JsonProperty("creatorId") val creatorId: Long, | ||||||
|     @JsonProperty("creatorNickname") val creatorNickname: String |     @JsonProperty("creatorNickname") val creatorNickname: String, | ||||||
|  |     @JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -109,9 +109,7 @@ class AudioContentCurationQueryRepository(private val queryFactory: JPAQueryFact | |||||||
|     fun findByContentMainTabIdAndTitle( |     fun findByContentMainTabIdAndTitle( | ||||||
|         tabId: Long, |         tabId: Long, | ||||||
|         title: String, |         title: String, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean | ||||||
|         offset: Long = 0, |  | ||||||
|         limit: Long = 12 |  | ||||||
|     ): List<AudioContentCuration> { |     ): List<AudioContentCuration> { | ||||||
|         var where = audioContentCuration.isActive.isTrue |         var where = audioContentCuration.isActive.isTrue | ||||||
|             .and(audioContentMainTab.id.eq(tabId)) |             .and(audioContentMainTab.id.eq(tabId)) | ||||||
|   | |||||||
| @@ -0,0 +1,62 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab | ||||||
|  |  | ||||||
|  | import com.querydsl.core.types.dsl.Expressions | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||||
|  | import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.QContentCreatorResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme | ||||||
|  | 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.stereotype.Repository | ||||||
|  |  | ||||||
|  | @Repository | ||||||
|  | class AudioContentMainTabRepository( | ||||||
|  |     private val queryFactory: JPAQueryFactory, | ||||||
|  |  | ||||||
|  |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|  |     private val imageHost: String | ||||||
|  | ) { | ||||||
|  |     fun findCreatorByThemeContent( | ||||||
|  |         memberId: Long, | ||||||
|  |         theme: String, | ||||||
|  |         minCount: Int | ||||||
|  |     ): List<ContentCreatorResponse> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         val where = member.isActive.isTrue | ||||||
|  |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(audioContentTheme.isActive.isTrue) | ||||||
|  |             .and(audioContentTheme.theme.eq(theme)) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QContentCreatorResponse( | ||||||
|  |                     member.id, | ||||||
|  |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(member) | ||||||
|  |             .innerJoin(audioContent).on(member.id.eq(audioContent.member.id)) | ||||||
|  |             .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(member.id) | ||||||
|  |             .having(audioContent.id.count().goe(minCount)) | ||||||
|  |             .orderBy( | ||||||
|  |                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||||
|  |             ) | ||||||
|  |             .offset(0) | ||||||
|  |             .limit(20) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||||
|  |  | ||||||
|  | data class GetPopularContentByCreatorResponse( | ||||||
|  |     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val salesCountRankContentList: List<GetAudioContentRankingItem> | ||||||
|  | ) | ||||||
| @@ -7,5 +7,6 @@ data class GetRecommendSeriesListResponse @QueryProjection constructor( | |||||||
|     val title: String, |     val title: String, | ||||||
|     val imageUrl: String, |     val imageUrl: String, | ||||||
|     val creatorId: Long, |     val creatorId: Long, | ||||||
|     val creatorNickname: String |     val creatorNickname: String, | ||||||
|  |     val creatorProfileImageUrl: String | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -30,7 +30,8 @@ class RecommendSeriesRepository( | |||||||
|                     series.title, |                     series.title, | ||||||
|                     recommendSeries.imagePath.prepend("/").prepend(imageHost), |                     recommendSeries.imagePath.prepend("/").prepend(imageHost), | ||||||
|                     member.id, |                     member.id, | ||||||
|                     member.nickname |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|             .from(recommendSeries) |             .from(recommendSeries) | ||||||
| @@ -57,7 +58,8 @@ class RecommendSeriesRepository( | |||||||
|                     series.title, |                     series.title, | ||||||
|                     recommendSeries.imagePath.prepend("/").prepend(imageHost), |                     recommendSeries.imagePath.prepend("/").prepend(imageHost), | ||||||
|                     member.id, |                     member.id, | ||||||
|                     member.nickname |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|             .from(recommendSeries) |             .from(recommendSeries) | ||||||
|   | |||||||
| @@ -3,9 +3,11 @@ package kr.co.vividnext.sodalive.content.main.tab.alarm | |||||||
| import kr.co.vividnext.sodalive.common.ApiResponse | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
| import kr.co.vividnext.sodalive.common.SodaException | import kr.co.vividnext.sodalive.common.SodaException | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.data.domain.Pageable | ||||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -19,4 +21,22 @@ class AudioContentMainTabAlarmController(private val service: AudioContentMainTa | |||||||
|  |  | ||||||
|         ApiResponse.ok(service.fetchData(member)) |         ApiResponse.ok(service.fetchData(member)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/all") | ||||||
|  |     fun fetchAlarmContentByTheme( | ||||||
|  |         @RequestParam("theme") theme: String, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.fetchAlarmContentByTheme( | ||||||
|  |                 theme, | ||||||
|  |                 member, | ||||||
|  |                 offset = pageable.offset, | ||||||
|  |                 limit = pageable.pageSize.toLong() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package kr.co.vividnext.sodalive.content.main.tab.alarm | package kr.co.vividnext.sodalive.content.main.tab.alarm | ||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.content.AudioContentRepository | import kr.co.vividnext.sodalive.content.AudioContentRepository | ||||||
| import kr.co.vividnext.sodalive.content.ContentType | import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | 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.curation.AudioContentCurationQueryRepository | ||||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
| @@ -32,11 +32,10 @@ class AudioContentMainTabAlarmService( | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         val alarmThemeList = listOf("모닝콜", "슬립콜", "알람") |         val alarmThemeList = listOf("모닝콜", "슬립콜", "알람") | ||||||
|         val newAlarmContentList = contentRepository.findByTheme( |         val newAlarmContentList = contentRepository.findAlarmContentByTheme( | ||||||
|             memberId = memberId, |             memberId = memberId, | ||||||
|             theme = alarmThemeList[0], |             theme = alarmThemeList, | ||||||
|             isAdult = isAdult, |             isAdult = isAdult, | ||||||
|             contentType = ContentType.ALL, |  | ||||||
|             limit = 10 |             limit = 10 | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
| @@ -64,10 +63,10 @@ class AudioContentMainTabAlarmService( | |||||||
|             .map { |             .map { | ||||||
|                 GetContentCurationResponse( |                 GetContentCurationResponse( | ||||||
|                     title = it.title, |                     title = it.title, | ||||||
|                     items = contentRepository.findAudioContentByCurationId( |                     items = contentRepository.findAudioContentByCurationIdV2( | ||||||
|                         curationId = it.id!!, |                         curationId = it.id!!, | ||||||
|                         isAdult = isAdult, |                         memberId = memberId, | ||||||
|                         contentType = ContentType.ALL |                         isAdult = isAdult | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| @@ -81,4 +80,28 @@ class AudioContentMainTabAlarmService( | |||||||
|             curationList = curationList |             curationList = curationList | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun fetchAlarmContentByTheme( | ||||||
|  |         theme: String, | ||||||
|  |         member: Member, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): List<GetAudioContentMainItem> { | ||||||
|  |         val alarmThemeList = if (theme.isNotBlank()) { | ||||||
|  |             listOf(theme) | ||||||
|  |         } else { | ||||||
|  |             listOf("모닝콜", "슬립콜", "알람") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val memberId = member.id!! | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |  | ||||||
|  |         return contentRepository.findAlarmContentByTheme( | ||||||
|  |             memberId = memberId, | ||||||
|  |             theme = alarmThemeList, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             offset = offset, | ||||||
|  |             limit = limit | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.member.Member | |||||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -19,4 +20,19 @@ class AudioContentMainTabAsmrController(private val service: AudioContentMainTab | |||||||
|  |  | ||||||
|         ApiResponse.ok(service.fetchData(member)) |         ApiResponse.ok(service.fetchData(member)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/popular-content-by-creator") | ||||||
|  |     fun getPopularContentByCreator( | ||||||
|  |         @RequestParam creatorId: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getPopularContentByCreator( | ||||||
|  |                 creatorId = creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,9 @@ import kr.co.vividnext.sodalive.content.AudioContentRepository | |||||||
| import kr.co.vividnext.sodalive.content.ContentType | import kr.co.vividnext.sodalive.content.ContentType | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | 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.curation.AudioContentCurationQueryRepository | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository | ||||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse | ||||||
| import kr.co.vividnext.sodalive.event.EventService | import kr.co.vividnext.sodalive.event.EventService | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| import kr.co.vividnext.sodalive.rank.RankingService | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
| @@ -15,6 +17,7 @@ import java.time.temporal.TemporalAdjusters | |||||||
|  |  | ||||||
| @Service | @Service | ||||||
| class AudioContentMainTabAsmrService( | class AudioContentMainTabAsmrService( | ||||||
|  |     private val repository: AudioContentMainTabRepository, | ||||||
|     private val bannerService: AudioContentBannerService, |     private val bannerService: AudioContentBannerService, | ||||||
|     private val contentRepository: AudioContentRepository, |     private val contentRepository: AudioContentRepository, | ||||||
|     private val rankingService: RankingService, |     private val rankingService: RankingService, | ||||||
| @@ -59,16 +62,42 @@ class AudioContentMainTabAsmrService( | |||||||
|             theme = theme |             theme = theme | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |         val creatorList = repository.findCreatorByThemeContent( | ||||||
|  |             memberId = memberId, | ||||||
|  |             theme = theme, | ||||||
|  |             minCount = 4 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesRankContentList = if (creatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorContentBySalesTop2( | ||||||
|  |                 creatorId = creatorList[0].creatorId, | ||||||
|  |                 isAdult = isAdult, | ||||||
|  |                 theme = theme | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val salesCountRankContentList = if (creatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorContentBySalesCountTop2( | ||||||
|  |                 creatorId = creatorList[0].creatorId, | ||||||
|  |                 isAdult = isAdult, | ||||||
|  |                 theme = theme | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|         val eventBannerList = eventService.getEventList(isAdult = isAdult) |         val eventBannerList = eventService.getEventList(isAdult = isAdult) | ||||||
|  |  | ||||||
|         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) |         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) | ||||||
|             .map { |             .map { | ||||||
|                 GetContentCurationResponse( |                 GetContentCurationResponse( | ||||||
|                     title = it.title, |                     title = it.title, | ||||||
|                     items = contentRepository.findAudioContentByCurationId( |                     items = contentRepository.findAudioContentByCurationIdV2( | ||||||
|                         curationId = it.id!!, |                         curationId = it.id!!, | ||||||
|                         isAdult = isAdult, |                         memberId = memberId, | ||||||
|                         contentType = ContentType.ALL |                         isAdult = isAdult | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| @@ -77,8 +106,31 @@ class AudioContentMainTabAsmrService( | |||||||
|             contentBannerList = contentBannerList, |             contentBannerList = contentBannerList, | ||||||
|             newAsmrContentList = newAsmrContentList, |             newAsmrContentList = newAsmrContentList, | ||||||
|             rankAsmrContentList = rankAsmrContentList, |             rankAsmrContentList = rankAsmrContentList, | ||||||
|  |             creatorList = creatorList, | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             salesCountRankContentList = salesCountRankContentList, | ||||||
|             eventBannerList = eventBannerList, |             eventBannerList = eventBannerList, | ||||||
|             curationList = curationList |             curationList = curationList | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse { | ||||||
|  |         val theme = "ASMR" | ||||||
|  |         val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             theme = theme | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             theme = theme | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return GetPopularContentByCreatorResponse( | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             salesCountRankContentList = salesCountRankContentList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package kr.co.vividnext.sodalive.content.main.tab.asmr | package kr.co.vividnext.sodalive.content.main.tab.asmr | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse | ||||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse | import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse | ||||||
| @@ -11,6 +12,9 @@ data class GetContentMainTabAsmrResponse( | |||||||
|     val contentBannerList: List<GetAudioContentBannerResponse>, |     val contentBannerList: List<GetAudioContentBannerResponse>, | ||||||
|     val newAsmrContentList: List<GetAudioContentMainItem>, |     val newAsmrContentList: List<GetAudioContentMainItem>, | ||||||
|     val rankAsmrContentList: List<GetAudioContentRankingItem>, |     val rankAsmrContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val creatorList: List<ContentCreatorResponse>, | ||||||
|  |     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val salesCountRankContentList: List<GetAudioContentRankingItem>, | ||||||
|     val eventBannerList: GetEventResponse, |     val eventBannerList: GetEventResponse, | ||||||
|     val curationList: List<GetContentCurationResponse> |     val curationList: List<GetContentCurationResponse> | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -2,10 +2,13 @@ package kr.co.vividnext.sodalive.content.main.tab.content | |||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.common.ApiResponse | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
| import kr.co.vividnext.sodalive.common.SodaException | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.content.ContentType | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.data.domain.Pageable | ||||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -13,10 +16,70 @@ import org.springframework.web.bind.annotation.RestController | |||||||
| class AudioContentMainTabContentController(private val service: AudioContentMainTabContentService) { | class AudioContentMainTabContentController(private val service: AudioContentMainTabContentService) { | ||||||
|     @GetMapping |     @GetMapping | ||||||
|     fun fetchContentMainTabContent( |     fun fetchContentMainTabContent( | ||||||
|  |         @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, | ||||||
|  |         @RequestParam("contentType", required = false) contentType: ContentType? = null, | ||||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|     ) = run { |     ) = run { | ||||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|         ApiResponse.ok(service.fetchData(member)) |         ApiResponse.ok( | ||||||
|  |             service.fetchData( | ||||||
|  |                 isAdultContentVisible = isAdultContentVisible ?: true, | ||||||
|  |                 contentType = contentType ?: ContentType.ALL, | ||||||
|  |                 member | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/ranking") | ||||||
|  |     fun getAudioContentRanking( | ||||||
|  |         @RequestParam("sort-type", required = false) sortType: String? = "매출", | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getAudioContentRanking( | ||||||
|  |                 memberId = member.id!!, | ||||||
|  |                 isAdult = member.auth != null, | ||||||
|  |                 sortType = sortType ?: "매출" | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/new-content-by-theme") | ||||||
|  |     fun getNewContentByTheme( | ||||||
|  |         @RequestParam("theme") theme: String, | ||||||
|  |         @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, | ||||||
|  |         @RequestParam("contentType", required = false) contentType: ContentType? = null, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getNewContentByTheme( | ||||||
|  |                 theme, | ||||||
|  |                 isAdultContentVisible = isAdultContentVisible ?: true, | ||||||
|  |                 contentType = contentType ?: ContentType.ALL, | ||||||
|  |                 member | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/popular-content-by-creator") | ||||||
|  |     fun getPopularContentByCreator( | ||||||
|  |         @RequestParam creatorId: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getPopularContentByCreator( | ||||||
|  |                 creatorId = creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,10 @@ package kr.co.vividnext.sodalive.content.main.tab.content | |||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.content.AudioContentRepository | import kr.co.vividnext.sodalive.content.AudioContentRepository | ||||||
| import kr.co.vividnext.sodalive.content.ContentType | import kr.co.vividnext.sodalive.content.ContentType | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse | ||||||
| import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository | import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository | ||||||
| import kr.co.vividnext.sodalive.event.EventService | import kr.co.vividnext.sodalive.event.EventService | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| @@ -18,7 +21,11 @@ class AudioContentMainTabContentService( | |||||||
|     private val rankingService: RankingService, |     private val rankingService: RankingService, | ||||||
|     private val eventService: EventService |     private val eventService: EventService | ||||||
| ) { | ) { | ||||||
|     fun fetchData(member: Member): GetContentMainTabContentResponse { |     fun fetchData( | ||||||
|  |         isAdultContentVisible: Boolean, | ||||||
|  |         contentType: ContentType, | ||||||
|  |         member: Member | ||||||
|  |     ): GetContentMainTabContentResponse { | ||||||
|         val memberId = member.id!! |         val memberId = member.id!! | ||||||
|         val isAdult = member.auth != null |         val isAdult = member.auth != null | ||||||
|  |  | ||||||
| @@ -33,18 +40,14 @@ class AudioContentMainTabContentService( | |||||||
|         val themeOfContentList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult) |         val themeOfContentList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult) | ||||||
|  |  | ||||||
|         // 새로운 단편 |         // 새로운 단편 | ||||||
|         val newContentList = if (themeOfContentList.isNotEmpty()) { |         val newContentList = audioContentRepository.findByTheme( | ||||||
|             audioContentRepository.findByTheme( |             memberId = member.id!!, | ||||||
|                 memberId = member.id!!, |             theme = "", | ||||||
|                 theme = themeOfContentList[0], |             isAdult = member.auth != null, | ||||||
|                 isAdult = member.auth != null, |             contentType = ContentType.ALL, | ||||||
|                 contentType = ContentType.ALL, |             offset = 0, | ||||||
|                 offset = 0, |             limit = 10 | ||||||
|                 limit = 10 |         ) | ||||||
|             ) |  | ||||||
|         } else { |  | ||||||
|             emptyList() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 일간 랭킹 |         // 일간 랭킹 | ||||||
|         val currentDateTime = LocalDateTime.now() |         val currentDateTime = LocalDateTime.now() | ||||||
| @@ -102,4 +105,59 @@ class AudioContentMainTabContentService( | |||||||
|             eventBannerList = eventBannerList |             eventBannerList = eventBannerList | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getAudioContentRanking( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         sortType: String = "매출" | ||||||
|  |     ): List<GetAudioContentRankingItem> { | ||||||
|  |         val currentDateTime = LocalDateTime.now() | ||||||
|  |         val dailyRankingStartDate = currentDateTime | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusDays(2) | ||||||
|  |         val dailyRankingEndDate = dailyRankingStartDate | ||||||
|  |             .plusDays(1) | ||||||
|  |  | ||||||
|  |         return rankingService.getContentRanking( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = dailyRankingStartDate, | ||||||
|  |             endDate = dailyRankingEndDate | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getNewContentByTheme( | ||||||
|  |         theme: String, | ||||||
|  |         isAdultContentVisible: Boolean, | ||||||
|  |         contentType: ContentType, | ||||||
|  |         member: Member | ||||||
|  |     ): List<GetAudioContentMainItem> { | ||||||
|  |         return audioContentRepository.findByTheme( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             theme = theme, | ||||||
|  |             isAdult = member.auth != null && isAdultContentVisible, | ||||||
|  |             contentType = contentType, | ||||||
|  |             offset = 0, | ||||||
|  |             limit = 10 | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse { | ||||||
|  |         val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return GetPopularContentByCreatorResponse( | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             salesCountRankContentList = salesCountRankContentList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,10 +2,13 @@ package kr.co.vividnext.sodalive.content.main.tab.free | |||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.common.ApiResponse | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
| import kr.co.vividnext.sodalive.common.SodaException | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.content.ContentType | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.data.domain.Pageable | ||||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -19,4 +22,42 @@ class AudioContentMainTabFreeController(private val service: AudioContentMainTab | |||||||
|  |  | ||||||
|         ApiResponse.ok(service.fetchData(member)) |         ApiResponse.ok(service.fetchData(member)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/introduce-creator") | ||||||
|  |     fun getIntroduceCreator( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getIntroduceCreator( | ||||||
|  |                 member, | ||||||
|  |                 offset = pageable.offset, | ||||||
|  |                 limit = pageable.pageSize.toLong() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/new-content-by-theme") | ||||||
|  |     fun getNewContentByTheme( | ||||||
|  |         @RequestParam("theme") theme: String, | ||||||
|  |         @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, | ||||||
|  |         @RequestParam("contentType", required = false) contentType: ContentType? = null, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getNewContentByTheme( | ||||||
|  |                 theme, | ||||||
|  |                 isAdultContentVisible = isAdultContentVisible ?: true, | ||||||
|  |                 contentType = contentType ?: ContentType.ALL, | ||||||
|  |                 member, | ||||||
|  |                 offset = pageable.offset, | ||||||
|  |                 limit = pageable.pageSize.toLong() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.content.main.tab.free | |||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.content.AudioContentRepository | import kr.co.vividnext.sodalive.content.AudioContentRepository | ||||||
| import kr.co.vividnext.sodalive.content.ContentType | import kr.co.vividnext.sodalive.content.ContentType | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | 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.curation.AudioContentCurationQueryRepository | ||||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
| @@ -38,10 +39,10 @@ class AudioContentMainTabFreeService( | |||||||
|             .map { |             .map { | ||||||
|                 GetContentCurationResponse( |                 GetContentCurationResponse( | ||||||
|                     title = it.title, |                     title = it.title, | ||||||
|                     items = contentRepository.findAudioContentByCurationId( |                     items = contentRepository.findAudioContentByCurationIdV2( | ||||||
|                         curationId = it.id!!, |                         curationId = it.id!!, | ||||||
|                         isAdult = isAdult, |                         memberId = memberId, | ||||||
|                         contentType = ContentType.ALL |                         isAdult = isAdult | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| @@ -64,13 +65,14 @@ class AudioContentMainTabFreeService( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) |         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) | ||||||
|  |             .filter { it.title != "크리에이터 소개" } | ||||||
|             .map { |             .map { | ||||||
|                 GetContentCurationResponse( |                 GetContentCurationResponse( | ||||||
|                     title = it.title, |                     title = it.title, | ||||||
|                     items = contentRepository.findAudioContentByCurationId( |                     items = contentRepository.findAudioContentByCurationIdV2( | ||||||
|                         curationId = it.id!!, |                         curationId = it.id!!, | ||||||
|                         isAdult = isAdult, |                         memberId = memberId, | ||||||
|                         contentType = ContentType.ALL |                         isAdult = isAdult | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| @@ -88,4 +90,46 @@ class AudioContentMainTabFreeService( | |||||||
|             curationList = curationList |             curationList = curationList | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getIntroduceCreator(member: Member, offset: Long, limit: Long): List<GetAudioContentMainItem> { | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |         val memberId = member.id!! | ||||||
|  |  | ||||||
|  |         val introduceCreatorCuration = curationRepository.findByContentMainTabIdAndTitle( | ||||||
|  |             tabId = 7L, | ||||||
|  |             title = "크리에이터 소개", | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return if (introduceCreatorCuration.isNotEmpty()) { | ||||||
|  |             contentRepository.findAudioContentByCurationIdV2( | ||||||
|  |                 curationId = introduceCreatorCuration[0].id!!, | ||||||
|  |                 memberId = memberId, | ||||||
|  |                 isAdult = isAdult, | ||||||
|  |                 offset = offset, | ||||||
|  |                 limit = limit | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getNewContentByTheme( | ||||||
|  |         theme: String, | ||||||
|  |         isAdultContentVisible: Boolean, | ||||||
|  |         contentType: ContentType, | ||||||
|  |         member: Member, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): List<GetAudioContentMainItem> { | ||||||
|  |         return audioContentRepository.findByTheme( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             theme = theme, | ||||||
|  |             isAdult = member.auth != null && isAdultContentVisible, | ||||||
|  |             contentType = contentType, | ||||||
|  |             offset = offset, | ||||||
|  |             limit = limit, | ||||||
|  |             isFree = true | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.member.Member | |||||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -19,4 +20,19 @@ class AudioContentMainTabHomeController(private val service: AudioContentMainTab | |||||||
|  |  | ||||||
|         ApiResponse.ok(service.fetchData(member)) |         ApiResponse.ok(service.fetchData(member)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/popular-content-by-creator") | ||||||
|  |     fun getPopularContentByCreator( | ||||||
|  |         @RequestParam creatorId: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getPopularContentByCreator( | ||||||
|  |                 creatorId = creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package kr.co.vividnext.sodalive.content.main.tab.home | package kr.co.vividnext.sodalive.content.main.tab.home | ||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse | ||||||
| import kr.co.vividnext.sodalive.event.EventService | import kr.co.vividnext.sodalive.event.EventService | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| import kr.co.vividnext.sodalive.notice.ServiceNoticeService | import kr.co.vividnext.sodalive.notice.ServiceNoticeService | ||||||
| @@ -114,4 +115,21 @@ class AudioContentMainTabHomeService( | |||||||
|             salesCountRankContentList = salesCountRankContentList |             salesCountRankContentList = salesCountRankContentList | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse { | ||||||
|  |         val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return GetPopularContentByCreatorResponse( | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             salesCountRankContentList = salesCountRankContentList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,11 +6,11 @@ import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerRespons | |||||||
| import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse | import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse | ||||||
| import kr.co.vividnext.sodalive.event.GetEventResponse | import kr.co.vividnext.sodalive.event.GetEventResponse | ||||||
| import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse | import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse | ||||||
| import kr.co.vividnext.sodalive.notice.NoticeTitleItem | import kr.co.vividnext.sodalive.notice.NoticeItem | ||||||
|  |  | ||||||
| data class GetContentMainTabHomeResponse( | data class GetContentMainTabHomeResponse( | ||||||
|     val tabId: Long = 1, |     val tabId: Long = 1, | ||||||
|     val latestNotice: NoticeTitleItem?, |     val latestNotice: NoticeItem?, | ||||||
|     val bannerList: List<GetAudioContentBannerResponse>, |     val bannerList: List<GetAudioContentBannerResponse>, | ||||||
|     val rankCreatorList: GetExplorerSectionResponse, |     val rankCreatorList: GetExplorerSectionResponse, | ||||||
|     val rankSeriesList: List<GetSeriesListResponse.SeriesListItem>, |     val rankSeriesList: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.member.Member | |||||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -19,4 +20,19 @@ class AudioContentMainTabLiveReplayController(private val service: AudioContentM | |||||||
|  |  | ||||||
|         ApiResponse.ok(service.fetchData(member)) |         ApiResponse.ok(service.fetchData(member)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/popular-content-by-creator") | ||||||
|  |     fun getPopularContentByCreator( | ||||||
|  |         @RequestParam creatorId: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getPopularContentByCreator( | ||||||
|  |                 creatorId = creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,9 @@ import kr.co.vividnext.sodalive.content.AudioContentRepository | |||||||
| import kr.co.vividnext.sodalive.content.ContentType | import kr.co.vividnext.sodalive.content.ContentType | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | 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.curation.AudioContentCurationQueryRepository | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository | ||||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse | ||||||
| import kr.co.vividnext.sodalive.event.EventService | import kr.co.vividnext.sodalive.event.EventService | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| import kr.co.vividnext.sodalive.rank.RankingService | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
| @@ -15,6 +17,7 @@ import java.time.temporal.TemporalAdjusters | |||||||
|  |  | ||||||
| @Service | @Service | ||||||
| class AudioContentMainTabLiveReplayService( | class AudioContentMainTabLiveReplayService( | ||||||
|  |     private val repository: AudioContentMainTabRepository, | ||||||
|     private val bannerService: AudioContentBannerService, |     private val bannerService: AudioContentBannerService, | ||||||
|     private val contentRepository: AudioContentRepository, |     private val contentRepository: AudioContentRepository, | ||||||
|     private val rankingService: RankingService, |     private val rankingService: RankingService, | ||||||
| @@ -59,16 +62,42 @@ class AudioContentMainTabLiveReplayService( | |||||||
|             theme = theme |             theme = theme | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |         val creatorList = repository.findCreatorByThemeContent( | ||||||
|  |             memberId = memberId, | ||||||
|  |             theme = theme, | ||||||
|  |             minCount = 4 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesRankContentList = if (creatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorContentBySalesTop2( | ||||||
|  |                 creatorId = creatorList[0].creatorId, | ||||||
|  |                 isAdult = isAdult, | ||||||
|  |                 theme = theme | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val salesCountRankContentList = if (creatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorContentBySalesCountTop2( | ||||||
|  |                 creatorId = creatorList[0].creatorId, | ||||||
|  |                 isAdult = isAdult, | ||||||
|  |                 theme = theme | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|         val eventBannerList = eventService.getEventList(isAdult = isAdult) |         val eventBannerList = eventService.getEventList(isAdult = isAdult) | ||||||
|  |  | ||||||
|         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) |         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) | ||||||
|             .map { |             .map { | ||||||
|                 GetContentCurationResponse( |                 GetContentCurationResponse( | ||||||
|                     title = it.title, |                     title = it.title, | ||||||
|                     items = contentRepository.findAudioContentByCurationId( |                     items = contentRepository.findAudioContentByCurationIdV2( | ||||||
|                         curationId = it.id!!, |                         curationId = it.id!!, | ||||||
|                         isAdult = isAdult, |                         memberId = memberId, | ||||||
|                         contentType = ContentType.ALL |                         isAdult = isAdult | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| @@ -77,8 +106,31 @@ class AudioContentMainTabLiveReplayService( | |||||||
|             contentBannerList = contentBannerList, |             contentBannerList = contentBannerList, | ||||||
|             newLiveReplayContentList = newLiveReplayContentList, |             newLiveReplayContentList = newLiveReplayContentList, | ||||||
|             rankLiveReplayContentList = rankLiveReplayContentList, |             rankLiveReplayContentList = rankLiveReplayContentList, | ||||||
|  |             creatorList = creatorList, | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             salesCountRankContentList = salesCountRankContentList, | ||||||
|             eventBannerList = eventBannerList, |             eventBannerList = eventBannerList, | ||||||
|             curationList = curationList |             curationList = curationList | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse { | ||||||
|  |         val theme = "다시듣기" | ||||||
|  |         val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             theme = theme | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             theme = theme | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return GetPopularContentByCreatorResponse( | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             salesCountRankContentList = salesCountRankContentList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package kr.co.vividnext.sodalive.content.main.tab.replay | package kr.co.vividnext.sodalive.content.main.tab.replay | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse | ||||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse | import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse | ||||||
| @@ -11,6 +12,9 @@ data class GetContentMainTabLiveReplayResponse( | |||||||
|     val contentBannerList: List<GetAudioContentBannerResponse>, |     val contentBannerList: List<GetAudioContentBannerResponse>, | ||||||
|     val newLiveReplayContentList: List<GetAudioContentMainItem>, |     val newLiveReplayContentList: List<GetAudioContentMainItem>, | ||||||
|     val rankLiveReplayContentList: List<GetAudioContentRankingItem>, |     val rankLiveReplayContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val creatorList: List<ContentCreatorResponse>, | ||||||
|  |     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val salesCountRankContentList: List<GetAudioContentRankingItem>, | ||||||
|     val eventBannerList: GetEventResponse, |     val eventBannerList: GetEventResponse, | ||||||
|     val curationList: List<GetContentCurationResponse> |     val curationList: List<GetContentCurationResponse> | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -3,9 +3,11 @@ package kr.co.vividnext.sodalive.content.main.tab.series | |||||||
| import kr.co.vividnext.sodalive.common.ApiResponse | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
| import kr.co.vividnext.sodalive.common.SodaException | import kr.co.vividnext.sodalive.common.SodaException | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.data.domain.Pageable | ||||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -19,4 +21,69 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT | |||||||
|  |  | ||||||
|         ApiResponse.ok(service.fetchData(member)) |         ApiResponse.ok(service.fetchData(member)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/original") | ||||||
|  |     fun getOriginalAudioDramaList( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getOriginalAudioDramaList( | ||||||
|  |                 memberId = member.id!!, | ||||||
|  |                 isAdult = member.auth != null, | ||||||
|  |                 offset = pageable.offset, | ||||||
|  |                 limit = pageable.pageSize.toLong() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/completed-monthly-rank") | ||||||
|  |     fun getRankMonthlyCompletedSeriesList( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getRankMonthlyCompletedSeriesList( | ||||||
|  |                 memberId = member.id!!, | ||||||
|  |                 isAdult = member.auth != null, | ||||||
|  |                 offset = pageable.offset, | ||||||
|  |                 limit = pageable.pageSize.toLong() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/recommend-by-genre") | ||||||
|  |     fun getRecommendSeriesListByGenre( | ||||||
|  |         @RequestParam genreId: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getRecommendSeriesListByGenre( | ||||||
|  |                 genreId, | ||||||
|  |                 memberId = member.id!!, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/recommend-series-by-creator") | ||||||
|  |     fun getRecommendSeriesByCreator( | ||||||
|  |         @RequestParam creatorId: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getRecommendSeriesByCreator( | ||||||
|  |                 creatorId = creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ 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.curation.AudioContentCurationQueryRepository | ||||||
| import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository | import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository | ||||||
| import kr.co.vividnext.sodalive.content.series.ContentSeriesService | import kr.co.vividnext.sodalive.content.series.ContentSeriesService | ||||||
|  | import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse | ||||||
| import kr.co.vividnext.sodalive.event.EventService | import kr.co.vividnext.sodalive.event.EventService | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| import kr.co.vividnext.sodalive.rank.RankingService | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
| @@ -23,15 +24,17 @@ class AudioContentMainTabSeriesService( | |||||||
| ) { | ) { | ||||||
|     fun fetchData(member: Member): GetContentMainTabSeriesResponse { |     fun fetchData(member: Member): GetContentMainTabSeriesResponse { | ||||||
|         val isAdult = member.auth != null |         val isAdult = member.auth != null | ||||||
|  |         val memberId = member.id!! | ||||||
|  |  | ||||||
|         // 메인 배너 (시리즈) |         // 메인 배너 (시리즈) | ||||||
|         val contentBannerList = bannerService.getBannerList( |         val contentBannerList = bannerService.getBannerList( | ||||||
|             tabId = 2, |             tabId = 2, | ||||||
|             memberId = member.id!!, |             memberId = memberId, | ||||||
|             isAdult = isAdult |             isAdult = isAdult | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         val originalAudioDrama = seriesService.getOriginalAudioDramaList( |         val originalAudioDrama = seriesService.getOriginalAudioDramaList( | ||||||
|  |             memberId = memberId, | ||||||
|             isAdult = isAdult, |             isAdult = isAdult, | ||||||
|             offset = 0, |             offset = 0, | ||||||
|             limit = 20 |             limit = 20 | ||||||
| @@ -48,7 +51,7 @@ class AudioContentMainTabSeriesService( | |||||||
|             .plusDays(1) |             .plusDays(1) | ||||||
|  |  | ||||||
|         val rankSeriesList = rankingService.getSeriesRanking( |         val rankSeriesList = rankingService.getSeriesRanking( | ||||||
|             memberId = member.id!!, |             memberId = memberId, | ||||||
|             isAdult = isAdult, |             isAdult = isAdult, | ||||||
|             startDate = dailyRankingStartDate, |             startDate = dailyRankingStartDate, | ||||||
|             endDate = dailyRankingEndDate |             endDate = dailyRankingEndDate | ||||||
| @@ -60,7 +63,7 @@ class AudioContentMainTabSeriesService( | |||||||
|         // 장르별 추천 시리즈 |         // 장르별 추천 시리즈 | ||||||
|         val recommendSeriesList = if (genreList.isNotEmpty()) { |         val recommendSeriesList = if (genreList.isNotEmpty()) { | ||||||
|             rankingService.getSeriesAllRankingByGenre( |             rankingService.getSeriesAllRankingByGenre( | ||||||
|                 memberId = member.id!!, |                 memberId = memberId, | ||||||
|                 isAdult = isAdult, |                 isAdult = isAdult, | ||||||
|                 genreId = genreList[0].id |                 genreId = genreList[0].id | ||||||
|             ) |             ) | ||||||
| @@ -82,7 +85,7 @@ class AudioContentMainTabSeriesService( | |||||||
|             .plusMonths(1) |             .plusMonths(1) | ||||||
|  |  | ||||||
|         val rankCompleteSeriesList = rankingService.getCompleteSeriesRanking( |         val rankCompleteSeriesList = rankingService.getCompleteSeriesRanking( | ||||||
|             memberId = member.id!!, |             memberId = memberId, | ||||||
|             isAdult = isAdult, |             isAdult = isAdult, | ||||||
|             startDate = monthlyRankingStartDate, |             startDate = monthlyRankingStartDate, | ||||||
|             endDate = monthlyRankingEndDate |             endDate = monthlyRankingEndDate | ||||||
| @@ -98,7 +101,7 @@ class AudioContentMainTabSeriesService( | |||||||
|             .plusDays(6) |             .plusDays(6) | ||||||
|  |  | ||||||
|         val seriesRankCreatorList = rankingService.fetchCreatorBySeriesRevenueRankTop20( |         val seriesRankCreatorList = rankingService.fetchCreatorBySeriesRevenueRankTop20( | ||||||
|             memberId = member.id!!, |             memberId = memberId, | ||||||
|             startDate = startDate.minusDays(1), |             startDate = startDate.minusDays(1), | ||||||
|             endDate = endDate |             endDate = endDate | ||||||
|         ) |         ) | ||||||
| @@ -122,7 +125,7 @@ class AudioContentMainTabSeriesService( | |||||||
|                     title = it.title, |                     title = it.title, | ||||||
|                     items = seriesService.fetchSeriesByCurationId( |                     items = seriesService.fetchSeriesByCurationId( | ||||||
|                         curationId = it.id!!, |                         curationId = it.id!!, | ||||||
|                         memberId = member.id!!, |                         memberId = memberId, | ||||||
|                         isAdult = isAdult |                         isAdult = isAdult | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
| @@ -141,9 +144,79 @@ class AudioContentMainTabSeriesService( | |||||||
|             newSeriesList = newSeriesList, |             newSeriesList = newSeriesList, | ||||||
|             rankCompleteSeriesList = rankCompleteSeriesList, |             rankCompleteSeriesList = rankCompleteSeriesList, | ||||||
|             seriesRankCreatorList = seriesRankCreatorList, |             seriesRankCreatorList = seriesRankCreatorList, | ||||||
|             salesRankContentList = salesRankContentList, |             recommendSeriesByChannel = salesRankContentList, | ||||||
|             eventBannerList = eventBannerList, |             eventBannerList = eventBannerList, | ||||||
|             curationList = curationList |             curationList = curationList | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getOriginalAudioDramaList( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): GetSeriesListResponse { | ||||||
|  |         val totalCount = seriesService.getOriginalAudioDramaTotalCount(memberId, isAdult) | ||||||
|  |         val items = seriesService.getOriginalAudioDramaList( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             offset = offset, | ||||||
|  |             limit = limit | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return GetSeriesListResponse(totalCount, items) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getRankMonthlyCompletedSeriesList( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): GetSeriesListResponse { | ||||||
|  |         val monthlyRankingStartDate = LocalDateTime.now() | ||||||
|  |             .withDayOfMonth(1) | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusDays(1) | ||||||
|  |         val monthlyRankingEndDate = monthlyRankingStartDate | ||||||
|  |             .plusMonths(1) | ||||||
|  |  | ||||||
|  |         val totalCount = rankingService.getCompleteSeriesRankingTotalCount( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = monthlyRankingStartDate, | ||||||
|  |             endDate = monthlyRankingEndDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val items = rankingService.getCompleteSeriesRanking( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = monthlyRankingStartDate, | ||||||
|  |             endDate = monthlyRankingEndDate, | ||||||
|  |             offset = offset, | ||||||
|  |             limit = limit | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return GetSeriesListResponse(totalCount, items) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getRecommendSeriesListByGenre( | ||||||
|  |         genreId: Long, | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean | ||||||
|  |     ): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         return rankingService.getSeriesAllRankingByGenre( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             genreId = genreId | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getRecommendSeriesByCreator(creatorId: Long, isAdult: Boolean): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         return rankingService.fetchCreatorSeriesBySales( | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ data class GetContentMainTabSeriesResponse( | |||||||
|     val newSeriesList: List<GetRecommendSeriesListResponse>, |     val newSeriesList: List<GetRecommendSeriesListResponse>, | ||||||
|     val rankCompleteSeriesList: List<GetSeriesListResponse.SeriesListItem>, |     val rankCompleteSeriesList: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|     val seriesRankCreatorList: List<ContentCreatorResponse>, |     val seriesRankCreatorList: List<ContentCreatorResponse>, | ||||||
|     val salesRankContentList: List<GetSeriesListResponse.SeriesListItem>, |     val recommendSeriesByChannel: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|     val eventBannerList: GetEventResponse, |     val eventBannerList: GetEventResponse, | ||||||
|     val curationList: List<GetSeriesCurationResponse> |     val curationList: List<GetSeriesCurationResponse> | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -35,7 +35,8 @@ interface ContentSeriesQueryRepository { | |||||||
|     fun getKeywordList(seriesId: Long): List<String> |     fun getKeywordList(seriesId: Long): List<String> | ||||||
|     fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse |     fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse | ||||||
|     fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series> |     fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series> | ||||||
|     fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long = 0, limit: Long = 20): List<Series> |     fun getOriginalAudioDramaList(memberId: Long, isAdult: Boolean, offset: Long = 0, limit: Long = 20): List<Series> | ||||||
|  |     fun getOriginalAudioDramaTotalCount(memberId: Long, isAdult: Boolean): Int | ||||||
|     fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> |     fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> | ||||||
|     fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List<Series> |     fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List<Series> | ||||||
| } | } | ||||||
| @@ -143,9 +144,14 @@ class ContentSeriesQueryRepositoryImpl( | |||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long, limit: Long): List<Series> { |     override fun getOriginalAudioDramaList(memberId: Long, isAdult: Boolean, offset: Long, limit: Long): List<Series> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|         var where = series.isOriginal.isTrue |         var where = series.isOriginal.isTrue | ||||||
|             .and(series.isActive.isTrue) |             .and(series.isActive.isTrue) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|         if (!isAdult) { |         if (!isAdult) { | ||||||
|             where = where.and(series.isAdult.isFalse) |             where = where.and(series.isAdult.isFalse) | ||||||
| @@ -153,13 +159,38 @@ class ContentSeriesQueryRepositoryImpl( | |||||||
|  |  | ||||||
|         return queryFactory |         return queryFactory | ||||||
|             .selectFrom(series) |             .selectFrom(series) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|             .where(where) |             .where(where) | ||||||
|             .orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc()) |             .orderBy(series.id.desc()) | ||||||
|             .offset(offset) |             .offset(offset) | ||||||
|             .limit(limit) |             .limit(limit) | ||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun getOriginalAudioDramaTotalCount(memberId: Long, isAdult: Boolean): Int { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         var where = series.isOriginal.isTrue | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(series.id) | ||||||
|  |             .from(series) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .fetch() | ||||||
|  |             .size | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> { |     override fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> { | ||||||
|         var where = seriesGenre.isActive.isTrue |         var where = seriesGenre.isActive.isTrue | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,12 +30,17 @@ class ContentSeriesService( | |||||||
|     @Value("\${cloud.aws.cloud-front.host}") |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|     private val coverImageHost: String |     private val coverImageHost: String | ||||||
| ) { | ) { | ||||||
|  |     fun getOriginalAudioDramaTotalCount(memberId: Long, isAdult: Boolean): Int { | ||||||
|  |         return repository.getOriginalAudioDramaTotalCount(memberId, isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun getOriginalAudioDramaList( |     fun getOriginalAudioDramaList( | ||||||
|  |         memberId: Long, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
|         offset: Long = 0, |         offset: Long = 0, | ||||||
|         limit: Long = 20 |         limit: Long = 20 | ||||||
|     ): List<GetSeriesListResponse.SeriesListItem> { |     ): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|         val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, offset, limit) |         val originalAudioDramaList = repository.getOriginalAudioDramaList(memberId, isAdult, offset, limit) | ||||||
|         return seriesToSeriesListItem(originalAudioDramaList, isAdult) |         return seriesToSeriesListItem(originalAudioDramaList, isAdult) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -219,6 +224,9 @@ class ContentSeriesService( | |||||||
|  |  | ||||||
|                 it |                 it | ||||||
|             } |             } | ||||||
|  |             .filter { | ||||||
|  |                 it.numberOfContent > 0 | ||||||
|  |             } | ||||||
|             .map { |             .map { | ||||||
|                 val nowDateTime = LocalDateTime.now() |                 val nowDateTime = LocalDateTime.now() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,14 +7,9 @@ data class GetNoticeResponse( | |||||||
|     val noticeList: List<NoticeItem> |     val noticeList: List<NoticeItem> | ||||||
| ) | ) | ||||||
|  |  | ||||||
| data class NoticeItem( | data class NoticeItem @QueryProjection constructor( | ||||||
|     val id: Long, |     val id: Long, | ||||||
|     val title: String, |     val title: String, | ||||||
|     val content: String, |     val content: String, | ||||||
|     val date: String |     val date: String | ||||||
| ) | ) | ||||||
|  |  | ||||||
| data class NoticeTitleItem @QueryProjection constructor( |  | ||||||
|     val id: Long, |  | ||||||
|     val title: String |  | ||||||
| ) |  | ||||||
|   | |||||||
| @@ -5,8 +5,6 @@ import org.springframework.data.domain.Pageable | |||||||
| import org.springframework.data.repository.findByIdOrNull | import org.springframework.data.repository.findByIdOrNull | ||||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||||
| import java.time.ZoneId |  | ||||||
| import java.time.format.DateTimeFormatter |  | ||||||
|  |  | ||||||
| @Service | @Service | ||||||
| @Transactional(readOnly = true) | @Transactional(readOnly = true) | ||||||
| @@ -46,25 +44,11 @@ class ServiceNoticeService(private val repository: ServiceServiceNoticeRepositor | |||||||
|     fun getNoticeList(pageable: Pageable, timezone: String): GetNoticeResponse { |     fun getNoticeList(pageable: Pageable, timezone: String): GetNoticeResponse { | ||||||
|         val totalCount = repository.getNoticeTotalCount() |         val totalCount = repository.getNoticeTotalCount() | ||||||
|         val noticeList = repository.getNoticeList(pageable) |         val noticeList = repository.getNoticeList(pageable) | ||||||
|             .asSequence() |  | ||||||
|             .map { |  | ||||||
|                 val createdAt = it.createdAt!! |  | ||||||
|                     .atZone(ZoneId.of("UTC")) |  | ||||||
|                     .withZoneSameInstant(ZoneId.of(timezone)) |  | ||||||
|  |  | ||||||
|                 NoticeItem( |  | ||||||
|                     it.id!!, |  | ||||||
|                     it.title, |  | ||||||
|                     it.content, |  | ||||||
|                     createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|             .toList() |  | ||||||
|  |  | ||||||
|         return GetNoticeResponse(totalCount, noticeList) |         return GetNoticeResponse(totalCount, noticeList) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getLatestNotice(): NoticeTitleItem? { |     fun getLatestNotice(): NoticeItem? { | ||||||
|         return repository.getLatestNotice() |         return repository.getLatestNotice() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,18 +1,22 @@ | |||||||
| package kr.co.vividnext.sodalive.notice | package kr.co.vividnext.sodalive.notice | ||||||
|  |  | ||||||
|  | import com.querydsl.core.types.dsl.DateTimePath | ||||||
|  | import com.querydsl.core.types.dsl.Expressions | ||||||
|  | import com.querydsl.core.types.dsl.StringTemplate | ||||||
| import com.querydsl.jpa.impl.JPAQueryFactory | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
| import kr.co.vividnext.sodalive.notice.QServiceNotice.serviceNotice | import kr.co.vividnext.sodalive.notice.QServiceNotice.serviceNotice | ||||||
| import org.springframework.data.domain.Pageable | import org.springframework.data.domain.Pageable | ||||||
| import org.springframework.data.jpa.repository.JpaRepository | import org.springframework.data.jpa.repository.JpaRepository | ||||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||||
|  | import java.time.LocalDateTime | ||||||
|  |  | ||||||
| @Repository | @Repository | ||||||
| interface ServiceServiceNoticeRepository : JpaRepository<ServiceNotice, Long>, ServiceNoticeQueryRepository | interface ServiceServiceNoticeRepository : JpaRepository<ServiceNotice, Long>, ServiceNoticeQueryRepository | ||||||
|  |  | ||||||
| interface ServiceNoticeQueryRepository { | interface ServiceNoticeQueryRepository { | ||||||
|     fun getNoticeTotalCount(): Int |     fun getNoticeTotalCount(): Int | ||||||
|     fun getNoticeList(pageable: Pageable): List<ServiceNotice> |     fun getNoticeList(pageable: Pageable): List<NoticeItem> | ||||||
|     fun getLatestNotice(): NoticeTitleItem? |     fun getLatestNotice(): NoticeItem? | ||||||
| } | } | ||||||
|  |  | ||||||
| @Repository | @Repository | ||||||
| @@ -26,9 +30,17 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory | |||||||
|             .size |             .size | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun getNoticeList(pageable: Pageable): List<ServiceNotice> { |     override fun getNoticeList(pageable: Pageable): List<NoticeItem> { | ||||||
|         return queryFactory |         return queryFactory | ||||||
|             .selectFrom(serviceNotice) |             .select( | ||||||
|  |                 QNoticeItem( | ||||||
|  |                     serviceNotice.id, | ||||||
|  |                     serviceNotice.title, | ||||||
|  |                     serviceNotice.content, | ||||||
|  |                     getFormattedDate(serviceNotice.createdAt) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(serviceNotice) | ||||||
|             .where(serviceNotice.isActive.isTrue) |             .where(serviceNotice.isActive.isTrue) | ||||||
|             .offset(pageable.offset) |             .offset(pageable.offset) | ||||||
|             .limit(pageable.pageSize.toLong()) |             .limit(pageable.pageSize.toLong()) | ||||||
| @@ -36,12 +48,14 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory | |||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun getLatestNotice(): NoticeTitleItem? { |     override fun getLatestNotice(): NoticeItem? { | ||||||
|         return queryFactory |         return queryFactory | ||||||
|             .select( |             .select( | ||||||
|                 QNoticeTitleItem( |                 QNoticeItem( | ||||||
|                     serviceNotice.id, |                     serviceNotice.id, | ||||||
|                     serviceNotice.title |                     serviceNotice.title, | ||||||
|  |                     serviceNotice.content, | ||||||
|  |                     getFormattedDate(serviceNotice.createdAt) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|             .from(serviceNotice) |             .from(serviceNotice) | ||||||
| @@ -49,4 +63,18 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory | |||||||
|             .orderBy(serviceNotice.id.desc()) |             .orderBy(serviceNotice.id.desc()) | ||||||
|             .fetchFirst() |             .fetchFirst() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun getFormattedDate(dateTimePath: DateTimePath<LocalDateTime>): StringTemplate { | ||||||
|  |         return Expressions.stringTemplate( | ||||||
|  |             "DATE_FORMAT({0}, {1})", | ||||||
|  |             Expressions.dateTimeTemplate( | ||||||
|  |                 LocalDateTime::class.java, | ||||||
|  |                 "CONVERT_TZ({0},{1},{2})", | ||||||
|  |                 dateTimePath, | ||||||
|  |                 "UTC", | ||||||
|  |                 "Asia/Seoul" | ||||||
|  |             ), | ||||||
|  |             "%Y-%m-%d" | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -82,7 +82,8 @@ class RankingRepository( | |||||||
|                     audioContent.price, |                     audioContent.price, | ||||||
|                     audioContent.duration, |                     audioContent.duration, | ||||||
|                     member.id, |                     member.id, | ||||||
|                     member.nickname |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
| @@ -208,6 +209,48 @@ class RankingRepository( | |||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getCompleteSeriesRankingTotalCount( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime | ||||||
|  |     ): Int { | ||||||
|  |         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.id) | ||||||
|  |             .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()) | ||||||
|  |             .fetch() | ||||||
|  |             .size | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun getCompleteSeriesRanking( |     fun getCompleteSeriesRanking( | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
| @@ -247,10 +290,7 @@ class RankingRepository( | |||||||
|             .leftJoin(blockMember).on(blockMemberCondition) |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|             .where(where) |             .where(where) | ||||||
|             .groupBy(series.id) |             .groupBy(series.id) | ||||||
|             .orderBy( |             .orderBy(order.can.sum().desc()) | ||||||
|                 order.can.sum().desc(), |  | ||||||
|                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() |  | ||||||
|             ) |  | ||||||
|             .offset(offset) |             .offset(offset) | ||||||
|             .limit(limit) |             .limit(limit) | ||||||
|             .fetch() |             .fetch() | ||||||
| @@ -288,7 +328,7 @@ class RankingRepository( | |||||||
|             .where(where) |             .where(where) | ||||||
|             .groupBy(series.id) |             .groupBy(series.id) | ||||||
|             .orderBy( |             .orderBy( | ||||||
|                 order.can.sum().desc(), |                 order.id.countDistinct().desc(), | ||||||
|                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() |                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||||
|             ) |             ) | ||||||
|             .offset(0) |             .offset(0) | ||||||
| @@ -341,12 +381,17 @@ class RankingRepository( | |||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { |     fun fetchCreatorContentBySalesTop2( | ||||||
|  |         creatorId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         theme: String | ||||||
|  |     ): List<GetAudioContentRankingItem> { | ||||||
|         var where = member.isActive.isTrue |         var where = member.isActive.isTrue | ||||||
|             .and(member.role.eq(MemberRole.CREATOR)) |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|             .and(audioContent.isActive.isTrue) |             .and(audioContent.isActive.isTrue) | ||||||
|             .and(audioContent.duration.isNotNull) |             .and(audioContent.duration.isNotNull) | ||||||
|             .and(audioContent.limited.isNull) |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(audioContentTheme.isActive.isTrue) | ||||||
|             .and(order.isActive.isTrue) |             .and(order.isActive.isTrue) | ||||||
|             .and(member.id.eq(creatorId)) |             .and(member.id.eq(creatorId)) | ||||||
|  |  | ||||||
| @@ -354,6 +399,10 @@ class RankingRepository( | |||||||
|             where = where.and(series.isAdult.isFalse) |             where = where.and(series.isAdult.isFalse) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (theme.isNotBlank()) { | ||||||
|  |             where = where.and(audioContentTheme.theme.eq(theme)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return queryFactory |         return queryFactory | ||||||
|             .select( |             .select( | ||||||
|                 QGetAudioContentRankingItem( |                 QGetAudioContentRankingItem( | ||||||
| @@ -364,11 +413,13 @@ class RankingRepository( | |||||||
|                     audioContent.price, |                     audioContent.price, | ||||||
|                     audioContent.duration, |                     audioContent.duration, | ||||||
|                     member.id, |                     member.id, | ||||||
|                     member.nickname |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|             .from(order) |             .from(order) | ||||||
|             .innerJoin(order.audioContent, audioContent) |             .innerJoin(order.audioContent, audioContent) | ||||||
|  |             .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|             .innerJoin(audioContent.member, member) |             .innerJoin(audioContent.member, member) | ||||||
|             .where(where) |             .where(where) | ||||||
|             .groupBy(audioContent.id) |             .groupBy(audioContent.id) | ||||||
| @@ -378,12 +429,17 @@ class RankingRepository( | |||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { |     fun fetchCreatorContentBySalesCountTop2( | ||||||
|  |         creatorId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         theme: String | ||||||
|  |     ): List<GetAudioContentRankingItem> { | ||||||
|         var where = member.isActive.isTrue |         var where = member.isActive.isTrue | ||||||
|             .and(member.role.eq(MemberRole.CREATOR)) |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|             .and(audioContent.isActive.isTrue) |             .and(audioContent.isActive.isTrue) | ||||||
|             .and(audioContent.duration.isNotNull) |             .and(audioContent.duration.isNotNull) | ||||||
|             .and(audioContent.limited.isNull) |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(audioContentTheme.isActive.isTrue) | ||||||
|             .and(order.isActive.isTrue) |             .and(order.isActive.isTrue) | ||||||
|             .and(member.id.eq(creatorId)) |             .and(member.id.eq(creatorId)) | ||||||
|  |  | ||||||
| @@ -391,6 +447,10 @@ class RankingRepository( | |||||||
|             where = where.and(series.isAdult.isFalse) |             where = where.and(series.isAdult.isFalse) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (theme.isNotBlank()) { | ||||||
|  |             where = where.and(audioContentTheme.theme.eq(theme)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return queryFactory |         return queryFactory | ||||||
|             .select( |             .select( | ||||||
|                 QGetAudioContentRankingItem( |                 QGetAudioContentRankingItem( | ||||||
| @@ -401,11 +461,13 @@ class RankingRepository( | |||||||
|                     audioContent.price, |                     audioContent.price, | ||||||
|                     audioContent.duration, |                     audioContent.duration, | ||||||
|                     member.id, |                     member.id, | ||||||
|                     member.nickname |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|             .from(order) |             .from(order) | ||||||
|             .innerJoin(order.audioContent, audioContent) |             .innerJoin(order.audioContent, audioContent) | ||||||
|  |             .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|             .innerJoin(audioContent.member, member) |             .innerJoin(audioContent.member, member) | ||||||
|             .where(where) |             .where(where) | ||||||
|             .groupBy(audioContent.id) |             .groupBy(audioContent.id) | ||||||
|   | |||||||
| @@ -82,6 +82,20 @@ class RankingService( | |||||||
|         return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) |         return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getCompleteSeriesRankingTotalCount( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime | ||||||
|  |     ): Int { | ||||||
|  |         return repository.getCompleteSeriesRankingTotalCount( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = startDate, | ||||||
|  |             endDate = endDate | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun getCompleteSeriesRanking( |     fun getCompleteSeriesRanking( | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
| @@ -175,12 +189,20 @@ class RankingService( | |||||||
|         return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate) |         return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { |     fun fetchCreatorContentBySalesTop2( | ||||||
|         return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult) |         creatorId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         theme: String = "" | ||||||
|  |     ): List<GetAudioContentRankingItem> { | ||||||
|  |         return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult, theme) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { |     fun fetchCreatorContentBySalesCountTop2( | ||||||
|         return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult) |         creatorId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         theme: String = "" | ||||||
|  |     ): List<GetAudioContentRankingItem> { | ||||||
|  |         return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult, theme) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun fetchCreatorBySeriesRevenueRankTop20( |     fun fetchCreatorBySeriesRevenueRankTop20( | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user