시리즈 리스트 상세 API

This commit is contained in:
Klaus 2024-04-26 00:39:37 +09:00
parent b10af9d9f1
commit 6db8013e34
5 changed files with 192 additions and 1 deletions

View File

@ -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)
)
}
}

View File

@ -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()
}
}

View File

@ -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"
}
}
}

View File

@ -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()
}
}

View File

@ -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
)