diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationController.kt index 3bbe3ed..10c897d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationController.kt @@ -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), "수정되었습니다.") } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationItemRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationItemRepository.kt index 28bcc4d..bc1a5ee 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationItemRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationItemRepository.kt @@ -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() } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationService.kt index 2d3427a..a9745f5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationService.kt @@ -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 + } + } + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/UpdateCurationItemOrdersRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/UpdateCurationItemOrdersRequest.kt new file mode 100644 index 0000000..b1975ed --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/UpdateCurationItemOrdersRequest.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.admin.content.curation + +data class UpdateCurationItemOrdersRequest( + val curationId: Long, + val itemIds: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminContentHashTagCurationItemRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminContentHashTagCurationItemRepository.kt new file mode 100644 index 0000000..17173da --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminContentHashTagCurationItemRepository.kt @@ -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, + AdminContentHashTagCurationItemQueryRepository + +interface AdminContentHashTagCurationItemQueryRepository { + fun getContentHashTagCurationItemList(curationId: Long): List + 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 { + 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() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationController.kt new file mode 100644 index 0000000..de905b7 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationController.kt @@ -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), "수정되었습니다.") +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationRepository.kt new file mode 100644 index 0000000..e892a00 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationRepository.kt @@ -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, + AdminHashTagCurationQueryRepository + +interface AdminHashTagCurationQueryRepository { + fun getContentHashTagCurationList(): List + fun searchHashTagCurationContentItem(curationId: Long, searchWord: String): List + 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 { + 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 { + 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() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationService.kt new file mode 100644 index 0000000..dc45006 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationService.kt @@ -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) { + for (index in ids.indices) { + val contentHashTagCuration = repository.findByIdOrNull(ids[index]) + + if (contentHashTagCuration != null) { + contentHashTagCuration.orders = index + 1 + } + } + } + + fun getContentHashTagCurationList(): List { + return repository.getContentHashTagCurationList() + } + + fun getHashTagCurationItemList(curationId: Long): List { + return itemRepository.getContentHashTagCurationItemList(curationId) + } + + fun searchHashTagCurationContentItem(curationId: Long, searchWord: String): List { + 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 + } + } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AudioContentHashTagCurationRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AudioContentHashTagCurationRequest.kt new file mode 100644 index 0000000..22970b6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AudioContentHashTagCurationRequest.kt @@ -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 +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminContentHashTagCurationResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminContentHashTagCurationResponse.kt new file mode 100644 index 0000000..c41daf6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminContentHashTagCurationResponse.kt @@ -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 +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminHashTagCurationItemResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminHashTagCurationItemResponse.kt new file mode 100644 index 0000000..b42d042 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminHashTagCurationItemResponse.kt @@ -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 +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt index 688ac4d..00d3eb3 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt @@ -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, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index d0e30dc..1148ed8 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -81,6 +81,12 @@ interface AudioContentQueryRepository { limit: Long = 20 ): List + fun totalAlarmCountByTheme( + memberId: Long, + theme: List, + isAdult: Boolean = false + ): Int + fun totalCountByTheme( memberId: Long, theme: String = "", @@ -486,6 +492,39 @@ class AudioContentQueryRepositoryImpl( .fetch() } + override fun totalAlarmCountByTheme(memberId: Long, theme: List, 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 { 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) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/tag/ContentHashTagCuration.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/tag/ContentHashTagCuration.kt new file mode 100644 index 0000000..f1acaf2 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/tag/ContentHashTagCuration.kt @@ -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 +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/AudioContentMainTabRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/AudioContentMainTabRepository.kt index a5ef40a..3604c0e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/AudioContentMainTabRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/AudioContentMainTabRepository.kt @@ -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 { + 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() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetPopularContentByCreatorResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetPopularContentByCreatorResponse.kt deleted file mode 100644 index 6f8cdc8..0000000 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetPopularContentByCreatorResponse.kt +++ /dev/null @@ -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, - val salesCountRankContentList: List -) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt index bac8749..53e9eb2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt @@ -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 { + ): 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) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt index 4ac40c4..70eb0b0 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt @@ -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 { + 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" ) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/GetContentMainTabAsmrResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/GetContentMainTabAsmrResponse.kt index 9162e40..5e324f5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/GetContentMainTabAsmrResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/GetContentMainTabAsmrResponse.kt @@ -13,7 +13,6 @@ data class GetContentMainTabAsmrResponse( val newAsmrContentList: List, val rankAsmrContentList: List, val creatorList: List, - val salesRankContentList: List, val salesCountRankContentList: List, val eventBannerList: GetEventResponse, val curationList: List diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentController.kt index f50efb8..3ecb4fa 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentController.kt @@ -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) + ) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt index 205951e..c968f8e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt @@ -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 { + 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 { + return tagCurationService.getTagCurationContentList(memberId = memberId, tag = tag) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/ContentMainTabTagCurationRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/ContentMainTabTagCurationRepository.kt new file mode 100644 index 0000000..3e7b85e --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/ContentMainTabTagCurationRepository.kt @@ -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 { + 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 { + 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() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/ContentMainTabTagCurationService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/ContentMainTabTagCurationService.kt new file mode 100644 index 0000000..f95134d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/ContentMainTabTagCurationService.kt @@ -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 { + return repository.getTagList(isAdult = isAdult) + } + + fun getTagCurationContentList(memberId: Long, tag: String): List { + return repository.getTagCurationContentList(memberId = memberId, tag = tag) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/GetContentMainTabContentResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/GetContentMainTabContentResponse.kt index 721e960..da16051 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/GetContentMainTabContentResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/GetContentMainTabContentResponse.kt @@ -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, val rankContentList: List, val contentRankCreatorList: List, - val salesRankContentList: List, val salesCountRankContentList: List, - val eventBannerList: GetEventResponse + val eventBannerList: GetEventResponse, + val tagList: List, + val tagCurationContentList: List, + val curationList: List ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt index 4c8fa63..2547d48 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt @@ -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 + ) + ) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt index df99edc..0b1f9ea 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt @@ -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 { + return rankingService.fetchFreeContentByCreatorIdTop4(creatorId, isAdult) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/GetContentMainTabFreeResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/GetContentMainTabFreeResponse.kt index aed9911..e0035ad 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/GetContentMainTabFreeResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/GetContentMainTabFreeResponse.kt @@ -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, val themeList: List, val newFreeContentList: List, + val creatorList: List, + val playCountRankContentList: List, val curationList: List ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt index 1a1a0ec..9eb9f3b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt @@ -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 { + return rankingService.fetchCreatorContentBySalesCountTop4( creatorId = creatorId, isAdult = isAdult ) - - val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( - creatorId = creatorId, - isAdult = isAdult - ) - - return GetPopularContentByCreatorResponse( - salesRankContentList = salesRankContentList, - salesCountRankContentList = salesCountRankContentList - ) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/GetContentMainTabHomeResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/GetContentMainTabHomeResponse.kt index db9b01f..24ae94d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/GetContentMainTabHomeResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/GetContentMainTabHomeResponse.kt @@ -18,6 +18,5 @@ data class GetContentMainTabHomeResponse( val rankContentList: List, val eventBannerList: GetEventResponse, val contentRankCreatorList: List, - val salesRankContentList: List, val salesCountRankContentList: List ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt index d6d1164..daecdfb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt @@ -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 { + 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 = "다시듣기" ) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/GetContentMainTabLiveReplayResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/GetContentMainTabLiveReplayResponse.kt index 05860b2..6991faf 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/GetContentMainTabLiveReplayResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/GetContentMainTabLiveReplayResponse.kt @@ -13,7 +13,6 @@ data class GetContentMainTabLiveReplayResponse( val newLiveReplayContentList: List, val rankLiveReplayContentList: List, val creatorList: List, - val salesRankContentList: List, val salesCountRankContentList: List, val eventBannerList: GetEventResponse, val curationList: List diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt index c0e9c65..6e1b34c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt @@ -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 { - 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 { + 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() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt index 5f657d0..f94e672 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt @@ -181,28 +181,20 @@ class RankingService( } } - fun fetchCreatorByContentRevenueRankTop20( + fun fetchCreatorBySellContentCountRankTop20( memberId: Long, startDate: LocalDateTime, endDate: LocalDateTime ): List { - return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate) + return repository.fetchCreatorBySellContentCountRankTop20(memberId, startDate, endDate) } - fun fetchCreatorContentBySalesTop2( + fun fetchCreatorContentBySalesCountTop4( creatorId: Long, isAdult: Boolean, theme: String = "" ): List { - return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult, theme) - } - - fun fetchCreatorContentBySalesCountTop2( - creatorId: Long, - isAdult: Boolean, - theme: String = "" - ): List { - 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 { + return repository.fetchFreeContentByCreatorIdTop4(creatorId, isAdult) + } }