| @@ -134,6 +134,15 @@ class AudioContentController(private val service: AudioContentService) { | |||||||
|         ApiResponse.ok(service.getDetail(id = id, member = member, timezone = timezone)) |         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") |     @PostMapping("/playback-tracking") | ||||||
|     fun addAllPlaybackTracking( |     fun addAllPlaybackTracking( | ||||||
|         @RequestBody request: AddAllPlaybackTrackingRequest, |         @RequestBody request: AddAllPlaybackTrackingRequest, | ||||||
|   | |||||||
| @@ -797,11 +797,15 @@ class AudioContentQueryRepositoryImpl( | |||||||
|                     audioContent.title, |                     audioContent.title, | ||||||
|                     audioContentTheme.theme, |                     audioContentTheme.theme, | ||||||
|                     audioContent.coverImage.prepend("/").prepend(imageHost), |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|                     audioContent.duration |                     audioContent.duration, | ||||||
|  |                     member.nickname, | ||||||
|  |                     member.profileImage.prepend("/").prepend(imageHost) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|             .from(audioContent) |             .from(audioContent) | ||||||
|  |             .innerJoin(audioContent.member, member) | ||||||
|             .innerJoin(audioContent.theme, audioContentTheme) |             .innerJoin(audioContent.theme, audioContentTheme) | ||||||
|  |             .where(audioContent.id.`in`(contentIdList)) | ||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -809,6 +813,7 @@ class AudioContentQueryRepositoryImpl( | |||||||
|         return queryFactory |         return queryFactory | ||||||
|             .select(audioContent.coverImage.prepend("/").prepend(imageHost)) |             .select(audioContent.coverImage.prepend("/").prepend(imageHost)) | ||||||
|             .from(audioContent) |             .from(audioContent) | ||||||
|  |             .where(audioContent.id.eq(id)) | ||||||
|             .fetchFirst() |             .fetchFirst() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -827,4 +827,25 @@ class AudioContentService( | |||||||
|  |  | ||||||
|         pinContent.isActive = false |         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) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content | ||||||
|  |  | ||||||
|  | data class GenerateUrlResponse( | ||||||
|  |     val contentUrl: String | ||||||
|  | ) | ||||||
| @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.GetMapping | |||||||
| import org.springframework.web.bind.annotation.PostMapping | import org.springframework.web.bind.annotation.PostMapping | ||||||
| import org.springframework.web.bind.annotation.RequestBody | import org.springframework.web.bind.annotation.RequestBody | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -33,6 +34,7 @@ class OrderController(private val service: OrderService) { | |||||||
|  |  | ||||||
|     @GetMapping("/audio-content") |     @GetMapping("/audio-content") | ||||||
|     fun getAudioContentOrderList( |     fun getAudioContentOrderList( | ||||||
|  |         @RequestParam(value = "orderType", required = false) orderType: OrderType? = null, | ||||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|         pageable: Pageable |         pageable: Pageable | ||||||
|     ) = run { |     ) = run { | ||||||
| @@ -40,6 +42,7 @@ class OrderController(private val service: OrderService) { | |||||||
|  |  | ||||||
|         ApiResponse.ok( |         ApiResponse.ok( | ||||||
|             service.getAudioContentOrderList( |             service.getAudioContentOrderList( | ||||||
|  |                 orderType = orderType, | ||||||
|                 member = member, |                 member = member, | ||||||
|                 offset = pageable.offset, |                 offset = pageable.offset, | ||||||
|                 limit = pageable.pageSize.toLong() |                 limit = pageable.pageSize.toLong() | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ interface OrderQueryRepository { | |||||||
|     fun isExistOrderedAndOrderType(memberId: Long, contentId: Long): Pair<Boolean, OrderType?> |     fun isExistOrderedAndOrderType(memberId: Long, contentId: Long): Pair<Boolean, OrderType?> | ||||||
|     fun getAudioContentRemainingTime(memberId: Long, contentId: Long, timezone: String): String |     fun getAudioContentRemainingTime(memberId: Long, contentId: Long, timezone: String): String | ||||||
|     fun getAudioContentOrderList( |     fun getAudioContentOrderList( | ||||||
|  |         orderType: OrderType?, | ||||||
|         dateTime: LocalDateTime, |         dateTime: LocalDateTime, | ||||||
|         coverImageHost: String, |         coverImageHost: String, | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
| @@ -27,7 +28,7 @@ interface OrderQueryRepository { | |||||||
|         limit: Long = 10 |         limit: Long = 10 | ||||||
|     ): List<GetAudioContentOrderListItem> |     ): List<GetAudioContentOrderListItem> | ||||||
|  |  | ||||||
|     fun totalAudioContentOrderListCount(memberId: Long, dateTime: LocalDateTime): Int |     fun totalAudioContentOrderListCount(orderType: OrderType?, memberId: Long, dateTime: LocalDateTime): Int | ||||||
|     fun getAudioContentMainOrderList( |     fun getAudioContentMainOrderList( | ||||||
|         dateTime: LocalDateTime, |         dateTime: LocalDateTime, | ||||||
|         coverImageHost: String, |         coverImageHost: String, | ||||||
| @@ -117,12 +118,41 @@ class OrderQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Orde | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun getAudioContentOrderList( |     override fun getAudioContentOrderList( | ||||||
|  |         orderType: OrderType?, | ||||||
|         dateTime: LocalDateTime, |         dateTime: LocalDateTime, | ||||||
|         coverImageHost: String, |         coverImageHost: String, | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|         offset: Long, |         offset: Long, | ||||||
|         limit: Long |         limit: Long | ||||||
|     ): List<GetAudioContentOrderListItem> { |     ): List<GetAudioContentOrderListItem> { | ||||||
|  |         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 |         return queryFactory | ||||||
|             .select( |             .select( | ||||||
|                 QGetAudioContentOrderListItem( |                 QGetAudioContentOrderListItem( | ||||||
| @@ -141,31 +171,32 @@ class OrderQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Orde | |||||||
|             ) |             ) | ||||||
|             .from(order) |             .from(order) | ||||||
|             .innerJoin(order.audioContent, audioContent) |             .innerJoin(order.audioContent, audioContent) | ||||||
|             .where( |             .where(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)) |  | ||||||
|                             ) |  | ||||||
|                     ) |  | ||||||
|             ) |  | ||||||
|             .offset(offset) |             .offset(offset) | ||||||
|             .limit(limit) |             .limit(limit) | ||||||
|             .orderBy(order.createdAt.desc()) |             .orderBy(order.createdAt.desc()) | ||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun totalAudioContentOrderListCount(memberId: Long, dateTime: LocalDateTime): Int { |     override fun totalAudioContentOrderListCount(orderType: OrderType?, memberId: Long, dateTime: LocalDateTime): Int { | ||||||
|         return queryFactory.select(order.id) |         var where = order.member.id.eq(memberId) | ||||||
|             .from(order) |  | ||||||
|             .where( |  | ||||||
|                 order.member.id.eq(memberId) |  | ||||||
|             .and(order.isActive.isTrue) |             .and(order.isActive.isTrue) | ||||||
|                     .and( |  | ||||||
|  |         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) |                     order.type.eq(OrderType.KEEP) | ||||||
|                         .or( |                         .or( | ||||||
|                             order.type.eq(OrderType.RENTAL) |                             order.type.eq(OrderType.RENTAL) | ||||||
| @@ -173,7 +204,12 @@ class OrderQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Orde | |||||||
|                                 .and(order.endDate.after(dateTime)) |                                 .and(order.endDate.after(dateTime)) | ||||||
|                         ) |                         ) | ||||||
|                 ) |                 ) | ||||||
|             ) |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory.select(order.id) | ||||||
|  |             .from(order) | ||||||
|  |             .where(where) | ||||||
|             .fetch() |             .fetch() | ||||||
|             .size |             .size | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -90,15 +90,18 @@ class OrderService( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getAudioContentOrderList( |     fun getAudioContentOrderList( | ||||||
|  |         orderType: OrderType? = null, | ||||||
|         member: Member, |         member: Member, | ||||||
|         offset: Long, |         offset: Long, | ||||||
|         limit: Long |         limit: Long | ||||||
|     ): GetAudioContentOrderListResponse { |     ): GetAudioContentOrderListResponse { | ||||||
|         val totalCount = repository.totalAudioContentOrderListCount( |         val totalCount = repository.totalAudioContentOrderListCount( | ||||||
|  |             orderType = orderType, | ||||||
|             memberId = member.id!!, |             memberId = member.id!!, | ||||||
|             dateTime = LocalDateTime.now() |             dateTime = LocalDateTime.now() | ||||||
|         ) |         ) | ||||||
|         val orderItems = repository.getAudioContentOrderList( |         val orderItems = repository.getAudioContentOrderList( | ||||||
|  |             orderType = orderType, | ||||||
|             dateTime = LocalDateTime.now(), |             dateTime = LocalDateTime.now(), | ||||||
|             coverImageHost = audioContentCoverImageHost, |             coverImageHost = audioContentCoverImageHost, | ||||||
|             memberId = member.id!!, |             memberId = member.id!!, | ||||||
|   | |||||||
| @@ -29,5 +29,7 @@ data class AudioContentPlaylistContent @QueryProjection constructor( | |||||||
|     val title: String, |     val title: String, | ||||||
|     val category: String, |     val category: String, | ||||||
|     val coverUrl: String, |     val coverUrl: String, | ||||||
|     val duration: String |     val duration: String, | ||||||
|  |     val creatorNickname: String, | ||||||
|  |     val creatorProfileUrl: String | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ class AudioContentPlaylistService( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 콘텐츠 유효성 검사 (소장으로 구매한 콘텐츠 인가?) |         // 콘텐츠 유효성 검사 (소장으로 구매한 콘텐츠 인가?) | ||||||
|         checkOrderedContent( |         validateContent( | ||||||
|             contentIdList = request.contentIdAndOrderList.map { it.contentId }, |             contentIdList = request.contentIdAndOrderList.map { it.contentId }, | ||||||
|             memberId = member.id!! |             memberId = member.id!! | ||||||
|         ) |         ) | ||||||
| @@ -44,6 +44,17 @@ class AudioContentPlaylistService( | |||||||
|         redisRepository.save(playlist) |         redisRepository.save(playlist) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun validateContent(contentIdList: List<Long>, memberId: Long) { | ||||||
|  |         if (contentIdList.isEmpty()) { | ||||||
|  |             throw SodaException("콘텐츠를 1개 이상 추가하세요") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         checkOrderedContent( | ||||||
|  |             contentIdList = contentIdList, | ||||||
|  |             memberId = memberId | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun checkOrderedContent(contentIdList: List<Long>, memberId: Long) { |     private fun checkOrderedContent(contentIdList: List<Long>, memberId: Long) { | ||||||
|         val orderedContentIdList = orderRepository.findOrderedContent(contentIdList, memberId).toSet() |         val orderedContentIdList = orderRepository.findOrderedContent(contentIdList, memberId).toSet() | ||||||
|         val orderedContentMap = contentIdList.associateWith { it in orderedContentIdList } |         val orderedContentMap = contentIdList.associateWith { it in orderedContentIdList } | ||||||
| @@ -81,7 +92,9 @@ class AudioContentPlaylistService( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getPlaylists(member: Member): GetPlaylistsResponse { |     fun getPlaylists(member: Member): GetPlaylistsResponse { | ||||||
|         val playlists = redisRepository.findByMemberId(memberId = member.id!!) |         val playlists = redisRepository | ||||||
|  |             .findByMemberId(memberId = member.id!!) | ||||||
|  |             .sortedByDescending { it.createdAt } | ||||||
|  |  | ||||||
|         return GetPlaylistsResponse( |         return GetPlaylistsResponse( | ||||||
|             totalCount = playlists.size, |             totalCount = playlists.size, | ||||||
| @@ -123,7 +136,7 @@ class AudioContentPlaylistService( | |||||||
|             throw SodaException("잘못된 요청입니다.") |             throw SodaException("잘못된 요청입니다.") | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") |         val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd") | ||||||
|         val createdDate = playlist.createdAt |         val createdDate = playlist.createdAt | ||||||
|             .atZone(ZoneId.of("UTC")) |             .atZone(ZoneId.of("UTC")) | ||||||
|             .withZoneSameInstant(ZoneId.of("Asia/Seoul")) |             .withZoneSameInstant(ZoneId.of("Asia/Seoul")) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user