| @@ -0,0 +1,40 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.content.series.recommend | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
|  | import org.springframework.security.access.prepost.PreAuthorize | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.PostMapping | ||||||
|  | import org.springframework.web.bind.annotation.PutMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestBody | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
|  | import org.springframework.web.bind.annotation.RequestPart | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  | import org.springframework.web.multipart.MultipartFile | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @PreAuthorize("hasRole('ADMIN')") | ||||||
|  | @RequestMapping("/admin/audio-content/series/recommend") | ||||||
|  | class AdminRecommendSeriesController(private val service: AdminRecommendSeriesService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun getRecommendSeriesList(@RequestParam isFree: Boolean) = ApiResponse.ok( | ||||||
|  |         service.getRecommendSeriesList(isFree = isFree) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     @PostMapping | ||||||
|  |     fun createRecommendSeries( | ||||||
|  |         @RequestPart("image") image: MultipartFile, | ||||||
|  |         @RequestPart("request") requestString: String | ||||||
|  |     ) = ApiResponse.ok(service.createRecommendSeries(image, requestString)) | ||||||
|  |  | ||||||
|  |     @PutMapping | ||||||
|  |     fun modifyRecommendSeries( | ||||||
|  |         @RequestPart("image", required = false) image: MultipartFile? = null, | ||||||
|  |         @RequestPart("request") requestString: String | ||||||
|  |     ) = ApiResponse.ok(service.updateRecommendSeries(image, requestString)) | ||||||
|  |  | ||||||
|  |     @PutMapping("/orders") | ||||||
|  |     fun updateRecommendSeriesOrders( | ||||||
|  |         @RequestBody request: UpdateRecommendSeriesOrdersRequest | ||||||
|  |     ) = ApiResponse.ok(service.updateRecommendSeriesOrders(request.ids)) | ||||||
|  | } | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.content.series.recommend | ||||||
|  |  | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.QRecommendSeries.recommendSeries | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.RecommendSeries | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||||
|  | import org.springframework.beans.factory.annotation.Value | ||||||
|  | import org.springframework.data.jpa.repository.JpaRepository | ||||||
|  |  | ||||||
|  | interface AdminRecommendSeriesRepository : | ||||||
|  |     JpaRepository<RecommendSeries, Long>, | ||||||
|  |     AdminRecommendSeriesQueryRepository | ||||||
|  |  | ||||||
|  | interface AdminRecommendSeriesQueryRepository { | ||||||
|  |     fun getRecommendSeriesList(isFree: Boolean): List<GetAdminRecommendSeriesListResponse> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class AdminRecommendSeriesQueryRepositoryImpl( | ||||||
|  |     private val queryFactory: JPAQueryFactory, | ||||||
|  |  | ||||||
|  |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|  |     private val imageHost: String | ||||||
|  | ) : AdminRecommendSeriesQueryRepository { | ||||||
|  |     override fun getRecommendSeriesList(isFree: Boolean): List<GetAdminRecommendSeriesListResponse> { | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetAdminRecommendSeriesListResponse( | ||||||
|  |                     recommendSeries.id, | ||||||
|  |                     series.id, | ||||||
|  |                     series.title, | ||||||
|  |                     recommendSeries.imagePath.prepend("/").prepend(imageHost) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(recommendSeries) | ||||||
|  |             .innerJoin(recommendSeries.series, series) | ||||||
|  |             .where( | ||||||
|  |                 recommendSeries.isActive.isTrue | ||||||
|  |                     .and(series.isActive.isTrue) | ||||||
|  |                     .and(recommendSeries.isFree.eq(isFree)) | ||||||
|  |             ) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,87 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.content.series.recommend | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper | ||||||
|  | import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository | ||||||
|  | import kr.co.vividnext.sodalive.aws.s3.S3Uploader | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.RecommendSeries | ||||||
|  | import kr.co.vividnext.sodalive.utils.generateFileName | ||||||
|  | import org.springframework.beans.factory.annotation.Value | ||||||
|  | import org.springframework.data.repository.findByIdOrNull | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import org.springframework.transaction.annotation.Transactional | ||||||
|  | import org.springframework.web.multipart.MultipartFile | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AdminRecommendSeriesService( | ||||||
|  |     private val s3Uploader: S3Uploader, | ||||||
|  |     private val repository: AdminRecommendSeriesRepository, | ||||||
|  |     private val seriesRepository: AdminContentSeriesRepository, | ||||||
|  |     private val objectMapper: ObjectMapper, | ||||||
|  |  | ||||||
|  |     @Value("\${cloud.aws.s3.bucket}") | ||||||
|  |     private val bucket: String | ||||||
|  | ) { | ||||||
|  |     fun getRecommendSeriesList(isFree: Boolean): List<GetAdminRecommendSeriesListResponse> { | ||||||
|  |         return repository.getRecommendSeriesList(isFree = isFree) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Transactional | ||||||
|  |     fun createRecommendSeries(image: MultipartFile, requestString: String) { | ||||||
|  |         val request = objectMapper.readValue(requestString, CreateRecommendSeriesRequest::class.java) | ||||||
|  |         val series = seriesRepository.findByIdOrNull(request.seriesId) | ||||||
|  |             ?: throw SodaException("잘못된 요청입니다.") | ||||||
|  |  | ||||||
|  |         val recommendSeries = RecommendSeries(isFree = request.isFree) | ||||||
|  |         recommendSeries.series = series | ||||||
|  |         repository.save(recommendSeries) | ||||||
|  |  | ||||||
|  |         val fileName = generateFileName() | ||||||
|  |         val imagePath = s3Uploader.upload( | ||||||
|  |             inputStream = image.inputStream, | ||||||
|  |             bucket = bucket, | ||||||
|  |             filePath = "recommend_series/${recommendSeries.id}/$fileName" | ||||||
|  |         ) | ||||||
|  |         recommendSeries.imagePath = imagePath | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Transactional | ||||||
|  |     fun updateRecommendSeries(image: MultipartFile?, requestString: String) { | ||||||
|  |         val request = objectMapper.readValue(requestString, UpdateRecommendSeriesRequest::class.java) | ||||||
|  |         val recommendSeries = repository.findByIdOrNull(request.id) | ||||||
|  |             ?: throw SodaException("잘못된 요청입니다.") | ||||||
|  |  | ||||||
|  |         if (image != null) { | ||||||
|  |             val fileName = generateFileName() | ||||||
|  |             val imagePath = s3Uploader.upload( | ||||||
|  |                 inputStream = image.inputStream, | ||||||
|  |                 bucket = bucket, | ||||||
|  |                 filePath = "recommend_series/${recommendSeries.id}/$fileName" | ||||||
|  |             ) | ||||||
|  |             recommendSeries.imagePath = imagePath | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (request.isActive != null) { | ||||||
|  |             recommendSeries.isActive = request.isActive | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (request.seriesId != null) { | ||||||
|  |             val series = seriesRepository.findByIdOrNull(request.seriesId) | ||||||
|  |  | ||||||
|  |             if (series != null) { | ||||||
|  |                 recommendSeries.series = series | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Transactional | ||||||
|  |     fun updateRecommendSeriesOrders(ids: List<Long>) { | ||||||
|  |         for (index in ids.indices) { | ||||||
|  |             val recommendSeries = repository.findByIdOrNull(ids[index]) | ||||||
|  |  | ||||||
|  |             if (recommendSeries != null) { | ||||||
|  |                 recommendSeries.orders = index + 1 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.content.series.recommend | ||||||
|  |  | ||||||
|  | data class CreateRecommendSeriesRequest( | ||||||
|  |     val seriesId: Long, | ||||||
|  |     val isFree: Boolean | ||||||
|  | ) | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.content.series.recommend | ||||||
|  |  | ||||||
|  | import com.querydsl.core.annotations.QueryProjection | ||||||
|  |  | ||||||
|  | data class GetAdminRecommendSeriesListResponse @QueryProjection constructor( | ||||||
|  |     val id: Long, | ||||||
|  |     val seriesId: Long, | ||||||
|  |     val seriesTitle: String, | ||||||
|  |     val imageUrl: String | ||||||
|  | ) | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.content.series.recommend | ||||||
|  |  | ||||||
|  | data class UpdateRecommendSeriesOrdersRequest( | ||||||
|  |     val ids: List<Long> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package kr.co.vividnext.sodalive.admin.content.series.recommend | ||||||
|  |  | ||||||
|  | data class UpdateRecommendSeriesRequest( | ||||||
|  |     val id: Long, | ||||||
|  |     val seriesId: Long?, | ||||||
|  |     val isActive: Boolean? | ||||||
|  | ) | ||||||
| @@ -42,8 +42,8 @@ class AuditionVoteService( | |||||||
|             endDate = endDate |             endDate = endDate | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         if (voteCount > 10) { |         if (voteCount > 100) { | ||||||
|             throw SodaException("오늘 응원은 여기까지!\n하루 최대 10회까지 응원이 가능합니다.\n내일 다시 이용해주세요.") |             throw SodaException("오늘 응원은 여기까지!\n하루 최대 100회까지 응원이 가능합니다.\n내일 다시 이용해주세요.") | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (voteCount > 0) { |         if (voteCount > 0) { | ||||||
|   | |||||||
| @@ -6,9 +6,9 @@ import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | |||||||
| import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent | import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent | ||||||
| import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment | import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment | ||||||
| import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike | import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike | ||||||
|  | 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.GetNewContentUploadCreator |  | ||||||
| import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem | import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem | ||||||
| import kr.co.vividnext.sodalive.content.main.QGetAudioContentRankingItem | import kr.co.vividnext.sodalive.content.main.QGetAudioContentRankingItem | ||||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner | import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner | ||||||
| @@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentThe | |||||||
| import kr.co.vividnext.sodalive.event.QEvent.event | import kr.co.vividnext.sodalive.event.QEvent.event | ||||||
| import kr.co.vividnext.sodalive.member.MemberRole | import kr.co.vividnext.sodalive.member.MemberRole | ||||||
| import kr.co.vividnext.sodalive.member.QMember.member | 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.beans.factory.annotation.Value | ||||||
| import org.springframework.data.jpa.repository.JpaRepository | import org.springframework.data.jpa.repository.JpaRepository | ||||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||||
| @@ -61,14 +62,14 @@ interface AudioContentQueryRepository { | |||||||
|     ): List<OtherContentResponse> |     ): List<OtherContentResponse> | ||||||
|  |  | ||||||
|     fun findByTheme( |     fun findByTheme( | ||||||
|         cloudfrontHost: String, |  | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         theme: String = "", |         theme: String = "", | ||||||
|         sortType: SortType = SortType.NEWEST, |         sortType: SortType = SortType.NEWEST, | ||||||
|         isAdult: Boolean = false, |         isAdult: Boolean = false, | ||||||
|         contentType: ContentType, |         contentType: ContentType, | ||||||
|         offset: Long = 0, |         offset: Long = 0, | ||||||
|         limit: Long = 20 |         limit: Long = 20, | ||||||
|  |         isFree: Boolean = false | ||||||
|     ): List<GetAudioContentMainItem> |     ): List<GetAudioContentMainItem> | ||||||
|  |  | ||||||
|     fun totalCountByTheme( |     fun totalCountByTheme( | ||||||
| @@ -98,13 +99,12 @@ interface AudioContentQueryRepository { | |||||||
|     fun getNewContentUploadCreatorList( |     fun getNewContentUploadCreatorList( | ||||||
|         cloudfrontHost: String, |         cloudfrontHost: String, | ||||||
|         isAdult: Boolean = false |         isAdult: Boolean = false | ||||||
|     ): List<GetNewContentUploadCreator> |     ): List<ContentCreatorResponse> | ||||||
|  |  | ||||||
|     fun getAudioContentMainBannerList(isAdult: Boolean): List<AudioContentBanner> |     fun getAudioContentMainBannerList(isAdult: Boolean): List<AudioContentBanner> | ||||||
|     fun getAudioContentCurations(isAdult: Boolean): List<AudioContentCuration> |     fun getAudioContentCurations(isAdult: Boolean): List<AudioContentCuration> | ||||||
|     fun findAudioContentByCurationId( |     fun findAudioContentByCurationId( | ||||||
|         curationId: Long, |         curationId: Long, | ||||||
|         cloudfrontHost: String, |  | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
|         contentType: ContentType |         contentType: ContentType | ||||||
|     ): List<GetAudioContentMainItem> |     ): List<GetAudioContentMainItem> | ||||||
| @@ -334,15 +334,19 @@ class AudioContentQueryRepositoryImpl( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun findByTheme( |     override fun findByTheme( | ||||||
|         cloudfrontHost: String, |  | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         theme: String, |         theme: String, | ||||||
|         sortType: SortType, |         sortType: SortType, | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
|         contentType: ContentType, |         contentType: ContentType, | ||||||
|         offset: Long, |         offset: Long, | ||||||
|         limit: Long |         limit: Long, | ||||||
|  |         isFree: Boolean | ||||||
|     ): List<GetAudioContentMainItem> { |     ): List<GetAudioContentMainItem> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|         val orderBy = when (sortType) { |         val orderBy = when (sortType) { | ||||||
|             SortType.NEWEST -> listOf(audioContent.releaseDate.desc(), audioContent.id.desc()) |             SortType.NEWEST -> listOf(audioContent.releaseDate.desc(), audioContent.id.desc()) | ||||||
|             SortType.PRICE_HIGH -> listOf( |             SortType.PRICE_HIGH -> listOf( | ||||||
| @@ -365,6 +369,7 @@ class AudioContentQueryRepositoryImpl( | |||||||
|                     .or(audioContent.releaseDate.loe(LocalDateTime.now())) |                     .or(audioContent.releaseDate.loe(LocalDateTime.now())) | ||||||
|                     .or(audioContent.member.id.eq(memberId)) |                     .or(audioContent.member.id.eq(memberId)) | ||||||
|             ) |             ) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|         if (!isAdult) { |         if (!isAdult) { | ||||||
|             where = where.and(audioContent.isAdult.isFalse) |             where = where.and(audioContent.isAdult.isFalse) | ||||||
| @@ -380,14 +385,18 @@ 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( | ||||||
|                     audioContent.id, |                     audioContent.id, | ||||||
|                     audioContent.coverImage.prepend("/").prepend(cloudfrontHost), |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|                     audioContent.title, |                     audioContent.title, | ||||||
|                     member.id, |                     member.id, | ||||||
|                     member.profileImage.prepend("/").prepend(cloudfrontHost), |                     member.profileImage.prepend("/").prepend(imageHost), | ||||||
|                     member.nickname, |                     member.nickname, | ||||||
|                     audioContent.price, |                     audioContent.price, | ||||||
|                     audioContent.duration |                     audioContent.duration | ||||||
| @@ -396,6 +405,7 @@ class AudioContentQueryRepositoryImpl( | |||||||
|             .from(audioContent) |             .from(audioContent) | ||||||
|             .innerJoin(audioContent.member, member) |             .innerJoin(audioContent.member, member) | ||||||
|             .innerJoin(audioContent.theme, audioContentTheme) |             .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|             .where(where) |             .where(where) | ||||||
|             .offset(offset) |             .offset(offset) | ||||||
|             .limit(limit) |             .limit(limit) | ||||||
| @@ -533,7 +543,7 @@ class AudioContentQueryRepositoryImpl( | |||||||
|     override fun getNewContentUploadCreatorList( |     override fun getNewContentUploadCreatorList( | ||||||
|         cloudfrontHost: String, |         cloudfrontHost: String, | ||||||
|         isAdult: Boolean |         isAdult: Boolean | ||||||
|     ): List<GetNewContentUploadCreator> { |     ): List<ContentCreatorResponse> { | ||||||
|         var where = audioContent.releaseDate.after(LocalDateTime.now().minusWeeks(2)) |         var where = audioContent.releaseDate.after(LocalDateTime.now().minusWeeks(2)) | ||||||
|             .and(audioContent.isActive.isTrue) |             .and(audioContent.isActive.isTrue) | ||||||
|             .and(audioContent.duration.isNotNull) |             .and(audioContent.duration.isNotNull) | ||||||
| @@ -552,7 +562,7 @@ class AudioContentQueryRepositoryImpl( | |||||||
|             .limit(20) |             .limit(20) | ||||||
|             .fetch() |             .fetch() | ||||||
|             .map { |             .map { | ||||||
|                 GetNewContentUploadCreator( |                 ContentCreatorResponse( | ||||||
|                     it.id!!, |                     it.id!!, | ||||||
|                     it.nickname, |                     it.nickname, | ||||||
|                     creatorProfileImageUrl = if (it.profileImage != null) { |                     creatorProfileImageUrl = if (it.profileImage != null) { | ||||||
| @@ -596,7 +606,6 @@ class AudioContentQueryRepositoryImpl( | |||||||
|  |  | ||||||
|     override fun findAudioContentByCurationId( |     override fun findAudioContentByCurationId( | ||||||
|         curationId: Long, |         curationId: Long, | ||||||
|         cloudfrontHost: String, |  | ||||||
|         isAdult: Boolean, |         isAdult: Boolean, | ||||||
|         contentType: ContentType |         contentType: ContentType | ||||||
|     ): List<GetAudioContentMainItem> { |     ): List<GetAudioContentMainItem> { | ||||||
| @@ -620,12 +629,12 @@ class AudioContentQueryRepositoryImpl( | |||||||
|             .select( |             .select( | ||||||
|                 QGetAudioContentMainItem( |                 QGetAudioContentMainItem( | ||||||
|                     audioContent.id, |                     audioContent.id, | ||||||
|                     audioContent.coverImage.prepend("/").prepend(cloudfrontHost), |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|                     audioContent.title, |                     audioContent.title, | ||||||
|                     member.id, |                     member.id, | ||||||
|                     member.profileImage |                     member.profileImage | ||||||
|                         .prepend("/") |                         .prepend("/") | ||||||
|                         .prepend(cloudfrontHost), |                         .prepend(imageHost), | ||||||
|                     member.nickname, |                     member.nickname, | ||||||
|                     audioContent.price, |                     audioContent.price, | ||||||
|                     audioContent.duration |                     audioContent.duration | ||||||
|   | |||||||
| @@ -39,7 +39,6 @@ class AudioContentMainService( | |||||||
|         pageable: Pageable |         pageable: Pageable | ||||||
|     ): List<GetAudioContentMainItem> { |     ): List<GetAudioContentMainItem> { | ||||||
|         return repository.findByTheme( |         return repository.findByTheme( | ||||||
|             cloudfrontHost = imageHost, |  | ||||||
|             memberId = member.id!!, |             memberId = member.id!!, | ||||||
|             theme = theme, |             theme = theme, | ||||||
|             isAdult = member.auth != null && isAdultContentVisible, |             isAdult = member.auth != null && isAdultContentVisible, | ||||||
| @@ -47,7 +46,6 @@ class AudioContentMainService( | |||||||
|             offset = pageable.offset, |             offset = pageable.offset, | ||||||
|             limit = pageable.pageSize.toLong() |             limit = pageable.pageSize.toLong() | ||||||
|         ) |         ) | ||||||
|             .filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.creatorId) } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Transactional(readOnly = true) |     @Transactional(readOnly = true) | ||||||
| @@ -80,7 +78,7 @@ class AudioContentMainService( | |||||||
|  |  | ||||||
|     @Transactional(readOnly = true) |     @Transactional(readOnly = true) | ||||||
|     @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult") |     @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult") | ||||||
|     fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List<GetNewContentUploadCreator> { |     fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List<ContentCreatorResponse> { | ||||||
|         return repository.getNewContentUploadCreatorList( |         return repository.getNewContentUploadCreatorList( | ||||||
|             cloudfrontHost = imageHost, |             cloudfrontHost = imageHost, | ||||||
|             isAdult = isAdult |             isAdult = isAdult | ||||||
| @@ -164,7 +162,6 @@ class AudioContentMainService( | |||||||
|                 description = it.description, |                 description = it.description, | ||||||
|                 contents = repository.findAudioContentByCurationId( |                 contents = repository.findAudioContentByCurationId( | ||||||
|                     curationId = it.id!!, |                     curationId = it.id!!, | ||||||
|                     cloudfrontHost = imageHost, |  | ||||||
|                     isAdult = isAdult, |                     isAdult = isAdult, | ||||||
|                     contentType = contentType |                     contentType = contentType | ||||||
|                 ) |                 ) | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package kr.co.vividnext.sodalive.content.main | |||||||
| import com.fasterxml.jackson.annotation.JsonProperty | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.querydsl.core.annotations.QueryProjection | import com.querydsl.core.annotations.QueryProjection | ||||||
| 
 | 
 | ||||||
| data class GetNewContentUploadCreator @QueryProjection constructor( | data class ContentCreatorResponse @QueryProjection constructor( | ||||||
|     @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 |     @JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.banner | ||||||
|  |  | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab | ||||||
|  | import kr.co.vividnext.sodalive.event.QEvent.event | ||||||
|  | import kr.co.vividnext.sodalive.member.QMember.member | ||||||
|  | import org.springframework.data.jpa.repository.JpaRepository | ||||||
|  |  | ||||||
|  | interface AudioContentBannerRepository : JpaRepository<AudioContentBanner, Long>, AudioContentBannerQueryRepository | ||||||
|  |  | ||||||
|  | interface AudioContentBannerQueryRepository { | ||||||
|  |     fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean): List<AudioContentBanner> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class AudioContentBannerQueryRepositoryImpl( | ||||||
|  |     private val queryFactory: JPAQueryFactory | ||||||
|  | ) : AudioContentBannerQueryRepository { | ||||||
|  |     override fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean): List<AudioContentBanner> { | ||||||
|  |         var where = audioContentBanner.isActive.isTrue | ||||||
|  |  | ||||||
|  |         where = if (tabId == 1L) { | ||||||
|  |             where.and(audioContentBanner.tab.isNull) | ||||||
|  |         } else { | ||||||
|  |             where.and(audioContentBanner.tab.id.eq(tabId)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(audioContentBanner.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .selectFrom(audioContentBanner) | ||||||
|  |             .leftJoin(audioContentBanner.tab, audioContentMainTab) | ||||||
|  |             .leftJoin(audioContentBanner.event, event) | ||||||
|  |             .leftJoin(audioContentBanner.creator, member) | ||||||
|  |             .where(where) | ||||||
|  |             .orderBy(audioContentBanner.orders.asc()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,67 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.banner | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.event.EventItem | ||||||
|  | import kr.co.vividnext.sodalive.member.block.BlockMemberRepository | ||||||
|  | import org.springframework.beans.factory.annotation.Value | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AudioContentBannerService( | ||||||
|  |     private val repository: AudioContentBannerRepository, | ||||||
|  |     private val blockMemberRepository: BlockMemberRepository, | ||||||
|  |  | ||||||
|  |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|  |     private val imageHost: String | ||||||
|  | ) { | ||||||
|  |     fun getBannerList(tabId: Long, memberId: Long, isAdult: Boolean): List<GetAudioContentBannerResponse> { | ||||||
|  |         return repository.getAudioContentMainBannerList(tabId, isAdult) | ||||||
|  |             .filter { | ||||||
|  |                 if (it.type == AudioContentBannerType.CREATOR && it.creator != null) { | ||||||
|  |                     !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.creator!!.id!!) | ||||||
|  |                 } else if (it.type == AudioContentBannerType.SERIES && it.series != null) { | ||||||
|  |                     !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.series!!.member!!.id!!) | ||||||
|  |                 } else { | ||||||
|  |                     true | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .map { | ||||||
|  |                 GetAudioContentBannerResponse( | ||||||
|  |                     type = it.type, | ||||||
|  |                     thumbnailImageUrl = "$imageHost/${it.thumbnailImage}", | ||||||
|  |                     eventItem = if (it.type == AudioContentBannerType.EVENT && it.event != null) { | ||||||
|  |                         EventItem( | ||||||
|  |                             id = it.event!!.id!!, | ||||||
|  |                             thumbnailImageUrl = if (!it.event!!.thumbnailImage.startsWith("https://")) { | ||||||
|  |                                 "$imageHost/${it.event!!.thumbnailImage}" | ||||||
|  |                             } else { | ||||||
|  |                                 it.event!!.thumbnailImage | ||||||
|  |                             }, | ||||||
|  |                             detailImageUrl = if ( | ||||||
|  |                                 it.event!!.detailImage != null && | ||||||
|  |                                 !it.event!!.detailImage!!.startsWith("https://") | ||||||
|  |                             ) { | ||||||
|  |                                 "$imageHost/${it.event!!.detailImage}" | ||||||
|  |                             } else { | ||||||
|  |                                 it.event!!.detailImage | ||||||
|  |                             }, | ||||||
|  |                             popupImageUrl = null, | ||||||
|  |                             link = it.event!!.link | ||||||
|  |                         ) | ||||||
|  |                     } else { | ||||||
|  |                         null | ||||||
|  |                     }, | ||||||
|  |                     creatorId = if (it.type == AudioContentBannerType.CREATOR && it.creator != null) { | ||||||
|  |                         it.creator!!.id | ||||||
|  |                     } else { | ||||||
|  |                         null | ||||||
|  |                     }, | ||||||
|  |                     seriesId = if (it.type == AudioContentBannerType.SERIES && it.series != null) { | ||||||
|  |                         it.series!!.id | ||||||
|  |                     } else { | ||||||
|  |                         null | ||||||
|  |                     }, | ||||||
|  |                     link = it.link | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -6,6 +6,8 @@ import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | |||||||
| import kr.co.vividnext.sodalive.content.SortType | import kr.co.vividnext.sodalive.content.SortType | ||||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||||
| import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem | import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem | ||||||
|  | import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab | ||||||
| import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme | import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme | ||||||
| import kr.co.vividnext.sodalive.member.QMember.member | import kr.co.vividnext.sodalive.member.QMember.member | ||||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||||
| @@ -87,4 +89,42 @@ class AudioContentCurationQueryRepository(private val queryFactory: JPAQueryFact | |||||||
|             .orderBy(orderBy) |             .orderBy(orderBy) | ||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun findByContentMainTabId(tabId: Long, isAdult: Boolean): List<AudioContentCuration> { | ||||||
|  |         var where = audioContentCuration.isActive.isTrue | ||||||
|  |             .and(audioContentMainTab.id.eq(tabId)) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(audioContentCuration.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .selectFrom(audioContentCuration) | ||||||
|  |             .innerJoin(audioContentCuration.tab, audioContentMainTab) | ||||||
|  |             .where(where) | ||||||
|  |             .orderBy(audioContentCuration.orders.asc()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun findByContentMainTabIdAndTitle( | ||||||
|  |         tabId: Long, | ||||||
|  |         title: String, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         offset: Long = 0, | ||||||
|  |         limit: Long = 12 | ||||||
|  |     ): List<AudioContentCuration> { | ||||||
|  |         var where = audioContentCuration.isActive.isTrue | ||||||
|  |             .and(audioContentMainTab.id.eq(tabId)) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(audioContentCuration.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .selectFrom(audioContentCuration) | ||||||
|  |             .innerJoin(audioContentCuration.tab, audioContentMainTab) | ||||||
|  |             .where(where) | ||||||
|  |             .orderBy(audioContentCuration.orders.asc()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||||
|  |  | ||||||
|  | data class GetContentCurationResponse( | ||||||
|  |     val title: String, | ||||||
|  |     val items: List<GetAudioContentMainItem> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab | ||||||
|  |  | ||||||
|  | import com.querydsl.core.annotations.QueryProjection | ||||||
|  |  | ||||||
|  | data class GetRecommendSeriesListResponse @QueryProjection constructor( | ||||||
|  |     val seriesId: Long, | ||||||
|  |     val title: String, | ||||||
|  |     val imageUrl: String, | ||||||
|  |     val creatorId: Long, | ||||||
|  |     val creatorNickname: String | ||||||
|  | ) | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.BaseEntity | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.Series | ||||||
|  | import javax.persistence.Column | ||||||
|  | import javax.persistence.Entity | ||||||
|  | import javax.persistence.FetchType | ||||||
|  | import javax.persistence.JoinColumn | ||||||
|  | import javax.persistence.ManyToOne | ||||||
|  |  | ||||||
|  | @Entity | ||||||
|  | data class RecommendSeries( | ||||||
|  |     @Column(nullable = false) | ||||||
|  |     var imagePath: String = "", | ||||||
|  |  | ||||||
|  |     @Column(nullable = false) | ||||||
|  |     var orders: Int = 1, | ||||||
|  |  | ||||||
|  |     @Column(nullable = false) | ||||||
|  |     var isFree: Boolean = false, | ||||||
|  |  | ||||||
|  |     @Column(nullable = false) | ||||||
|  |     var isActive: Boolean = true | ||||||
|  | ) : BaseEntity() { | ||||||
|  |     @ManyToOne(fetch = FetchType.LAZY) | ||||||
|  |     @JoinColumn(name = "series_id", nullable = false) | ||||||
|  |     var series: Series? = null | ||||||
|  | } | ||||||
| @@ -0,0 +1,70 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab | ||||||
|  |  | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.QRecommendSeries.recommendSeries | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||||
|  | import kr.co.vividnext.sodalive.member.QMember.member | ||||||
|  | import org.springframework.beans.factory.annotation.Value | ||||||
|  | import org.springframework.stereotype.Repository | ||||||
|  |  | ||||||
|  | @Repository | ||||||
|  | class RecommendSeriesRepository( | ||||||
|  |     private val queryFactory: JPAQueryFactory, | ||||||
|  |  | ||||||
|  |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|  |     private val imageHost: String | ||||||
|  | ) { | ||||||
|  |     fun getNewSeriesList(isAdult: Boolean): List<GetRecommendSeriesListResponse> { | ||||||
|  |         var where = recommendSeries.isActive.isTrue | ||||||
|  |             .and(recommendSeries.isFree.isFalse) | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetRecommendSeriesListResponse( | ||||||
|  |                     series.id, | ||||||
|  |                     series.title, | ||||||
|  |                     recommendSeries.imagePath.prepend("/").prepend(imageHost), | ||||||
|  |                     member.id, | ||||||
|  |                     member.nickname | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(recommendSeries) | ||||||
|  |             .innerJoin(recommendSeries.series, series) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .where(where) | ||||||
|  |             .orderBy(recommendSeries.orders.asc()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getRecommendSeriesList(isAdult: Boolean): List<GetRecommendSeriesListResponse> { | ||||||
|  |         var where = recommendSeries.isActive.isTrue | ||||||
|  |             .and(recommendSeries.isFree.isTrue) | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetRecommendSeriesListResponse( | ||||||
|  |                     series.id, | ||||||
|  |                     series.title, | ||||||
|  |                     recommendSeries.imagePath.prepend("/").prepend(imageHost), | ||||||
|  |                     member.id, | ||||||
|  |                     member.nickname | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(recommendSeries) | ||||||
|  |             .innerJoin(recommendSeries.series, series) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .where(where) | ||||||
|  |             .orderBy(recommendSeries.orders.asc()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | 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.security.core.annotation.AuthenticationPrincipal | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/v2/audio-content/main/alarm") | ||||||
|  | class AudioContentMainTabAlarmController(private val service: AudioContentMainTabAlarmService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun fetchContentMainTabAlarm( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.fetchData(member)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,84 @@ | |||||||
|  | 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.banner.AudioContentBannerService | ||||||
|  | import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.event.EventService | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.time.DayOfWeek | ||||||
|  | import java.time.LocalDateTime | ||||||
|  | import java.time.temporal.TemporalAdjusters | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AudioContentMainTabAlarmService( | ||||||
|  |     private val bannerService: AudioContentBannerService, | ||||||
|  |     private val contentRepository: AudioContentRepository, | ||||||
|  |     private val rankingService: RankingService, | ||||||
|  |     private val eventService: EventService, | ||||||
|  |     private val curationRepository: AudioContentCurationQueryRepository | ||||||
|  | ) { | ||||||
|  |     fun fetchData(member: Member): GetContentMainTabAlarmResponse { | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |         val memberId = member.id!! | ||||||
|  |  | ||||||
|  |         val contentBannerList = bannerService.getBannerList( | ||||||
|  |             tabId = 4, | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val alarmThemeList = listOf("모닝콜", "슬립콜", "알람") | ||||||
|  |         val newAlarmContentList = contentRepository.findByTheme( | ||||||
|  |             memberId = memberId, | ||||||
|  |             theme = alarmThemeList[0], | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             contentType = ContentType.ALL, | ||||||
|  |             limit = 10 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 주간 랭킹 기간 | ||||||
|  |         val currentDateTime = LocalDateTime.now() | ||||||
|  |         val startDate = currentDateTime | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusWeeks(1) | ||||||
|  |             .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) | ||||||
|  |         val endDate = startDate | ||||||
|  |             .plusDays(6) | ||||||
|  |  | ||||||
|  |         val rankAlarmContentList = rankingService.getContentRanking( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = startDate, | ||||||
|  |             endDate = endDate, | ||||||
|  |             theme = alarmThemeList[0] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val eventBannerList = eventService.getEventList(isAdult = isAdult) | ||||||
|  |         val curationList = curationRepository.findByContentMainTabId(tabId = 4, isAdult = isAdult) | ||||||
|  |             .map { | ||||||
|  |                 GetContentCurationResponse( | ||||||
|  |                     title = it.title, | ||||||
|  |                     items = contentRepository.findAudioContentByCurationId( | ||||||
|  |                         curationId = it.id!!, | ||||||
|  |                         isAdult = isAdult, | ||||||
|  |                         contentType = ContentType.ALL | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         return GetContentMainTabAlarmResponse( | ||||||
|  |             contentBannerList = contentBannerList, | ||||||
|  |             alarmThemeList = alarmThemeList, | ||||||
|  |             newAlarmContentList = newAlarmContentList, | ||||||
|  |             rankAlarmContentList = rankAlarmContentList, | ||||||
|  |             eventBannerList = eventBannerList, | ||||||
|  |             curationList = curationList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,17 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.alarm | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.event.GetEventResponse | ||||||
|  |  | ||||||
|  | data class GetContentMainTabAlarmResponse( | ||||||
|  |     val tab: Long = 4, | ||||||
|  |     val contentBannerList: List<GetAudioContentBannerResponse>, | ||||||
|  |     val alarmThemeList: List<String>, | ||||||
|  |     val newAlarmContentList: List<GetAudioContentMainItem>, | ||||||
|  |     val rankAlarmContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val eventBannerList: GetEventResponse, | ||||||
|  |     val curationList: List<GetContentCurationResponse> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.asmr | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/v2/audio-content/main/asmr") | ||||||
|  | class AudioContentMainTabAsmrController(private val service: AudioContentMainTabAsmrService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun fetchContentMainTabAsmr( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.fetchData(member)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,84 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.asmr | ||||||
|  |  | ||||||
|  | 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.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.event.EventService | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.time.DayOfWeek | ||||||
|  | import java.time.LocalDateTime | ||||||
|  | import java.time.temporal.TemporalAdjusters | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AudioContentMainTabAsmrService( | ||||||
|  |     private val bannerService: AudioContentBannerService, | ||||||
|  |     private val contentRepository: AudioContentRepository, | ||||||
|  |     private val rankingService: RankingService, | ||||||
|  |     private val eventService: EventService, | ||||||
|  |     private val curationRepository: AudioContentCurationQueryRepository | ||||||
|  | ) { | ||||||
|  |     fun fetchData(member: Member): GetContentMainTabAsmrResponse { | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |         val memberId = member.id!! | ||||||
|  |         val theme = "ASMR" | ||||||
|  |         val tabId = 5L | ||||||
|  |  | ||||||
|  |         val contentBannerList = bannerService.getBannerList( | ||||||
|  |             tabId = tabId, | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val newAsmrContentList = contentRepository.findByTheme( | ||||||
|  |             memberId = memberId, | ||||||
|  |             theme = theme, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             contentType = ContentType.ALL, | ||||||
|  |             limit = 10 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val currentDateTime = LocalDateTime.now() | ||||||
|  |         val startDate = currentDateTime | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusWeeks(1) | ||||||
|  |             .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) | ||||||
|  |         val endDate = startDate | ||||||
|  |             .plusDays(6) | ||||||
|  |  | ||||||
|  |         val rankAsmrContentList = rankingService.getContentRanking( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = startDate, | ||||||
|  |             endDate = endDate, | ||||||
|  |             theme = theme | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val eventBannerList = eventService.getEventList(isAdult = isAdult) | ||||||
|  |  | ||||||
|  |         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) | ||||||
|  |             .map { | ||||||
|  |                 GetContentCurationResponse( | ||||||
|  |                     title = it.title, | ||||||
|  |                     items = contentRepository.findAudioContentByCurationId( | ||||||
|  |                         curationId = it.id!!, | ||||||
|  |                         isAdult = isAdult, | ||||||
|  |                         contentType = ContentType.ALL | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         return GetContentMainTabAsmrResponse( | ||||||
|  |             contentBannerList = contentBannerList, | ||||||
|  |             newAsmrContentList = newAsmrContentList, | ||||||
|  |             rankAsmrContentList = rankAsmrContentList, | ||||||
|  |             eventBannerList = eventBannerList, | ||||||
|  |             curationList = curationList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.asmr | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.event.GetEventResponse | ||||||
|  |  | ||||||
|  | data class GetContentMainTabAsmrResponse( | ||||||
|  |     val tab: Long = 5, | ||||||
|  |     val contentBannerList: List<GetAudioContentBannerResponse>, | ||||||
|  |     val newAsmrContentList: List<GetAudioContentMainItem>, | ||||||
|  |     val rankAsmrContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val eventBannerList: GetEventResponse, | ||||||
|  |     val curationList: List<GetContentCurationResponse> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | 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.member.Member | ||||||
|  | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/v2/audio-content/main/content") | ||||||
|  | class AudioContentMainTabContentController(private val service: AudioContentMainTabContentService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun fetchContentMainTabContent( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.fetchData(member)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,105 @@ | |||||||
|  | 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.banner.AudioContentBannerService | ||||||
|  | import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository | ||||||
|  | import kr.co.vividnext.sodalive.event.EventService | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.time.LocalDateTime | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AudioContentMainTabContentService( | ||||||
|  |     private val bannerService: AudioContentBannerService, | ||||||
|  |     private val audioContentRepository: AudioContentRepository, | ||||||
|  |     private val audioContentThemeRepository: AudioContentThemeQueryRepository, | ||||||
|  |     private val rankingService: RankingService, | ||||||
|  |     private val eventService: EventService | ||||||
|  | ) { | ||||||
|  |     fun fetchData(member: Member): GetContentMainTabContentResponse { | ||||||
|  |         val memberId = member.id!! | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |  | ||||||
|  |         // 단편 배너 | ||||||
|  |         val contentBannerList = bannerService.getBannerList( | ||||||
|  |             tabId = 3, | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 새로운 단편 테마 | ||||||
|  |         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 currentDateTime = LocalDateTime.now() | ||||||
|  |         val dailyRankingStartDate = currentDateTime | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusDays(2) | ||||||
|  |         val dailyRankingEndDate = dailyRankingStartDate | ||||||
|  |             .plusDays(1) | ||||||
|  |  | ||||||
|  |         val rankContentList = rankingService.getContentRanking( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = dailyRankingStartDate, | ||||||
|  |             endDate = dailyRankingEndDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 이벤트 배너 | ||||||
|  |         val eventBannerList = eventService.getEventList(isAdult = isAdult) | ||||||
|  |  | ||||||
|  |         val contentRankCreatorList = rankingService.fetchCreatorByContentRevenueRankTop20( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             startDate = dailyRankingStartDate.minusDays(1), | ||||||
|  |             endDate = dailyRankingEndDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesRankContentList = if (contentRankCreatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorContentBySalesTop2( | ||||||
|  |                 creatorId = contentRankCreatorList[0].creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val salesCountRankContentList = if (contentRankCreatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorContentBySalesCountTop2( | ||||||
|  |                 creatorId = contentRankCreatorList[0].creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return GetContentMainTabContentResponse( | ||||||
|  |             bannerList = contentBannerList, | ||||||
|  |             contentThemeList = themeOfContentList, | ||||||
|  |             newContentList = newContentList, | ||||||
|  |             rankSortTypeList = listOf("매출", "댓글", "좋아요"), | ||||||
|  |             rankContentList = rankContentList, | ||||||
|  |             contentRankCreatorList = contentRankCreatorList, | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             salesCountRankContentList = salesCountRankContentList, | ||||||
|  |             eventBannerList = eventBannerList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.content | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | import kr.co.vividnext.sodalive.event.GetEventResponse | ||||||
|  |  | ||||||
|  | data class GetContentMainTabContentResponse( | ||||||
|  |     val tabId: Long = 3, | ||||||
|  |     val bannerList: List<GetAudioContentBannerResponse>, | ||||||
|  |     val contentThemeList: List<String>, | ||||||
|  |     val newContentList: List<GetAudioContentMainItem>, | ||||||
|  |     val rankSortTypeList: List<String>, | ||||||
|  |     val rankContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val contentRankCreatorList: List<ContentCreatorResponse>, | ||||||
|  |     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val salesCountRankContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val eventBannerList: GetEventResponse | ||||||
|  | ) | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | 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.member.Member | ||||||
|  | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/v2/audio-content/main/free") | ||||||
|  | class AudioContentMainTabFreeController(private val service: AudioContentMainTabFreeService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun fetchContentMainFree( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.fetchData(member)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,91 @@ | |||||||
|  | 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.banner.AudioContentBannerService | ||||||
|  | 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.RecommendSeriesRepository | ||||||
|  | import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AudioContentMainTabFreeService( | ||||||
|  |     private val bannerService: AudioContentBannerService, | ||||||
|  |     private val recommendSeriesRepository: RecommendSeriesRepository, | ||||||
|  |     private val curationRepository: AudioContentCurationQueryRepository, | ||||||
|  |     private val contentRepository: AudioContentRepository, | ||||||
|  |     private val audioContentRepository: AudioContentRepository, | ||||||
|  |     private val audioContentThemeRepository: AudioContentThemeQueryRepository | ||||||
|  | ) { | ||||||
|  |     fun fetchData(member: Member): GetContentMainTabFreeResponse { | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |         val memberId = member.id!! | ||||||
|  |         val tabId = 7L | ||||||
|  |  | ||||||
|  |         val contentBannerList = bannerService.getBannerList( | ||||||
|  |             tabId = tabId, | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val introduceCreator = curationRepository.findByContentMainTabIdAndTitle( | ||||||
|  |             tabId = tabId, | ||||||
|  |             title = "크리에이터 소개", | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |             .map { | ||||||
|  |                 GetContentCurationResponse( | ||||||
|  |                     title = it.title, | ||||||
|  |                     items = contentRepository.findAudioContentByCurationId( | ||||||
|  |                         curationId = it.id!!, | ||||||
|  |                         isAdult = isAdult, | ||||||
|  |                         contentType = ContentType.ALL | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         val recommendSeriesList = recommendSeriesRepository.getRecommendSeriesList(isAdult = isAdult) | ||||||
|  |  | ||||||
|  |         val themeList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult) | ||||||
|  |         val newFreeContentList = if (themeList.isNotEmpty()) { | ||||||
|  |             audioContentRepository.findByTheme( | ||||||
|  |                 memberId = member.id!!, | ||||||
|  |                 theme = themeList[0], | ||||||
|  |                 isAdult = member.auth != null, | ||||||
|  |                 contentType = ContentType.ALL, | ||||||
|  |                 offset = 0, | ||||||
|  |                 limit = 10, | ||||||
|  |                 isFree = true | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) | ||||||
|  |             .map { | ||||||
|  |                 GetContentCurationResponse( | ||||||
|  |                     title = it.title, | ||||||
|  |                     items = contentRepository.findAudioContentByCurationId( | ||||||
|  |                         curationId = it.id!!, | ||||||
|  |                         isAdult = isAdult, | ||||||
|  |                         contentType = ContentType.ALL | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         return GetContentMainTabFreeResponse( | ||||||
|  |             contentBannerList = contentBannerList, | ||||||
|  |             introduceCreator = if (introduceCreator.isNotEmpty()) { | ||||||
|  |                 introduceCreator[0] | ||||||
|  |             } else { | ||||||
|  |                 null | ||||||
|  |             }, | ||||||
|  |             recommendSeriesList = recommendSeriesList, | ||||||
|  |             themeList = themeList, | ||||||
|  |             newFreeContentList = newFreeContentList, | ||||||
|  |             curationList = curationList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.free | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||||
|  | import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetRecommendSeriesListResponse | ||||||
|  |  | ||||||
|  | data class GetContentMainTabFreeResponse( | ||||||
|  |     val tabId: Long = 7, | ||||||
|  |     val contentBannerList: List<GetAudioContentBannerResponse>, | ||||||
|  |     val introduceCreator: GetContentCurationResponse?, | ||||||
|  |     val recommendSeriesList: List<GetRecommendSeriesListResponse>, | ||||||
|  |     val themeList: List<String>, | ||||||
|  |     val newFreeContentList: List<GetAudioContentMainItem>, | ||||||
|  |     val curationList: List<GetContentCurationResponse> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.home | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/v2/audio-content/main/home") | ||||||
|  | class AudioContentMainTabHomeController(private val service: AudioContentMainTabHomeService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun fetchContentMainHome( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.fetchData(member)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,117 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.home | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||||
|  | import kr.co.vividnext.sodalive.event.EventService | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.notice.ServiceNoticeService | ||||||
|  | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.time.DayOfWeek | ||||||
|  | import java.time.LocalDateTime | ||||||
|  | import java.time.format.DateTimeFormatter | ||||||
|  | import java.time.temporal.TemporalAdjusters | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AudioContentMainTabHomeService( | ||||||
|  |     private val noticeService: ServiceNoticeService, | ||||||
|  |     private val bannerService: AudioContentBannerService, | ||||||
|  |     private val rankingService: RankingService, | ||||||
|  |     private val eventService: EventService | ||||||
|  | ) { | ||||||
|  |     fun fetchData(member: Member): GetContentMainTabHomeResponse { | ||||||
|  |         // 주간 랭킹 기간 | ||||||
|  |         val currentDateTime = LocalDateTime.now() | ||||||
|  |         val startDate = currentDateTime | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusWeeks(1) | ||||||
|  |             .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) | ||||||
|  |         val endDate = startDate | ||||||
|  |             .plusDays(6) | ||||||
|  |  | ||||||
|  |         val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") | ||||||
|  |         val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일") | ||||||
|  |  | ||||||
|  |         val formattedLastMonday = startDate.format(startDateFormatter) | ||||||
|  |         val formattedLastSunday = endDate.format(endDateFormatter) | ||||||
|  |  | ||||||
|  |         // 최근 공지사항 | ||||||
|  |         val latestNotice = noticeService.getLatestNotice() | ||||||
|  |  | ||||||
|  |         // 메인 배너 (홈) | ||||||
|  |         val contentBannerList = bannerService.getBannerList( | ||||||
|  |             tabId = 1, | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             isAdult = member.auth != null | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 인기 크리에이터 | ||||||
|  |         val rankCreatorList = rankingService.getCreatorRanking( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             rankingDate = "$formattedLastMonday ~ $formattedLastSunday" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 인기 시리즈 | ||||||
|  |         val rankSeriesList = rankingService.getSeriesRanking( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             isAdult = member.auth != null, | ||||||
|  |             startDate = startDate.minusDays(1), | ||||||
|  |             endDate = endDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 인기 콘텐츠 | ||||||
|  |         val rankContentList = rankingService.getContentRanking( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             isAdult = member.auth != null, | ||||||
|  |             startDate = startDate.minusDays(1), | ||||||
|  |             endDate = endDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 이벤트 배너 | ||||||
|  |         val eventBannerList = eventService.getEventList(isAdult = member.auth != null) | ||||||
|  |  | ||||||
|  |         /* 채널별 인기 콘텐츠 | ||||||
|  |          * - 콘텐츠를 4개 이상 등록한 채널 | ||||||
|  |          * - 주간 콘텐츠 매출 Top 20 채널 | ||||||
|  |          * - 해당 채널의 누적 매출 Top 2 | ||||||
|  |          * - 해당 채널의 누적 판매 개수 Top 2 | ||||||
|  |          */ | ||||||
|  |         val contentRankCreatorList = rankingService.fetchCreatorByContentRevenueRankTop20( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             startDate = startDate.minusDays(1), | ||||||
|  |             endDate = endDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesRankContentList = if (contentRankCreatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorContentBySalesTop2( | ||||||
|  |                 creatorId = contentRankCreatorList[0].creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val salesCountRankContentList = if (contentRankCreatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorContentBySalesCountTop2( | ||||||
|  |                 creatorId = contentRankCreatorList[0].creatorId, | ||||||
|  |                 isAdult = member.auth != null | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return GetContentMainTabHomeResponse( | ||||||
|  |             latestNotice = latestNotice, | ||||||
|  |             bannerList = contentBannerList, | ||||||
|  |             rankCreatorList = rankCreatorList, | ||||||
|  |             rankSeriesList = rankSeriesList, | ||||||
|  |             rankSortTypeList = listOf("매출", "댓글", "좋아요"), | ||||||
|  |             rankContentList = rankContentList, | ||||||
|  |             eventBannerList = eventBannerList, | ||||||
|  |             contentRankCreatorList = contentRankCreatorList, | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             salesCountRankContentList = salesCountRankContentList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.home | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||||
|  | import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse | ||||||
|  | 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 | ||||||
|  |  | ||||||
|  | data class GetContentMainTabHomeResponse( | ||||||
|  |     val tabId: Long = 1, | ||||||
|  |     val latestNotice: NoticeTitleItem?, | ||||||
|  |     val bannerList: List<GetAudioContentBannerResponse>, | ||||||
|  |     val rankCreatorList: GetExplorerSectionResponse, | ||||||
|  |     val rankSeriesList: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|  |     val rankSortTypeList: List<String>, | ||||||
|  |     val rankContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val eventBannerList: GetEventResponse, | ||||||
|  |     val contentRankCreatorList: List<ContentCreatorResponse>, | ||||||
|  |     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val salesCountRankContentList: List<GetAudioContentRankingItem> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.replay | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/v2/audio-content/main/replay") | ||||||
|  | class AudioContentMainTabLiveReplayController(private val service: AudioContentMainTabLiveReplayService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun fetchContentMainTabLiveReplay( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.fetchData(member)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,84 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.replay | ||||||
|  |  | ||||||
|  | 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.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.event.EventService | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.time.DayOfWeek | ||||||
|  | import java.time.LocalDateTime | ||||||
|  | import java.time.temporal.TemporalAdjusters | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AudioContentMainTabLiveReplayService( | ||||||
|  |     private val bannerService: AudioContentBannerService, | ||||||
|  |     private val contentRepository: AudioContentRepository, | ||||||
|  |     private val rankingService: RankingService, | ||||||
|  |     private val eventService: EventService, | ||||||
|  |     private val curationRepository: AudioContentCurationQueryRepository | ||||||
|  | ) { | ||||||
|  |     fun fetchData(member: Member): GetContentMainTabLiveReplayResponse { | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |         val memberId = member.id!! | ||||||
|  |         val theme = "다시듣기" | ||||||
|  |         val tabId = 6L | ||||||
|  |  | ||||||
|  |         val contentBannerList = bannerService.getBannerList( | ||||||
|  |             tabId = tabId, | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val newLiveReplayContentList = contentRepository.findByTheme( | ||||||
|  |             memberId = memberId, | ||||||
|  |             theme = theme, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             contentType = ContentType.ALL, | ||||||
|  |             limit = 10 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val currentDateTime = LocalDateTime.now() | ||||||
|  |         val startDate = currentDateTime | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusWeeks(1) | ||||||
|  |             .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) | ||||||
|  |         val endDate = startDate | ||||||
|  |             .plusDays(6) | ||||||
|  |  | ||||||
|  |         val rankLiveReplayContentList = rankingService.getContentRanking( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = startDate, | ||||||
|  |             endDate = endDate, | ||||||
|  |             theme = theme | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val eventBannerList = eventService.getEventList(isAdult = isAdult) | ||||||
|  |  | ||||||
|  |         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) | ||||||
|  |             .map { | ||||||
|  |                 GetContentCurationResponse( | ||||||
|  |                     title = it.title, | ||||||
|  |                     items = contentRepository.findAudioContentByCurationId( | ||||||
|  |                         curationId = it.id!!, | ||||||
|  |                         isAdult = isAdult, | ||||||
|  |                         contentType = ContentType.ALL | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         return GetContentMainTabLiveReplayResponse( | ||||||
|  |             contentBannerList = contentBannerList, | ||||||
|  |             newLiveReplayContentList = newLiveReplayContentList, | ||||||
|  |             rankLiveReplayContentList = rankLiveReplayContentList, | ||||||
|  |             eventBannerList = eventBannerList, | ||||||
|  |             curationList = curationList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.replay | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||||
|  | import kr.co.vividnext.sodalive.event.GetEventResponse | ||||||
|  |  | ||||||
|  | data class GetContentMainTabLiveReplayResponse( | ||||||
|  |     val tabId: Long = 6, | ||||||
|  |     val contentBannerList: List<GetAudioContentBannerResponse>, | ||||||
|  |     val newLiveReplayContentList: List<GetAudioContentMainItem>, | ||||||
|  |     val rankLiveReplayContentList: List<GetAudioContentRankingItem>, | ||||||
|  |     val eventBannerList: GetEventResponse, | ||||||
|  |     val curationList: List<GetContentCurationResponse> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.series | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/v2/audio-content/main/series") | ||||||
|  | class AudioContentMainTabSeriesController(private val service: AudioContentMainTabSeriesService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun fetchContentMainSeries( | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.fetchData(member)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,149 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.series | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||||
|  | import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository | ||||||
|  | import kr.co.vividnext.sodalive.content.series.ContentSeriesService | ||||||
|  | import kr.co.vividnext.sodalive.event.EventService | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.rank.RankingService | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.time.DayOfWeek | ||||||
|  | import java.time.LocalDateTime | ||||||
|  | import java.time.temporal.TemporalAdjusters | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class AudioContentMainTabSeriesService( | ||||||
|  |     private val bannerService: AudioContentBannerService, | ||||||
|  |     private val seriesService: ContentSeriesService, | ||||||
|  |     private val rankingService: RankingService, | ||||||
|  |     private val recommendSeriesRepository: RecommendSeriesRepository, | ||||||
|  |     private val eventService: EventService, | ||||||
|  |     private val curationRepository: AudioContentCurationQueryRepository | ||||||
|  | ) { | ||||||
|  |     fun fetchData(member: Member): GetContentMainTabSeriesResponse { | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |  | ||||||
|  |         // 메인 배너 (시리즈) | ||||||
|  |         val contentBannerList = bannerService.getBannerList( | ||||||
|  |             tabId = 2, | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             isAdult = isAdult | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val originalAudioDrama = seriesService.getOriginalAudioDramaList( | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             offset = 0, | ||||||
|  |             limit = 20 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 일간 랭킹 | ||||||
|  |         val currentDateTime = LocalDateTime.now() | ||||||
|  |         val dailyRankingStartDate = currentDateTime | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusDays(2) | ||||||
|  |         val dailyRankingEndDate = dailyRankingStartDate | ||||||
|  |             .plusDays(1) | ||||||
|  |  | ||||||
|  |         val rankSeriesList = rankingService.getSeriesRanking( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = dailyRankingStartDate, | ||||||
|  |             endDate = dailyRankingEndDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         // 시리즈 장르 | ||||||
|  |         val genreList = seriesService.getGenreList(isAdult = isAdult) | ||||||
|  |  | ||||||
|  |         // 장르별 추천 시리즈 | ||||||
|  |         val recommendSeriesList = if (genreList.isNotEmpty()) { | ||||||
|  |             rankingService.getSeriesAllRankingByGenre( | ||||||
|  |                 memberId = member.id!!, | ||||||
|  |                 isAdult = isAdult, | ||||||
|  |                 genreId = genreList[0].id | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 새로운 시리즈 | ||||||
|  |         val newSeriesList = recommendSeriesRepository.getNewSeriesList(isAdult = isAdult) | ||||||
|  |  | ||||||
|  |         // 완결 시리즈 월간 랭킹 | ||||||
|  |         val monthlyRankingStartDate = currentDateTime | ||||||
|  |             .withDayOfMonth(1) | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusDays(1) | ||||||
|  |         val monthlyRankingEndDate = monthlyRankingStartDate | ||||||
|  |             .plusMonths(1) | ||||||
|  |  | ||||||
|  |         val rankCompleteSeriesList = rankingService.getCompleteSeriesRanking( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = monthlyRankingStartDate, | ||||||
|  |             endDate = monthlyRankingEndDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val startDate = currentDateTime | ||||||
|  |             .withHour(15) | ||||||
|  |             .withMinute(0) | ||||||
|  |             .withSecond(0) | ||||||
|  |             .minusWeeks(1) | ||||||
|  |             .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) | ||||||
|  |         val endDate = startDate | ||||||
|  |             .plusDays(6) | ||||||
|  |  | ||||||
|  |         val seriesRankCreatorList = rankingService.fetchCreatorBySeriesRevenueRankTop20( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             startDate = startDate.minusDays(1), | ||||||
|  |             endDate = endDate | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val salesRankContentList = if (seriesRankCreatorList.isNotEmpty()) { | ||||||
|  |             rankingService.fetchCreatorSeriesBySales( | ||||||
|  |                 creatorId = seriesRankCreatorList[0].creatorId, | ||||||
|  |                 isAdult = isAdult | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             emptyList() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 이벤트 배너 | ||||||
|  |         val eventBannerList = eventService.getEventList(isAdult = isAdult) | ||||||
|  |  | ||||||
|  |         // 큐레이션 | ||||||
|  |         val curationList = curationRepository.findByContentMainTabId(tabId = 2, isAdult = isAdult) | ||||||
|  |             .map { | ||||||
|  |                 GetSeriesCurationResponse( | ||||||
|  |                     title = it.title, | ||||||
|  |                     items = seriesService.fetchSeriesByCurationId( | ||||||
|  |                         curationId = it.id!!, | ||||||
|  |                         memberId = member.id!!, | ||||||
|  |                         isAdult = isAdult | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         return GetContentMainTabSeriesResponse( | ||||||
|  |             contentBannerList = contentBannerList, | ||||||
|  |             originalAudioDrama = if (originalAudioDrama.size >= 3) { | ||||||
|  |                 originalAudioDrama | ||||||
|  |             } else { | ||||||
|  |                 emptyList() | ||||||
|  |             }, | ||||||
|  |             rankSeriesList = rankSeriesList, | ||||||
|  |             genreList = genreList, | ||||||
|  |             recommendSeriesList = recommendSeriesList, | ||||||
|  |             newSeriesList = newSeriesList, | ||||||
|  |             rankCompleteSeriesList = rankCompleteSeriesList, | ||||||
|  |             seriesRankCreatorList = seriesRankCreatorList, | ||||||
|  |             salesRankContentList = salesRankContentList, | ||||||
|  |             eventBannerList = eventBannerList, | ||||||
|  |             curationList = curationList | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.series | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.tab.GetRecommendSeriesListResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.series.GetSeriesGenreListResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse | ||||||
|  | import kr.co.vividnext.sodalive.event.GetEventResponse | ||||||
|  |  | ||||||
|  | data class GetContentMainTabSeriesResponse( | ||||||
|  |     val tabId: Long = 2, | ||||||
|  |     val contentBannerList: List<GetAudioContentBannerResponse>, | ||||||
|  |     val originalAudioDrama: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|  |     val rankSeriesList: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|  |     val genreList: List<GetSeriesGenreListResponse>, | ||||||
|  |     val recommendSeriesList: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|  |     val newSeriesList: List<GetRecommendSeriesListResponse>, | ||||||
|  |     val rankCompleteSeriesList: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|  |     val seriesRankCreatorList: List<ContentCreatorResponse>, | ||||||
|  |     val salesRankContentList: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|  |     val eventBannerList: GetEventResponse, | ||||||
|  |     val curationList: List<GetSeriesCurationResponse> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.main.tab.series | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse | ||||||
|  |  | ||||||
|  | data class GetSeriesCurationResponse( | ||||||
|  |     val title: String, | ||||||
|  |     val items: List<GetSeriesListResponse.SeriesListItem> | ||||||
|  | ) | ||||||
| @@ -2,15 +2,21 @@ package kr.co.vividnext.sodalive.content.series | |||||||
|  |  | ||||||
| import com.querydsl.core.types.dsl.Expressions | import com.querydsl.core.types.dsl.Expressions | ||||||
| import com.querydsl.jpa.impl.JPAQueryFactory | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre | ||||||
| import kr.co.vividnext.sodalive.content.ContentType | import kr.co.vividnext.sodalive.content.ContentType | ||||||
| import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||||
| import kr.co.vividnext.sodalive.content.hashtag.QHashTag.hashTag | import kr.co.vividnext.sodalive.content.hashtag.QHashTag.hashTag | ||||||
|  | import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration | ||||||
|  | import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCurationItem.audioContentCurationItem | ||||||
| import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentMinMaxPriceResponse | import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentMinMaxPriceResponse | ||||||
| import kr.co.vividnext.sodalive.content.series.content.QGetSeriesContentMinMaxPriceResponse | import kr.co.vividnext.sodalive.content.series.content.QGetSeriesContentMinMaxPriceResponse | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.Series | import kr.co.vividnext.sodalive.creator.admin.content.series.Series | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword | import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword | ||||||
|  | import kr.co.vividnext.sodalive.member.MemberRole | ||||||
|  | import kr.co.vividnext.sodalive.member.QMember.member | ||||||
|  | import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember | ||||||
| import org.springframework.data.jpa.repository.JpaRepository | import org.springframework.data.jpa.repository.JpaRepository | ||||||
|  |  | ||||||
| interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository | interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository | ||||||
| @@ -29,6 +35,9 @@ 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 getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> | ||||||
|  |     fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List<Series> | ||||||
| } | } | ||||||
|  |  | ||||||
| class ContentSeriesQueryRepositoryImpl( | class ContentSeriesQueryRepositoryImpl( | ||||||
| @@ -133,4 +142,61 @@ class ContentSeriesQueryRepositoryImpl( | |||||||
|             .limit(limit) |             .limit(limit) | ||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long, limit: Long): List<Series> { | ||||||
|  |         var where = series.isOriginal.isTrue | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .selectFrom(series) | ||||||
|  |             .where(where) | ||||||
|  |             .orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc()) | ||||||
|  |             .offset(offset) | ||||||
|  |             .limit(limit) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> { | ||||||
|  |         var where = seriesGenre.isActive.isTrue | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(seriesGenre.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(QGetSeriesGenreListResponse(seriesGenre.id, seriesGenre.genre)) | ||||||
|  |             .from(seriesGenre) | ||||||
|  |             .where(where) | ||||||
|  |             .orderBy(seriesGenre.orders.asc()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List<Series> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         var where = series.isActive.isTrue | ||||||
|  |             .and(member.isActive.isTrue) | ||||||
|  |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(series) | ||||||
|  |             .from(audioContentCurationItem) | ||||||
|  |             .innerJoin(audioContentCurationItem.curation, audioContentCuration) | ||||||
|  |             .innerJoin(audioContentCurationItem.series, series) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -30,6 +30,19 @@ class ContentSeriesService( | |||||||
|     @Value("\${cloud.aws.cloud-front.host}") |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|     private val coverImageHost: String |     private val coverImageHost: String | ||||||
| ) { | ) { | ||||||
|  |     fun getOriginalAudioDramaList( | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         offset: Long = 0, | ||||||
|  |         limit: Long = 20 | ||||||
|  |     ): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, offset, limit) | ||||||
|  |         return seriesToSeriesListItem(originalAudioDramaList, isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> { | ||||||
|  |         return repository.getGenreList(isAdult = isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun getSeriesList( |     fun getSeriesList( | ||||||
|         creatorId: Long, |         creatorId: Long, | ||||||
|         member: Member, |         member: Member, | ||||||
| @@ -170,6 +183,15 @@ class ContentSeriesService( | |||||||
|         return seriesToSeriesListItem(seriesList = seriesList, isAdult = member.auth != null) |         return seriesToSeriesListItem(seriesList = seriesList, isAdult = member.auth != null) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun fetchSeriesByCurationId( | ||||||
|  |         curationId: Long, | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean | ||||||
|  |     ): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         val seriesList = repository.findByCurationId(curationId = curationId, memberId = memberId, isAdult = isAdult) | ||||||
|  |         return seriesToSeriesListItem(seriesList, isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun seriesToSeriesListItem( |     private fun seriesToSeriesListItem( | ||||||
|         seriesList: List<Series>, |         seriesList: List<Series>, | ||||||
|         isAdult: Boolean |         isAdult: Boolean | ||||||
|   | |||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series | ||||||
|  |  | ||||||
|  | import com.querydsl.core.annotations.QueryProjection | ||||||
|  |  | ||||||
|  | data class GetSeriesGenreListResponse @QueryProjection constructor( | ||||||
|  |     val id: Long, | ||||||
|  |     val genre: String | ||||||
|  | ) | ||||||
| @@ -6,19 +6,13 @@ import kr.co.vividnext.sodalive.content.ContentType | |||||||
| import kr.co.vividnext.sodalive.content.SortType | import kr.co.vividnext.sodalive.content.SortType | ||||||
| import kr.co.vividnext.sodalive.content.theme.content.GetContentByThemeResponse | import kr.co.vividnext.sodalive.content.theme.content.GetContentByThemeResponse | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| import kr.co.vividnext.sodalive.member.block.BlockMemberRepository |  | ||||||
| import org.springframework.beans.factory.annotation.Value |  | ||||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||||
|  |  | ||||||
| @Service | @Service | ||||||
| class AudioContentThemeService( | class AudioContentThemeService( | ||||||
|     private val queryRepository: AudioContentThemeQueryRepository, |     private val queryRepository: AudioContentThemeQueryRepository, | ||||||
|     private val blockMemberRepository: BlockMemberRepository, |     private val contentRepository: AudioContentRepository | ||||||
|     private val contentRepository: AudioContentRepository, |  | ||||||
|  |  | ||||||
|     @Value("\${cloud.aws.cloud-front.host}") |  | ||||||
|     private val imageHost: String |  | ||||||
| ) { | ) { | ||||||
|     @Transactional(readOnly = true) |     @Transactional(readOnly = true) | ||||||
|     fun getThemes(): List<GetAudioContentThemeResponse> { |     fun getThemes(): List<GetAudioContentThemeResponse> { | ||||||
| @@ -46,7 +40,6 @@ class AudioContentThemeService( | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         val items = contentRepository.findByTheme( |         val items = contentRepository.findByTheme( | ||||||
|             cloudfrontHost = imageHost, |  | ||||||
|             memberId = member.id!!, |             memberId = member.id!!, | ||||||
|             theme = theme.theme, |             theme = theme.theme, | ||||||
|             sortType = sortType, |             sortType = sortType, | ||||||
| @@ -55,9 +48,6 @@ class AudioContentThemeService( | |||||||
|             offset = offset, |             offset = offset, | ||||||
|             limit = limit |             limit = limit | ||||||
|         ) |         ) | ||||||
|             .asSequence() |  | ||||||
|             .filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.creatorId) } |  | ||||||
|             .toList() |  | ||||||
|  |  | ||||||
|         return GetContentByThemeResponse( |         return GetContentByThemeResponse( | ||||||
|             theme = theme.theme, |             theme = theme.theme, | ||||||
|   | |||||||
| @@ -24,10 +24,8 @@ class AuthController(private val service: AuthService) { | |||||||
|         if (service.isBlockAuth(authenticateData)) { |         if (service.isBlockAuth(authenticateData)) { | ||||||
|             service.signOut(member.id!!) |             service.signOut(member.id!!) | ||||||
|             throw SodaException("운영정책을 위반하여 이용을 제한합니다.") |             throw SodaException("운영정책을 위반하여 이용을 제한합니다.") | ||||||
|         } else { |  | ||||||
|             service.authenticate(authenticateData, member.id!!) |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         ApiResponse.ok(null, null) |         ApiResponse.ok(service.authenticate(authenticateData, member.id!!)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package kr.co.vividnext.sodalive.member.auth | ||||||
|  |  | ||||||
|  | data class AuthResponse(val gender: Int) | ||||||
| @@ -73,7 +73,7 @@ class AuthService( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Transactional |     @Transactional | ||||||
|     fun authenticate(certificate: AuthVerifyCertificate, memberId: Long) { |     fun authenticate(certificate: AuthVerifyCertificate, memberId: Long): AuthResponse { | ||||||
|         val memberIds = repository.getActiveMemberIdsByDi(di = certificate.di) |         val memberIds = repository.getActiveMemberIdsByDi(di = certificate.di) | ||||||
|         if (memberIds.size >= 3) { |         if (memberIds.size >= 3) { | ||||||
|             throw SodaException( |             throw SodaException( | ||||||
| @@ -96,8 +96,9 @@ class AuthService( | |||||||
|             auth.member = member |             auth.member = member | ||||||
|  |  | ||||||
|             repository.save(auth) |             repository.save(auth) | ||||||
|  |             return AuthResponse(gender = certificate.gender) | ||||||
|         } else { |         } else { | ||||||
|             throw SodaException("2005년 1월 1일 이전 출생자만 본인인증이 가능합니다.") |             throw SodaException("${nowYear - 19}년 1월 1일 이전 출생자만 본인인증이 가능합니다.") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| package kr.co.vividnext.sodalive.notice | package kr.co.vividnext.sodalive.notice | ||||||
|  |  | ||||||
|  | import com.querydsl.core.annotations.QueryProjection | ||||||
|  |  | ||||||
| data class GetNoticeResponse( | data class GetNoticeResponse( | ||||||
|     val totalCount: Int, |     val totalCount: Int, | ||||||
|     val noticeList: List<NoticeItem> |     val noticeList: List<NoticeItem> | ||||||
| @@ -11,3 +13,8 @@ data class NoticeItem( | |||||||
|     val content: String, |     val content: String, | ||||||
|     val date: String |     val date: String | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | data class NoticeTitleItem @QueryProjection constructor( | ||||||
|  |     val id: Long, | ||||||
|  |     val title: String | ||||||
|  | ) | ||||||
|   | |||||||
| @@ -63,4 +63,8 @@ class ServiceNoticeService(private val repository: ServiceServiceNoticeRepositor | |||||||
|  |  | ||||||
|         return GetNoticeResponse(totalCount, noticeList) |         return GetNoticeResponse(totalCount, noticeList) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getLatestNotice(): NoticeTitleItem? { | ||||||
|  |         return repository.getLatestNotice() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ interface ServiceServiceNoticeRepository : JpaRepository<ServiceNotice, Long>, S | |||||||
| interface ServiceNoticeQueryRepository { | interface ServiceNoticeQueryRepository { | ||||||
|     fun getNoticeTotalCount(): Int |     fun getNoticeTotalCount(): Int | ||||||
|     fun getNoticeList(pageable: Pageable): List<ServiceNotice> |     fun getNoticeList(pageable: Pageable): List<ServiceNotice> | ||||||
|  |     fun getLatestNotice(): NoticeTitleItem? | ||||||
| } | } | ||||||
|  |  | ||||||
| @Repository | @Repository | ||||||
| @@ -34,4 +35,18 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory | |||||||
|             .orderBy(serviceNotice.id.desc()) |             .orderBy(serviceNotice.id.desc()) | ||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun getLatestNotice(): NoticeTitleItem? { | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QNoticeTitleItem( | ||||||
|  |                     serviceNotice.id, | ||||||
|  |                     serviceNotice.title | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(serviceNotice) | ||||||
|  |             .where(serviceNotice.isActive.isTrue) | ||||||
|  |             .orderBy(serviceNotice.id.desc()) | ||||||
|  |             .fetchFirst() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,497 @@ | |||||||
|  | package kr.co.vividnext.sodalive.rank | ||||||
|  |  | ||||||
|  | import com.querydsl.core.types.dsl.Expressions | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre | ||||||
|  | import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||||
|  | import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment | ||||||
|  | import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike | ||||||
|  | import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||||
|  | import kr.co.vividnext.sodalive.content.main.QContentCreatorResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.QGetAudioContentRankingItem | ||||||
|  | import kr.co.vividnext.sodalive.content.order.QOrder.order | ||||||
|  | import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.Series | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState | ||||||
|  | import kr.co.vividnext.sodalive.explorer.QCreatorRanking.creatorRanking | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.member.MemberRole | ||||||
|  | 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 | ||||||
|  | import java.time.LocalDateTime | ||||||
|  |  | ||||||
|  | @Repository | ||||||
|  | class RankingRepository( | ||||||
|  |     private val queryFactory: JPAQueryFactory, | ||||||
|  |  | ||||||
|  |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|  |     private val imageHost: String | ||||||
|  | ) { | ||||||
|  |     fun getCreatorRankings(): List<Member> { | ||||||
|  |         return queryFactory | ||||||
|  |             .select(member) | ||||||
|  |             .from(creatorRanking) | ||||||
|  |             .innerJoin(creatorRanking.member, member) | ||||||
|  |             .orderBy(creatorRanking.ranking.asc()) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getAudioContentRanking( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long, | ||||||
|  |         sortType: String, | ||||||
|  |         theme: String = "" | ||||||
|  |     ): List<GetAudioContentRankingItem> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         var where = audioContent.isActive.isTrue | ||||||
|  |             .and(audioContent.member.isActive.isTrue) | ||||||
|  |             .and(audioContent.member.isNotNull) | ||||||
|  |             .and(audioContent.member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContentTheme.isActive.isTrue) | ||||||
|  |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(audioContent.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (theme.isNotBlank()) { | ||||||
|  |             where = where.and(audioContentTheme.theme.eq(theme)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var select = queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetAudioContentRankingItem( | ||||||
|  |                     audioContent.id, | ||||||
|  |                     audioContent.title, | ||||||
|  |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|  |                     audioContentTheme.theme, | ||||||
|  |                     audioContent.price, | ||||||
|  |                     audioContent.duration, | ||||||
|  |                     member.id, | ||||||
|  |                     member.nickname | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         select = when (sortType) { | ||||||
|  |             "후원" -> { | ||||||
|  |                 select | ||||||
|  |                     .from(audioContentComment) | ||||||
|  |                     .innerJoin(audioContentComment.audioContent, audioContent) | ||||||
|  |                     .innerJoin(audioContent.member, member) | ||||||
|  |                     .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|  |                     .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |                     .where( | ||||||
|  |                         where | ||||||
|  |                             .and(audioContentComment.isActive.isTrue) | ||||||
|  |                             .and(audioContentComment.donationCan.gt(0)) | ||||||
|  |                             .and(audioContentComment.createdAt.goe(startDate)) | ||||||
|  |                             .and(audioContentComment.createdAt.lt(endDate)) | ||||||
|  |                     ) | ||||||
|  |                     .groupBy(audioContent.id) | ||||||
|  |                     .orderBy(audioContentComment.donationCan.sum().desc(), audioContent.createdAt.asc()) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             "댓글" -> { | ||||||
|  |                 select | ||||||
|  |                     .from(audioContentComment) | ||||||
|  |                     .innerJoin(audioContentComment.audioContent, audioContent) | ||||||
|  |                     .innerJoin(audioContent.member, member) | ||||||
|  |                     .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|  |                     .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |                     .where( | ||||||
|  |                         where | ||||||
|  |                             .and(audioContentComment.isActive.isTrue) | ||||||
|  |                             .and(audioContentComment.createdAt.goe(startDate)) | ||||||
|  |                             .and(audioContentComment.createdAt.lt(endDate)) | ||||||
|  |                     ) | ||||||
|  |                     .groupBy(audioContentComment.audioContent.id) | ||||||
|  |                     .orderBy(audioContentComment.id.count().desc(), audioContent.createdAt.asc()) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             "좋아요" -> { | ||||||
|  |                 select | ||||||
|  |                     .from(audioContentLike) | ||||||
|  |                     .innerJoin(audioContentLike.audioContent, audioContent) | ||||||
|  |                     .innerJoin(audioContent.member, member) | ||||||
|  |                     .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|  |                     .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |                     .where( | ||||||
|  |                         where | ||||||
|  |                             .and(audioContentLike.isActive.isTrue) | ||||||
|  |                             .and(audioContentLike.createdAt.goe(startDate)) | ||||||
|  |                             .and(audioContentLike.createdAt.lt(endDate)) | ||||||
|  |                     ) | ||||||
|  |                     .groupBy(audioContentLike.audioContent.id) | ||||||
|  |                     .orderBy(audioContentLike.id.count().desc(), audioContent.createdAt.asc()) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             else -> { | ||||||
|  |                 select | ||||||
|  |                     .from(order) | ||||||
|  |                     .innerJoin(order.audioContent, audioContent) | ||||||
|  |                     .innerJoin(audioContent.member, member) | ||||||
|  |                     .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|  |                     .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |                     .where( | ||||||
|  |                         where | ||||||
|  |                             .and(order.isActive.isTrue) | ||||||
|  |                             .and(order.createdAt.goe(startDate)) | ||||||
|  |                             .and(order.createdAt.lt(endDate)) | ||||||
|  |                     ) | ||||||
|  |                     .groupBy(audioContent.id) | ||||||
|  |                     .orderBy(order.can.sum().desc(), audioContent.createdAt.asc()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return select | ||||||
|  |             .offset(offset) | ||||||
|  |             .limit(limit) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getSeriesRanking( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime | ||||||
|  |     ): List<Series> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         var where = series.isActive.isTrue | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(member.isActive.isTrue) | ||||||
|  |             .and(member.isNotNull) | ||||||
|  |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |             .and(order.isActive.isTrue) | ||||||
|  |             .and(order.createdAt.goe(startDate)) | ||||||
|  |             .and(order.createdAt.lt(endDate)) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(series) | ||||||
|  |             .from(seriesContent) | ||||||
|  |             .innerJoin(seriesContent.series, series) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(series.id) | ||||||
|  |             .orderBy( | ||||||
|  |                 order.can.sum().desc(), | ||||||
|  |                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||||
|  |             ) | ||||||
|  |             .offset(0) | ||||||
|  |             .limit(10) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getCompleteSeriesRanking( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime, | ||||||
|  |         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.isActive.isTrue | ||||||
|  |             .and(series.state.eq(SeriesState.COMPLETE)) | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(member.isActive.isTrue) | ||||||
|  |             .and(member.isNotNull) | ||||||
|  |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |             .and(order.isActive.isTrue) | ||||||
|  |             .and(order.createdAt.goe(startDate)) | ||||||
|  |             .and(order.createdAt.lt(endDate)) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(series) | ||||||
|  |             .from(seriesContent) | ||||||
|  |             .innerJoin(seriesContent.series, series) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(series.id) | ||||||
|  |             .orderBy( | ||||||
|  |                 order.can.sum().desc(), | ||||||
|  |                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||||
|  |             ) | ||||||
|  |             .offset(offset) | ||||||
|  |             .limit(limit) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getSeriesAllRankingByGenre(memberId: Long, isAdult: Boolean, genreId: Long): List<Series> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         var where = series.isActive.isTrue | ||||||
|  |             .and(seriesGenre.id.eq(genreId)) | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(member.isActive.isTrue) | ||||||
|  |             .and(member.isNotNull) | ||||||
|  |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |             .and(order.isActive.isTrue) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(series) | ||||||
|  |             .from(seriesContent) | ||||||
|  |             .innerJoin(seriesContent.series, series) | ||||||
|  |             .innerJoin(series.genre, seriesGenre) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(series.id) | ||||||
|  |             .orderBy( | ||||||
|  |                 order.can.sum().desc(), | ||||||
|  |                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||||
|  |             ) | ||||||
|  |             .offset(0) | ||||||
|  |             .limit(20) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorByContentRevenueRankTop20( | ||||||
|  |         memberId: Long, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime | ||||||
|  |     ): List<ContentCreatorResponse> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         val ordersCondition = order.audioContent.id.eq(audioContent.id) | ||||||
|  |             .and(order.isActive.isTrue) | ||||||
|  |             .and(order.createdAt.goe(startDate)) | ||||||
|  |             .and(order.createdAt.lt(startDate)) | ||||||
|  |  | ||||||
|  |         val where = member.isActive.isTrue | ||||||
|  |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QContentCreatorResponse( | ||||||
|  |                     member.id, | ||||||
|  |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(member) | ||||||
|  |             .innerJoin(audioContent).on(member.id.eq(audioContent.member.id)) | ||||||
|  |             .leftJoin(order).on(ordersCondition) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(member.id) | ||||||
|  |             .having(audioContent.id.count().goe(4)) | ||||||
|  |             .orderBy( | ||||||
|  |                 order.can.sum().desc(), | ||||||
|  |                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||||
|  |             ) | ||||||
|  |             .offset(0) | ||||||
|  |             .limit(20) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): 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(order.isActive.isTrue) | ||||||
|  |             .and(member.id.eq(creatorId)) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetAudioContentRankingItem( | ||||||
|  |                     audioContent.id, | ||||||
|  |                     audioContent.title, | ||||||
|  |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|  |                     audioContentTheme.theme, | ||||||
|  |                     audioContent.price, | ||||||
|  |                     audioContent.duration, | ||||||
|  |                     member.id, | ||||||
|  |                     member.nickname | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(order) | ||||||
|  |             .innerJoin(order.audioContent, audioContent) | ||||||
|  |             .innerJoin(audioContent.member, member) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(audioContent.id) | ||||||
|  |             .orderBy(order.can.sum().desc()) | ||||||
|  |             .offset(0) | ||||||
|  |             .limit(2) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): 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(order.isActive.isTrue) | ||||||
|  |             .and(member.id.eq(creatorId)) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetAudioContentRankingItem( | ||||||
|  |                     audioContent.id, | ||||||
|  |                     audioContent.title, | ||||||
|  |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|  |                     audioContentTheme.theme, | ||||||
|  |                     audioContent.price, | ||||||
|  |                     audioContent.duration, | ||||||
|  |                     member.id, | ||||||
|  |                     member.nickname | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(order) | ||||||
|  |             .innerJoin(order.audioContent, audioContent) | ||||||
|  |             .innerJoin(audioContent.member, member) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(audioContent.id) | ||||||
|  |             .orderBy(order.id.count().desc()) | ||||||
|  |             .offset(0) | ||||||
|  |             .limit(2) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorBySeriesRevenueRankTop20( | ||||||
|  |         memberId: Long, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime | ||||||
|  |     ): List<ContentCreatorResponse> { | ||||||
|  |         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||||
|  |             .and(blockMember.isActive.isTrue) | ||||||
|  |             .and(blockMember.blockedMember.id.eq(memberId)) | ||||||
|  |  | ||||||
|  |         val ordersCondition = order.audioContent.id.eq(audioContent.id) | ||||||
|  |             .and(order.isActive.isTrue) | ||||||
|  |             .and(order.createdAt.goe(startDate)) | ||||||
|  |             .and(order.createdAt.lt(startDate)) | ||||||
|  |  | ||||||
|  |         val where = member.isActive.isTrue | ||||||
|  |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(blockMember.id.isNull) | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QContentCreatorResponse( | ||||||
|  |                     member.id, | ||||||
|  |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(seriesContent) | ||||||
|  |             .innerJoin(seriesContent.series, series) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .leftJoin(order).on(ordersCondition) | ||||||
|  |             .leftJoin(blockMember).on(blockMemberCondition) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(member.id) | ||||||
|  |             .having(series.id.countDistinct().goe(3)) | ||||||
|  |             .orderBy( | ||||||
|  |                 order.can.sum().desc(), | ||||||
|  |                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||||
|  |             ) | ||||||
|  |             .offset(0) | ||||||
|  |             .limit(20) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorSeriesBySales(creatorId: Long, isAdult: Boolean): List<Series> { | ||||||
|  |         var where = member.isActive.isTrue | ||||||
|  |             .and(member.role.eq(MemberRole.CREATOR)) | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |             .and(audioContent.limited.isNull) | ||||||
|  |             .and(order.isActive.isTrue) | ||||||
|  |             .and(member.id.eq(creatorId)) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(series) | ||||||
|  |             .from(seriesContent) | ||||||
|  |             .innerJoin(seriesContent.series, series) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .innerJoin(series.member, member) | ||||||
|  |             .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) | ||||||
|  |             .where(where) | ||||||
|  |             .groupBy(series.id) | ||||||
|  |             .orderBy( | ||||||
|  |                 order.can.sum().desc(), | ||||||
|  |                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||||
|  |             ) | ||||||
|  |             .offset(0) | ||||||
|  |             .limit(10) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										198
									
								
								src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | |||||||
|  | package kr.co.vividnext.sodalive.rank | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||||
|  | import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.series.content.ContentSeriesContentRepository | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.Series | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState | ||||||
|  | import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse | ||||||
|  | import kr.co.vividnext.sodalive.member.MemberService | ||||||
|  | import org.springframework.beans.factory.annotation.Value | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.time.LocalDateTime | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class RankingService( | ||||||
|  |     private val repository: RankingRepository, | ||||||
|  |     private val memberService: MemberService, | ||||||
|  |     private val seriesContentRepository: ContentSeriesContentRepository, | ||||||
|  |  | ||||||
|  |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|  |     private val imageHost: String | ||||||
|  | ) { | ||||||
|  |     fun getCreatorRanking(memberId: Long, rankingDate: String): GetExplorerSectionResponse { | ||||||
|  |         val creatorRankings = repository | ||||||
|  |             .getCreatorRankings() | ||||||
|  |             .filter { !memberService.isBlocked(blockedMemberId = memberId, memberId = it.id!!) } | ||||||
|  |             .map { it.toExplorerSectionCreator(imageHost) } | ||||||
|  |  | ||||||
|  |         return GetExplorerSectionResponse( | ||||||
|  |             title = "인기 크리에이터", | ||||||
|  |             coloredTitle = "인기", | ||||||
|  |             color = "FF5C49", | ||||||
|  |             desc = rankingDate, | ||||||
|  |             creators = creatorRankings | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getContentRanking( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime, | ||||||
|  |         offset: Long = 0, | ||||||
|  |         limit: Long = 12, | ||||||
|  |         sortType: String = "매출", | ||||||
|  |         theme: String = "" | ||||||
|  |     ): List<GetAudioContentRankingItem> { | ||||||
|  |         return repository.getAudioContentRanking( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = startDate, | ||||||
|  |             endDate = endDate, | ||||||
|  |             offset = offset, | ||||||
|  |             limit = limit, | ||||||
|  |             sortType = sortType, | ||||||
|  |             theme = theme | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getSeriesRanking( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime | ||||||
|  |     ): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         val seriesList = repository.getSeriesRanking(memberId, isAdult, startDate, endDate) | ||||||
|  |         return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getSeriesAllRankingByGenre( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         genreId: Long | ||||||
|  |     ): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         val seriesList = repository.getSeriesAllRankingByGenre( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             genreId = genreId | ||||||
|  |         ) | ||||||
|  |         return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getCompleteSeriesRanking( | ||||||
|  |         memberId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime, | ||||||
|  |         offset: Long = 0, | ||||||
|  |         limit: Long = 10 | ||||||
|  |     ): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         val seriesList = repository.getCompleteSeriesRanking( | ||||||
|  |             memberId = memberId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             startDate = startDate, | ||||||
|  |             endDate = endDate, | ||||||
|  |             offset = offset, | ||||||
|  |             limit = limit | ||||||
|  |         ) | ||||||
|  |         return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun seriesToSeriesListItem( | ||||||
|  |         seriesList: List<Series>, | ||||||
|  |         isAdult: Boolean | ||||||
|  |     ): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         return seriesList | ||||||
|  |             .map { | ||||||
|  |                 GetSeriesListResponse.SeriesListItem( | ||||||
|  |                     seriesId = it.id!!, | ||||||
|  |                     title = it.title, | ||||||
|  |                     coverImage = "$imageHost/${it.coverImage!!}", | ||||||
|  |                     publishedDaysOfWeek = publishedDaysOfWeekText(it.publishedDaysOfWeek), | ||||||
|  |                     isComplete = it.state == SeriesState.COMPLETE, | ||||||
|  |                     creator = GetSeriesListResponse.SeriesListItemCreator( | ||||||
|  |                         creatorId = it.member!!.id!!, | ||||||
|  |                         nickname = it.member!!.nickname, | ||||||
|  |                         profileImage = "$imageHost/${it.member!!.profileImage!!}" | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             .map { | ||||||
|  |                 it.numberOfContent = seriesContentRepository.getContentCount( | ||||||
|  |                     seriesId = it.seriesId, | ||||||
|  |                     isAdult = isAdult | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |                 it | ||||||
|  |             } | ||||||
|  |             .map { | ||||||
|  |                 val nowDateTime = LocalDateTime.now() | ||||||
|  |  | ||||||
|  |                 it.isNew = seriesContentRepository.isNewContent( | ||||||
|  |                     seriesId = it.seriesId, | ||||||
|  |                     isAdult = isAdult, | ||||||
|  |                     fromDate = nowDateTime.minusDays(7), | ||||||
|  |                     nowDate = nowDateTime | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |                 it | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun publishedDaysOfWeekText(publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>): String { | ||||||
|  |         val dayOfWeekText = publishedDaysOfWeek.toList().sortedBy { it.ordinal } | ||||||
|  |             .map { | ||||||
|  |                 when (it) { | ||||||
|  |                     SeriesPublishedDaysOfWeek.SUN -> "일" | ||||||
|  |                     SeriesPublishedDaysOfWeek.MON -> "월" | ||||||
|  |                     SeriesPublishedDaysOfWeek.TUE -> "화" | ||||||
|  |                     SeriesPublishedDaysOfWeek.WED -> "수" | ||||||
|  |                     SeriesPublishedDaysOfWeek.THU -> "목" | ||||||
|  |                     SeriesPublishedDaysOfWeek.FRI -> "금" | ||||||
|  |                     SeriesPublishedDaysOfWeek.SAT -> "토" | ||||||
|  |                     SeriesPublishedDaysOfWeek.RANDOM -> "랜덤" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .joinToString(", ") { it } | ||||||
|  |  | ||||||
|  |         return if (publishedDaysOfWeek.contains(SeriesPublishedDaysOfWeek.RANDOM)) { | ||||||
|  |             dayOfWeekText | ||||||
|  |         } else if (publishedDaysOfWeek.size < 7) { | ||||||
|  |             "매주 $dayOfWeekText" | ||||||
|  |         } else { | ||||||
|  |             "매일" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorByContentRevenueRankTop20( | ||||||
|  |         memberId: Long, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime | ||||||
|  |     ): List<ContentCreatorResponse> { | ||||||
|  |         return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||||
|  |         return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||||
|  |         return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorBySeriesRevenueRankTop20( | ||||||
|  |         memberId: Long, | ||||||
|  |         startDate: LocalDateTime, | ||||||
|  |         endDate: LocalDateTime | ||||||
|  |     ): List<ContentCreatorResponse> { | ||||||
|  |         return repository.fetchCreatorBySeriesRevenueRankTop20(memberId, startDate, endDate) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun fetchCreatorSeriesBySales(creatorId: Long, isAdult: Boolean): List<GetSeriesListResponse.SeriesListItem> { | ||||||
|  |         val seriesList = repository.fetchCreatorSeriesBySales(creatorId = creatorId, isAdult = isAdult) | ||||||
|  |         return seriesToSeriesListItem(seriesList, isAdult) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user