시리즈 리스트 API

This commit is contained in:
Klaus 2024-04-24 23:54:39 +09:00
parent 9bc1c610ac
commit 84007e1b72
7 changed files with 326 additions and 0 deletions

View File

@ -0,0 +1,36 @@
package kr.co.vividnext.sodalive.content.series
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType
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.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/audio-content/series")
class ContentSeriesController(private val service: ContentSeriesService) {
@GetMapping
fun getSeriesList(
@RequestParam creatorId: Long,
@RequestParam("sortType", required = false) sortType: SeriesSortType = SeriesSortType.NEWEST,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getSeriesList(
creatorId = creatorId,
sortType = sortType,
member = member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
}

View File

@ -0,0 +1,81 @@
package kr.co.vividnext.sodalive.content.series
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.member.QMember.member
import org.springframework.data.jpa.repository.JpaRepository
interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository
interface ContentSeriesQueryRepository {
fun getSeriesTotalCount(creatorId: Long, isAuth: Boolean): Int
fun getSeriesRawItemList(
imageHost: String,
creatorId: Long,
isAuth: Boolean,
offset: Long,
limit: Long
): List<GetSeriesListRawItem>
}
class ContentSeriesQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : ContentSeriesQueryRepository {
override fun getSeriesTotalCount(creatorId: Long, isAuth: Boolean): Int {
var where = series.member.id.eq(creatorId)
.and(series.isActive.isTrue)
if (!isAuth) {
where = where.and(series.isAdult.isFalse)
}
return queryFactory
.select(series.id)
.from(series)
.where(where)
.fetch()
.size
}
override fun getSeriesRawItemList(
imageHost: String,
creatorId: Long,
isAuth: Boolean,
offset: Long,
limit: Long
): List<GetSeriesListRawItem> {
var where = series.member.id.eq(creatorId)
.and(series.isActive.isTrue)
if (!isAuth) {
where = where.and(series.isAdult.isFalse)
}
return queryFactory
.select(
QGetSeriesListRawItem(
series.id,
series.title,
series.coverImage.coalesce("profile/default-profile.png")
.prepend("/")
.prepend(imageHost),
series.publishedDaysOfWeek,
series.state,
series.genre.genre,
series.isAdult,
series.member.id,
series.member.nickname,
series.member.profileImage.coalesce("profile/default-profile.png")
.prepend("/")
.prepend(imageHost)
)
)
.from(series)
.innerJoin(series.member, member)
.innerJoin(series.genre, seriesGenre)
.where(where)
.fetch()
}
}

View File

@ -0,0 +1,59 @@
package kr.co.vividnext.sodalive.content.series
import kr.co.vividnext.sodalive.content.series.content.ContentSeriesContentRepository
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType
import kr.co.vividnext.sodalive.member.Member
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import java.time.LocalDateTime
@Service
class ContentSeriesService(
private val repository: ContentSeriesRepository,
private val seriesContentRepository: ContentSeriesContentRepository,
@Value("\${cloud.aws.cloud-front.host}")
private val coverImageHost: String
) {
fun getSeriesList(
creatorId: Long,
member: Member,
sortType: SeriesSortType = SeriesSortType.NEWEST,
offset: Long = 0,
limit: Long = 10
): GetSeriesListResponse {
val totalCount = repository.getSeriesTotalCount(creatorId = creatorId, isAuth = member.auth != null)
val rawItems = repository.getSeriesRawItemList(
imageHost = coverImageHost,
creatorId = creatorId,
isAuth = member.auth != null,
offset = offset,
limit = limit
)
val items = rawItems
.map { it.toSeriesListItem() }
.map {
it.numberOfContent = seriesContentRepository.getContentCount(
seriesId = it.seriesId,
isAdult = member.auth == null
)
it
}
.map {
val nowDateTime = LocalDateTime.now()
it.isNew = seriesContentRepository.isNewContent(
seriesId = it.seriesId,
isAdult = member.auth == null,
fromDate = nowDateTime.minusDays(7),
nowDate = nowDateTime
)
it
}
return GetSeriesListResponse(totalCount, items)
}
}

View File

@ -0,0 +1,57 @@
package kr.co.vividnext.sodalive.content.series
import com.querydsl.core.annotations.QueryProjection
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
data class GetSeriesListRawItem @QueryProjection constructor(
val seriesId: Long,
val title: String,
val coverImage: String,
val publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>,
val state: SeriesState,
val genre: String,
val isAdult: Boolean,
val creatorId: Long,
val creatorNickname: String,
val creatorProfileImage: String
) {
fun toSeriesListItem(): GetSeriesListResponse.SeriesListItem {
return GetSeriesListResponse.SeriesListItem(
seriesId = seriesId,
title = title,
coverImage = coverImage,
publishedDaysOfWeek = publishedDaysOfWeekText(),
isComplete = state == SeriesState.COMPLETE,
creator = GetSeriesListResponse.SeriesListItemCreator(
creatorId = creatorId,
nickname = creatorNickname,
profileImage = creatorProfileImage
)
)
}
private fun publishedDaysOfWeekText(): String {
val dayOfWeekText = publishedDaysOfWeek.toList().sortedBy { it.ordinal }
.map {
when (it) {
SeriesPublishedDaysOfWeek.SUN -> ""
SeriesPublishedDaysOfWeek.MON -> ""
SeriesPublishedDaysOfWeek.TUE -> ""
SeriesPublishedDaysOfWeek.WED -> ""
SeriesPublishedDaysOfWeek.THU -> ""
SeriesPublishedDaysOfWeek.FRI -> ""
SeriesPublishedDaysOfWeek.SAT -> ""
SeriesPublishedDaysOfWeek.RANDOM -> "랜덤"
}
}
.joinToString(", ") { it }
return if (publishedDaysOfWeek.contains(SeriesPublishedDaysOfWeek.RANDOM)) {
dayOfWeekText
} else {
"매주 ${dayOfWeekText}요일"
}
}
}

View File

@ -0,0 +1,24 @@
package kr.co.vividnext.sodalive.content.series
data class GetSeriesListResponse(
val totalCount: Int,
val items: List<SeriesListItem>
) {
data class SeriesListItem(
val seriesId: Long,
val title: String,
val coverImage: String,
val publishedDaysOfWeek: String,
val isComplete: Boolean = false,
val creator: SeriesListItemCreator,
var numberOfContent: Int = 0,
var isNew: Boolean = false,
var isPopular: Boolean = false
)
data class SeriesListItemCreator(
val creatorId: Long,
val nickname: String,
val profileImage: String
)
}

View File

@ -0,0 +1,65 @@
package kr.co.vividnext.sodalive.content.series.content
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
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.SeriesContent
import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalDateTime
interface ContentSeriesContentRepository : JpaRepository<SeriesContent, Long>, ContentSeriesContentQueryRepository
interface ContentSeriesContentQueryRepository {
fun getContentCount(seriesId: Long, isAdult: Boolean): Int
fun isNewContent(seriesId: Long, isAdult: Boolean, fromDate: LocalDateTime, nowDate: LocalDateTime): Boolean
}
class ContentSeriesContentQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : ContentSeriesContentQueryRepository {
override fun getContentCount(seriesId: Long, isAdult: Boolean): Int {
var where = seriesContent.series.id.eq(seriesId)
.and(seriesContent.content.isActive.isTrue)
if (!isAdult) {
where = where.and(seriesContent.content.isAdult.isFalse)
}
return queryFactory
.select(seriesContent.id)
.from(seriesContent)
.innerJoin(seriesContent.series, series)
.innerJoin(seriesContent.content, audioContent)
.where(where)
.fetch()
.size
}
override fun isNewContent(
seriesId: Long,
isAdult: Boolean,
fromDate: LocalDateTime,
nowDate: LocalDateTime
): Boolean {
var where = seriesContent.series.id.eq(seriesId)
.and(seriesContent.content.isActive.isTrue)
.and(seriesContent.content.releaseDate.after(fromDate))
.and(seriesContent.content.releaseDate.before(nowDate))
if (!isAdult) {
where = where.and(seriesContent.content.isAdult.isFalse)
}
val itemCount = queryFactory
.select(seriesContent.id)
.from(seriesContent)
.innerJoin(seriesContent.series, series)
.innerJoin(seriesContent.content, audioContent)
.where(where)
.fetch()
.size
return itemCount > 0
}
}

View File

@ -25,6 +25,10 @@ enum class SeriesState {
PROCEEDING, SUSPEND, COMPLETE
}
enum class SeriesSortType {
NEWEST, POPULAR
}
@Entity
data class Series(
var title: String,