From 6db8013e3402d39a2521036e043bad07dc3e79a0 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 26 Apr 2024 00:39:37 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=9C=EB=A6=AC=EC=A6=88=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=81=EC=84=B8=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/series/ContentSeriesController.kt | 13 +++ .../content/series/ContentSeriesRepository.kt | 104 ++++++++++++++++++ .../content/series/ContentSeriesService.kt | 34 +++++- .../content/series/GetSeriesDetailResponse.kt | 34 ++++++ .../GetSeriesContentMinMaxPriceResponse.kt | 8 ++ 5 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesDetailResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/series/content/GetSeriesContentMinMaxPriceResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesController.kt index f7a187b..946625e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesController.kt @@ -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) + ) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt index 5116afb..6d96e6d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt @@ -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, ContentSeriesQueryRepository @@ -16,6 +27,11 @@ interface ContentSeriesQueryRepository { offset: Long, limit: Long ): List + + fun getSeriesDetail(seriesId: Long, isAuth: Boolean, imageHost: String): GetSeriesDetailResponse? + fun getPublishedDaysOfWeek(seriesId: Long): Set + fun getKeywordList(seriesId: Long): List + 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>(emptyList()), + Expressions.constant(null) + ) + ) + .from(series) + .innerJoin(series.member, qCreator) + .innerJoin(series.genre, seriesGenre) + .where(where) + .fetchFirst() + } + + override fun getPublishedDaysOfWeek(seriesId: Long): Set { + return queryFactory + .select(series.publishedDaysOfWeek) + .from(series) + .where(series.id.eq(seriesId)) + .fetchFirst() + } + + override fun getKeywordList(seriesId: Long): List { + 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() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt index e3324ba..d6c9e27 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt @@ -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): 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" } } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesDetailResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesDetailResponse.kt new file mode 100644 index 0000000..ffbb94e --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesDetailResponse.kt @@ -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, + 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() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/content/GetSeriesContentMinMaxPriceResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/content/GetSeriesContentMinMaxPriceResponse.kt new file mode 100644 index 0000000..ff19274 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/content/GetSeriesContentMinMaxPriceResponse.kt @@ -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 +)