| @@ -60,4 +60,9 @@ class AdminContentCurationController(private val service: AdminContentCurationSe | ||||
|     fun removeItemInCuration( | ||||
|         @RequestBody request: RemoveItemInCurationRequest | ||||
|     ) = ApiResponse.ok(service.removeItemInCuration(request), "큐레이션 아이템을 제거했습니다.") | ||||
|  | ||||
|     @PutMapping("/orders/item") | ||||
|     fun updateItemInCurationOrders( | ||||
|         @RequestBody request: UpdateCurationItemOrdersRequest | ||||
|     ) = ApiResponse.ok(service.updateItemInCurationOrders(request), "수정되었습니다.") | ||||
| } | ||||
|   | ||||
| @@ -77,6 +77,7 @@ class AdminContentCurationItemQueryRepositoryImpl( | ||||
|                 audioContentCuration.id.eq(curationId), | ||||
|                 audioContentCurationItem.isActive.isTrue | ||||
|             ) | ||||
|             .orderBy(audioContentCurationItem.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
| @@ -99,6 +100,7 @@ class AdminContentCurationItemQueryRepositoryImpl( | ||||
|                 audioContentCuration.id.eq(curationId), | ||||
|                 audioContentCurationItem.isActive.isTrue | ||||
|             ) | ||||
|             .orderBy(audioContentCurationItem.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -150,4 +150,19 @@ class AdminContentCurationService( | ||||
|  | ||||
|         audioContentCurationItem?.isActive = false | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun updateItemInCurationOrders(request: UpdateCurationItemOrdersRequest) { | ||||
|         val ids = request.itemIds | ||||
|         for (index in ids.indices) { | ||||
|             val item = contentCurationItemRepository.findByCurationIdAndItemId( | ||||
|                 curationId = request.curationId, | ||||
|                 itemId = ids[index] | ||||
|             ) | ||||
|  | ||||
|             if (item != null) { | ||||
|                 item.orders = index + 1 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,6 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation | ||||
|  | ||||
| data class UpdateCurationItemOrdersRequest( | ||||
|     val curationId: Long, | ||||
|     val itemIds: List<Long> | ||||
| ) | ||||
| @@ -0,0 +1,72 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation.tag | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.ContentHashTagCurationItem | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCuration.contentHashTagCuration | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCurationItem.contentHashTagCurationItem | ||||
| import org.springframework.beans.factory.annotation.Value | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
|  | ||||
| interface AdminContentHashTagCurationItemRepository : | ||||
|     JpaRepository<ContentHashTagCurationItem, Long>, | ||||
|     AdminContentHashTagCurationItemQueryRepository | ||||
|  | ||||
| interface AdminContentHashTagCurationItemQueryRepository { | ||||
|     fun getContentHashTagCurationItemList(curationId: Long): List<GetAdminHashTagCurationItemResponse> | ||||
|     fun findByCurationIdAndContentId(curationId: Long, contentId: Long?): ContentHashTagCurationItem? | ||||
|     fun findByCurationIdAndItemId(curationId: Long, itemId: Long): ContentHashTagCurationItem? | ||||
| } | ||||
|  | ||||
| class AdminContentHashTagCurationItemQueryRepositoryImpl( | ||||
|     val queryFactory: JPAQueryFactory, | ||||
|  | ||||
|     @Value("\${cloud.aws.cloud-front.host}") | ||||
|     private val imageHost: String | ||||
| ) : AdminContentHashTagCurationItemQueryRepository { | ||||
|     override fun getContentHashTagCurationItemList(curationId: Long): List<GetAdminHashTagCurationItemResponse> { | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAdminHashTagCurationItemResponse( | ||||
|                     contentHashTagCurationItem.id, | ||||
|                     audioContent.title, | ||||
|                     audioContent.detail, | ||||
|                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||
|                     audioContent.member.nickname.coalesce(""), | ||||
|                     audioContent.isAdult | ||||
|                 ) | ||||
|             ) | ||||
|             .from(contentHashTagCurationItem) | ||||
|             .innerJoin(contentHashTagCurationItem.curation, contentHashTagCuration) | ||||
|             .innerJoin(contentHashTagCurationItem.content, audioContent) | ||||
|             .where( | ||||
|                 contentHashTagCuration.id.eq(curationId), | ||||
|                 contentHashTagCurationItem.isActive.isTrue, | ||||
|                 audioContent.isActive.isTrue | ||||
|             ) | ||||
|             .orderBy(contentHashTagCurationItem.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun findByCurationIdAndContentId(curationId: Long, contentId: Long?): ContentHashTagCurationItem? { | ||||
|         return queryFactory | ||||
|             .selectFrom(contentHashTagCurationItem) | ||||
|             .innerJoin(contentHashTagCurationItem.curation, contentHashTagCuration) | ||||
|             .innerJoin(contentHashTagCurationItem.content, audioContent) | ||||
|             .where( | ||||
|                 contentHashTagCuration.id.eq(curationId), | ||||
|                 audioContent.id.eq(contentId) | ||||
|             ) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     override fun findByCurationIdAndItemId(curationId: Long, itemId: Long): ContentHashTagCurationItem? { | ||||
|         return queryFactory.selectFrom(contentHashTagCurationItem) | ||||
|             .innerJoin(contentHashTagCurationItem.curation, contentHashTagCuration) | ||||
|             .where( | ||||
|                 contentHashTagCuration.id.eq(curationId), | ||||
|                 contentHashTagCurationItem.id.eq(itemId) | ||||
|             ) | ||||
|             .fetchFirst() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,63 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation.tag | ||||
|  | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.AddItemToCurationRequest | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.RemoveItemInCurationRequest | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.UpdateCurationItemOrdersRequest | ||||
| 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.RestController | ||||
|  | ||||
| @RestController | ||||
| @RequestMapping("/admin/audio-content/tag/curation") | ||||
| @PreAuthorize("hasRole('ADMIN')") | ||||
| class AdminHashTagCurationController(private val service: AdminHashTagCurationService) { | ||||
|     @GetMapping | ||||
|     fun getContentHashTagCurationList() = ApiResponse.ok(service.getContentHashTagCurationList()) | ||||
|  | ||||
|     @PostMapping | ||||
|     fun createContentHashTagCuration( | ||||
|         @RequestBody request: CreateContentHashTagCurationRequest | ||||
|     ) = ApiResponse.ok(service.createContentHashTagCuration(request)) | ||||
|  | ||||
|     @PutMapping | ||||
|     fun updateContentHashTagCuration( | ||||
|         @RequestBody request: UpdateContentHashTagCurationRequest | ||||
|     ) = ApiResponse.ok(service.updateContentHashTagCuration(request)) | ||||
|  | ||||
|     @PutMapping("/orders") | ||||
|     fun updateContentHashTagCurationOrders( | ||||
|         @RequestBody request: UpdateContentHashTagCurationOrderRequest | ||||
|     ) = ApiResponse.ok(service.updateContentHashTagCurationOrders(request.ids), "수정되었습니다.") | ||||
|  | ||||
|     @GetMapping("/items") | ||||
|     fun getHashTagCurationItemList( | ||||
|         @RequestParam curationId: Long | ||||
|     ) = ApiResponse.ok(service.getHashTagCurationItemList(curationId = curationId)) | ||||
|  | ||||
|     @GetMapping("/search/content") | ||||
|     fun searchHashTagCurationContentItem( | ||||
|         @RequestParam curationId: Long, | ||||
|         @RequestParam searchWord: String | ||||
|     ) = ApiResponse.ok(service.searchHashTagCurationContentItem(curationId, searchWord)) | ||||
|  | ||||
|     @PostMapping("/add/item") | ||||
|     fun addItemToHashTagCuration( | ||||
|         @RequestBody request: AddItemToCurationRequest | ||||
|     ) = ApiResponse.ok(service.addItemToHashTagCuration(request), "큐레이션 아이템을 등록했습니다.") | ||||
|  | ||||
|     @PutMapping("/remove/item") | ||||
|     fun removeItemInHashTagCuration( | ||||
|         @RequestBody request: RemoveItemInCurationRequest | ||||
|     ) = ApiResponse.ok(service.removeItemInHashTagCuration(request), "큐레이션 아이템을 제거했습니다.") | ||||
|  | ||||
|     @PutMapping("/orders/item") | ||||
|     fun updateItemInHashTagCurationOrders( | ||||
|         @RequestBody request: UpdateCurationItemOrdersRequest | ||||
|     ) = ApiResponse.ok(service.updateItemInHashTagCurationOrders(request), "수정되었습니다.") | ||||
| } | ||||
| @@ -0,0 +1,84 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation.tag | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.QSearchCurationItemResponse | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.SearchCurationItemResponse | ||||
| import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.ContentHashTagCuration | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCuration.contentHashTagCuration | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCurationItem.contentHashTagCurationItem | ||||
| import org.springframework.beans.factory.annotation.Value | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
| import org.springframework.stereotype.Repository | ||||
|  | ||||
| interface AdminHashTagCurationRepository : | ||||
|     JpaRepository<ContentHashTagCuration, Long>, | ||||
|     AdminHashTagCurationQueryRepository | ||||
|  | ||||
| interface AdminHashTagCurationQueryRepository { | ||||
|     fun getContentHashTagCurationList(): List<GetAdminContentHashTagCurationResponse> | ||||
|     fun searchHashTagCurationContentItem(curationId: Long, searchWord: String): List<SearchCurationItemResponse> | ||||
|     fun isExistsTag(tag: String): Boolean | ||||
| } | ||||
|  | ||||
| @Repository | ||||
| class AdminHashTagCurationQueryRepositoryImpl( | ||||
|     private val queryFactory: JPAQueryFactory, | ||||
|  | ||||
|     @Value("\${cloud.aws.cloud-front.host}") | ||||
|     private val imageHost: String | ||||
| ) : AdminHashTagCurationQueryRepository { | ||||
|     override fun getContentHashTagCurationList(): List<GetAdminContentHashTagCurationResponse> { | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAdminContentHashTagCurationResponse( | ||||
|                     contentHashTagCuration.id, | ||||
|                     contentHashTagCuration.tag, | ||||
|                     contentHashTagCuration.isAdult | ||||
|                 ) | ||||
|             ) | ||||
|             .from(contentHashTagCuration) | ||||
|             .where(contentHashTagCuration.isActive.isTrue) | ||||
|             .orderBy(contentHashTagCuration.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun searchHashTagCurationContentItem( | ||||
|         curationId: Long, | ||||
|         searchWord: String | ||||
|     ): List<SearchCurationItemResponse> { | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QSearchCurationItemResponse( | ||||
|                     audioContent.id, | ||||
|                     audioContent.title, | ||||
|                     audioContent.coverImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
|             .leftJoin(contentHashTagCurationItem) | ||||
|             .on( | ||||
|                 audioContent.id.eq(contentHashTagCurationItem.content.id) | ||||
|                     .and(contentHashTagCurationItem.curation.id.eq(curationId)) | ||||
|             ) | ||||
|             .where( | ||||
|                 audioContent.duration.isNotNull | ||||
|                     .and(audioContent.member.isNotNull) | ||||
|                     .and(audioContent.isActive.isTrue) | ||||
|                     .and(audioContent.title.contains(searchWord)) | ||||
|                     .and(contentHashTagCurationItem.id.isNull) | ||||
|             ) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun isExistsTag(tag: String): Boolean { | ||||
|         return queryFactory | ||||
|             .select(contentHashTagCuration.id) | ||||
|             .from(contentHashTagCuration) | ||||
|             .where( | ||||
|                 contentHashTagCuration.tag.eq(tag), | ||||
|                 contentHashTagCuration.isActive.isTrue | ||||
|             ) | ||||
|             .fetch().isNotEmpty() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,133 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation.tag | ||||
|  | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.AddItemToCurationRequest | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.RemoveItemInCurationRequest | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.SearchCurationItemResponse | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.UpdateCurationItemOrdersRequest | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.content.AudioContentRepository | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.ContentHashTagCuration | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.ContentHashTagCurationItem | ||||
| import org.springframework.data.repository.findByIdOrNull | ||||
| import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | ||||
|  | ||||
| @Service | ||||
| class AdminHashTagCurationService( | ||||
|     private val repository: AdminHashTagCurationRepository, | ||||
|     private val itemRepository: AdminContentHashTagCurationItemRepository, | ||||
|     private val audioContentRepository: AudioContentRepository | ||||
| ) { | ||||
|     @Transactional | ||||
|     fun createContentHashTagCuration(request: CreateContentHashTagCurationRequest) { | ||||
|         var tag = request.tag.trim() | ||||
|         if (!tag.startsWith("#")) { | ||||
|             tag = "#$tag" | ||||
|         } | ||||
|  | ||||
|         val isExists = repository.isExistsTag(tag = tag) | ||||
|  | ||||
|         if (isExists) { | ||||
|             throw SodaException("이미 등록된 태그 입니다.") | ||||
|         } | ||||
|  | ||||
|         repository.save( | ||||
|             ContentHashTagCuration( | ||||
|                 tag = tag, | ||||
|                 isAdult = request.isAdult | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun updateContentHashTagCuration(request: UpdateContentHashTagCurationRequest) { | ||||
|         val hashTagCuration = repository.findByIdOrNull(id = request.id) | ||||
|             ?: throw SodaException("잘못된 요청입니다.") | ||||
|  | ||||
|         if (request.tag != null) { | ||||
|             var tag = request.tag.trim() | ||||
|             if (!tag.startsWith("#")) { | ||||
|                 tag = "#$tag" | ||||
|             } | ||||
|  | ||||
|             hashTagCuration.tag = tag | ||||
|         } | ||||
|  | ||||
|         if (request.isAdult != null) { | ||||
|             hashTagCuration.isAdult = request.isAdult | ||||
|         } | ||||
|  | ||||
|         if (request.isActive != null) { | ||||
|             hashTagCuration.isActive = request.isActive | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun updateContentHashTagCurationOrders(ids: List<Long>) { | ||||
|         for (index in ids.indices) { | ||||
|             val contentHashTagCuration = repository.findByIdOrNull(ids[index]) | ||||
|  | ||||
|             if (contentHashTagCuration != null) { | ||||
|                 contentHashTagCuration.orders = index + 1 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getContentHashTagCurationList(): List<GetAdminContentHashTagCurationResponse> { | ||||
|         return repository.getContentHashTagCurationList() | ||||
|     } | ||||
|  | ||||
|     fun getHashTagCurationItemList(curationId: Long): List<GetAdminHashTagCurationItemResponse> { | ||||
|         return itemRepository.getContentHashTagCurationItemList(curationId) | ||||
|     } | ||||
|  | ||||
|     fun searchHashTagCurationContentItem(curationId: Long, searchWord: String): List<SearchCurationItemResponse> { | ||||
|         return repository.searchHashTagCurationContentItem(curationId, searchWord) | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun addItemToHashTagCuration(request: AddItemToCurationRequest) { | ||||
|         val curation = repository.findByIdOrNull(id = request.curationId) | ||||
|             ?: throw SodaException("잘못된 요청입니다.") | ||||
|  | ||||
|         request.itemIdList.forEach { contentId -> | ||||
|             val audioContent = audioContentRepository.findByIdAndActive(contentId) | ||||
|  | ||||
|             if (audioContent != null) { | ||||
|                 val item = itemRepository.findByCurationIdAndContentId( | ||||
|                     curationId = request.curationId, | ||||
|                     contentId = audioContent.id | ||||
|                 ) ?: ContentHashTagCurationItem() | ||||
|                 item.curation = curation | ||||
|                 item.content = audioContent | ||||
|                 item.isActive = true | ||||
|                 itemRepository.save(item) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun removeItemInHashTagCuration(request: RemoveItemInCurationRequest) { | ||||
|         val item = itemRepository.findByCurationIdAndItemId( | ||||
|             curationId = request.curationId, | ||||
|             itemId = request.itemId | ||||
|         ) | ||||
|  | ||||
|         item?.isActive = false | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun updateItemInHashTagCurationOrders(request: UpdateCurationItemOrdersRequest) { | ||||
|         val ids = request.itemIds | ||||
|         for (index in ids.indices) { | ||||
|             val item = itemRepository.findByCurationIdAndItemId( | ||||
|                 curationId = request.curationId, | ||||
|                 itemId = ids[index] | ||||
|             ) | ||||
|  | ||||
|             if (item != null) { | ||||
|                 item.orders = index + 1 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation.tag | ||||
|  | ||||
| data class CreateContentHashTagCurationRequest( | ||||
|     val tag: String, | ||||
|     val isAdult: Boolean | ||||
| ) | ||||
|  | ||||
| data class UpdateContentHashTagCurationRequest( | ||||
|     val id: Long, | ||||
|     val tag: String?, | ||||
|     val isAdult: Boolean?, | ||||
|     val isActive: Boolean? | ||||
| ) | ||||
|  | ||||
| data class UpdateContentHashTagCurationOrderRequest( | ||||
|     val ids: List<Long> | ||||
| ) | ||||
| @@ -0,0 +1,9 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation.tag | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetAdminContentHashTagCurationResponse @QueryProjection constructor( | ||||
|     val id: Long, | ||||
|     val tag: String, | ||||
|     val isAdult: Boolean | ||||
| ) | ||||
| @@ -0,0 +1,12 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation.tag | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetAdminHashTagCurationItemResponse @QueryProjection constructor( | ||||
|     val id: Long, | ||||
|     val title: String, | ||||
|     val desc: String, | ||||
|     val coverImageUrl: String, | ||||
|     val creatorNickname: String, | ||||
|     val isAdult: Boolean | ||||
| ) | ||||
| @@ -32,6 +32,7 @@ data class AudioContent( | ||||
|     var title: String, | ||||
|     @Column(columnDefinition = "TEXT", nullable = false) | ||||
|     var detail: String, | ||||
|     var playCount: Long = 0, | ||||
|     var price: Int = 0, | ||||
|     var releaseDate: LocalDateTime? = null, | ||||
|     val limited: Int? = null, | ||||
|   | ||||
| @@ -81,6 +81,12 @@ interface AudioContentQueryRepository { | ||||
|         limit: Long = 20 | ||||
|     ): List<GetAudioContentMainItem> | ||||
|  | ||||
|     fun totalAlarmCountByTheme( | ||||
|         memberId: Long, | ||||
|         theme: List<String>, | ||||
|         isAdult: Boolean = false | ||||
|     ): Int | ||||
|  | ||||
|     fun totalCountByTheme( | ||||
|         memberId: Long, | ||||
|         theme: String = "", | ||||
| @@ -486,6 +492,39 @@ class AudioContentQueryRepositoryImpl( | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun totalAlarmCountByTheme(memberId: Long, theme: List<String>, isAdult: Boolean): Int { | ||||
|         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.duration.isNotNull) | ||||
|             .and( | ||||
|                 audioContent.releaseDate.isNull | ||||
|                     .or(audioContent.releaseDate.loe(LocalDateTime.now())) | ||||
|                     .or(audioContent.member.id.eq(memberId)) | ||||
|             ) | ||||
|             .and(blockMember.id.isNull) | ||||
|  | ||||
|         if (!isAdult) { | ||||
|             where = where.and(audioContent.isAdult.isFalse) | ||||
|         } | ||||
|  | ||||
|         if (theme.isNotEmpty()) { | ||||
|             where = where.and(audioContentTheme.theme.`in`(theme)) | ||||
|         } | ||||
|  | ||||
|         return queryFactory | ||||
|             .select(audioContent.id) | ||||
|             .from(audioContent) | ||||
|             .innerJoin(audioContent.member, member) | ||||
|             .innerJoin(audioContent.theme, audioContentTheme) | ||||
|             .leftJoin(blockMember).on(blockMemberCondition) | ||||
|             .where(where) | ||||
|             .fetch() | ||||
|             .size | ||||
|     } | ||||
|  | ||||
|     override fun totalCountByTheme(memberId: Long, theme: String, isAdult: Boolean, contentType: ContentType): Int { | ||||
|         var where = audioContent.isActive.isTrue | ||||
|             .and(audioContent.duration.isNotNull) | ||||
| @@ -659,6 +698,7 @@ class AudioContentQueryRepositoryImpl( | ||||
|  | ||||
|     override fun getAudioContentMainBannerList(isAdult: Boolean): List<AudioContentBanner> { | ||||
|         var where = audioContentBanner.isActive.isTrue | ||||
|             .and(audioContentBanner.tab.isNull) | ||||
|  | ||||
|         if (!isAdult) { | ||||
|             where = where.and(audioContentBanner.isAdult.isFalse) | ||||
| @@ -666,6 +706,7 @@ class AudioContentQueryRepositoryImpl( | ||||
|  | ||||
|         return queryFactory | ||||
|             .selectFrom(audioContentBanner) | ||||
|             .leftJoin(audioContentBanner.tab, audioContentMainTab) | ||||
|             .leftJoin(audioContentBanner.event, event) | ||||
|             .leftJoin(audioContentBanner.creator, member) | ||||
|             .where(where) | ||||
|   | ||||
| @@ -0,0 +1,30 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.curation.tag | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.BaseEntity | ||||
| import kr.co.vividnext.sodalive.content.AudioContent | ||||
| import javax.persistence.Entity | ||||
| import javax.persistence.FetchType | ||||
| import javax.persistence.JoinColumn | ||||
| import javax.persistence.ManyToOne | ||||
|  | ||||
| @Entity | ||||
| data class ContentHashTagCuration( | ||||
|     var tag: String, | ||||
|     var orders: Int = 1, | ||||
|     var isAdult: Boolean = false, | ||||
|     var isActive: Boolean = true | ||||
| ) : BaseEntity() | ||||
|  | ||||
| @Entity | ||||
| data class ContentHashTagCurationItem( | ||||
|     var orders: Int = 1, | ||||
|     var isActive: Boolean = true | ||||
| ) : BaseEntity() { | ||||
|     @ManyToOne(fetch = FetchType.LAZY) | ||||
|     @JoinColumn(name = "curation_id", nullable = false) | ||||
|     var curation: ContentHashTagCuration? = null | ||||
|  | ||||
|     @ManyToOne(fetch = FetchType.LAZY) | ||||
|     @JoinColumn(name = "content_id", nullable = false) | ||||
|     var content: AudioContent? = null | ||||
| } | ||||
| @@ -31,6 +31,7 @@ class AudioContentMainTabRepository( | ||||
|         val where = member.isActive.isTrue | ||||
|             .and(member.role.eq(MemberRole.CREATOR)) | ||||
|             .and(audioContent.isActive.isTrue) | ||||
|             .and(audioContent.price.gt(0)) | ||||
|             .and(audioContent.duration.isNotNull) | ||||
|             .and(audioContent.limited.isNull) | ||||
|             .and(audioContentTheme.isActive.isTrue) | ||||
| @@ -59,4 +60,39 @@ class AudioContentMainTabRepository( | ||||
|             .limit(20) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     fun findCreatorWithHasFreeContent(memberId: Long, minCount: Int): List<ContentCreatorResponse> { | ||||
|         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||
|             .and(blockMember.isActive.isTrue) | ||||
|             .and(blockMember.blockedMember.id.eq(memberId)) | ||||
|  | ||||
|         val where = member.isActive.isTrue | ||||
|             .and(member.role.eq(MemberRole.CREATOR)) | ||||
|             .and(audioContent.isActive.isTrue) | ||||
|             .and(audioContent.price.loe(0)) | ||||
|             .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(blockMember).on(blockMemberCondition) | ||||
|             .where(where) | ||||
|             .groupBy(member.id) | ||||
|             .having(audioContent.id.count().goe(minCount)) | ||||
|             .orderBy( | ||||
|                 Expressions.numberTemplate(Double::class.java, "function('rand')").asc() | ||||
|             ) | ||||
|             .offset(0) | ||||
|             .limit(20) | ||||
|             .fetch() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,8 +0,0 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.tab | ||||
|  | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||
|  | ||||
| data class GetPopularContentByCreatorResponse( | ||||
|     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val salesCountRankContentList: List<GetAudioContentRankingItem> | ||||
| ) | ||||
| @@ -1,7 +1,7 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.tab.alarm | ||||
|  | ||||
| import kr.co.vividnext.sodalive.content.AudioContentRepository | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||
| import kr.co.vividnext.sodalive.content.main.GetNewContentAllResponse | ||||
| 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 | ||||
| @@ -86,7 +86,7 @@ class AudioContentMainTabAlarmService( | ||||
|         member: Member, | ||||
|         offset: Long, | ||||
|         limit: Long | ||||
|     ): List<GetAudioContentMainItem> { | ||||
|     ): GetNewContentAllResponse { | ||||
|         val alarmThemeList = if (theme.isNotBlank()) { | ||||
|             listOf(theme) | ||||
|         } else { | ||||
| @@ -96,12 +96,20 @@ class AudioContentMainTabAlarmService( | ||||
|         val memberId = member.id!! | ||||
|         val isAdult = member.auth != null | ||||
|  | ||||
|         return contentRepository.findAlarmContentByTheme( | ||||
|         val totalCount = contentRepository.totalAlarmCountByTheme( | ||||
|             memberId = memberId, | ||||
|             theme = alarmThemeList, | ||||
|             isAdult = isAdult | ||||
|         ) | ||||
|  | ||||
|         val items = contentRepository.findAlarmContentByTheme( | ||||
|             memberId = memberId, | ||||
|             theme = alarmThemeList, | ||||
|             isAdult = isAdult, | ||||
|             offset = offset, | ||||
|             limit = limit | ||||
|         ) | ||||
|  | ||||
|         return GetNewContentAllResponse(totalCount, items) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,11 +2,11 @@ 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.GetAudioContentRankingItem | ||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||
| import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository | ||||
| import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse | ||||
| import kr.co.vividnext.sodalive.event.EventService | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import kr.co.vividnext.sodalive.rank.RankingService | ||||
| @@ -68,18 +68,8 @@ class AudioContentMainTabAsmrService( | ||||
|             minCount = 4 | ||||
|         ) | ||||
|  | ||||
|         val salesRankContentList = if (creatorList.isNotEmpty()) { | ||||
|             rankingService.fetchCreatorContentBySalesTop2( | ||||
|                 creatorId = creatorList[0].creatorId, | ||||
|                 isAdult = isAdult, | ||||
|                 theme = theme | ||||
|             ) | ||||
|         } else { | ||||
|             emptyList() | ||||
|         } | ||||
|  | ||||
|         val salesCountRankContentList = if (creatorList.isNotEmpty()) { | ||||
|             rankingService.fetchCreatorContentBySalesCountTop2( | ||||
|             rankingService.fetchCreatorContentBySalesCountTop4( | ||||
|                 creatorId = creatorList[0].creatorId, | ||||
|                 isAdult = isAdult, | ||||
|                 theme = theme | ||||
| @@ -107,30 +97,17 @@ class AudioContentMainTabAsmrService( | ||||
|             newAsmrContentList = newAsmrContentList, | ||||
|             rankAsmrContentList = rankAsmrContentList, | ||||
|             creatorList = creatorList, | ||||
|             salesRankContentList = salesRankContentList, | ||||
|             salesCountRankContentList = salesCountRankContentList, | ||||
|             eventBannerList = eventBannerList, | ||||
|             curationList = curationList | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse { | ||||
|         val theme = "ASMR" | ||||
|         val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||
|         return rankingService.fetchCreatorContentBySalesCountTop4( | ||||
|             creatorId = creatorId, | ||||
|             isAdult = isAdult, | ||||
|             theme = theme | ||||
|         ) | ||||
|  | ||||
|         val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( | ||||
|             creatorId = creatorId, | ||||
|             isAdult = isAdult, | ||||
|             theme = theme | ||||
|         ) | ||||
|  | ||||
|         return GetPopularContentByCreatorResponse( | ||||
|             salesRankContentList = salesRankContentList, | ||||
|             salesCountRankContentList = salesCountRankContentList | ||||
|             theme = "ASMR" | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,6 @@ data class GetContentMainTabAsmrResponse( | ||||
|     val newAsmrContentList: List<GetAudioContentMainItem>, | ||||
|     val rankAsmrContentList: List<GetAudioContentRankingItem>, | ||||
|     val creatorList: List<ContentCreatorResponse>, | ||||
|     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val salesCountRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val eventBannerList: GetEventResponse, | ||||
|     val curationList: List<GetContentCurationResponse> | ||||
|   | ||||
| @@ -82,4 +82,15 @@ class AudioContentMainTabContentController(private val service: AudioContentMain | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/recommend-content-by-tag") | ||||
|     fun getRecommendedContentByTag( | ||||
|         @RequestParam tag: String, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|         ApiResponse.ok( | ||||
|             service.getRecommendedContentByTag(memberId = member.id!!, tag = tag) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,8 @@ import kr.co.vividnext.sodalive.content.ContentType | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse | ||||
| import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||
| import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository | ||||
| import kr.co.vividnext.sodalive.event.EventService | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| @@ -19,7 +20,9 @@ class AudioContentMainTabContentService( | ||||
|     private val audioContentRepository: AudioContentRepository, | ||||
|     private val audioContentThemeRepository: AudioContentThemeQueryRepository, | ||||
|     private val rankingService: RankingService, | ||||
|     private val eventService: EventService | ||||
|     private val eventService: EventService, | ||||
|     private val tagCurationService: ContentMainTabTagCurationService, | ||||
|     private val curationRepository: AudioContentCurationQueryRepository | ||||
| ) { | ||||
|     fun fetchData( | ||||
|         isAdultContentVisible: Boolean, | ||||
| @@ -28,10 +31,11 @@ class AudioContentMainTabContentService( | ||||
|     ): GetContentMainTabContentResponse { | ||||
|         val memberId = member.id!! | ||||
|         val isAdult = member.auth != null | ||||
|         val tabId = 3L | ||||
|  | ||||
|         // 단편 배너 | ||||
|         val contentBannerList = bannerService.getBannerList( | ||||
|             tabId = 3, | ||||
|             tabId = tabId, | ||||
|             memberId = memberId, | ||||
|             isAdult = isAdult | ||||
|         ) | ||||
| @@ -69,14 +73,14 @@ class AudioContentMainTabContentService( | ||||
|         // 이벤트 배너 | ||||
|         val eventBannerList = eventService.getEventList(isAdult = isAdult) | ||||
|  | ||||
|         val contentRankCreatorList = rankingService.fetchCreatorByContentRevenueRankTop20( | ||||
|         val contentRankCreatorList = rankingService.fetchCreatorBySellContentCountRankTop20( | ||||
|             memberId = member.id!!, | ||||
|             startDate = dailyRankingStartDate.minusDays(1), | ||||
|             endDate = dailyRankingEndDate | ||||
|         ) | ||||
|  | ||||
|         val salesRankContentList = if (contentRankCreatorList.isNotEmpty()) { | ||||
|             rankingService.fetchCreatorContentBySalesTop2( | ||||
|         val salesCountRankContentList = if (contentRankCreatorList.isNotEmpty()) { | ||||
|             rankingService.fetchCreatorContentBySalesCountTop4( | ||||
|                 creatorId = contentRankCreatorList[0].creatorId, | ||||
|                 isAdult = member.auth != null | ||||
|             ) | ||||
| @@ -84,15 +88,30 @@ class AudioContentMainTabContentService( | ||||
|             emptyList() | ||||
|         } | ||||
|  | ||||
|         val salesCountRankContentList = if (contentRankCreatorList.isNotEmpty()) { | ||||
|             rankingService.fetchCreatorContentBySalesCountTop2( | ||||
|                 creatorId = contentRankCreatorList[0].creatorId, | ||||
|                 isAdult = member.auth != null | ||||
|             ) | ||||
|         val tagList = if (isAdult) { | ||||
|             tagCurationService.getTagList(isAdult = isAdult) | ||||
|         } else { | ||||
|             emptyList() | ||||
|         } | ||||
|  | ||||
|         val tagCurationContentList = if (tagList.isNotEmpty()) { | ||||
|             tagCurationService.getTagCurationContentList(memberId = memberId, tag = tagList[0]) | ||||
|         } else { | ||||
|             emptyList() | ||||
|         } | ||||
|  | ||||
|         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) | ||||
|             .map { | ||||
|                 GetContentCurationResponse( | ||||
|                     title = it.title, | ||||
|                     items = audioContentRepository.findAudioContentByCurationIdV2( | ||||
|                         curationId = it.id!!, | ||||
|                         memberId = memberId, | ||||
|                         isAdult = isAdult | ||||
|                     ) | ||||
|                 ) | ||||
|             } | ||||
|  | ||||
|         return GetContentMainTabContentResponse( | ||||
|             bannerList = contentBannerList, | ||||
|             contentThemeList = themeOfContentList, | ||||
| @@ -100,9 +119,11 @@ class AudioContentMainTabContentService( | ||||
|             rankSortTypeList = listOf("매출", "댓글", "좋아요"), | ||||
|             rankContentList = rankContentList, | ||||
|             contentRankCreatorList = contentRankCreatorList, | ||||
|             salesRankContentList = salesRankContentList, | ||||
|             salesCountRankContentList = salesCountRankContentList, | ||||
|             eventBannerList = eventBannerList | ||||
|             eventBannerList = eventBannerList, | ||||
|             tagList = tagList, | ||||
|             tagCurationContentList = tagCurationContentList, | ||||
|             curationList = curationList | ||||
|         ) | ||||
|     } | ||||
|  | ||||
| @@ -144,20 +165,14 @@ class AudioContentMainTabContentService( | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse { | ||||
|         val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||
|         return rankingService.fetchCreatorContentBySalesCountTop4( | ||||
|             creatorId = creatorId, | ||||
|             isAdult = isAdult | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|         val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( | ||||
|             creatorId = creatorId, | ||||
|             isAdult = isAdult | ||||
|         ) | ||||
|  | ||||
|         return GetPopularContentByCreatorResponse( | ||||
|             salesRankContentList = salesRankContentList, | ||||
|             salesCountRankContentList = salesCountRankContentList | ||||
|         ) | ||||
|     fun getRecommendedContentByTag(memberId: Long, tag: String): List<GetAudioContentMainItem> { | ||||
|         return tagCurationService.getTagCurationContentList(memberId = memberId, tag = tag) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,79 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.tab.content | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||
| import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCuration.contentHashTagCuration | ||||
| import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCurationItem.contentHashTagCurationItem | ||||
| import kr.co.vividnext.sodalive.member.QMember.member | ||||
| import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember | ||||
| import org.springframework.beans.factory.annotation.Value | ||||
| import org.springframework.stereotype.Repository | ||||
|  | ||||
| @Repository | ||||
| class ContentMainTabTagCurationRepository( | ||||
|     private val queryFactory: JPAQueryFactory, | ||||
|  | ||||
|     @Value("\${cloud.aws.cloud-front.host}") | ||||
|     private val imageHost: String | ||||
| ) { | ||||
|     fun getTagList(isAdult: Boolean): List<String> { | ||||
|         var where = contentHashTagCuration.isActive.isTrue | ||||
|             .and(audioContent.isActive.isTrue) | ||||
|             .and(audioContent.duration.isNotNull) | ||||
|             .and(audioContent.limited.isNull) | ||||
|             .and(contentHashTagCurationItem.isActive.isTrue) | ||||
|  | ||||
|         if (!isAdult) { | ||||
|             where = where.and(contentHashTagCuration.isAdult.isFalse) | ||||
|         } | ||||
|  | ||||
|         return queryFactory | ||||
|             .select(contentHashTagCuration.tag) | ||||
|             .from(contentHashTagCuration) | ||||
|             .innerJoin(contentHashTagCurationItem) | ||||
|             .on(contentHashTagCurationItem.curation.id.eq(contentHashTagCuration.id)) | ||||
|             .innerJoin(contentHashTagCurationItem.content, audioContent) | ||||
|             .where(where) | ||||
|             .groupBy(contentHashTagCuration.id) | ||||
|             .having(contentHashTagCurationItem.id.countDistinct().gt(0)) | ||||
|             .orderBy(contentHashTagCuration.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     fun getTagCurationContentList(memberId: Long, tag: String): List<GetAudioContentMainItem> { | ||||
|         val blockMemberCondition = blockMember.member.id.eq(member.id) | ||||
|             .and(blockMember.isActive.isTrue) | ||||
|             .and(blockMember.blockedMember.id.eq(memberId)) | ||||
|  | ||||
|         val where = audioContent.isActive.isTrue | ||||
|             .and(audioContent.duration.isNotNull) | ||||
|             .and(audioContent.limited.isNull) | ||||
|             .and(blockMember.id.isNull) | ||||
|             .and(contentHashTagCurationItem.isActive.isTrue) | ||||
|             .and(contentHashTagCuration.tag.eq(tag)) | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAudioContentMainItem( | ||||
|                     audioContent.id, | ||||
|                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||
|                     audioContent.title, | ||||
|                     member.id, | ||||
|                     member.profileImage.prepend("/").prepend(imageHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                 ) | ||||
|             ) | ||||
|             .from(contentHashTagCurationItem) | ||||
|             .innerJoin(contentHashTagCurationItem.curation, contentHashTagCuration) | ||||
|             .innerJoin(contentHashTagCurationItem.content, audioContent) | ||||
|             .innerJoin(audioContent.member, member) | ||||
|             .leftJoin(blockMember).on(blockMemberCondition) | ||||
|             .where(where) | ||||
|             .orderBy(contentHashTagCurationItem.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,15 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.tab.content | ||||
|  | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||
| import org.springframework.stereotype.Service | ||||
|  | ||||
| @Service | ||||
| class ContentMainTabTagCurationService(private val repository: ContentMainTabTagCurationRepository) { | ||||
|     fun getTagList(isAdult: Boolean): List<String> { | ||||
|         return repository.getTagList(isAdult = isAdult) | ||||
|     } | ||||
|  | ||||
|     fun getTagCurationContentList(memberId: Long, tag: String): List<GetAudioContentMainItem> { | ||||
|         return repository.getTagCurationContentList(memberId = memberId, tag = tag) | ||||
|     } | ||||
| } | ||||
| @@ -4,6 +4,7 @@ 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.content.main.tab.GetContentCurationResponse | ||||
| import kr.co.vividnext.sodalive.event.GetEventResponse | ||||
|  | ||||
| data class GetContentMainTabContentResponse( | ||||
| @@ -14,7 +15,9 @@ data class GetContentMainTabContentResponse( | ||||
|     val rankSortTypeList: List<String>, | ||||
|     val rankContentList: List<GetAudioContentRankingItem>, | ||||
|     val contentRankCreatorList: List<ContentCreatorResponse>, | ||||
|     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val salesCountRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val eventBannerList: GetEventResponse | ||||
|     val eventBannerList: GetEventResponse, | ||||
|     val tagList: List<String>, | ||||
|     val tagCurationContentList: List<GetAudioContentMainItem>, | ||||
|     val curationList: List<GetContentCurationResponse> | ||||
| ) | ||||
|   | ||||
| @@ -60,4 +60,19 @@ class AudioContentMainTabFreeController(private val service: AudioContentMainTab | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/popular-content-by-creator") | ||||
|     fun getPopularContentByCreator( | ||||
|         @RequestParam creatorId: Long, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|  | ||||
|         ApiResponse.ok( | ||||
|             service.getPopularContentByCreator( | ||||
|                 creatorId = creatorId, | ||||
|                 isAdult = member.auth != null | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,20 +3,25 @@ package kr.co.vividnext.sodalive.content.main.tab.free | ||||
| import kr.co.vividnext.sodalive.content.AudioContentRepository | ||||
| import kr.co.vividnext.sodalive.content.ContentType | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||
| import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository | ||||
| import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||
| import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository | ||||
| import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import kr.co.vividnext.sodalive.rank.RankingService | ||||
| import org.springframework.stereotype.Service | ||||
|  | ||||
| @Service | ||||
| class AudioContentMainTabFreeService( | ||||
|     private val repository: AudioContentMainTabRepository, | ||||
|     private val bannerService: AudioContentBannerService, | ||||
|     private val recommendSeriesRepository: RecommendSeriesRepository, | ||||
|     private val curationRepository: AudioContentCurationQueryRepository, | ||||
|     private val contentRepository: AudioContentRepository, | ||||
|     private val rankingService: RankingService, | ||||
|     private val audioContentRepository: AudioContentRepository, | ||||
|     private val audioContentThemeRepository: AudioContentThemeQueryRepository | ||||
| ) { | ||||
| @@ -53,7 +58,6 @@ class AudioContentMainTabFreeService( | ||||
|         val newFreeContentList = if (themeList.isNotEmpty()) { | ||||
|             audioContentRepository.findByTheme( | ||||
|                 memberId = member.id!!, | ||||
|                 theme = themeList[0], | ||||
|                 isAdult = member.auth != null, | ||||
|                 contentType = ContentType.ALL, | ||||
|                 offset = 0, | ||||
| @@ -64,6 +68,13 @@ class AudioContentMainTabFreeService( | ||||
|             emptyList() | ||||
|         } | ||||
|  | ||||
|         val creatorList = repository.findCreatorWithHasFreeContent(memberId, 4) | ||||
|         val playCountRankContentList = if (creatorList.isNotEmpty()) { | ||||
|             rankingService.fetchFreeContentByCreatorIdTop4(creatorList[0].creatorId, isAdult) | ||||
|         } else { | ||||
|             emptyList() | ||||
|         } | ||||
|  | ||||
|         val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) | ||||
|             .filter { it.title != "크리에이터 소개" } | ||||
|             .map { | ||||
| @@ -87,6 +98,8 @@ class AudioContentMainTabFreeService( | ||||
|             recommendSeriesList = recommendSeriesList, | ||||
|             themeList = themeList, | ||||
|             newFreeContentList = newFreeContentList, | ||||
|             creatorList = creatorList, | ||||
|             playCountRankContentList = playCountRankContentList, | ||||
|             curationList = curationList | ||||
|         ) | ||||
|     } | ||||
| @@ -132,4 +145,8 @@ class AudioContentMainTabFreeService( | ||||
|             isFree = true | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||
|         return rankingService.fetchFreeContentByCreatorIdTop4(creatorId, isAdult) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.tab.free | ||||
|  | ||||
| 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.content.main.tab.GetContentCurationResponse | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetRecommendSeriesListResponse | ||||
| @@ -12,5 +14,7 @@ data class GetContentMainTabFreeResponse( | ||||
|     val recommendSeriesList: List<GetRecommendSeriesListResponse>, | ||||
|     val themeList: List<String>, | ||||
|     val newFreeContentList: List<GetAudioContentMainItem>, | ||||
|     val creatorList: List<ContentCreatorResponse>, | ||||
|     val playCountRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val curationList: List<GetContentCurationResponse> | ||||
| ) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.tab.home | ||||
|  | ||||
| import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem | ||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse | ||||
| import kr.co.vividnext.sodalive.event.EventService | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import kr.co.vividnext.sodalive.notice.ServiceNoticeService | ||||
| @@ -74,27 +74,17 @@ class AudioContentMainTabHomeService( | ||||
|  | ||||
|         /* 채널별 인기 콘텐츠 | ||||
|          * - 콘텐츠를 4개 이상 등록한 채널 | ||||
|          * - 주간 콘텐츠 매출 Top 20 채널 | ||||
|          * - 해당 채널의 누적 매출 Top 2 | ||||
|          * - 해당 채널의 누적 판매 개수 Top 2 | ||||
|          * - 주간 콘텐츠 판매 개수 Top 20 채널 | ||||
|          * - 해당 채널의 누적 판매 개수 Top 4 | ||||
|          */ | ||||
|         val contentRankCreatorList = rankingService.fetchCreatorByContentRevenueRankTop20( | ||||
|         val contentRankCreatorList = rankingService.fetchCreatorBySellContentCountRankTop20( | ||||
|             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( | ||||
|             rankingService.fetchCreatorContentBySalesCountTop4( | ||||
|                 creatorId = contentRankCreatorList[0].creatorId, | ||||
|                 isAdult = member.auth != null | ||||
|             ) | ||||
| @@ -111,25 +101,14 @@ class AudioContentMainTabHomeService( | ||||
|             rankContentList = rankContentList, | ||||
|             eventBannerList = eventBannerList, | ||||
|             contentRankCreatorList = contentRankCreatorList, | ||||
|             salesRankContentList = salesRankContentList, | ||||
|             salesCountRankContentList = salesCountRankContentList | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse { | ||||
|         val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||
|         return rankingService.fetchCreatorContentBySalesCountTop4( | ||||
|             creatorId = creatorId, | ||||
|             isAdult = isAdult | ||||
|         ) | ||||
|  | ||||
|         val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( | ||||
|             creatorId = creatorId, | ||||
|             isAdult = isAdult | ||||
|         ) | ||||
|  | ||||
|         return GetPopularContentByCreatorResponse( | ||||
|             salesRankContentList = salesRankContentList, | ||||
|             salesCountRankContentList = salesCountRankContentList | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -18,6 +18,5 @@ data class GetContentMainTabHomeResponse( | ||||
|     val rankContentList: List<GetAudioContentRankingItem>, | ||||
|     val eventBannerList: GetEventResponse, | ||||
|     val contentRankCreatorList: List<ContentCreatorResponse>, | ||||
|     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val salesCountRankContentList: List<GetAudioContentRankingItem> | ||||
| ) | ||||
|   | ||||
| @@ -2,11 +2,11 @@ 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.GetAudioContentRankingItem | ||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService | ||||
| import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository | ||||
| import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse | ||||
| import kr.co.vividnext.sodalive.event.EventService | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import kr.co.vividnext.sodalive.rank.RankingService | ||||
| @@ -68,18 +68,8 @@ class AudioContentMainTabLiveReplayService( | ||||
|             minCount = 4 | ||||
|         ) | ||||
|  | ||||
|         val salesRankContentList = if (creatorList.isNotEmpty()) { | ||||
|             rankingService.fetchCreatorContentBySalesTop2( | ||||
|                 creatorId = creatorList[0].creatorId, | ||||
|                 isAdult = isAdult, | ||||
|                 theme = theme | ||||
|             ) | ||||
|         } else { | ||||
|             emptyList() | ||||
|         } | ||||
|  | ||||
|         val salesCountRankContentList = if (creatorList.isNotEmpty()) { | ||||
|             rankingService.fetchCreatorContentBySalesCountTop2( | ||||
|             rankingService.fetchCreatorContentBySalesCountTop4( | ||||
|                 creatorId = creatorList[0].creatorId, | ||||
|                 isAdult = isAdult, | ||||
|                 theme = theme | ||||
| @@ -107,30 +97,17 @@ class AudioContentMainTabLiveReplayService( | ||||
|             newLiveReplayContentList = newLiveReplayContentList, | ||||
|             rankLiveReplayContentList = rankLiveReplayContentList, | ||||
|             creatorList = creatorList, | ||||
|             salesRankContentList = salesRankContentList, | ||||
|             salesCountRankContentList = salesCountRankContentList, | ||||
|             eventBannerList = eventBannerList, | ||||
|             curationList = curationList | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse { | ||||
|         val theme = "다시듣기" | ||||
|         val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( | ||||
|     fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||
|         return rankingService.fetchCreatorContentBySalesCountTop4( | ||||
|             creatorId = creatorId, | ||||
|             isAdult = isAdult, | ||||
|             theme = theme | ||||
|         ) | ||||
|  | ||||
|         val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( | ||||
|             creatorId = creatorId, | ||||
|             isAdult = isAdult, | ||||
|             theme = theme | ||||
|         ) | ||||
|  | ||||
|         return GetPopularContentByCreatorResponse( | ||||
|             salesRankContentList = salesRankContentList, | ||||
|             salesCountRankContentList = salesCountRankContentList | ||||
|             theme = "다시듣기" | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,6 @@ data class GetContentMainTabLiveReplayResponse( | ||||
|     val newLiveReplayContentList: List<GetAudioContentMainItem>, | ||||
|     val rankLiveReplayContentList: List<GetAudioContentRankingItem>, | ||||
|     val creatorList: List<ContentCreatorResponse>, | ||||
|     val salesRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val salesCountRankContentList: List<GetAudioContentRankingItem>, | ||||
|     val eventBannerList: GetEventResponse, | ||||
|     val curationList: List<GetContentCurationResponse> | ||||
|   | ||||
| @@ -336,7 +336,7 @@ class RankingRepository( | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     fun fetchCreatorByContentRevenueRankTop20( | ||||
|     fun fetchCreatorBySellContentCountRankTop20( | ||||
|         memberId: Long, | ||||
|         startDate: LocalDateTime, | ||||
|         endDate: LocalDateTime | ||||
| @@ -350,9 +350,12 @@ class RankingRepository( | ||||
|             .and(order.createdAt.goe(startDate)) | ||||
|             .and(order.createdAt.lt(startDate)) | ||||
|  | ||||
|         val where = member.isActive.isTrue | ||||
|         val memberCondition = member.isActive.isTrue | ||||
|             .and(member.role.eq(MemberRole.CREATOR)) | ||||
|             .and(audioContent.isActive.isTrue) | ||||
|             .and(member.id.eq(audioContent.member.id)) | ||||
|  | ||||
|         val where = audioContent.isActive.isTrue | ||||
|             .and(audioContent.price.gt(0)) | ||||
|             .and(audioContent.duration.isNotNull) | ||||
|             .and(audioContent.limited.isNull) | ||||
|             .and(blockMember.id.isNull) | ||||
| @@ -365,71 +368,20 @@ class RankingRepository( | ||||
|                     member.profileImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
|             .from(member) | ||||
|             .innerJoin(audioContent).on(member.id.eq(audioContent.member.id)) | ||||
|             .from(audioContent) | ||||
|             .innerJoin(member).on(memberCondition) | ||||
|             .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() | ||||
|             ) | ||||
|             .orderBy(order.id.count().desc(), member.id.desc()) | ||||
|             .offset(0) | ||||
|             .limit(20) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     fun fetchCreatorContentBySalesTop2( | ||||
|         creatorId: Long, | ||||
|         isAdult: Boolean, | ||||
|         theme: String | ||||
|     ): List<GetAudioContentRankingItem> { | ||||
|         var where = member.isActive.isTrue | ||||
|             .and(member.role.eq(MemberRole.CREATOR)) | ||||
|             .and(audioContent.isActive.isTrue) | ||||
|             .and(audioContent.duration.isNotNull) | ||||
|             .and(audioContent.limited.isNull) | ||||
|             .and(audioContentTheme.isActive.isTrue) | ||||
|             .and(order.isActive.isTrue) | ||||
|             .and(member.id.eq(creatorId)) | ||||
|  | ||||
|         if (!isAdult) { | ||||
|             where = where.and(series.isAdult.isFalse) | ||||
|         } | ||||
|  | ||||
|         if (theme.isNotBlank()) { | ||||
|             where = where.and(audioContentTheme.theme.eq(theme)) | ||||
|         } | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAudioContentRankingItem( | ||||
|                     audioContent.id, | ||||
|                     audioContent.title, | ||||
|                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||
|                     audioContentTheme.theme, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration, | ||||
|                     member.id, | ||||
|                     member.nickname, | ||||
|                     member.profileImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
|             .from(order) | ||||
|             .innerJoin(order.audioContent, audioContent) | ||||
|             .innerJoin(audioContent.theme, audioContentTheme) | ||||
|             .innerJoin(audioContent.member, member) | ||||
|             .where(where) | ||||
|             .groupBy(audioContent.id) | ||||
|             .orderBy(order.can.sum().desc()) | ||||
|             .offset(0) | ||||
|             .limit(2) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     fun fetchCreatorContentBySalesCountTop2( | ||||
|     fun fetchCreatorContentBySalesCountTop4( | ||||
|         creatorId: Long, | ||||
|         isAdult: Boolean, | ||||
|         theme: String | ||||
| @@ -473,7 +425,7 @@ class RankingRepository( | ||||
|             .groupBy(audioContent.id) | ||||
|             .orderBy(order.id.count().desc()) | ||||
|             .offset(0) | ||||
|             .limit(2) | ||||
|             .limit(4) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
| @@ -556,4 +508,41 @@ class RankingRepository( | ||||
|             .limit(10) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     fun fetchFreeContentByCreatorIdTop4(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||
|         var where = member.isActive.isTrue | ||||
|             .and(member.id.eq(creatorId)) | ||||
|             .and(member.role.eq(MemberRole.CREATOR)) | ||||
|             .and(audioContent.isActive.isTrue) | ||||
|             .and(audioContent.duration.isNotNull) | ||||
|             .and(audioContent.limited.isNull) | ||||
|             .and(audioContent.price.loe(0)) | ||||
|  | ||||
|         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, | ||||
|                     member.profileImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
|             .innerJoin(audioContent.theme, audioContentTheme) | ||||
|             .innerJoin(audioContent.member, member) | ||||
|             .where(where) | ||||
|             .orderBy(audioContent.playCount.desc()) | ||||
|             .offset(0) | ||||
|             .limit(4) | ||||
|             .fetch() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -181,28 +181,20 @@ class RankingService( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun fetchCreatorByContentRevenueRankTop20( | ||||
|     fun fetchCreatorBySellContentCountRankTop20( | ||||
|         memberId: Long, | ||||
|         startDate: LocalDateTime, | ||||
|         endDate: LocalDateTime | ||||
|     ): List<ContentCreatorResponse> { | ||||
|         return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate) | ||||
|         return repository.fetchCreatorBySellContentCountRankTop20(memberId, startDate, endDate) | ||||
|     } | ||||
|  | ||||
|     fun fetchCreatorContentBySalesTop2( | ||||
|     fun fetchCreatorContentBySalesCountTop4( | ||||
|         creatorId: Long, | ||||
|         isAdult: Boolean, | ||||
|         theme: String = "" | ||||
|     ): List<GetAudioContentRankingItem> { | ||||
|         return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult, theme) | ||||
|     } | ||||
|  | ||||
|     fun fetchCreatorContentBySalesCountTop2( | ||||
|         creatorId: Long, | ||||
|         isAdult: Boolean, | ||||
|         theme: String = "" | ||||
|     ): List<GetAudioContentRankingItem> { | ||||
|         return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult, theme) | ||||
|         return repository.fetchCreatorContentBySalesCountTop4(creatorId, isAdult, theme) | ||||
|     } | ||||
|  | ||||
|     fun fetchCreatorBySeriesRevenueRankTop20( | ||||
| @@ -217,4 +209,8 @@ class RankingService( | ||||
|         val seriesList = repository.fetchCreatorSeriesBySales(creatorId = creatorId, isAdult = isAdult) | ||||
|         return seriesToSeriesListItem(seriesList, isAdult) | ||||
|     } | ||||
|  | ||||
|     fun fetchFreeContentByCreatorIdTop4(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { | ||||
|         return repository.fetchFreeContentByCreatorIdTop4(creatorId, isAdult) | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user