시리즈 리스트 상세 API
This commit is contained in:
		| @@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.member.Member | ||||
| import org.springframework.data.domain.Pageable | ||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||
| import org.springframework.web.bind.annotation.GetMapping | ||||
| import org.springframework.web.bind.annotation.PathVariable | ||||
| import org.springframework.web.bind.annotation.RequestMapping | ||||
| import org.springframework.web.bind.annotation.RequestParam | ||||
| import org.springframework.web.bind.annotation.RestController | ||||
| @@ -33,4 +34,16 @@ class ContentSeriesController(private val service: ContentSeriesService) { | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/{id}") | ||||
|     fun getSeriesDetail( | ||||
|         @PathVariable id: Long, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|  | ||||
|         ApiResponse.ok( | ||||
|             service.getSeriesDetail(seriesId = id, member = member) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,20 @@ | ||||
| package kr.co.vividnext.sodalive.content.series | ||||
|  | ||||
| import com.querydsl.core.types.dsl.Expressions | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre | ||||
| import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||
| import kr.co.vividnext.sodalive.content.hashtag.QHashTag.hashTag | ||||
| import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentMinMaxPriceResponse | ||||
| import kr.co.vividnext.sodalive.content.series.content.QGetSeriesContentMinMaxPriceResponse | ||||
| 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.creator.admin.content.series.SeriesPublishedDaysOfWeek | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword | ||||
| import kr.co.vividnext.sodalive.member.QMember | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
| import java.time.LocalDateTime | ||||
|  | ||||
| interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository | ||||
|  | ||||
| @@ -16,6 +27,11 @@ interface ContentSeriesQueryRepository { | ||||
|         offset: Long, | ||||
|         limit: Long | ||||
|     ): List<Series> | ||||
|  | ||||
|     fun getSeriesDetail(seriesId: Long, isAuth: Boolean, imageHost: String): GetSeriesDetailResponse? | ||||
|     fun getPublishedDaysOfWeek(seriesId: Long): Set<SeriesPublishedDaysOfWeek> | ||||
|     fun getKeywordList(seriesId: Long): List<String> | ||||
|     fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse | ||||
| } | ||||
|  | ||||
| class ContentSeriesQueryRepositoryImpl( | ||||
| @@ -58,4 +74,92 @@ class ContentSeriesQueryRepositoryImpl( | ||||
|             .limit(limit) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun getSeriesDetail(seriesId: Long, isAuth: Boolean, imageHost: String): GetSeriesDetailResponse? { | ||||
|         val qCreator = QMember.member | ||||
|         var where = series.id.eq(seriesId) | ||||
|             .and(series.isActive.isTrue) | ||||
|  | ||||
|         if (!isAuth) { | ||||
|             where = where.and(series.isAdult.isFalse) | ||||
|         } | ||||
|  | ||||
|         val formattedDate = Expressions.stringTemplate( | ||||
|             "DATE_FORMAT({0}, {1})", | ||||
|             Expressions.dateTimeTemplate( | ||||
|                 LocalDateTime::class.java, | ||||
|                 "CONVERT_TZ({0},{1},{2})", | ||||
|                 series.createdAt, | ||||
|                 "UTC", | ||||
|                 "Asia/Seoul" | ||||
|             ), | ||||
|             "%Y-%m-%d" | ||||
|         ) | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetSeriesDetailResponse( | ||||
|                     series.id, | ||||
|                     series.title, | ||||
|                     series.coverImage.prepend("/").prepend(imageHost), | ||||
|                     series.introduction, | ||||
|                     seriesGenre.genre, | ||||
|                     series.isAdult, | ||||
|                     series.writer, | ||||
|                     series.studio, | ||||
|                     formattedDate, | ||||
|                     QGetSeriesDetailResponse_GetSeriesDetailCreator( | ||||
|                         qCreator.id, | ||||
|                         qCreator.nickname, | ||||
|                         qCreator.profileImage.prepend("/").prepend(imageHost), | ||||
|                         Expressions.constant(false) | ||||
|                     ), | ||||
|                     Expressions.constant(0), | ||||
|                     Expressions.constant(0), | ||||
|                     Expressions.constant(15), | ||||
|                     Expressions.constant(0), | ||||
|                     Expressions.constant(0), | ||||
|                     Expressions.constant<List<String>>(emptyList()), | ||||
|                     Expressions.constant(null) | ||||
|                 ) | ||||
|             ) | ||||
|             .from(series) | ||||
|             .innerJoin(series.member, qCreator) | ||||
|             .innerJoin(series.genre, seriesGenre) | ||||
|             .where(where) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     override fun getPublishedDaysOfWeek(seriesId: Long): Set<SeriesPublishedDaysOfWeek> { | ||||
|         return queryFactory | ||||
|             .select(series.publishedDaysOfWeek) | ||||
|             .from(series) | ||||
|             .where(series.id.eq(seriesId)) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     override fun getKeywordList(seriesId: Long): List<String> { | ||||
|         return queryFactory | ||||
|             .select(hashTag.tag) | ||||
|             .from(series) | ||||
|             .innerJoin(series.keywordList, seriesKeyword).fetchJoin() | ||||
|             .innerJoin(seriesKeyword.keyword, hashTag) | ||||
|             .where(series.id.eq(seriesId)) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse { | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetSeriesContentMinMaxPriceResponse( | ||||
|                     audioContent.price.min(), | ||||
|                     audioContent.price.max() | ||||
|                 ) | ||||
|             ) | ||||
|             .from(series) | ||||
|             .innerJoin(series.contentList, seriesContent).fetchJoin() | ||||
|             .innerJoin(seriesContent.content, audioContent) | ||||
|             .where(series.id.eq(seriesId)) | ||||
|             .fetchFirst() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| package kr.co.vividnext.sodalive.content.series | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.content.series.content.ContentSeriesContentRepository | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState | ||||
| import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import org.springframework.beans.factory.annotation.Value | ||||
| import org.springframework.stereotype.Service | ||||
| @@ -12,6 +14,7 @@ import java.time.LocalDateTime | ||||
| @Service | ||||
| class ContentSeriesService( | ||||
|     private val repository: ContentSeriesRepository, | ||||
|     private val explorerQueryRepository: ExplorerQueryRepository, | ||||
|     private val seriesContentRepository: ContentSeriesContentRepository, | ||||
|  | ||||
|     @Value("\${cloud.aws.cloud-front.host}") | ||||
| @@ -72,6 +75,35 @@ class ContentSeriesService( | ||||
|         return GetSeriesListResponse(totalCount, items) | ||||
|     } | ||||
|  | ||||
|     fun getSeriesDetail(seriesId: Long, member: Member): GetSeriesDetailResponse { | ||||
|         val seriesDetail = repository.getSeriesDetail( | ||||
|             seriesId = seriesId, | ||||
|             isAuth = member.auth != null, | ||||
|             imageHost = coverImageHost | ||||
|         ) ?: throw SodaException("잘못된 시리즈 입니다.\n다시 시도해 주세요") | ||||
|  | ||||
|         seriesDetail.creator.isFollow = explorerQueryRepository.isFollow( | ||||
|             creatorId = seriesDetail.creator.creatorId, | ||||
|             memberId = member.id!! | ||||
|         ) | ||||
|  | ||||
|         seriesDetail.publishedDaysOfWeek = publishedDaysOfWeekText( | ||||
|             repository.getPublishedDaysOfWeek(seriesId = seriesId) | ||||
|         ) | ||||
|  | ||||
|         seriesDetail.keywordList = repository.getKeywordList(seriesId = seriesId) | ||||
|             .filter { it.isNotBlank() } | ||||
|  | ||||
|         val minMaxPrice = repository.getSeriesContentMinMaxPrice(seriesId = seriesId) | ||||
|         seriesDetail.minPrice = minMaxPrice.minPrice | ||||
|         seriesDetail.maxPrice = minMaxPrice.maxPrice | ||||
|         seriesDetail.rentalMinPrice = (minMaxPrice.minPrice * 0.7).toInt() | ||||
|         seriesDetail.rentalMaxPrice = (minMaxPrice.maxPrice * 0.7).toInt() | ||||
|  | ||||
|         if (!seriesDetail.validate()) throw SodaException("잘못된 시리즈 입니다.\n다시 시도해 주세요") | ||||
|         return seriesDetail | ||||
|     } | ||||
|  | ||||
|     private fun publishedDaysOfWeekText(publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>): String { | ||||
|         val dayOfWeekText = publishedDaysOfWeek.toList().sortedBy { it.ordinal } | ||||
|             .map { | ||||
| @@ -91,7 +123,7 @@ class ContentSeriesService( | ||||
|         return if (publishedDaysOfWeek.contains(SeriesPublishedDaysOfWeek.RANDOM)) { | ||||
|             dayOfWeekText | ||||
|         } else { | ||||
|             "매주 ${dayOfWeekText}요일" | ||||
|             "매주 $dayOfWeekText" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,34 @@ | ||||
| package kr.co.vividnext.sodalive.content.series | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetSeriesDetailResponse @QueryProjection constructor( | ||||
|     val seriesId: Long, | ||||
|     val title: String, | ||||
|     val coverImage: String, | ||||
|     val introduction: String, | ||||
|     val genre: String, | ||||
|     val isAdult: Boolean, | ||||
|     val writer: String?, | ||||
|     val studio: String?, | ||||
|     val publishedDate: String, | ||||
|     val creator: GetSeriesDetailCreator, | ||||
|     var rentalMinPrice: Int, | ||||
|     var rentalMaxPrice: Int, | ||||
|     val rentalPeriod: Int, | ||||
|     var minPrice: Int, | ||||
|     var maxPrice: Int, | ||||
|     var keywordList: List<String>, | ||||
|     var publishedDaysOfWeek: String? | ||||
| ) { | ||||
|     data class GetSeriesDetailCreator @QueryProjection constructor( | ||||
|         val creatorId: Long, | ||||
|         val nickname: String, | ||||
|         val coverImage: String, | ||||
|         var isFollow: Boolean | ||||
|     ) | ||||
|  | ||||
|     fun validate(): Boolean { | ||||
|         return keywordList.isNotEmpty() && !publishedDaysOfWeek.isNullOrBlank() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.content.series.content | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetSeriesContentMinMaxPriceResponse @QueryProjection constructor( | ||||
|     val minPrice: Int, | ||||
|     val maxPrice: Int | ||||
| ) | ||||
		Reference in New Issue
	
	Block a user