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