시리즈 목록 조회 쿼리 최적화
This commit is contained in:
@@ -12,7 +12,6 @@ import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
|
|||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
|
||||||
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
|
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
|
||||||
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
|
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
|
||||||
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationRepository
|
|
||||||
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeService
|
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeService
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
import kr.co.vividnext.sodalive.event.GetEventResponse
|
import kr.co.vividnext.sodalive.event.GetEventResponse
|
||||||
@@ -50,7 +49,6 @@ class HomeService(
|
|||||||
private val explorerQueryRepository: ExplorerQueryRepository,
|
private val explorerQueryRepository: ExplorerQueryRepository,
|
||||||
|
|
||||||
private val aiCharacterTranslationRepository: AiCharacterTranslationRepository,
|
private val aiCharacterTranslationRepository: AiCharacterTranslationRepository,
|
||||||
private val seriesTranslationRepository: SeriesTranslationRepository,
|
|
||||||
|
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
|
||||||
@@ -139,13 +137,12 @@ class HomeService(
|
|||||||
val auditionList = auditionService.getInProgressAuditionList(isAdult = isAdult)
|
val auditionList = auditionService.getInProgressAuditionList(isAdult = isAdult)
|
||||||
|
|
||||||
// 요일별 시리즈
|
// 요일별 시리즈
|
||||||
val dayOfWeekSeriesList = seriesService.getDayOfWeekSeriesList(
|
val translatedDayOfWeekSeriesList = seriesService.getDayOfWeekSeriesList(
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
dayOfWeek = getDayOfWeekByTimezone(timezone)
|
dayOfWeek = getDayOfWeekByTimezone(timezone)
|
||||||
)
|
)
|
||||||
val translatedDayOfWeekSeriesList = getTranslatedSeriesList(seriesList = dayOfWeekSeriesList)
|
|
||||||
|
|
||||||
// 인기 캐릭터 조회
|
// 인기 캐릭터 조회
|
||||||
val translatedPopularCharacters = getTranslatedAiCharacterList(aiCharacterList = characterService.getPopularCharacters())
|
val translatedPopularCharacters = getTranslatedAiCharacterList(aiCharacterList = characterService.getPopularCharacters())
|
||||||
@@ -265,14 +262,12 @@ class HomeService(
|
|||||||
val memberId = member?.id
|
val memberId = member?.id
|
||||||
val isAdult = member?.auth != null && isAdultContentVisible
|
val isAdult = member?.auth != null && isAdultContentVisible
|
||||||
|
|
||||||
val dayOfWeekSeriesList = seriesService.getDayOfWeekSeriesList(
|
return seriesService.getDayOfWeekSeriesList(
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
dayOfWeek = dayOfWeek
|
dayOfWeek = dayOfWeek
|
||||||
)
|
)
|
||||||
|
|
||||||
return getTranslatedSeriesList(seriesList = dayOfWeekSeriesList)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getContentRankingBySort(
|
fun getContentRankingBySort(
|
||||||
@@ -479,44 +474,6 @@ class HomeService(
|
|||||||
return result.take(targetSize).shuffled()
|
return result.take(targetSize).shuffled()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 시리즈 리스트의 제목을 현재 언어(locale)에 맞춰 일괄 번역한다.
|
|
||||||
*
|
|
||||||
* 처리 절차:
|
|
||||||
* - 입력된 시리즈들의 seriesId 집합을 만들고, 요청 언어 코드(langContext.lang.code)로
|
|
||||||
* seriesTranslationRepository에서 번역 데이터를 한 번에 조회한다.
|
|
||||||
* - 각 항목에 대해 번역된 제목이 존재하고 비어있지 않으면 title만 번역 값으로 교체한다.
|
|
||||||
* - 번역이 없거나 공백이면 원본 항목을 그대로 반환한다.
|
|
||||||
*
|
|
||||||
* 성능:
|
|
||||||
* - N건의 항목을 1회의 조회로 해결하기 위해 IN 쿼리를 사용한다.
|
|
||||||
*
|
|
||||||
* @param seriesList 번역 대상 SeriesListItem 목록
|
|
||||||
* @return 제목이 가능한 항목은 번역된 목록(불변 사본), 그 외는 원본 항목 유지
|
|
||||||
*/
|
|
||||||
private fun getTranslatedSeriesList(
|
|
||||||
seriesList: List<GetSeriesListResponse.SeriesListItem>
|
|
||||||
): List<GetSeriesListResponse.SeriesListItem> {
|
|
||||||
val seriesIds = seriesList.map { it.seriesId }
|
|
||||||
|
|
||||||
return if (seriesIds.isNotEmpty()) {
|
|
||||||
val translations = seriesTranslationRepository
|
|
||||||
.findBySeriesIdInAndLocale(seriesIds = seriesIds, locale = langContext.lang.code)
|
|
||||||
.associateBy { it.seriesId }
|
|
||||||
|
|
||||||
seriesList.map { item ->
|
|
||||||
val translatedTitle = translations[item.seriesId]?.renderedPayload?.title
|
|
||||||
if (translatedTitle.isNullOrBlank()) {
|
|
||||||
item
|
|
||||||
} else {
|
|
||||||
item.copy(title = translatedTitle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
seriesList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI 캐릭터 리스트의 이름/설명을 현재 언어(locale)에 맞춰 일괄 번역한다.
|
* AI 캐릭터 리스트의 이름/설명을 현재 언어(locale)에 맞춰 일괄 번역한다.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ interface ContentSeriesQueryRepository {
|
|||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
isOriginal: Boolean,
|
isOriginal: Boolean,
|
||||||
isCompleted: Boolean
|
isCompleted: Boolean,
|
||||||
|
memberId: Long? = null
|
||||||
): Int
|
): Int
|
||||||
|
|
||||||
fun getSeriesList(
|
fun getSeriesListV2(
|
||||||
imageHost: String,
|
imageHost: String,
|
||||||
creatorId: Long?,
|
creatorId: Long?,
|
||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
@@ -43,28 +44,41 @@ interface ContentSeriesQueryRepository {
|
|||||||
isCompleted: Boolean,
|
isCompleted: Boolean,
|
||||||
orderByRandom: Boolean,
|
orderByRandom: Boolean,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
): List<Series>
|
locale: String,
|
||||||
|
memberId: Long? = null
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem>
|
||||||
|
|
||||||
fun getSeriesByGenreTotalCount(
|
fun getSeriesByGenreTotalCount(
|
||||||
genreId: Long,
|
genreId: Long,
|
||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
contentType: ContentType
|
contentType: ContentType,
|
||||||
|
memberId: Long? = null
|
||||||
): Int
|
): Int
|
||||||
|
|
||||||
fun getSeriesByGenreList(
|
fun getSeriesByGenreListV2(
|
||||||
imageHost: String,
|
imageHost: String,
|
||||||
genreId: Long,
|
genreId: Long,
|
||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
): List<Series>
|
locale: String,
|
||||||
|
memberId: Long? = null
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem>
|
||||||
|
|
||||||
fun getSeriesDetail(seriesId: Long, isAuth: Boolean, contentType: ContentType): Series?
|
fun getSeriesDetail(seriesId: Long, isAuth: Boolean, contentType: ContentType): Series?
|
||||||
fun getKeywordList(seriesId: Long): List<String>
|
fun getKeywordList(seriesId: Long): List<String>
|
||||||
fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse
|
fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse
|
||||||
fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series>
|
fun getRecommendSeriesListV2(
|
||||||
|
imageHost: String,
|
||||||
|
isAuth: Boolean,
|
||||||
|
contentType: ContentType,
|
||||||
|
limit: Long,
|
||||||
|
locale: String,
|
||||||
|
memberId: Long? = null
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem>
|
||||||
|
|
||||||
fun getOriginalAudioDramaList(
|
fun getOriginalAudioDramaList(
|
||||||
imageHost: String,
|
imageHost: String,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
@@ -76,27 +90,57 @@ interface ContentSeriesQueryRepository {
|
|||||||
|
|
||||||
fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int
|
fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int
|
||||||
fun getGenreList(isAdult: Boolean, memberId: Long, contentType: ContentType): List<GetSeriesGenreListResponse>
|
fun getGenreList(isAdult: Boolean, memberId: Long, contentType: ContentType): List<GetSeriesGenreListResponse>
|
||||||
fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean, contentType: ContentType): List<Series>
|
fun findByCurationIdV2(
|
||||||
fun getDayOfWeekSeriesList(
|
imageHost: String,
|
||||||
|
curationId: Long,
|
||||||
|
memberId: Long,
|
||||||
|
isAdult: Boolean,
|
||||||
|
contentType: ContentType,
|
||||||
|
locale: String
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem>
|
||||||
|
|
||||||
|
fun getDayOfWeekSeriesListV2(
|
||||||
|
imageHost: String,
|
||||||
dayOfWeek: SeriesPublishedDaysOfWeek,
|
dayOfWeek: SeriesPublishedDaysOfWeek,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
): List<Series>
|
locale: String,
|
||||||
|
memberId: Long? = null
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem>
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContentSeriesQueryRepositoryImpl(
|
class ContentSeriesQueryRepositoryImpl(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : ContentSeriesQueryRepository {
|
) : ContentSeriesQueryRepository {
|
||||||
|
private fun parsePublishedDaysOfWeek(raw: String?): Set<SeriesPublishedDaysOfWeek> {
|
||||||
|
if (raw.isNullOrBlank()) {
|
||||||
|
return emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.split(",")
|
||||||
|
.mapNotNull { value ->
|
||||||
|
val trimmed = value.trim()
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
runCatching { SeriesPublishedDaysOfWeek.valueOf(trimmed) }.getOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSeriesTotalCount(
|
override fun getSeriesTotalCount(
|
||||||
creatorId: Long?,
|
creatorId: Long?,
|
||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
isOriginal: Boolean,
|
isOriginal: Boolean,
|
||||||
isCompleted: Boolean
|
isCompleted: Boolean,
|
||||||
|
memberId: Long?
|
||||||
): Int {
|
): Int {
|
||||||
var where = series.isActive.isTrue
|
var where = series.isActive.isTrue
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
if (creatorId != null) {
|
if (creatorId != null) {
|
||||||
where = where.and(series.member.id.eq(creatorId))
|
where = where.and(series.member.id.eq(creatorId))
|
||||||
@@ -111,7 +155,9 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where
|
||||||
|
.and(series.isAdult.isFalse)
|
||||||
|
.and(audioContent.isAdult.isFalse)
|
||||||
} else {
|
} else {
|
||||||
if (contentType != ContentType.ALL) {
|
if (contentType != ContentType.ALL) {
|
||||||
where = where.and(
|
where = where.and(
|
||||||
@@ -128,16 +174,31 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
if (memberId != null) {
|
||||||
.select(series.id)
|
val blockedSubquery = queryFactory
|
||||||
.from(series)
|
.select(blockMember.id)
|
||||||
.innerJoin(series.member, member)
|
.from(blockMember)
|
||||||
.where(where)
|
.where(
|
||||||
.fetch()
|
blockMember.member.id.eq(series.member.id),
|
||||||
.size
|
blockMember.blockedMember.id.eq(memberId),
|
||||||
|
blockMember.isActive.isTrue
|
||||||
|
)
|
||||||
|
where = where.and(blockedSubquery.exists().not())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSeriesList(
|
return (
|
||||||
|
queryFactory
|
||||||
|
.select(series.id.countDistinct())
|
||||||
|
.from(series)
|
||||||
|
.innerJoin(series.member, member)
|
||||||
|
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
|
||||||
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
|
.where(where)
|
||||||
|
.fetchOne() ?: 0L
|
||||||
|
).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSeriesListV2(
|
||||||
imageHost: String,
|
imageHost: String,
|
||||||
creatorId: Long?,
|
creatorId: Long?,
|
||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
@@ -146,9 +207,12 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
isCompleted: Boolean,
|
isCompleted: Boolean,
|
||||||
orderByRandom: Boolean,
|
orderByRandom: Boolean,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
): List<Series> {
|
locale: String,
|
||||||
|
memberId: Long?
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
var where = series.isActive.isTrue
|
var where = series.isActive.isTrue
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
if (creatorId != null) {
|
if (creatorId != null) {
|
||||||
where = where.and(series.member.id.eq(creatorId))
|
where = where.and(series.member.id.eq(creatorId))
|
||||||
@@ -162,54 +226,134 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where
|
||||||
|
.and(series.isAdult.isFalse)
|
||||||
|
.and(audioContent.isAdult.isFalse)
|
||||||
} else {
|
} else {
|
||||||
if (contentType != ContentType.ALL) {
|
if (contentType != ContentType.ALL) {
|
||||||
where = where.and(
|
where = where.and(
|
||||||
series.member.isNull.or(
|
series.member.isNull.or(
|
||||||
series.member.auth.gender.eq(
|
series.member.auth.gender.eq(
|
||||||
if (contentType == ContentType.MALE) {
|
if (contentType == ContentType.MALE) 0 else 1
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 차단 필터
|
||||||
|
if (memberId != null) {
|
||||||
|
val blockedSubquery = queryFactory
|
||||||
|
.select(blockMember.id)
|
||||||
|
.from(blockMember)
|
||||||
|
.where(
|
||||||
|
blockMember.member.id.eq(series.member.id),
|
||||||
|
blockMember.blockedMember.id.eq(memberId),
|
||||||
|
blockMember.isActive.isTrue
|
||||||
|
)
|
||||||
|
where = where.and(blockedSubquery.exists().not())
|
||||||
|
}
|
||||||
|
|
||||||
|
val latestReleaseDate = audioContent.releaseDate.max()
|
||||||
|
|
||||||
val orderBy = if (orderByRandom) {
|
val orderBy = if (orderByRandom) {
|
||||||
listOf(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
listOf(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||||
} else if (creatorId != null) {
|
} else if (creatorId != null) {
|
||||||
listOf(series.orders.asc(), series.createdAt.asc())
|
listOf(series.orders.asc(), series.createdAt.asc())
|
||||||
} else {
|
} else {
|
||||||
listOf(audioContent.releaseDate.max().desc(), series.createdAt.asc())
|
listOf(latestReleaseDate.desc(), series.createdAt.asc())
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
val now = LocalDateTime.now()
|
||||||
.selectFrom(series)
|
val sevenDaysAgo = now.minusDays(7)
|
||||||
|
|
||||||
|
val contentCount = seriesContent.id.countDistinct()
|
||||||
|
val isNewCase = Expressions.numberTemplate(
|
||||||
|
Int::class.java,
|
||||||
|
"case when {0} then 1 else 0 end",
|
||||||
|
audioContent.releaseDate.between(sevenDaysAgo, now)
|
||||||
|
)
|
||||||
|
val isNewFlag = isNewCase.max()
|
||||||
|
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
|
||||||
|
val publishedDaysConcat = Expressions.stringTemplate(
|
||||||
|
"group_concat(distinct {0} order by {0} separator ',')",
|
||||||
|
seriesPublishedDay
|
||||||
|
)
|
||||||
|
|
||||||
|
val results = queryFactory
|
||||||
|
.select(
|
||||||
|
series.id,
|
||||||
|
series.title,
|
||||||
|
seriesTranslation.renderedPayload,
|
||||||
|
series.coverImage,
|
||||||
|
series.state,
|
||||||
|
member.id,
|
||||||
|
member.nickname,
|
||||||
|
member.profileImage,
|
||||||
|
contentCount,
|
||||||
|
isNewFlag,
|
||||||
|
publishedDaysConcat
|
||||||
|
)
|
||||||
|
.from(series)
|
||||||
.innerJoin(series.member, member)
|
.innerJoin(series.member, member)
|
||||||
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
|
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
|
||||||
.innerJoin(seriesContent.content, audioContent)
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
|
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
|
||||||
|
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||||
.where(where)
|
.where(where)
|
||||||
.groupBy(series.id)
|
.groupBy(series.id)
|
||||||
|
.having(contentCount.gt(0))
|
||||||
.orderBy(*orderBy.toTypedArray())
|
.orderBy(*orderBy.toTypedArray())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|
||||||
|
return results.map { row ->
|
||||||
|
val sId = row.get(series.id)!!
|
||||||
|
val originTitle = row.get(series.title)!!
|
||||||
|
val payload = row.get(seriesTranslation.renderedPayload)
|
||||||
|
val translatedTitle = payload?.title
|
||||||
|
val coverImg = row.get(series.coverImage)
|
||||||
|
val state = row.get(series.state)
|
||||||
|
val cId = row.get(member.id)!!
|
||||||
|
val nick = row.get(member.nickname)!!
|
||||||
|
val profImg = row.get(member.profileImage)
|
||||||
|
val nContent = row.get(contentCount) ?: 0L
|
||||||
|
val isN = (row.get(isNewFlag) ?: 0) > 0
|
||||||
|
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat))
|
||||||
|
|
||||||
|
GetSeriesListResponse.SeriesListItem(
|
||||||
|
seriesId = sId,
|
||||||
|
title = if (translatedTitle.isNullOrBlank()) originTitle else translatedTitle,
|
||||||
|
coverImage = "$imageHost/$coverImg",
|
||||||
|
publishedDaysOfWeek = "", // Service layer will fill this
|
||||||
|
isComplete = state == SeriesState.COMPLETE,
|
||||||
|
creator = GetSeriesListResponse.SeriesListItemCreator(
|
||||||
|
creatorId = cId,
|
||||||
|
nickname = nick,
|
||||||
|
profileImage = "$imageHost/$profImg"
|
||||||
|
),
|
||||||
|
numberOfContent = nContent.toInt(),
|
||||||
|
isNew = isN,
|
||||||
|
rawPublishedDaysOfWeek = rawDays
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSeriesByGenreTotalCount(
|
override fun getSeriesByGenreTotalCount(
|
||||||
genreId: Long,
|
genreId: Long,
|
||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
contentType: ContentType
|
contentType: ContentType,
|
||||||
|
memberId: Long?
|
||||||
): Int {
|
): Int {
|
||||||
var where = series.isActive.isTrue
|
var where = series.isActive.isTrue
|
||||||
.and(series.genre.id.eq(genreId))
|
.and(series.genre.id.eq(genreId))
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where
|
||||||
|
.and(series.isAdult.isFalse)
|
||||||
|
.and(audioContent.isAdult.isFalse)
|
||||||
} else {
|
} else {
|
||||||
if (contentType != ContentType.ALL) {
|
if (contentType != ContentType.ALL) {
|
||||||
where = where.and(
|
where = where.and(
|
||||||
@@ -226,29 +370,44 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
if (memberId != null) {
|
||||||
.select(series.id)
|
val blockedSubquery = queryFactory
|
||||||
|
.select(blockMember.id)
|
||||||
|
.from(blockMember)
|
||||||
|
.where(
|
||||||
|
blockMember.member.id.eq(series.member.id),
|
||||||
|
blockMember.blockedMember.id.eq(memberId),
|
||||||
|
blockMember.isActive.isTrue
|
||||||
|
)
|
||||||
|
where = where.and(blockedSubquery.exists().not())
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
queryFactory
|
||||||
|
.select(series.id.countDistinct())
|
||||||
.from(seriesContent)
|
.from(seriesContent)
|
||||||
.innerJoin(seriesContent.series, series)
|
.innerJoin(seriesContent.series, series)
|
||||||
.innerJoin(seriesContent.content, audioContent)
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
.innerJoin(series.member, member)
|
.innerJoin(series.member, member)
|
||||||
.innerJoin(series.genre, seriesGenre)
|
.innerJoin(series.genre, seriesGenre)
|
||||||
.where(where)
|
.where(where)
|
||||||
.groupBy(series.id)
|
.fetchOne() ?: 0L
|
||||||
.fetch()
|
).toInt()
|
||||||
.size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSeriesByGenreList(
|
override fun getSeriesByGenreListV2(
|
||||||
imageHost: String,
|
imageHost: String,
|
||||||
genreId: Long,
|
genreId: Long,
|
||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
): List<Series> {
|
locale: String,
|
||||||
|
memberId: Long?
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
var where = series.isActive.isTrue
|
var where = series.isActive.isTrue
|
||||||
.and(series.genre.id.eq(genreId))
|
.and(series.genre.id.eq(genreId))
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where.and(series.isAdult.isFalse)
|
||||||
@@ -257,30 +416,106 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
where = where.and(
|
where = where.and(
|
||||||
series.member.isNull.or(
|
series.member.isNull.or(
|
||||||
series.member.auth.gender.eq(
|
series.member.auth.gender.eq(
|
||||||
if (contentType == ContentType.MALE) {
|
if (contentType == ContentType.MALE) 0 else 1
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
if (!isAuth) {
|
||||||
.select(series)
|
where = where.and(audioContent.isAdult.isFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 차단 필터
|
||||||
|
if (memberId != null) {
|
||||||
|
val blockedSubquery = queryFactory
|
||||||
|
.select(blockMember.id)
|
||||||
|
.from(blockMember)
|
||||||
|
.where(
|
||||||
|
blockMember.member.id.eq(series.member.id),
|
||||||
|
blockMember.blockedMember.id.eq(memberId),
|
||||||
|
blockMember.isActive.isTrue
|
||||||
|
)
|
||||||
|
where = where.and(blockedSubquery.exists().not())
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
val sevenDaysAgo = now.minusDays(7)
|
||||||
|
|
||||||
|
val contentCount = seriesContent.id.countDistinct()
|
||||||
|
val isNewCase = Expressions.numberTemplate(
|
||||||
|
Int::class.java,
|
||||||
|
"case when {0} then 1 else 0 end",
|
||||||
|
audioContent.releaseDate.between(sevenDaysAgo, now)
|
||||||
|
)
|
||||||
|
val isNewFlag = isNewCase.max()
|
||||||
|
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
|
||||||
|
val publishedDaysConcat = Expressions.stringTemplate(
|
||||||
|
"group_concat(distinct {0} order by {0} separator ',')",
|
||||||
|
seriesPublishedDay
|
||||||
|
)
|
||||||
|
val latestReleaseDate = audioContent.releaseDate.max()
|
||||||
|
|
||||||
|
val results = queryFactory
|
||||||
|
.select(
|
||||||
|
series.id,
|
||||||
|
series.title,
|
||||||
|
seriesTranslation.renderedPayload,
|
||||||
|
series.coverImage,
|
||||||
|
series.state,
|
||||||
|
member.id,
|
||||||
|
member.nickname,
|
||||||
|
member.profileImage,
|
||||||
|
contentCount,
|
||||||
|
isNewFlag,
|
||||||
|
publishedDaysConcat
|
||||||
|
)
|
||||||
.from(seriesContent)
|
.from(seriesContent)
|
||||||
.innerJoin(seriesContent.series, series)
|
.innerJoin(seriesContent.series, series)
|
||||||
.innerJoin(seriesContent.content, audioContent)
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
.innerJoin(series.member, member)
|
.innerJoin(series.member, member)
|
||||||
.innerJoin(series.genre, seriesGenre)
|
.innerJoin(series.genre, seriesGenre)
|
||||||
|
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
|
||||||
|
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||||
.where(where)
|
.where(where)
|
||||||
.groupBy(series.id)
|
.groupBy(series.id)
|
||||||
.orderBy(audioContent.releaseDate.max().desc(), series.createdAt.asc())
|
.having(contentCount.gt(0))
|
||||||
|
.orderBy(latestReleaseDate.desc(), series.createdAt.asc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|
||||||
|
return results.map { row ->
|
||||||
|
val sId = row.get(series.id)!!
|
||||||
|
val originTitle = row.get(series.title)!!
|
||||||
|
val payload = row.get(seriesTranslation.renderedPayload)
|
||||||
|
val translatedTitle = payload?.title
|
||||||
|
val coverImg = row.get(series.coverImage)
|
||||||
|
val state = row.get(series.state)
|
||||||
|
val cId = row.get(member.id)!!
|
||||||
|
val nick = row.get(member.nickname)!!
|
||||||
|
val profImg = row.get(member.profileImage)
|
||||||
|
val nContent = row.get(contentCount) ?: 0L
|
||||||
|
val isN = (row.get(isNewFlag) ?: 0) > 0
|
||||||
|
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat))
|
||||||
|
|
||||||
|
GetSeriesListResponse.SeriesListItem(
|
||||||
|
seriesId = sId,
|
||||||
|
title = if (translatedTitle.isNullOrBlank()) originTitle else translatedTitle,
|
||||||
|
coverImage = "$imageHost/$coverImg",
|
||||||
|
publishedDaysOfWeek = "", // Service layer will fill this
|
||||||
|
isComplete = state == SeriesState.COMPLETE,
|
||||||
|
creator = GetSeriesListResponse.SeriesListItemCreator(
|
||||||
|
creatorId = cId,
|
||||||
|
nickname = nick,
|
||||||
|
profileImage = "$imageHost/$profImg"
|
||||||
|
),
|
||||||
|
numberOfContent = nContent.toInt(),
|
||||||
|
isNew = isN,
|
||||||
|
rawPublishedDaysOfWeek = rawDays
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSeriesDetail(seriesId: Long, isAuth: Boolean, contentType: ContentType): Series? {
|
override fun getSeriesDetail(seriesId: Long, isAuth: Boolean, contentType: ContentType): Series? {
|
||||||
@@ -337,34 +572,120 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series> {
|
override fun getRecommendSeriesListV2(
|
||||||
|
imageHost: String,
|
||||||
|
isAuth: Boolean,
|
||||||
|
contentType: ContentType,
|
||||||
|
limit: Long,
|
||||||
|
locale: String,
|
||||||
|
memberId: Long?
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
var where = series.isActive.isTrue
|
var where = series.isActive.isTrue
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where
|
||||||
|
.and(series.isAdult.isFalse)
|
||||||
|
.and(audioContent.isAdult.isFalse)
|
||||||
} else {
|
} else {
|
||||||
if (contentType != ContentType.ALL) {
|
if (contentType != ContentType.ALL) {
|
||||||
where = where.and(
|
where = where.and(
|
||||||
series.member.isNull.or(
|
series.member.isNull.or(
|
||||||
series.member.auth.gender.eq(
|
series.member.auth.gender.eq(
|
||||||
if (contentType == ContentType.MALE) {
|
if (contentType == ContentType.MALE) 0 else 1
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
// 차단 필터
|
||||||
.selectFrom(series)
|
if (memberId != null) {
|
||||||
|
val blockedSubquery = queryFactory
|
||||||
|
.select(blockMember.id)
|
||||||
|
.from(blockMember)
|
||||||
|
.where(
|
||||||
|
blockMember.member.id.eq(series.member.id),
|
||||||
|
blockMember.blockedMember.id.eq(memberId),
|
||||||
|
blockMember.isActive.isTrue
|
||||||
|
)
|
||||||
|
where = where.and(blockedSubquery.exists().not())
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
val sevenDaysAgo = now.minusDays(7)
|
||||||
|
|
||||||
|
val contentCount = seriesContent.id.countDistinct()
|
||||||
|
val isNewCase = Expressions.numberTemplate(
|
||||||
|
Int::class.java,
|
||||||
|
"case when {0} then 1 else 0 end",
|
||||||
|
audioContent.releaseDate.between(sevenDaysAgo, now)
|
||||||
|
)
|
||||||
|
val isNewFlag = isNewCase.max()
|
||||||
|
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
|
||||||
|
val publishedDaysConcat = Expressions.stringTemplate(
|
||||||
|
"group_concat(distinct {0} order by {0} separator ',')",
|
||||||
|
seriesPublishedDay
|
||||||
|
)
|
||||||
|
|
||||||
|
val results = queryFactory
|
||||||
|
.select(
|
||||||
|
series.id,
|
||||||
|
series.title,
|
||||||
|
seriesTranslation.renderedPayload,
|
||||||
|
series.coverImage,
|
||||||
|
series.state,
|
||||||
|
member.id,
|
||||||
|
member.nickname,
|
||||||
|
member.profileImage,
|
||||||
|
contentCount,
|
||||||
|
isNewFlag,
|
||||||
|
publishedDaysConcat
|
||||||
|
)
|
||||||
|
.from(series)
|
||||||
|
.innerJoin(series.member, member)
|
||||||
|
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
|
||||||
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
|
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
|
||||||
|
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||||
.where(where)
|
.where(where)
|
||||||
|
.groupBy(series.id)
|
||||||
|
.having(contentCount.gt(0))
|
||||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||||
.offset(0)
|
.offset(0)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|
||||||
|
return results.map { row ->
|
||||||
|
val sId = row.get(series.id)!!
|
||||||
|
val originTitle = row.get(series.title)!!
|
||||||
|
val payload = row.get(seriesTranslation.renderedPayload)
|
||||||
|
val translatedTitle = payload?.title
|
||||||
|
val coverImg = row.get(series.coverImage)
|
||||||
|
val state = row.get(series.state)
|
||||||
|
val cId = row.get(member.id)!!
|
||||||
|
val nick = row.get(member.nickname)!!
|
||||||
|
val profImg = row.get(member.profileImage)
|
||||||
|
val nContent = row.get(contentCount) ?: 0L
|
||||||
|
val isN = (row.get(isNewFlag) ?: 0) > 0
|
||||||
|
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat))
|
||||||
|
|
||||||
|
GetSeriesListResponse.SeriesListItem(
|
||||||
|
seriesId = sId,
|
||||||
|
title = if (translatedTitle.isNullOrBlank()) originTitle else translatedTitle,
|
||||||
|
coverImage = "$imageHost/$coverImg",
|
||||||
|
publishedDaysOfWeek = "", // Service layer will fill this
|
||||||
|
isComplete = state == SeriesState.COMPLETE,
|
||||||
|
creator = GetSeriesListResponse.SeriesListItemCreator(
|
||||||
|
creatorId = cId,
|
||||||
|
nickname = nick,
|
||||||
|
profileImage = "$imageHost/$profImg"
|
||||||
|
),
|
||||||
|
numberOfContent = nContent.toInt(),
|
||||||
|
isNew = isN,
|
||||||
|
rawPublishedDaysOfWeek = rawDays
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOriginalAudioDramaList(
|
override fun getOriginalAudioDramaList(
|
||||||
@@ -377,6 +698,7 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
): List<GetSeriesListResponse.SeriesListItem> {
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
var where = series.isOriginal.isTrue
|
var where = series.isOriginal.isTrue
|
||||||
.and(series.isActive.isTrue)
|
.and(series.isActive.isTrue)
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
if (!isAdult) {
|
if (!isAdult) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where.and(series.isAdult.isFalse)
|
||||||
@@ -396,30 +718,25 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isAdult) {
|
||||||
|
where = where.and(audioContent.isAdult.isFalse)
|
||||||
|
}
|
||||||
|
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val sevenDaysAgo = now.minusDays(7)
|
val sevenDaysAgo = now.minusDays(7)
|
||||||
|
|
||||||
val contentCountSubquery = queryFactory
|
val contentCount = seriesContent.id.countDistinct()
|
||||||
.select(seriesContent.id.count())
|
val isNewCase = Expressions.numberTemplate(
|
||||||
.from(seriesContent)
|
Int::class.java,
|
||||||
.innerJoin(seriesContent.content, audioContent)
|
"case when {0} then 1 else 0 end",
|
||||||
.where(
|
|
||||||
seriesContent.series.id.eq(series.id),
|
|
||||||
audioContent.isActive.isTrue,
|
|
||||||
if (!isAdult) audioContent.isAdult.isFalse else null
|
|
||||||
)
|
|
||||||
|
|
||||||
val isNewSubquery = queryFactory
|
|
||||||
.select(seriesContent.id)
|
|
||||||
.from(seriesContent)
|
|
||||||
.innerJoin(seriesContent.content, audioContent)
|
|
||||||
.where(
|
|
||||||
seriesContent.series.id.eq(series.id),
|
|
||||||
audioContent.isActive.isTrue,
|
|
||||||
if (!isAdult) audioContent.isAdult.isFalse else null,
|
|
||||||
audioContent.releaseDate.between(sevenDaysAgo, now)
|
audioContent.releaseDate.between(sevenDaysAgo, now)
|
||||||
)
|
)
|
||||||
.limit(1)
|
val isNewFlag = isNewCase.max()
|
||||||
|
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
|
||||||
|
val publishedDaysConcat = Expressions.stringTemplate(
|
||||||
|
"group_concat(distinct {0} order by {0} separator ',')",
|
||||||
|
seriesPublishedDay
|
||||||
|
)
|
||||||
|
|
||||||
val results = queryFactory
|
val results = queryFactory
|
||||||
.select(
|
.select(
|
||||||
@@ -431,15 +748,19 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
member.nickname,
|
||||||
member.profileImage,
|
member.profileImage,
|
||||||
contentCountSubquery,
|
contentCount,
|
||||||
isNewSubquery.exists(),
|
isNewFlag,
|
||||||
series
|
publishedDaysConcat
|
||||||
)
|
)
|
||||||
.from(series)
|
.from(series)
|
||||||
.innerJoin(series.member, member)
|
.innerJoin(series.member, member)
|
||||||
|
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
|
||||||
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
|
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
|
||||||
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||||
.where(where)
|
.where(where)
|
||||||
.having(contentCountSubquery.gt(0))
|
.groupBy(series.id)
|
||||||
|
.having(contentCount.gt(0))
|
||||||
.orderBy(series.id.desc())
|
.orderBy(series.id.desc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@@ -455,9 +776,9 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
val creatorId = row.get(member.id)!!
|
val creatorId = row.get(member.id)!!
|
||||||
val nickname = row.get(member.nickname)!!
|
val nickname = row.get(member.nickname)!!
|
||||||
val profileImage = row.get(member.profileImage)
|
val profileImage = row.get(member.profileImage)
|
||||||
val numberOfContent = row.get(8, Long::class.java) ?: 0L
|
val numberOfContent = row.get(contentCount) ?: 0L
|
||||||
val isNew = row.get(9, Boolean::class.java) ?: false
|
val isNew = (row.get(isNewFlag) ?: 0) > 0
|
||||||
val seriesEntity = row.get(series)!!
|
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat))
|
||||||
|
|
||||||
GetSeriesListResponse.SeriesListItem(
|
GetSeriesListResponse.SeriesListItem(
|
||||||
seriesId = seriesId,
|
seriesId = seriesId,
|
||||||
@@ -472,7 +793,7 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
),
|
),
|
||||||
numberOfContent = numberOfContent.toInt(),
|
numberOfContent = numberOfContent.toInt(),
|
||||||
isNew = isNew,
|
isNew = isNew,
|
||||||
rawPublishedDaysOfWeek = seriesEntity.publishedDaysOfWeek
|
rawPublishedDaysOfWeek = rawDays
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,12 +881,14 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findByCurationId(
|
override fun findByCurationIdV2(
|
||||||
|
imageHost: String,
|
||||||
curationId: Long,
|
curationId: Long,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
contentType: ContentType
|
contentType: ContentType,
|
||||||
): List<Series> {
|
locale: String
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
val blockMemberCondition = blockMember.member.id.eq(member.id)
|
val blockMemberCondition = blockMember.member.id.eq(member.id)
|
||||||
.and(blockMember.isActive.isTrue)
|
.and(blockMember.isActive.isTrue)
|
||||||
.and(blockMember.blockedMember.id.eq(memberId))
|
.and(blockMember.blockedMember.id.eq(memberId))
|
||||||
@@ -576,6 +899,7 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
.and(audioContentCuration.id.eq(curationId))
|
.and(audioContentCuration.id.eq(curationId))
|
||||||
.and(audioContentCurationItem.isActive.isTrue)
|
.and(audioContentCurationItem.isActive.isTrue)
|
||||||
.and(blockMember.id.isNull)
|
.and(blockMember.id.isNull)
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
if (!isAdult) {
|
if (!isAdult) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where.and(series.isAdult.isFalse)
|
||||||
@@ -584,38 +908,108 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
where = where.and(
|
where = where.and(
|
||||||
series.member.isNull.or(
|
series.member.isNull.or(
|
||||||
series.member.auth.gender.eq(
|
series.member.auth.gender.eq(
|
||||||
if (contentType == ContentType.MALE) {
|
if (contentType == ContentType.MALE) 0 else 1
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
if (!isAdult) {
|
||||||
.select(series)
|
where = where.and(audioContent.isAdult.isFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
val sevenDaysAgo = now.minusDays(7)
|
||||||
|
|
||||||
|
val contentCount = seriesContent.id.countDistinct()
|
||||||
|
val isNewCase = Expressions.numberTemplate(
|
||||||
|
Int::class.java,
|
||||||
|
"case when {0} then 1 else 0 end",
|
||||||
|
audioContent.releaseDate.between(sevenDaysAgo, now)
|
||||||
|
)
|
||||||
|
val isNewFlag = isNewCase.max()
|
||||||
|
val minCurationOrder = audioContentCurationItem.orders.min()
|
||||||
|
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
|
||||||
|
val publishedDaysConcat = Expressions.stringTemplate(
|
||||||
|
"group_concat(distinct {0} order by {0} separator ',')",
|
||||||
|
seriesPublishedDay
|
||||||
|
)
|
||||||
|
|
||||||
|
val results = queryFactory
|
||||||
|
.select(
|
||||||
|
series.id,
|
||||||
|
series.title,
|
||||||
|
seriesTranslation.renderedPayload,
|
||||||
|
series.coverImage,
|
||||||
|
series.state,
|
||||||
|
member.id,
|
||||||
|
member.nickname,
|
||||||
|
member.profileImage,
|
||||||
|
contentCount,
|
||||||
|
isNewFlag,
|
||||||
|
publishedDaysConcat,
|
||||||
|
minCurationOrder
|
||||||
|
)
|
||||||
.from(audioContentCurationItem)
|
.from(audioContentCurationItem)
|
||||||
.innerJoin(audioContentCurationItem.curation, audioContentCuration)
|
.innerJoin(audioContentCurationItem.curation, audioContentCuration)
|
||||||
.innerJoin(audioContentCurationItem.series, series)
|
.innerJoin(audioContentCurationItem.series, series)
|
||||||
.innerJoin(series.member, member)
|
.innerJoin(series.member, member)
|
||||||
|
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
|
||||||
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
|
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
|
||||||
.leftJoin(blockMember).on(blockMemberCondition)
|
.leftJoin(blockMember).on(blockMemberCondition)
|
||||||
|
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||||
.where(where)
|
.where(where)
|
||||||
.orderBy(audioContentCurationItem.orders.asc())
|
.groupBy(series.id)
|
||||||
|
.orderBy(minCurationOrder.asc(), series.id.asc())
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|
||||||
|
return results.map { row ->
|
||||||
|
val sId = row.get(series.id)!!
|
||||||
|
val originTitle = row.get(series.title)!!
|
||||||
|
val payload = row.get(seriesTranslation.renderedPayload)
|
||||||
|
val translatedTitle = payload?.title
|
||||||
|
val coverImg = row.get(series.coverImage)
|
||||||
|
val state = row.get(series.state)
|
||||||
|
val cId = row.get(member.id)!!
|
||||||
|
val nick = row.get(member.nickname)!!
|
||||||
|
val profImg = row.get(member.profileImage)
|
||||||
|
val nContent = row.get(contentCount) ?: 0L
|
||||||
|
val isN = (row.get(isNewFlag) ?: 0) > 0
|
||||||
|
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat))
|
||||||
|
|
||||||
|
GetSeriesListResponse.SeriesListItem(
|
||||||
|
seriesId = sId,
|
||||||
|
title = if (translatedTitle.isNullOrBlank()) originTitle else translatedTitle,
|
||||||
|
coverImage = "$imageHost/$coverImg",
|
||||||
|
publishedDaysOfWeek = "", // Service layer will fill this
|
||||||
|
isComplete = state == SeriesState.COMPLETE,
|
||||||
|
creator = GetSeriesListResponse.SeriesListItemCreator(
|
||||||
|
creatorId = cId,
|
||||||
|
nickname = nick,
|
||||||
|
profileImage = "$imageHost/$profImg"
|
||||||
|
),
|
||||||
|
numberOfContent = nContent.toInt(),
|
||||||
|
isNew = isN,
|
||||||
|
rawPublishedDaysOfWeek = rawDays
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDayOfWeekSeriesList(
|
override fun getDayOfWeekSeriesListV2(
|
||||||
|
imageHost: String,
|
||||||
dayOfWeek: SeriesPublishedDaysOfWeek,
|
dayOfWeek: SeriesPublishedDaysOfWeek,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
): List<Series> {
|
locale: String,
|
||||||
|
memberId: Long?
|
||||||
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
var where = series.isActive.isTrue
|
var where = series.isActive.isTrue
|
||||||
.and(series.publishedDaysOfWeek.contains(dayOfWeek))
|
.and(series.publishedDaysOfWeek.contains(dayOfWeek))
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
if (!isAdult) {
|
if (!isAdult) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where.and(series.isAdult.isFalse)
|
||||||
@@ -624,24 +1018,103 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
where = where.and(
|
where = where.and(
|
||||||
series.member.isNull.or(
|
series.member.isNull.or(
|
||||||
series.member.auth.gender.eq(
|
series.member.auth.gender.eq(
|
||||||
if (contentType == ContentType.MALE) {
|
if (contentType == ContentType.MALE) 0 else 1
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
if (!isAdult) {
|
||||||
.selectFrom(series)
|
where = where.and(audioContent.isAdult.isFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 차단 필터
|
||||||
|
if (memberId != null) {
|
||||||
|
val blockedSubquery = queryFactory
|
||||||
|
.select(blockMember.id)
|
||||||
|
.from(blockMember)
|
||||||
|
.where(
|
||||||
|
blockMember.member.id.eq(series.member.id),
|
||||||
|
blockMember.blockedMember.id.eq(memberId),
|
||||||
|
blockMember.isActive.isTrue
|
||||||
|
)
|
||||||
|
where = where.and(blockedSubquery.exists().not())
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
val sevenDaysAgo = now.minusDays(7)
|
||||||
|
|
||||||
|
val contentCount = seriesContent.id.countDistinct()
|
||||||
|
val isNewCase = Expressions.numberTemplate(
|
||||||
|
Int::class.java,
|
||||||
|
"case when {0} then 1 else 0 end",
|
||||||
|
audioContent.releaseDate.between(sevenDaysAgo, now)
|
||||||
|
)
|
||||||
|
val isNewFlag = isNewCase.max()
|
||||||
|
val seriesPublishedDay = Expressions.enumPath(SeriesPublishedDaysOfWeek::class.java, "seriesPublishedDay")
|
||||||
|
val publishedDaysConcat = Expressions.stringTemplate(
|
||||||
|
"group_concat(distinct {0} order by {0} separator ',')",
|
||||||
|
seriesPublishedDay
|
||||||
|
)
|
||||||
|
|
||||||
|
val results = queryFactory
|
||||||
|
.select(
|
||||||
|
series.id,
|
||||||
|
series.title,
|
||||||
|
seriesTranslation.renderedPayload,
|
||||||
|
series.coverImage,
|
||||||
|
series.state,
|
||||||
|
member.id,
|
||||||
|
member.nickname,
|
||||||
|
member.profileImage,
|
||||||
|
contentCount,
|
||||||
|
isNewFlag,
|
||||||
|
publishedDaysConcat
|
||||||
|
)
|
||||||
|
.from(series)
|
||||||
|
.innerJoin(series.member, member)
|
||||||
|
.innerJoin(seriesContent).on(series.id.eq(seriesContent.series.id))
|
||||||
|
.innerJoin(seriesContent.content, audioContent)
|
||||||
|
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
|
||||||
|
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||||
.where(where)
|
.where(where)
|
||||||
.groupBy(series.id)
|
.groupBy(series.id)
|
||||||
|
.having(contentCount.gt(0))
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.orderBy(series.createdAt.desc())
|
.orderBy(series.createdAt.desc())
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|
||||||
|
return results.map { row ->
|
||||||
|
val sId = row.get(series.id)!!
|
||||||
|
val originTitle = row.get(series.title)!!
|
||||||
|
val payload = row.get(seriesTranslation.renderedPayload)
|
||||||
|
val translatedTitle = payload?.title
|
||||||
|
val coverImg = row.get(series.coverImage)
|
||||||
|
val state = row.get(series.state)
|
||||||
|
val cId = row.get(member.id)!!
|
||||||
|
val nick = row.get(member.nickname)!!
|
||||||
|
val profImg = row.get(member.profileImage)
|
||||||
|
val nContent = row.get(contentCount) ?: 0L
|
||||||
|
val isN = (row.get(isNewFlag) ?: 0) > 0
|
||||||
|
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat))
|
||||||
|
|
||||||
|
GetSeriesListResponse.SeriesListItem(
|
||||||
|
seriesId = sId,
|
||||||
|
title = if (translatedTitle.isNullOrBlank()) originTitle else translatedTitle,
|
||||||
|
coverImage = "$imageHost/$coverImg",
|
||||||
|
publishedDaysOfWeek = "", // Service layer will fill this
|
||||||
|
isComplete = state == SeriesState.COMPLETE,
|
||||||
|
creator = GetSeriesListResponse.SeriesListItemCreator(
|
||||||
|
creatorId = cId,
|
||||||
|
nickname = nick,
|
||||||
|
profileImage = "$imageHost/$profImg"
|
||||||
|
),
|
||||||
|
numberOfContent = nContent.toInt(),
|
||||||
|
isNew = isN,
|
||||||
|
rawPublishedDaysOfWeek = rawDays
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationPayl
|
|||||||
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationRepository
|
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationRepository
|
||||||
import kr.co.vividnext.sodalive.content.series.translation.TranslatedSeries
|
import kr.co.vividnext.sodalive.content.series.translation.TranslatedSeries
|
||||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
|
||||||
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.SeriesPublishedDaysOfWeek
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType
|
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.explorer.ExplorerQueryRepository
|
||||||
import kr.co.vividnext.sodalive.i18n.Lang
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
@@ -26,7 +24,6 @@ import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
|||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@@ -160,10 +157,11 @@ class ContentSeriesService(
|
|||||||
isAuth = isAuth,
|
isAuth = isAuth,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
isOriginal = isOriginal,
|
isOriginal = isOriginal,
|
||||||
isCompleted = isCompleted
|
isCompleted = isCompleted,
|
||||||
|
memberId = member.id
|
||||||
)
|
)
|
||||||
|
|
||||||
val rawItems = repository.getSeriesList(
|
val items = repository.getSeriesListV2(
|
||||||
imageHost = coverImageHost,
|
imageHost = coverImageHost,
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
isAuth = isAuth,
|
isAuth = isAuth,
|
||||||
@@ -172,11 +170,14 @@ class ContentSeriesService(
|
|||||||
isCompleted = isCompleted,
|
isCompleted = isCompleted,
|
||||||
orderByRandom = orderByRandom,
|
orderByRandom = orderByRandom,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
|
locale = langContext.lang.code,
|
||||||
|
memberId = member.id
|
||||||
|
).map { item ->
|
||||||
|
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||||
|
}
|
||||||
|
|
||||||
val items = seriesToSeriesListItem(seriesList = rawItems, isAdult = isAuth, contentType = contentType)
|
return GetSeriesListResponse(totalCount, items)
|
||||||
return GetSeriesListResponse(totalCount, getTranslatedSeriesList(items))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSeriesListByGenre(
|
fun getSeriesListByGenre(
|
||||||
@@ -192,20 +193,24 @@ class ContentSeriesService(
|
|||||||
val totalCount = repository.getSeriesByGenreTotalCount(
|
val totalCount = repository.getSeriesByGenreTotalCount(
|
||||||
genreId = genreId,
|
genreId = genreId,
|
||||||
isAuth = isAuth,
|
isAuth = isAuth,
|
||||||
contentType = contentType
|
contentType = contentType,
|
||||||
|
memberId = member.id
|
||||||
)
|
)
|
||||||
|
|
||||||
val rawItems = repository.getSeriesByGenreList(
|
val items = repository.getSeriesByGenreListV2(
|
||||||
imageHost = coverImageHost,
|
imageHost = coverImageHost,
|
||||||
genreId = genreId,
|
genreId = genreId,
|
||||||
isAuth = isAuth,
|
isAuth = isAuth,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
|
locale = langContext.lang.code,
|
||||||
|
memberId = member.id
|
||||||
|
).map { item ->
|
||||||
|
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||||
|
}
|
||||||
|
|
||||||
val items = seriesToSeriesListItem(seriesList = rawItems, isAdult = isAuth, contentType = contentType)
|
return GetSeriesListResponse(totalCount, items)
|
||||||
return GetSeriesListResponse(totalCount, getTranslatedSeriesList(items))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -468,19 +473,16 @@ class ContentSeriesService(
|
|||||||
member: Member
|
member: Member
|
||||||
): List<GetSeriesListResponse.SeriesListItem> {
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
val isAuth = member.auth != null && isAdultContentVisible
|
val isAuth = member.auth != null && isAdultContentVisible
|
||||||
val seriesList = repository.getRecommendSeriesList(
|
return repository.getRecommendSeriesListV2(
|
||||||
|
imageHost = coverImageHost,
|
||||||
isAuth = isAuth,
|
isAuth = isAuth,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
limit = 20
|
limit = 20,
|
||||||
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
|
locale = langContext.lang.code,
|
||||||
|
memberId = member.id
|
||||||
return getTranslatedSeriesList(
|
).map { item ->
|
||||||
seriesToSeriesListItem(
|
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||||
seriesList = seriesList,
|
}
|
||||||
isAdult = isAuth,
|
|
||||||
contentType = contentType
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchSeriesByCurationId(
|
fun fetchSeriesByCurationId(
|
||||||
@@ -489,13 +491,16 @@ class ContentSeriesService(
|
|||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
contentType: ContentType
|
contentType: ContentType
|
||||||
): List<GetSeriesListResponse.SeriesListItem> {
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
val seriesList = repository.findByCurationId(
|
return repository.findByCurationIdV2(
|
||||||
|
imageHost = coverImageHost,
|
||||||
curationId = curationId,
|
curationId = curationId,
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
contentType = contentType
|
contentType = contentType,
|
||||||
)
|
locale = langContext.lang.code
|
||||||
return getTranslatedSeriesList(seriesToSeriesListItem(seriesList, isAdult, contentType))
|
).map { item ->
|
||||||
|
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDayOfWeekSeriesList(
|
fun getDayOfWeekSeriesList(
|
||||||
@@ -506,71 +511,17 @@ class ContentSeriesService(
|
|||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 10
|
limit: Long = 10
|
||||||
): List<GetSeriesListResponse.SeriesListItem> {
|
): List<GetSeriesListResponse.SeriesListItem> {
|
||||||
var seriesList = repository.getDayOfWeekSeriesList(
|
return repository.getDayOfWeekSeriesListV2(
|
||||||
|
imageHost = coverImageHost,
|
||||||
dayOfWeek = dayOfWeek,
|
dayOfWeek = dayOfWeek,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit,
|
||||||
)
|
locale = langContext.lang.code,
|
||||||
|
memberId = memberId
|
||||||
seriesList = if (memberId != null) {
|
).map { item ->
|
||||||
seriesList.filter {
|
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||||
!blockMemberRepository.isBlocked(
|
|
||||||
blockedMemberId = memberId,
|
|
||||||
memberId = it.member!!.id!!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
seriesList
|
|
||||||
}
|
|
||||||
|
|
||||||
return getTranslatedSeriesList(seriesToSeriesListItem(seriesList, isAdult, contentType))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun seriesToSeriesListItem(
|
|
||||||
seriesList: List<Series>,
|
|
||||||
isAdult: Boolean,
|
|
||||||
contentType: ContentType
|
|
||||||
): List<GetSeriesListResponse.SeriesListItem> {
|
|
||||||
return seriesList
|
|
||||||
.map {
|
|
||||||
GetSeriesListResponse.SeriesListItem(
|
|
||||||
seriesId = it.id!!,
|
|
||||||
title = it.title,
|
|
||||||
coverImage = "$coverImageHost/${it.coverImage!!}",
|
|
||||||
publishedDaysOfWeek = publishedDaysOfWeekText(it.publishedDaysOfWeek),
|
|
||||||
isComplete = it.state == SeriesState.COMPLETE,
|
|
||||||
creator = GetSeriesListResponse.SeriesListItemCreator(
|
|
||||||
creatorId = it.member!!.id!!,
|
|
||||||
nickname = it.member!!.nickname,
|
|
||||||
profileImage = "$coverImageHost/${it.member!!.profileImage!!}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.map {
|
|
||||||
it.numberOfContent = seriesContentRepository.getContentCount(
|
|
||||||
seriesId = it.seriesId,
|
|
||||||
isAdult = isAdult,
|
|
||||||
contentType = contentType
|
|
||||||
)
|
|
||||||
|
|
||||||
it
|
|
||||||
}
|
|
||||||
.filter {
|
|
||||||
it.numberOfContent > 0
|
|
||||||
}
|
|
||||||
.map {
|
|
||||||
val nowDateTime = LocalDateTime.now()
|
|
||||||
|
|
||||||
it.isNew = seriesContentRepository.isNewContent(
|
|
||||||
seriesId = it.seriesId,
|
|
||||||
isAdult = isAdult,
|
|
||||||
fromDate = nowDateTime.minusDays(7),
|
|
||||||
nowDate = nowDateTime
|
|
||||||
)
|
|
||||||
|
|
||||||
it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,39 +592,4 @@ class ContentSeriesService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 시리즈 리스트의 제목을 현재 언어(locale)에 맞춰 일괄 번역한다.
|
|
||||||
*
|
|
||||||
* 처리 절차:
|
|
||||||
* - 입력된 시리즈들의 seriesId 집합을 만들고, 요청 언어 코드(langContext.lang.code)로
|
|
||||||
* seriesTranslationRepository에서 번역 데이터를 한 번에 조회한다.
|
|
||||||
* - 각 항목에 대해 번역된 제목이 존재하고 비어있지 않으면 title만 번역 값으로 교체한다.
|
|
||||||
* - 번역이 없거나 공백이면 원본 항목을 그대로 반환한다.
|
|
||||||
*
|
|
||||||
* 성능:
|
|
||||||
* - N건의 항목을 1회의 조회로 해결하기 위해 IN 쿼리를 사용한다.
|
|
||||||
*
|
|
||||||
* @param seriesList 번역 대상 SeriesListItem 목록
|
|
||||||
* @return 제목이 가능한 항목은 번역된 목록(불변 사본), 그 외는 원본 항목 유지
|
|
||||||
*/
|
|
||||||
private fun getTranslatedSeriesList(
|
|
||||||
seriesList: List<GetSeriesListResponse.SeriesListItem>
|
|
||||||
): List<GetSeriesListResponse.SeriesListItem> {
|
|
||||||
val seriesIds = seriesList.map { it.seriesId }
|
|
||||||
if (seriesIds.isEmpty()) return seriesList
|
|
||||||
|
|
||||||
val translations = seriesTranslationRepository
|
|
||||||
.findBySeriesIdInAndLocale(seriesIds = seriesIds, locale = langContext.lang.code)
|
|
||||||
.associateBy { it.seriesId }
|
|
||||||
|
|
||||||
return seriesList.map { item ->
|
|
||||||
val translatedTitle = translations[item.seriesId]?.renderedPayload?.title
|
|
||||||
if (translatedTitle.isNullOrBlank()) {
|
|
||||||
item
|
|
||||||
} else {
|
|
||||||
item.copy(title = translatedTitle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user