diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt index 2a2a983..fafccf7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt @@ -134,6 +134,15 @@ class AudioContentController(private val service: AudioContentService) { ApiResponse.ok(service.getDetail(id = id, member = member, timezone = timezone)) } + @GetMapping("/{id}/generate-url") + fun generateUrl( + @PathVariable id: Long, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + ApiResponse.ok(service.generateUrl(contentId = id, member = member)) + } + @PostMapping("/playback-tracking") fun addAllPlaybackTracking( @RequestBody request: AddAllPlaybackTrackingRequest, 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 628d5b3..09063db 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -797,11 +797,15 @@ class AudioContentQueryRepositoryImpl( audioContent.title, audioContentTheme.theme, audioContent.coverImage.prepend("/").prepend(imageHost), - audioContent.duration + audioContent.duration, + member.nickname, + member.profileImage.prepend("/").prepend(imageHost) ) ) .from(audioContent) + .innerJoin(audioContent.member, member) .innerJoin(audioContent.theme, audioContentTheme) + .where(audioContent.id.`in`(contentIdList)) .fetch() } @@ -809,6 +813,7 @@ class AudioContentQueryRepositoryImpl( return queryFactory .select(audioContent.coverImage.prepend("/").prepend(imageHost)) .from(audioContent) + .where(audioContent.id.eq(id)) .fetchFirst() } } 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 05f9738..d5231df 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -827,4 +827,25 @@ class AudioContentService( pinContent.isActive = false } + + fun generateUrl(contentId: Long, member: Member): GenerateUrlResponse { + val audioContent = repository.findByIdOrNull(contentId) + ?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.") + + val isExistsAudioContent = orderRepository.isExistOrdered( + memberId = member.id!!, + contentId = audioContent.id!! + ) + + val contentUrl = if (isExistsAudioContent) { + audioContentCloudFront.generateSignedURL( + resourcePath = audioContent.content!!, + expirationTime = 1000 * 60 * 60 * (audioContent.duration!!.split(":")[0].toLong() + 2) + ) + } else { + "" + } + + return GenerateUrlResponse(contentUrl) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/GenerateUrlResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/GenerateUrlResponse.kt new file mode 100644 index 0000000..9a51cfe --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/GenerateUrlResponse.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.content + +data class GenerateUrlResponse( + val contentUrl: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderController.kt index 89c1e08..889e6ed 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderController.kt @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping 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 @@ -33,6 +34,7 @@ class OrderController(private val service: OrderService) { @GetMapping("/audio-content") fun getAudioContentOrderList( + @RequestParam(value = "orderType", required = false) orderType: OrderType? = null, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, pageable: Pageable ) = run { @@ -40,6 +42,7 @@ class OrderController(private val service: OrderService) { ApiResponse.ok( service.getAudioContentOrderList( + orderType = orderType, member = member, offset = pageable.offset, limit = pageable.pageSize.toLong() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt index fb6022a..4c7887b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderRepository.kt @@ -20,6 +20,7 @@ interface OrderQueryRepository { fun isExistOrderedAndOrderType(memberId: Long, contentId: Long): Pair fun getAudioContentRemainingTime(memberId: Long, contentId: Long, timezone: String): String fun getAudioContentOrderList( + orderType: OrderType?, dateTime: LocalDateTime, coverImageHost: String, memberId: Long, @@ -27,7 +28,7 @@ interface OrderQueryRepository { limit: Long = 10 ): List - fun totalAudioContentOrderListCount(memberId: Long, dateTime: LocalDateTime): Int + fun totalAudioContentOrderListCount(orderType: OrderType?, memberId: Long, dateTime: LocalDateTime): Int fun getAudioContentMainOrderList( dateTime: LocalDateTime, coverImageHost: String, @@ -117,12 +118,41 @@ class OrderQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Orde } override fun getAudioContentOrderList( + orderType: OrderType?, dateTime: LocalDateTime, coverImageHost: String, memberId: Long, offset: Long, limit: Long ): List { + var where = order.member.id.eq(memberId) + .and(order.isActive.isTrue) + + when (orderType) { + OrderType.RENTAL -> { + where = where.and( + order.type.eq(OrderType.RENTAL) + .and(order.startDate.before(dateTime)) + .and(order.endDate.after(dateTime)) + ) + } + + OrderType.KEEP -> { + where = where.and(order.type.eq(OrderType.KEEP)) + } + + null -> { + where = where.and( + order.type.eq(OrderType.KEEP) + .or( + order.type.eq(OrderType.RENTAL) + .and(order.startDate.before(dateTime)) + .and(order.endDate.after(dateTime)) + ) + ) + } + } + return queryFactory .select( QGetAudioContentOrderListItem( @@ -141,39 +171,45 @@ class OrderQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Orde ) .from(order) .innerJoin(order.audioContent, audioContent) - .where( - order.member.id.eq(memberId) - .and(order.isActive.isTrue) - .and( - order.type.eq(OrderType.KEEP) - .or( - order.type.eq(OrderType.RENTAL) - .and(order.startDate.before(dateTime)) - .and(order.endDate.after(dateTime)) - ) - ) - ) + .where(where) .offset(offset) .limit(limit) .orderBy(order.createdAt.desc()) .fetch() } - override fun totalAudioContentOrderListCount(memberId: Long, dateTime: LocalDateTime): Int { + override fun totalAudioContentOrderListCount(orderType: OrderType?, memberId: Long, dateTime: LocalDateTime): Int { + var where = order.member.id.eq(memberId) + .and(order.isActive.isTrue) + + when (orderType) { + OrderType.RENTAL -> { + where = where.and( + order.type.eq(OrderType.RENTAL) + .and(order.startDate.before(dateTime)) + .and(order.endDate.after(dateTime)) + ) + } + + OrderType.KEEP -> { + where = where.and(order.type.eq(OrderType.KEEP)) + } + + null -> { + where = where.and( + order.type.eq(OrderType.KEEP) + .or( + order.type.eq(OrderType.RENTAL) + .and(order.startDate.before(dateTime)) + .and(order.endDate.after(dateTime)) + ) + ) + } + } + return queryFactory.select(order.id) .from(order) - .where( - order.member.id.eq(memberId) - .and(order.isActive.isTrue) - .and( - order.type.eq(OrderType.KEEP) - .or( - order.type.eq(OrderType.RENTAL) - .and(order.startDate.before(dateTime)) - .and(order.endDate.after(dateTime)) - ) - ) - ) + .where(where) .fetch() .size } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt index 630b22a..620aaaa 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/order/OrderService.kt @@ -90,15 +90,18 @@ class OrderService( } fun getAudioContentOrderList( + orderType: OrderType? = null, member: Member, offset: Long, limit: Long ): GetAudioContentOrderListResponse { val totalCount = repository.totalAudioContentOrderListCount( + orderType = orderType, memberId = member.id!!, dateTime = LocalDateTime.now() ) val orderItems = repository.getAudioContentOrderList( + orderType = orderType, dateTime = LocalDateTime.now(), coverImageHost = audioContentCoverImageHost, memberId = member.id!!, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt index d9dd2ec..f737f5c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylist.kt @@ -29,5 +29,7 @@ data class AudioContentPlaylistContent @QueryProjection constructor( val title: String, val category: String, val coverUrl: String, - val duration: String + val duration: String, + val creatorNickname: String, + val creatorProfileUrl: String ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt index 4bc76ce..46ccf02 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/playlist/AudioContentPlaylistService.kt @@ -28,7 +28,7 @@ class AudioContentPlaylistService( } // 콘텐츠 유효성 검사 (소장으로 구매한 콘텐츠 인가?) - checkOrderedContent( + validateContent( contentIdList = request.contentIdAndOrderList.map { it.contentId }, memberId = member.id!! ) @@ -44,6 +44,17 @@ class AudioContentPlaylistService( redisRepository.save(playlist) } + private fun validateContent(contentIdList: List, memberId: Long) { + if (contentIdList.isEmpty()) { + throw SodaException("콘텐츠를 1개 이상 추가하세요") + } + + checkOrderedContent( + contentIdList = contentIdList, + memberId = memberId + ) + } + private fun checkOrderedContent(contentIdList: List, memberId: Long) { val orderedContentIdList = orderRepository.findOrderedContent(contentIdList, memberId).toSet() val orderedContentMap = contentIdList.associateWith { it in orderedContentIdList } @@ -81,7 +92,9 @@ class AudioContentPlaylistService( } fun getPlaylists(member: Member): GetPlaylistsResponse { - val playlists = redisRepository.findByMemberId(memberId = member.id!!) + val playlists = redisRepository + .findByMemberId(memberId = member.id!!) + .sortedByDescending { it.createdAt } return GetPlaylistsResponse( totalCount = playlists.size, @@ -123,7 +136,7 @@ class AudioContentPlaylistService( throw SodaException("잘못된 요청입니다.") } - val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd") val createdDate = playlist.createdAt .atZone(ZoneId.of("UTC")) .withZoneSameInstant(ZoneId.of("Asia/Seoul"))