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 2a622f0..a1f5c15 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContent.kt @@ -35,6 +35,7 @@ data class AudioContent( @Enumerated(value = EnumType.STRING) val type: AudioContentType = AudioContentType.INDIVIDUAL, val isGeneratePreview: Boolean = true, + var isOnlyRental: Boolean = false, var isAdult: Boolean = false, var isCommentAvailable: Boolean = true ) : BaseEntity() { 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 c5df5c2..24c738a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -2,7 +2,6 @@ package kr.co.vividnext.sodalive.content import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory -import kr.co.vividnext.sodalive.can.use.QUseCan.useCan import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.QBundleAudioContent.bundleAudioContent import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem @@ -78,6 +77,9 @@ interface AudioContentQueryRepository { fun getAudioContentMainBannerList(isAdult: Boolean): List fun getAudioContentCurations(isAdult: Boolean): List + + fun getAudioContentCurationList(isAdult: Boolean, offset: Long = 0, limit: Long = 10): List + fun findAudioContentByCurationId( curationId: Long, cloudfrontHost: String, @@ -337,10 +339,6 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) .fetch() } - @Cacheable( - value = ["getNewContentUploadCreatorList"], - cacheManager = "cacheManager" - ) override fun getNewContentUploadCreatorList( cloudfrontHost: String, isAdult: Boolean @@ -410,6 +408,30 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) .fetch() } + override fun getAudioContentCurationList( + isAdult: Boolean, + offset: Long, + limit: Long + ): List { + var where = audioContentCuration.isActive.isTrue + + if (!isAdult) { + where = where.and(audioContentCuration.isAdult.isFalse) + } + + return queryFactory + .selectFrom(audioContentCuration) + .where(where) + .orderBy(audioContentCuration.orders.asc()) + .offset(offset) + .limit(limit) + .fetch() + } + + @Cacheable( + value = ["findAudioContentByCurationId"], + cacheManager = "cacheManager" + ) override fun findAudioContentByCurationId( curationId: Long, cloudfrontHost: String, @@ -459,12 +481,13 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) offset: Long, limit: Long ): List { - var where = audioContent.isActive.isTrue + var where = order.createdAt.goe(startDate) + .and(order.createdAt.lt(endDate)) + .and(audioContent.isActive.isTrue) .and(audioContent.member.isNotNull) .and(audioContent.duration.isNotNull) .and(audioContent.member.isActive.isTrue) - .and(useCan.createdAt.goe(startDate)) - .and(useCan.createdAt.lt(endDate)) + .and(audioContentTheme.isActive.isTrue) if (!isAdult) { where = where.and(audioContent.isAdult.isFalse) @@ -483,8 +506,7 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) member.nickname ) ) - .from(useCan) - .innerJoin(useCan.order, order) + .from(order) .innerJoin(order.audioContent, audioContent) .innerJoin(audioContent.member, member) .innerJoin(audioContent.theme, audioContentTheme) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index 45984ac..ea50196 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -176,6 +176,7 @@ class AudioContentService( } else { false }, + isOnlyRental = request.isOnlyRental, isCommentAvailable = request.isCommentAvailable ) audioContent.theme = theme @@ -486,6 +487,7 @@ class AudioContentService( duration = audioContent.duration ?: "", isAdult = audioContent.isAdult, isMosaic = audioContent.isAdult && member.auth == null, + isOnlyRental = audioContent.isOnlyRental, existOrdered = isExistsBundleAudioContent || isExistsAudioContent, orderType = orderType, remainingTime = remainingTime, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/CreateAudioContentRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/CreateAudioContentRequest.kt index cab17ae..619b27f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/CreateAudioContentRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/CreateAudioContentRequest.kt @@ -8,6 +8,7 @@ data class CreateAudioContentRequest( val themeId: Long = 0, val isAdult: Boolean = false, val isGeneratePreview: Boolean = true, + val isOnlyRental: Boolean = false, val isCommentAvailable: Boolean = false, val type: AudioContentType = AudioContentType.INDIVIDUAL, val childIds: List? = null, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/GetAudioContentDetailResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/GetAudioContentDetailResponse.kt index 0c5f3e9..8d0c468 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/GetAudioContentDetailResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/GetAudioContentDetailResponse.kt @@ -16,6 +16,7 @@ data class GetAudioContentDetailResponse( val duration: String, val isAdult: Boolean, val isMosaic: Boolean, + val isOnlyRental: Boolean, val existOrdered: Boolean, val orderType: OrderType?, val remainingTime: String?, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainController.kt index 41cee69..df51a73 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainController.kt @@ -23,6 +23,33 @@ class AudioContentMainController(private val service: AudioContentMainService) { ApiResponse.ok(service.getMain(member = member)) } + @GetMapping("/new-content-upload-creator") + fun getNewContentUploadCreatorList( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getNewContentUploadCreatorList(member = member)) + } + + @GetMapping("/banner-list") + fun getAudioContentMainBannerList( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getAudioContentMainBannerList(member = member)) + } + + @GetMapping("/order-list") + fun getAudioContentMainOrderList( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getAudioContentMainOrderList(member = member)) + } + @GetMapping("/new") fun getNewContentByTheme( @RequestParam("theme") theme: String, @@ -53,4 +80,29 @@ class AudioContentMainController(private val service: AudioContentMainService) { ApiResponse.ok(service.getNewContentFor2WeeksByTheme(theme, member, pageable)) } + + @GetMapping("/curation-list") + fun getAudioContentMainCurationList( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, + pageable: Pageable + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok( + service.getAudioContentMainCurationList( + member, + offset = pageable.offset, + limit = pageable.pageSize.toLong() + ) + ) + } + + @GetMapping("/content-ranking") + fun getAudioContentMainContentRanking( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.getAudioContentMainContentRanking(member)) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt index 5b6b050..159b661 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt @@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.event.EventItem import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import org.springframework.beans.factory.annotation.Value +import org.springframework.cache.annotation.Cacheable import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import java.time.DayOfWeek @@ -168,6 +169,87 @@ class AudioContentMainService( ) } + @Cacheable( + value = ["getNewContentUploadCreatorList"], + cacheManager = "cacheManager" + ) + fun getNewContentUploadCreatorList(member: Member): List { + val isAdult = member.auth != null + + // 2주일 이내에 콘텐츠를 올린 크리에이터 20명 조회 + return repository.getNewContentUploadCreatorList( + cloudfrontHost = imageHost, + isAdult = isAdult + ) + .asSequence() + .filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.creatorId) } + .toList() + } + + @Cacheable( + value = ["getAudioContentMainBannerList"], + cacheManager = "cacheManager" + ) + fun getAudioContentMainBannerList(member: Member): List { + val isAdult = member.auth != null + + return repository + .getAudioContentMainBannerList(isAdult = isAdult) + .asSequence() + .filter { + if (it.type == AudioContentBannerType.CREATOR && it.creator != null) { + !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.creator!!.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, + title = it.event!!.title, + isPopup = false + ) + } else { + null + }, + creatorId = if (it.type == AudioContentBannerType.CREATOR && it.creator != null) { + it.creator!!.id + } else { + null + }, + link = it.link + ) + } + .toList() + } + + fun getAudioContentMainOrderList(member: Member): List { + return orderService + .getAudioContentMainOrderList( + member = member, + limit = 20 + ) + } + fun getThemeList(member: Member): List { return audioContentThemeRepository.getActiveThemeOfContent(isAdult = member.auth != null) } @@ -197,4 +279,77 @@ class AudioContentMainService( return GetNewContentAllResponse(totalCount, items) } + + @Cacheable( + value = ["getAudioContentMainCurationList"], + cacheManager = "cacheManager" + ) + fun getAudioContentMainCurationList( + member: Member, + offset: Long, + limit: Long + ): List { + val isAdult = member.auth != null + + return repository + .getAudioContentCurationList( + isAdult = isAdult, + offset = offset, + limit = limit + ) + .asSequence() + .map { + GetAudioContentCurationResponse( + curationId = it.id!!, + title = it.title, + description = it.description, + contents = repository.findAudioContentByCurationId( + curationId = it.id!!, + cloudfrontHost = imageHost, + isAdult = isAdult + ) + .asSequence() + .filter { content -> + !blockMemberRepository.isBlocked( + blockedMemberId = member.id!!, + memberId = content.creatorId + ) + } + .toList() + ) + } + .filter { it.contents.isNotEmpty() } + .toList() + } + + fun getAudioContentMainContentRanking(member: Member): GetAudioContentRanking { + val isAdult = member.auth != null + + val currentDateTime = LocalDateTime.now() + val startDate = currentDateTime + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + .withHour(15) + .withMinute(0) + .withSecond(0) + val endDate = startDate + .plusDays(7) + + val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") + val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일") + + val contentRankingItemList = repository + .getAudioContentRanking( + cloudfrontHost = imageHost, + startDate = startDate.minusDays(1), + endDate = endDate.minusDays(1), + isAdult = isAdult + ) + + return GetAudioContentRanking( + startDate = startDate.format(startDateFormatter), + endDate = endDate.minusDays(1).format(endDateFormatter), + contentRankingItemList + ) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentMainItem.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentMainItem.kt index 1ab49eb..f349bb5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentMainItem.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetAudioContentMainItem.kt @@ -1,13 +1,14 @@ package kr.co.vividnext.sodalive.content.main +import com.fasterxml.jackson.annotation.JsonProperty import com.querydsl.core.annotations.QueryProjection data class GetAudioContentMainItem @QueryProjection constructor( - val contentId: Long, - val coverImageUrl: String, - val title: String, - val isAdult: Boolean, - val creatorId: Long, - val creatorProfileImageUrl: String, - val creatorNickname: String + @JsonProperty("contentId") val contentId: Long, + @JsonProperty("coverImageUrl") val coverImageUrl: String, + @JsonProperty("title") val title: String, + @JsonProperty("adult") val isAdult: Boolean, + @JsonProperty("creatorId") val creatorId: Long, + @JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String, + @JsonProperty("creatorNickname") val creatorNickname: String ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/Order.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/Order.kt index 5e0da19..be6d76a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/Order.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/Order.kt @@ -41,8 +41,8 @@ data class Order( @JoinColumn(name = "content_id", nullable = false) var audioContent: AudioContent? = null set(value) { - can = if (type == OrderType.RENTAL) { - ceil(value?.price!! * 0.6).toInt() + can = if (type == OrderType.RENTAL && !value?.isOnlyRental!!) { + ceil(value.price * 0.6).toInt() } else { value?.price!! } @@ -52,7 +52,7 @@ data class Order( override fun prePersist() { super.prePersist() if (type == OrderType.RENTAL) { - endDate = startDate.plusDays(7) + endDate = startDate.plusDays(15) } } }