콘텐츠 메인
- 홈 탭 API
This commit is contained in:
		@@ -6,9 +6,9 @@ import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
 | 
				
			|||||||
import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent
 | 
					import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
 | 
					import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike
 | 
					import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
 | 
					import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
 | 
					import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.main.GetNewContentUploadCreator
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem
 | 
					import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.main.QGetAudioContentRankingItem
 | 
					import kr.co.vividnext.sodalive.content.main.QGetAudioContentRankingItem
 | 
				
			||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
 | 
					import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
 | 
				
			||||||
@@ -98,7 +98,7 @@ interface AudioContentQueryRepository {
 | 
				
			|||||||
    fun getNewContentUploadCreatorList(
 | 
					    fun getNewContentUploadCreatorList(
 | 
				
			||||||
        cloudfrontHost: String,
 | 
					        cloudfrontHost: String,
 | 
				
			||||||
        isAdult: Boolean = false
 | 
					        isAdult: Boolean = false
 | 
				
			||||||
    ): List<GetNewContentUploadCreator>
 | 
					    ): List<ContentCreatorResponse>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getAudioContentMainBannerList(isAdult: Boolean): List<AudioContentBanner>
 | 
					    fun getAudioContentMainBannerList(isAdult: Boolean): List<AudioContentBanner>
 | 
				
			||||||
    fun getAudioContentCurations(isAdult: Boolean): List<AudioContentCuration>
 | 
					    fun getAudioContentCurations(isAdult: Boolean): List<AudioContentCuration>
 | 
				
			||||||
@@ -533,7 +533,7 @@ class AudioContentQueryRepositoryImpl(
 | 
				
			|||||||
    override fun getNewContentUploadCreatorList(
 | 
					    override fun getNewContentUploadCreatorList(
 | 
				
			||||||
        cloudfrontHost: String,
 | 
					        cloudfrontHost: String,
 | 
				
			||||||
        isAdult: Boolean
 | 
					        isAdult: Boolean
 | 
				
			||||||
    ): List<GetNewContentUploadCreator> {
 | 
					    ): List<ContentCreatorResponse> {
 | 
				
			||||||
        var where = audioContent.releaseDate.after(LocalDateTime.now().minusWeeks(2))
 | 
					        var where = audioContent.releaseDate.after(LocalDateTime.now().minusWeeks(2))
 | 
				
			||||||
            .and(audioContent.isActive.isTrue)
 | 
					            .and(audioContent.isActive.isTrue)
 | 
				
			||||||
            .and(audioContent.duration.isNotNull)
 | 
					            .and(audioContent.duration.isNotNull)
 | 
				
			||||||
@@ -552,7 +552,7 @@ class AudioContentQueryRepositoryImpl(
 | 
				
			|||||||
            .limit(20)
 | 
					            .limit(20)
 | 
				
			||||||
            .fetch()
 | 
					            .fetch()
 | 
				
			||||||
            .map {
 | 
					            .map {
 | 
				
			||||||
                GetNewContentUploadCreator(
 | 
					                ContentCreatorResponse(
 | 
				
			||||||
                    it.id!!,
 | 
					                    it.id!!,
 | 
				
			||||||
                    it.nickname,
 | 
					                    it.nickname,
 | 
				
			||||||
                    creatorProfileImageUrl = if (it.profileImage != null) {
 | 
					                    creatorProfileImageUrl = if (it.profileImage != null) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -80,7 +80,7 @@ class AudioContentMainService(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Transactional(readOnly = true)
 | 
					    @Transactional(readOnly = true)
 | 
				
			||||||
    @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult")
 | 
					    @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult")
 | 
				
			||||||
    fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List<GetNewContentUploadCreator> {
 | 
					    fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List<ContentCreatorResponse> {
 | 
				
			||||||
        return repository.getNewContentUploadCreatorList(
 | 
					        return repository.getNewContentUploadCreatorList(
 | 
				
			||||||
            cloudfrontHost = imageHost,
 | 
					            cloudfrontHost = imageHost,
 | 
				
			||||||
            isAdult = isAdult
 | 
					            isAdult = isAdult
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ package kr.co.vividnext.sodalive.content.main
 | 
				
			|||||||
import com.fasterxml.jackson.annotation.JsonProperty
 | 
					import com.fasterxml.jackson.annotation.JsonProperty
 | 
				
			||||||
import com.querydsl.core.annotations.QueryProjection
 | 
					import com.querydsl.core.annotations.QueryProjection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class GetNewContentUploadCreator @QueryProjection constructor(
 | 
					data class ContentCreatorResponse @QueryProjection constructor(
 | 
				
			||||||
    @JsonProperty("creatorId") val creatorId: Long,
 | 
					    @JsonProperty("creatorId") val creatorId: Long,
 | 
				
			||||||
    @JsonProperty("creatorNickname") val creatorNickname: String,
 | 
					    @JsonProperty("creatorNickname") val creatorNickname: String,
 | 
				
			||||||
    @JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String
 | 
					    @JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String
 | 
				
			||||||
@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.content.main.banner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.querydsl.jpa.impl.JPAQueryFactory
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.event.QEvent.event
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.QMember.member
 | 
				
			||||||
 | 
					import org.springframework.data.jpa.repository.JpaRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AudioContentBannerRepository : JpaRepository<AudioContentBanner, Long>, AudioContentBannerQueryRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AudioContentBannerQueryRepository {
 | 
				
			||||||
 | 
					    fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean): List<AudioContentBanner>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AudioContentBannerQueryRepositoryImpl(
 | 
				
			||||||
 | 
					    private val queryFactory: JPAQueryFactory
 | 
				
			||||||
 | 
					) : AudioContentBannerQueryRepository {
 | 
				
			||||||
 | 
					    override fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean): List<AudioContentBanner> {
 | 
				
			||||||
 | 
					        var where = audioContentBanner.isActive.isTrue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        where = if (tabId == 1L) {
 | 
				
			||||||
 | 
					            where.and(audioContentBanner.tab.isNull)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            where.and(audioContentBanner.tab.id.eq(tabId))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isAdult) {
 | 
				
			||||||
 | 
					            where = where.and(audioContentBanner.isAdult.isFalse)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .selectFrom(audioContentBanner)
 | 
				
			||||||
 | 
					            .leftJoin(audioContentBanner.tab, audioContentMainTab)
 | 
				
			||||||
 | 
					            .leftJoin(audioContentBanner.event, event)
 | 
				
			||||||
 | 
					            .leftJoin(audioContentBanner.creator, member)
 | 
				
			||||||
 | 
					            .where(where)
 | 
				
			||||||
 | 
					            .orderBy(audioContentBanner.orders.asc())
 | 
				
			||||||
 | 
					            .fetch()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.content.main.banner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.event.EventItem
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
 | 
				
			||||||
 | 
					import org.springframework.beans.factory.annotation.Value
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class AudioContentBannerService(
 | 
				
			||||||
 | 
					    private val repository: AudioContentBannerRepository,
 | 
				
			||||||
 | 
					    private val blockMemberRepository: BlockMemberRepository,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Value("\${cloud.aws.cloud-front.host}")
 | 
				
			||||||
 | 
					    private val imageHost: String
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun getBannerList(tabId: Long, memberId: Long, isAdult: Boolean): List<GetAudioContentBannerResponse> {
 | 
				
			||||||
 | 
					        return repository.getAudioContentMainBannerList(tabId, isAdult)
 | 
				
			||||||
 | 
					            .filter {
 | 
				
			||||||
 | 
					                if (it.type == AudioContentBannerType.CREATOR && it.creator != null) {
 | 
				
			||||||
 | 
					                    !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.creator!!.id!!)
 | 
				
			||||||
 | 
					                } else if (it.type == AudioContentBannerType.SERIES && it.series != null) {
 | 
				
			||||||
 | 
					                    !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.series!!.member!!.id!!)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .map {
 | 
				
			||||||
 | 
					                GetAudioContentBannerResponse(
 | 
				
			||||||
 | 
					                    type = it.type,
 | 
				
			||||||
 | 
					                    thumbnailImageUrl = "$imageHost/${it.thumbnailImage}",
 | 
				
			||||||
 | 
					                    eventItem = if (it.type == AudioContentBannerType.EVENT && it.event != null) {
 | 
				
			||||||
 | 
					                        EventItem(
 | 
				
			||||||
 | 
					                            id = it.event!!.id!!,
 | 
				
			||||||
 | 
					                            thumbnailImageUrl = if (!it.event!!.thumbnailImage.startsWith("https://")) {
 | 
				
			||||||
 | 
					                                "$imageHost/${it.event!!.thumbnailImage}"
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                it.event!!.thumbnailImage
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            detailImageUrl = if (
 | 
				
			||||||
 | 
					                                it.event!!.detailImage != null &&
 | 
				
			||||||
 | 
					                                !it.event!!.detailImage!!.startsWith("https://")
 | 
				
			||||||
 | 
					                            ) {
 | 
				
			||||||
 | 
					                                "$imageHost/${it.event!!.detailImage}"
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                it.event!!.detailImage
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            popupImageUrl = null,
 | 
				
			||||||
 | 
					                            link = it.event!!.link
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        null
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    creatorId = if (it.type == AudioContentBannerType.CREATOR && it.creator != null) {
 | 
				
			||||||
 | 
					                        it.creator!!.id
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        null
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    seriesId = if (it.type == AudioContentBannerType.SERIES && it.series != null) {
 | 
				
			||||||
 | 
					                        it.series!!.id
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        null
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    link = it.link
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.content.main.tab.home
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.common.ApiResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.common.SodaException
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.Member
 | 
				
			||||||
 | 
					import org.springframework.security.core.annotation.AuthenticationPrincipal
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.GetMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RestController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RestController
 | 
				
			||||||
 | 
					@RequestMapping("/v2/audio-content/main/home")
 | 
				
			||||||
 | 
					class AudioContentMainTabHomeController(private val service: AudioContentMainTabHomeService) {
 | 
				
			||||||
 | 
					    @GetMapping
 | 
				
			||||||
 | 
					    fun fetchContentMainHome(
 | 
				
			||||||
 | 
					        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
				
			||||||
 | 
					    ) = run {
 | 
				
			||||||
 | 
					        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ApiResponse.ok(service.fetchData(member))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.content.main.tab.home
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.event.EventService
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.Member
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.notice.ServiceNoticeService
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.rank.RankingService
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					import java.time.DayOfWeek
 | 
				
			||||||
 | 
					import java.time.LocalDateTime
 | 
				
			||||||
 | 
					import java.time.format.DateTimeFormatter
 | 
				
			||||||
 | 
					import java.time.temporal.TemporalAdjusters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class AudioContentMainTabHomeService(
 | 
				
			||||||
 | 
					    private val noticeService: ServiceNoticeService,
 | 
				
			||||||
 | 
					    private val bannerService: AudioContentBannerService,
 | 
				
			||||||
 | 
					    private val rankingService: RankingService,
 | 
				
			||||||
 | 
					    private val eventService: EventService
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun fetchData(member: Member): GetContentMainTabHomeResponse {
 | 
				
			||||||
 | 
					        // 주간 랭킹 기간
 | 
				
			||||||
 | 
					        val currentDateTime = LocalDateTime.now()
 | 
				
			||||||
 | 
					        val startDate = currentDateTime
 | 
				
			||||||
 | 
					            .withHour(15)
 | 
				
			||||||
 | 
					            .withMinute(0)
 | 
				
			||||||
 | 
					            .withSecond(0)
 | 
				
			||||||
 | 
					            .minusWeeks(1)
 | 
				
			||||||
 | 
					            .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
 | 
				
			||||||
 | 
					        val endDate = startDate
 | 
				
			||||||
 | 
					            .plusDays(7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")
 | 
				
			||||||
 | 
					        val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val formattedLastMonday = startDate.format(startDateFormatter)
 | 
				
			||||||
 | 
					        val formattedLastSunday = endDate.format(endDateFormatter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 최근 공지사항
 | 
				
			||||||
 | 
					        val latestNotice = noticeService.getLatestNotice()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 메인 배너 (홈)
 | 
				
			||||||
 | 
					        val contentBannerList = bannerService.getBannerList(
 | 
				
			||||||
 | 
					            tabId = 1,
 | 
				
			||||||
 | 
					            memberId = member.id!!,
 | 
				
			||||||
 | 
					            isAdult = member.auth != null
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 인기 크리에이터
 | 
				
			||||||
 | 
					        val rankCreatorList = rankingService.getCreatorRanking(
 | 
				
			||||||
 | 
					            memberId = member.id!!,
 | 
				
			||||||
 | 
					            rankingDate = "$formattedLastMonday ~ $formattedLastSunday"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 인기 시리즈
 | 
				
			||||||
 | 
					        val rankSeriesList = rankingService.getSeriesRanking(
 | 
				
			||||||
 | 
					            memberId = member.id!!,
 | 
				
			||||||
 | 
					            isAdult = member.auth != null,
 | 
				
			||||||
 | 
					            startDate = startDate.minusDays(1),
 | 
				
			||||||
 | 
					            endDate = endDate.minusDays(1)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 인기 콘텐츠
 | 
				
			||||||
 | 
					        val rankContentList = rankingService.getContentRanking(
 | 
				
			||||||
 | 
					            memberId = member.id!!,
 | 
				
			||||||
 | 
					            isAdult = member.auth != null,
 | 
				
			||||||
 | 
					            startDate = startDate.minusDays(1),
 | 
				
			||||||
 | 
					            endDate = endDate.minusDays(1)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 이벤트 배너
 | 
				
			||||||
 | 
					        val eventBannerList = eventService.getEventList(isAdult = member.auth != null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* 채널별 인기 콘텐츠
 | 
				
			||||||
 | 
					         * - 콘텐츠를 4개 이상 등록한 채널
 | 
				
			||||||
 | 
					         * - 주간 콘텐츠 매출 Top 20 채널
 | 
				
			||||||
 | 
					         * - 해당 채널의 누적 매출 Top 2
 | 
				
			||||||
 | 
					         * - 해당 채널의 누적 판매 개수 Top 2
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        val contentRankCreatorList = rankingService.fetchCreatorByContentRevenueRankTop20(
 | 
				
			||||||
 | 
					            memberId = member.id!!,
 | 
				
			||||||
 | 
					            startDate = startDate.minusDays(1),
 | 
				
			||||||
 | 
					            endDate = endDate.minusDays(1)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2(
 | 
				
			||||||
 | 
					            creatorId = contentRankCreatorList[0].creatorId,
 | 
				
			||||||
 | 
					            isAdult = member.auth != null
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2(
 | 
				
			||||||
 | 
					            creatorId = contentRankCreatorList[0].creatorId,
 | 
				
			||||||
 | 
					            isAdult = member.auth != null
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return GetContentMainTabHomeResponse(
 | 
				
			||||||
 | 
					            latestNotice = latestNotice,
 | 
				
			||||||
 | 
					            bannerList = contentBannerList,
 | 
				
			||||||
 | 
					            rankCreatorList = rankCreatorList,
 | 
				
			||||||
 | 
					            rankSeriesList = rankSeriesList,
 | 
				
			||||||
 | 
					            rankSortTypeList = listOf("매출", "댓글", "좋아요"),
 | 
				
			||||||
 | 
					            rankContentList = rankContentList,
 | 
				
			||||||
 | 
					            eventBannerList = eventBannerList,
 | 
				
			||||||
 | 
					            contentRankCreatorList = contentRankCreatorList,
 | 
				
			||||||
 | 
					            salesRankContentList = salesRankContentList,
 | 
				
			||||||
 | 
					            salesCountRankContentList = salesCountRankContentList
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.content.main.tab.home
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.event.GetEventResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.notice.NoticeTitleItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class GetContentMainTabHomeResponse(
 | 
				
			||||||
 | 
					    val tabId: Long = 1,
 | 
				
			||||||
 | 
					    val latestNotice: NoticeTitleItem?,
 | 
				
			||||||
 | 
					    val bannerList: List<GetAudioContentBannerResponse>,
 | 
				
			||||||
 | 
					    val rankCreatorList: GetExplorerSectionResponse,
 | 
				
			||||||
 | 
					    val rankSeriesList: List<GetSeriesListResponse.SeriesListItem>,
 | 
				
			||||||
 | 
					    val rankSortTypeList: List<String>,
 | 
				
			||||||
 | 
					    val rankContentList: List<GetAudioContentRankingItem>,
 | 
				
			||||||
 | 
					    val eventBannerList: GetEventResponse,
 | 
				
			||||||
 | 
					    val contentRankCreatorList: List<ContentCreatorResponse>,
 | 
				
			||||||
 | 
					    val salesRankContentList: List<GetAudioContentRankingItem>,
 | 
				
			||||||
 | 
					    val salesCountRankContentList: List<GetAudioContentRankingItem>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
package kr.co.vividnext.sodalive.notice
 | 
					package kr.co.vividnext.sodalive.notice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.querydsl.core.annotations.QueryProjection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class GetNoticeResponse(
 | 
					data class GetNoticeResponse(
 | 
				
			||||||
    val totalCount: Int,
 | 
					    val totalCount: Int,
 | 
				
			||||||
    val noticeList: List<NoticeItem>
 | 
					    val noticeList: List<NoticeItem>
 | 
				
			||||||
@@ -11,3 +13,8 @@ data class NoticeItem(
 | 
				
			|||||||
    val content: String,
 | 
					    val content: String,
 | 
				
			||||||
    val date: String
 | 
					    val date: String
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class NoticeTitleItem @QueryProjection constructor(
 | 
				
			||||||
 | 
					    val id: Long,
 | 
				
			||||||
 | 
					    val title: String
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,4 +63,8 @@ class ServiceNoticeService(private val repository: ServiceServiceNoticeRepositor
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return GetNoticeResponse(totalCount, noticeList)
 | 
					        return GetNoticeResponse(totalCount, noticeList)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getLatestNotice(): NoticeTitleItem? {
 | 
				
			||||||
 | 
					        return repository.getLatestNotice()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ interface ServiceServiceNoticeRepository : JpaRepository<ServiceNotice, Long>, S
 | 
				
			|||||||
interface ServiceNoticeQueryRepository {
 | 
					interface ServiceNoticeQueryRepository {
 | 
				
			||||||
    fun getNoticeTotalCount(): Int
 | 
					    fun getNoticeTotalCount(): Int
 | 
				
			||||||
    fun getNoticeList(pageable: Pageable): List<ServiceNotice>
 | 
					    fun getNoticeList(pageable: Pageable): List<ServiceNotice>
 | 
				
			||||||
 | 
					    fun getLatestNotice(): NoticeTitleItem?
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Repository
 | 
					@Repository
 | 
				
			||||||
@@ -34,4 +35,18 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
 | 
				
			|||||||
            .orderBy(serviceNotice.id.desc())
 | 
					            .orderBy(serviceNotice.id.desc())
 | 
				
			||||||
            .fetch()
 | 
					            .fetch()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getLatestNotice(): NoticeTitleItem? {
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .select(
 | 
				
			||||||
 | 
					                QNoticeTitleItem(
 | 
				
			||||||
 | 
					                    serviceNotice.id,
 | 
				
			||||||
 | 
					                    serviceNotice.title
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .from(serviceNotice)
 | 
				
			||||||
 | 
					            .where(serviceNotice.isActive.isTrue)
 | 
				
			||||||
 | 
					            .orderBy(serviceNotice.id.desc())
 | 
				
			||||||
 | 
					            .fetchFirst()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,318 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.rank
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.querydsl.jpa.impl.JPAQueryFactory
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.QContentCreatorResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.QGetAudioContentRankingItem
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.order.QOrder.order
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.creator.admin.content.series.Series
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.explorer.QCreatorRanking.creatorRanking
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.Member
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.MemberRole
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.QMember.member
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
 | 
				
			||||||
 | 
					import org.springframework.beans.factory.annotation.Value
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					import java.time.LocalDateTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Repository
 | 
				
			||||||
 | 
					class RankingRepository(
 | 
				
			||||||
 | 
					    private val queryFactory: JPAQueryFactory,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Value("\${cloud.aws.cloud-front.host}")
 | 
				
			||||||
 | 
					    private val imageHost: String
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun getCreatorRankings(): List<Member> {
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .select(member)
 | 
				
			||||||
 | 
					            .from(creatorRanking)
 | 
				
			||||||
 | 
					            .innerJoin(creatorRanking.member, member)
 | 
				
			||||||
 | 
					            .orderBy(creatorRanking.ranking.asc())
 | 
				
			||||||
 | 
					            .fetch()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAudioContentRanking(
 | 
				
			||||||
 | 
					        memberId: Long,
 | 
				
			||||||
 | 
					        isAdult: Boolean,
 | 
				
			||||||
 | 
					        startDate: LocalDateTime,
 | 
				
			||||||
 | 
					        endDate: LocalDateTime,
 | 
				
			||||||
 | 
					        offset: Long,
 | 
				
			||||||
 | 
					        limit: Long,
 | 
				
			||||||
 | 
					        sortType: String
 | 
				
			||||||
 | 
					    ): List<GetAudioContentRankingItem> {
 | 
				
			||||||
 | 
					        val blockMemberCondition = blockMember.member.id.eq(member.id)
 | 
				
			||||||
 | 
					            .and(blockMember.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(blockMember.blockedMember.id.eq(memberId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var where = audioContent.isActive.isTrue
 | 
				
			||||||
 | 
					            .and(audioContent.member.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(audioContent.member.isNotNull)
 | 
				
			||||||
 | 
					            .and(audioContent.member.role.eq(MemberRole.CREATOR))
 | 
				
			||||||
 | 
					            .and(audioContent.duration.isNotNull)
 | 
				
			||||||
 | 
					            .and(audioContentTheme.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(audioContent.limited.isNull)
 | 
				
			||||||
 | 
					            .and(blockMember.id.isNull)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isAdult) {
 | 
				
			||||||
 | 
					            where = where.and(audioContent.isAdult.isFalse)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var select = queryFactory
 | 
				
			||||||
 | 
					            .select(
 | 
				
			||||||
 | 
					                QGetAudioContentRankingItem(
 | 
				
			||||||
 | 
					                    audioContent.id,
 | 
				
			||||||
 | 
					                    audioContent.title,
 | 
				
			||||||
 | 
					                    audioContent.coverImage.prepend("/").prepend(imageHost),
 | 
				
			||||||
 | 
					                    audioContentTheme.theme,
 | 
				
			||||||
 | 
					                    audioContent.price,
 | 
				
			||||||
 | 
					                    audioContent.duration,
 | 
				
			||||||
 | 
					                    member.id,
 | 
				
			||||||
 | 
					                    member.nickname
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        select = when (sortType) {
 | 
				
			||||||
 | 
					            "후원" -> {
 | 
				
			||||||
 | 
					                select
 | 
				
			||||||
 | 
					                    .from(audioContentComment)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContentComment.audioContent, audioContent)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContent.member, member)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContent.theme, audioContentTheme)
 | 
				
			||||||
 | 
					                    .leftJoin(blockMember).on(blockMemberCondition)
 | 
				
			||||||
 | 
					                    .where(
 | 
				
			||||||
 | 
					                        where
 | 
				
			||||||
 | 
					                            .and(audioContentComment.isActive.isTrue)
 | 
				
			||||||
 | 
					                            .and(audioContentComment.donationCan.gt(0))
 | 
				
			||||||
 | 
					                            .and(audioContentComment.createdAt.goe(startDate))
 | 
				
			||||||
 | 
					                            .and(audioContentComment.createdAt.lt(endDate))
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .groupBy(audioContent.id)
 | 
				
			||||||
 | 
					                    .orderBy(audioContentComment.donationCan.sum().desc(), audioContent.createdAt.asc())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            "댓글" -> {
 | 
				
			||||||
 | 
					                select
 | 
				
			||||||
 | 
					                    .from(audioContentComment)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContentComment.audioContent, audioContent)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContent.member, member)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContent.theme, audioContentTheme)
 | 
				
			||||||
 | 
					                    .leftJoin(blockMember).on(blockMemberCondition)
 | 
				
			||||||
 | 
					                    .where(
 | 
				
			||||||
 | 
					                        where
 | 
				
			||||||
 | 
					                            .and(audioContentComment.isActive.isTrue)
 | 
				
			||||||
 | 
					                            .and(audioContentComment.createdAt.goe(startDate))
 | 
				
			||||||
 | 
					                            .and(audioContentComment.createdAt.lt(endDate))
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .groupBy(audioContentComment.audioContent.id)
 | 
				
			||||||
 | 
					                    .orderBy(audioContentComment.id.count().desc(), audioContent.createdAt.asc())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            "좋아요" -> {
 | 
				
			||||||
 | 
					                select
 | 
				
			||||||
 | 
					                    .from(audioContentLike)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContentLike.audioContent, audioContent)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContent.member, member)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContent.theme, audioContentTheme)
 | 
				
			||||||
 | 
					                    .leftJoin(blockMember).on(blockMemberCondition)
 | 
				
			||||||
 | 
					                    .where(
 | 
				
			||||||
 | 
					                        where
 | 
				
			||||||
 | 
					                            .and(audioContentLike.isActive.isTrue)
 | 
				
			||||||
 | 
					                            .and(audioContentLike.createdAt.goe(startDate))
 | 
				
			||||||
 | 
					                            .and(audioContentLike.createdAt.lt(endDate))
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .groupBy(audioContentLike.audioContent.id)
 | 
				
			||||||
 | 
					                    .orderBy(audioContentLike.id.count().desc(), audioContent.createdAt.asc())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else -> {
 | 
				
			||||||
 | 
					                select
 | 
				
			||||||
 | 
					                    .from(order)
 | 
				
			||||||
 | 
					                    .innerJoin(order.audioContent, audioContent)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContent.member, member)
 | 
				
			||||||
 | 
					                    .innerJoin(audioContent.theme, audioContentTheme)
 | 
				
			||||||
 | 
					                    .leftJoin(blockMember).on(blockMemberCondition)
 | 
				
			||||||
 | 
					                    .where(
 | 
				
			||||||
 | 
					                        where
 | 
				
			||||||
 | 
					                            .and(order.isActive.isTrue)
 | 
				
			||||||
 | 
					                            .and(order.createdAt.goe(startDate))
 | 
				
			||||||
 | 
					                            .and(order.createdAt.lt(endDate))
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .groupBy(audioContent.id)
 | 
				
			||||||
 | 
					                    .orderBy(order.can.sum().desc(), audioContent.createdAt.asc())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return select
 | 
				
			||||||
 | 
					            .offset(offset)
 | 
				
			||||||
 | 
					            .limit(limit)
 | 
				
			||||||
 | 
					            .fetch()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getSeriesRanking(
 | 
				
			||||||
 | 
					        memberId: Long,
 | 
				
			||||||
 | 
					        isAdult: Boolean,
 | 
				
			||||||
 | 
					        startDate: LocalDateTime,
 | 
				
			||||||
 | 
					        endDate: LocalDateTime
 | 
				
			||||||
 | 
					    ): List<Series> {
 | 
				
			||||||
 | 
					        val blockMemberCondition = blockMember.member.id.eq(member.id)
 | 
				
			||||||
 | 
					            .and(blockMember.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(blockMember.blockedMember.id.eq(memberId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var where = series.isActive.isTrue
 | 
				
			||||||
 | 
					            .and(audioContent.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(member.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(member.isNotNull)
 | 
				
			||||||
 | 
					            .and(member.role.eq(MemberRole.CREATOR))
 | 
				
			||||||
 | 
					            .and(audioContent.duration.isNotNull)
 | 
				
			||||||
 | 
					            .and(audioContent.limited.isNull)
 | 
				
			||||||
 | 
					            .and(blockMember.id.isNull)
 | 
				
			||||||
 | 
					            .and(order.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(order.createdAt.goe(startDate))
 | 
				
			||||||
 | 
					            .and(order.createdAt.lt(endDate))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isAdult) {
 | 
				
			||||||
 | 
					            where = where.and(series.isAdult.isFalse)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .select(series)
 | 
				
			||||||
 | 
					            .from(seriesContent)
 | 
				
			||||||
 | 
					            .innerJoin(seriesContent.series, series)
 | 
				
			||||||
 | 
					            .innerJoin(seriesContent.content, audioContent)
 | 
				
			||||||
 | 
					            .innerJoin(series.member, member)
 | 
				
			||||||
 | 
					            .innerJoin(order).on(audioContent.id.eq(order.audioContent.id))
 | 
				
			||||||
 | 
					            .leftJoin(blockMember).on(blockMemberCondition)
 | 
				
			||||||
 | 
					            .where(where)
 | 
				
			||||||
 | 
					            .groupBy(series.id)
 | 
				
			||||||
 | 
					            .orderBy(order.can.sum().desc(), series.createdAt.asc())
 | 
				
			||||||
 | 
					            .offset(0)
 | 
				
			||||||
 | 
					            .limit(10)
 | 
				
			||||||
 | 
					            .fetch()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun fetchCreatorByContentRevenueRankTop20(
 | 
				
			||||||
 | 
					        memberId: Long,
 | 
				
			||||||
 | 
					        startDate: LocalDateTime,
 | 
				
			||||||
 | 
					        endDate: LocalDateTime
 | 
				
			||||||
 | 
					    ): List<ContentCreatorResponse> {
 | 
				
			||||||
 | 
					        val blockMemberCondition = blockMember.member.id.eq(member.id)
 | 
				
			||||||
 | 
					            .and(blockMember.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(blockMember.blockedMember.id.eq(memberId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val ordersCondition = order.audioContent.id.eq(audioContent.id)
 | 
				
			||||||
 | 
					            .and(order.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(order.createdAt.goe(startDate))
 | 
				
			||||||
 | 
					            .and(order.createdAt.lt(startDate))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val where = member.isActive.isTrue
 | 
				
			||||||
 | 
					            .and(member.role.eq(MemberRole.CREATOR))
 | 
				
			||||||
 | 
					            .and(audioContent.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(audioContent.duration.isNotNull)
 | 
				
			||||||
 | 
					            .and(audioContent.limited.isNull)
 | 
				
			||||||
 | 
					            .and(blockMember.id.isNull)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .select(
 | 
				
			||||||
 | 
					                QContentCreatorResponse(
 | 
				
			||||||
 | 
					                    member.id,
 | 
				
			||||||
 | 
					                    member.nickname,
 | 
				
			||||||
 | 
					                    member.profileImage.prepend("/").prepend(imageHost)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .from(member)
 | 
				
			||||||
 | 
					            .innerJoin(audioContent).on(member.id.eq(audioContent.member.id))
 | 
				
			||||||
 | 
					            .leftJoin(order).on(ordersCondition)
 | 
				
			||||||
 | 
					            .leftJoin(blockMember).on(blockMemberCondition)
 | 
				
			||||||
 | 
					            .where(where)
 | 
				
			||||||
 | 
					            .groupBy(member.id)
 | 
				
			||||||
 | 
					            .having(
 | 
				
			||||||
 | 
					                audioContent.id.count().goe(4)
 | 
				
			||||||
 | 
					                    .and(order.can.sum().gt(0))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .orderBy(order.can.sum().desc())
 | 
				
			||||||
 | 
					            .offset(0)
 | 
				
			||||||
 | 
					            .limit(20)
 | 
				
			||||||
 | 
					            .fetch()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> {
 | 
				
			||||||
 | 
					        var where = member.isActive.isTrue
 | 
				
			||||||
 | 
					            .and(member.role.eq(MemberRole.CREATOR))
 | 
				
			||||||
 | 
					            .and(audioContent.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(audioContent.duration.isNotNull)
 | 
				
			||||||
 | 
					            .and(audioContent.limited.isNull)
 | 
				
			||||||
 | 
					            .and(order.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(member.id.eq(creatorId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isAdult) {
 | 
				
			||||||
 | 
					            where = where.and(series.isAdult.isFalse)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .select(
 | 
				
			||||||
 | 
					                QGetAudioContentRankingItem(
 | 
				
			||||||
 | 
					                    audioContent.id,
 | 
				
			||||||
 | 
					                    audioContent.title,
 | 
				
			||||||
 | 
					                    audioContent.coverImage.prepend("/").prepend(imageHost),
 | 
				
			||||||
 | 
					                    audioContentTheme.theme,
 | 
				
			||||||
 | 
					                    audioContent.price,
 | 
				
			||||||
 | 
					                    audioContent.duration,
 | 
				
			||||||
 | 
					                    member.id,
 | 
				
			||||||
 | 
					                    member.nickname
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .from(order)
 | 
				
			||||||
 | 
					            .innerJoin(order.audioContent, audioContent)
 | 
				
			||||||
 | 
					            .innerJoin(audioContent.member, member)
 | 
				
			||||||
 | 
					            .where(where)
 | 
				
			||||||
 | 
					            .groupBy(audioContent.id)
 | 
				
			||||||
 | 
					            .orderBy(order.can.sum().desc())
 | 
				
			||||||
 | 
					            .offset(0)
 | 
				
			||||||
 | 
					            .limit(2)
 | 
				
			||||||
 | 
					            .fetch()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> {
 | 
				
			||||||
 | 
					        var where = member.isActive.isTrue
 | 
				
			||||||
 | 
					            .and(member.role.eq(MemberRole.CREATOR))
 | 
				
			||||||
 | 
					            .and(audioContent.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(audioContent.duration.isNotNull)
 | 
				
			||||||
 | 
					            .and(audioContent.limited.isNull)
 | 
				
			||||||
 | 
					            .and(order.isActive.isTrue)
 | 
				
			||||||
 | 
					            .and(member.id.eq(creatorId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isAdult) {
 | 
				
			||||||
 | 
					            where = where.and(series.isAdult.isFalse)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .select(
 | 
				
			||||||
 | 
					                QGetAudioContentRankingItem(
 | 
				
			||||||
 | 
					                    audioContent.id,
 | 
				
			||||||
 | 
					                    audioContent.title,
 | 
				
			||||||
 | 
					                    audioContent.coverImage.prepend("/").prepend(imageHost),
 | 
				
			||||||
 | 
					                    audioContentTheme.theme,
 | 
				
			||||||
 | 
					                    audioContent.price,
 | 
				
			||||||
 | 
					                    audioContent.duration,
 | 
				
			||||||
 | 
					                    member.id,
 | 
				
			||||||
 | 
					                    member.nickname
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .from(order)
 | 
				
			||||||
 | 
					            .innerJoin(order.audioContent, audioContent)
 | 
				
			||||||
 | 
					            .innerJoin(audioContent.member, member)
 | 
				
			||||||
 | 
					            .where(where)
 | 
				
			||||||
 | 
					            .groupBy(audioContent.id)
 | 
				
			||||||
 | 
					            .orderBy(order.id.count().desc())
 | 
				
			||||||
 | 
					            .offset(0)
 | 
				
			||||||
 | 
					            .limit(2)
 | 
				
			||||||
 | 
					            .fetch()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										151
									
								
								src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.rank
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.content.series.content.ContentSeriesContentRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.creator.admin.content.series.Series
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.MemberService
 | 
				
			||||||
 | 
					import org.springframework.beans.factory.annotation.Value
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					import java.time.LocalDateTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class RankingService(
 | 
				
			||||||
 | 
					    private val repository: RankingRepository,
 | 
				
			||||||
 | 
					    private val memberService: MemberService,
 | 
				
			||||||
 | 
					    private val seriesContentRepository: ContentSeriesContentRepository,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Value("\${cloud.aws.cloud-front.host}")
 | 
				
			||||||
 | 
					    private val imageHost: String
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun getCreatorRanking(memberId: Long, rankingDate: String): GetExplorerSectionResponse {
 | 
				
			||||||
 | 
					        val creatorRankings = repository
 | 
				
			||||||
 | 
					            .getCreatorRankings()
 | 
				
			||||||
 | 
					            .filter { !memberService.isBlocked(blockedMemberId = memberId, memberId = it.id!!) }
 | 
				
			||||||
 | 
					            .map { it.toExplorerSectionCreator(imageHost) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return GetExplorerSectionResponse(
 | 
				
			||||||
 | 
					            title = "인기 크리에이터",
 | 
				
			||||||
 | 
					            coloredTitle = "인기",
 | 
				
			||||||
 | 
					            color = "FF5C49",
 | 
				
			||||||
 | 
					            desc = rankingDate,
 | 
				
			||||||
 | 
					            creators = creatorRankings
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getContentRanking(
 | 
				
			||||||
 | 
					        memberId: Long,
 | 
				
			||||||
 | 
					        isAdult: Boolean,
 | 
				
			||||||
 | 
					        startDate: LocalDateTime,
 | 
				
			||||||
 | 
					        endDate: LocalDateTime,
 | 
				
			||||||
 | 
					        offset: Long = 0,
 | 
				
			||||||
 | 
					        limit: Long = 12,
 | 
				
			||||||
 | 
					        sortType: String = "매출"
 | 
				
			||||||
 | 
					    ): List<GetAudioContentRankingItem> {
 | 
				
			||||||
 | 
					        return repository.getAudioContentRanking(
 | 
				
			||||||
 | 
					            memberId = memberId,
 | 
				
			||||||
 | 
					            isAdult = isAdult,
 | 
				
			||||||
 | 
					            startDate = startDate,
 | 
				
			||||||
 | 
					            endDate = endDate,
 | 
				
			||||||
 | 
					            offset = offset,
 | 
				
			||||||
 | 
					            limit = limit,
 | 
				
			||||||
 | 
					            sortType = sortType
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getSeriesRanking(
 | 
				
			||||||
 | 
					        memberId: Long,
 | 
				
			||||||
 | 
					        isAdult: Boolean,
 | 
				
			||||||
 | 
					        startDate: LocalDateTime,
 | 
				
			||||||
 | 
					        endDate: LocalDateTime
 | 
				
			||||||
 | 
					    ): List<GetSeriesListResponse.SeriesListItem> {
 | 
				
			||||||
 | 
					        val seriesList = repository.getSeriesRanking(memberId, isAdult, startDate, endDate)
 | 
				
			||||||
 | 
					        return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun seriesToSeriesListItem(
 | 
				
			||||||
 | 
					        seriesList: List<Series>,
 | 
				
			||||||
 | 
					        isAdult: Boolean
 | 
				
			||||||
 | 
					    ): List<GetSeriesListResponse.SeriesListItem> {
 | 
				
			||||||
 | 
					        return seriesList
 | 
				
			||||||
 | 
					            .map {
 | 
				
			||||||
 | 
					                GetSeriesListResponse.SeriesListItem(
 | 
				
			||||||
 | 
					                    seriesId = it.id!!,
 | 
				
			||||||
 | 
					                    title = it.title,
 | 
				
			||||||
 | 
					                    coverImage = "$imageHost/${it.coverImage!!}",
 | 
				
			||||||
 | 
					                    publishedDaysOfWeek = publishedDaysOfWeekText(it.publishedDaysOfWeek),
 | 
				
			||||||
 | 
					                    isComplete = it.state == SeriesState.COMPLETE,
 | 
				
			||||||
 | 
					                    creator = GetSeriesListResponse.SeriesListItemCreator(
 | 
				
			||||||
 | 
					                        creatorId = it.member!!.id!!,
 | 
				
			||||||
 | 
					                        nickname = it.member!!.nickname,
 | 
				
			||||||
 | 
					                        profileImage = "$imageHost/${it.member!!.profileImage!!}"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .map {
 | 
				
			||||||
 | 
					                it.numberOfContent = seriesContentRepository.getContentCount(
 | 
				
			||||||
 | 
					                    seriesId = it.seriesId,
 | 
				
			||||||
 | 
					                    isAdult = isAdult
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                it
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .map {
 | 
				
			||||||
 | 
					                val nowDateTime = LocalDateTime.now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                it.isNew = seriesContentRepository.isNewContent(
 | 
				
			||||||
 | 
					                    seriesId = it.seriesId,
 | 
				
			||||||
 | 
					                    isAdult = isAdult,
 | 
				
			||||||
 | 
					                    fromDate = nowDateTime.minusDays(7),
 | 
				
			||||||
 | 
					                    nowDate = nowDateTime
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                it
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun publishedDaysOfWeekText(publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>): String {
 | 
				
			||||||
 | 
					        val dayOfWeekText = publishedDaysOfWeek.toList().sortedBy { it.ordinal }
 | 
				
			||||||
 | 
					            .map {
 | 
				
			||||||
 | 
					                when (it) {
 | 
				
			||||||
 | 
					                    SeriesPublishedDaysOfWeek.SUN -> "일"
 | 
				
			||||||
 | 
					                    SeriesPublishedDaysOfWeek.MON -> "월"
 | 
				
			||||||
 | 
					                    SeriesPublishedDaysOfWeek.TUE -> "화"
 | 
				
			||||||
 | 
					                    SeriesPublishedDaysOfWeek.WED -> "수"
 | 
				
			||||||
 | 
					                    SeriesPublishedDaysOfWeek.THU -> "목"
 | 
				
			||||||
 | 
					                    SeriesPublishedDaysOfWeek.FRI -> "금"
 | 
				
			||||||
 | 
					                    SeriesPublishedDaysOfWeek.SAT -> "토"
 | 
				
			||||||
 | 
					                    SeriesPublishedDaysOfWeek.RANDOM -> "랜덤"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .joinToString(", ") { it }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return if (publishedDaysOfWeek.contains(SeriesPublishedDaysOfWeek.RANDOM)) {
 | 
				
			||||||
 | 
					            dayOfWeekText
 | 
				
			||||||
 | 
					        } else if (publishedDaysOfWeek.size < 7) {
 | 
				
			||||||
 | 
					            "매주 $dayOfWeekText"
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            "매일"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun fetchCreatorByContentRevenueRankTop20(
 | 
				
			||||||
 | 
					        memberId: Long,
 | 
				
			||||||
 | 
					        startDate: LocalDateTime,
 | 
				
			||||||
 | 
					        endDate: LocalDateTime
 | 
				
			||||||
 | 
					    ): List<ContentCreatorResponse> {
 | 
				
			||||||
 | 
					        return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> {
 | 
				
			||||||
 | 
					        return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> {
 | 
				
			||||||
 | 
					        return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user