시리즈 리스트 API
This commit is contained in:
parent
9bc1c610ac
commit
84007e1b72
|
@ -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()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}요일"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,10 @@ enum class SeriesState {
|
||||||
PROCEEDING, SUSPEND, COMPLETE
|
PROCEEDING, SUSPEND, COMPLETE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class SeriesSortType {
|
||||||
|
NEWEST, POPULAR
|
||||||
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class Series(
|
data class Series(
|
||||||
var title: String,
|
var title: String,
|
||||||
|
|
Loading…
Reference in New Issue