시리즈 목록 조회 쿼리 최적화
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.series.ContentSeriesService
|
||||
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.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
import kr.co.vividnext.sodalive.event.GetEventResponse
|
||||
@@ -50,7 +49,6 @@ class HomeService(
|
||||
private val explorerQueryRepository: ExplorerQueryRepository,
|
||||
|
||||
private val aiCharacterTranslationRepository: AiCharacterTranslationRepository,
|
||||
private val seriesTranslationRepository: SeriesTranslationRepository,
|
||||
|
||||
private val langContext: LangContext,
|
||||
|
||||
@@ -139,13 +137,12 @@ class HomeService(
|
||||
val auditionList = auditionService.getInProgressAuditionList(isAdult = isAdult)
|
||||
|
||||
// 요일별 시리즈
|
||||
val dayOfWeekSeriesList = seriesService.getDayOfWeekSeriesList(
|
||||
val translatedDayOfWeekSeriesList = seriesService.getDayOfWeekSeriesList(
|
||||
memberId = memberId,
|
||||
isAdult = isAdult,
|
||||
contentType = contentType,
|
||||
dayOfWeek = getDayOfWeekByTimezone(timezone)
|
||||
)
|
||||
val translatedDayOfWeekSeriesList = getTranslatedSeriesList(seriesList = dayOfWeekSeriesList)
|
||||
|
||||
// 인기 캐릭터 조회
|
||||
val translatedPopularCharacters = getTranslatedAiCharacterList(aiCharacterList = characterService.getPopularCharacters())
|
||||
@@ -265,14 +262,12 @@ class HomeService(
|
||||
val memberId = member?.id
|
||||
val isAdult = member?.auth != null && isAdultContentVisible
|
||||
|
||||
val dayOfWeekSeriesList = seriesService.getDayOfWeekSeriesList(
|
||||
return seriesService.getDayOfWeekSeriesList(
|
||||
memberId = memberId,
|
||||
isAdult = isAdult,
|
||||
contentType = contentType,
|
||||
dayOfWeek = dayOfWeek
|
||||
)
|
||||
|
||||
return getTranslatedSeriesList(seriesList = dayOfWeekSeriesList)
|
||||
}
|
||||
|
||||
fun getContentRankingBySort(
|
||||
@@ -479,44 +474,6 @@ class HomeService(
|
||||
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)에 맞춰 일괄 번역한다.
|
||||
*
|
||||
|
||||
@@ -31,10 +31,11 @@ interface ContentSeriesQueryRepository {
|
||||
isAuth: Boolean,
|
||||
contentType: ContentType,
|
||||
isOriginal: Boolean,
|
||||
isCompleted: Boolean
|
||||
isCompleted: Boolean,
|
||||
memberId: Long? = null
|
||||
): Int
|
||||
|
||||
fun getSeriesList(
|
||||
fun getSeriesListV2(
|
||||
imageHost: String,
|
||||
creatorId: Long?,
|
||||
isAuth: Boolean,
|
||||
@@ -43,28 +44,41 @@ interface ContentSeriesQueryRepository {
|
||||
isCompleted: Boolean,
|
||||
orderByRandom: Boolean,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<Series>
|
||||
limit: Long,
|
||||
locale: String,
|
||||
memberId: Long? = null
|
||||
): List<GetSeriesListResponse.SeriesListItem>
|
||||
|
||||
fun getSeriesByGenreTotalCount(
|
||||
genreId: Long,
|
||||
isAuth: Boolean,
|
||||
contentType: ContentType
|
||||
contentType: ContentType,
|
||||
memberId: Long? = null
|
||||
): Int
|
||||
|
||||
fun getSeriesByGenreList(
|
||||
fun getSeriesByGenreListV2(
|
||||
imageHost: String,
|
||||
genreId: Long,
|
||||
isAuth: Boolean,
|
||||
contentType: ContentType,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<Series>
|
||||
limit: Long,
|
||||
locale: String,
|
||||
memberId: Long? = null
|
||||
): List<GetSeriesListResponse.SeriesListItem>
|
||||
|
||||
fun getSeriesDetail(seriesId: Long, isAuth: Boolean, contentType: ContentType): Series?
|
||||
fun getKeywordList(seriesId: Long): List<String>
|
||||
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(
|
||||
imageHost: String,
|
||||
isAdult: Boolean,
|
||||
@@ -76,27 +90,57 @@ interface ContentSeriesQueryRepository {
|
||||
|
||||
fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int
|
||||
fun getGenreList(isAdult: Boolean, memberId: Long, contentType: ContentType): List<GetSeriesGenreListResponse>
|
||||
fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean, contentType: ContentType): List<Series>
|
||||
fun getDayOfWeekSeriesList(
|
||||
fun findByCurationIdV2(
|
||||
imageHost: String,
|
||||
curationId: Long,
|
||||
memberId: Long,
|
||||
isAdult: Boolean,
|
||||
contentType: ContentType,
|
||||
locale: String
|
||||
): List<GetSeriesListResponse.SeriesListItem>
|
||||
|
||||
fun getDayOfWeekSeriesListV2(
|
||||
imageHost: String,
|
||||
dayOfWeek: SeriesPublishedDaysOfWeek,
|
||||
contentType: ContentType,
|
||||
isAdult: Boolean,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<Series>
|
||||
limit: Long,
|
||||
locale: String,
|
||||
memberId: Long? = null
|
||||
): List<GetSeriesListResponse.SeriesListItem>
|
||||
}
|
||||
|
||||
class ContentSeriesQueryRepositoryImpl(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : 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(
|
||||
creatorId: Long?,
|
||||
isAuth: Boolean,
|
||||
contentType: ContentType,
|
||||
isOriginal: Boolean,
|
||||
isCompleted: Boolean
|
||||
isCompleted: Boolean,
|
||||
memberId: Long?
|
||||
): Int {
|
||||
var where = series.isActive.isTrue
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (creatorId != null) {
|
||||
where = where.and(series.member.id.eq(creatorId))
|
||||
@@ -111,7 +155,9 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
}
|
||||
|
||||
if (!isAuth) {
|
||||
where = where.and(series.isAdult.isFalse)
|
||||
where = where
|
||||
.and(series.isAdult.isFalse)
|
||||
.and(audioContent.isAdult.isFalse)
|
||||
} else {
|
||||
if (contentType != ContentType.ALL) {
|
||||
where = where.and(
|
||||
@@ -128,16 +174,31 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(series.id)
|
||||
.from(series)
|
||||
.innerJoin(series.member, member)
|
||||
.where(where)
|
||||
.fetch()
|
||||
.size
|
||||
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())
|
||||
}
|
||||
|
||||
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 getSeriesList(
|
||||
override fun getSeriesListV2(
|
||||
imageHost: String,
|
||||
creatorId: Long?,
|
||||
isAuth: Boolean,
|
||||
@@ -146,9 +207,12 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
isCompleted: Boolean,
|
||||
orderByRandom: Boolean,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<Series> {
|
||||
limit: Long,
|
||||
locale: String,
|
||||
memberId: Long?
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
var where = series.isActive.isTrue
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (creatorId != null) {
|
||||
where = where.and(series.member.id.eq(creatorId))
|
||||
@@ -162,54 +226,134 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
}
|
||||
|
||||
if (!isAuth) {
|
||||
where = where.and(series.isAdult.isFalse)
|
||||
where = where
|
||||
.and(series.isAdult.isFalse)
|
||||
.and(audioContent.isAdult.isFalse)
|
||||
} else {
|
||||
if (contentType != ContentType.ALL) {
|
||||
where = where.and(
|
||||
series.member.isNull.or(
|
||||
series.member.auth.gender.eq(
|
||||
if (contentType == ContentType.MALE) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
if (contentType == ContentType.MALE) 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) {
|
||||
listOf(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||
} else if (creatorId != null) {
|
||||
listOf(series.orders.asc(), series.createdAt.asc())
|
||||
} else {
|
||||
listOf(audioContent.releaseDate.max().desc(), series.createdAt.asc())
|
||||
listOf(latestReleaseDate.desc(), series.createdAt.asc())
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.selectFrom(series)
|
||||
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)
|
||||
.groupBy(series.id)
|
||||
.having(contentCount.gt(0))
|
||||
.orderBy(*orderBy.toTypedArray())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.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(
|
||||
genreId: Long,
|
||||
isAuth: Boolean,
|
||||
contentType: ContentType
|
||||
contentType: ContentType,
|
||||
memberId: Long?
|
||||
): Int {
|
||||
var where = series.isActive.isTrue
|
||||
.and(series.genre.id.eq(genreId))
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (!isAuth) {
|
||||
where = where.and(series.isAdult.isFalse)
|
||||
where = where
|
||||
.and(series.isAdult.isFalse)
|
||||
.and(audioContent.isAdult.isFalse)
|
||||
} else {
|
||||
if (contentType != ContentType.ALL) {
|
||||
where = where.and(
|
||||
@@ -226,29 +370,44 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(series.id)
|
||||
.from(seriesContent)
|
||||
.innerJoin(seriesContent.series, series)
|
||||
.innerJoin(seriesContent.content, audioContent)
|
||||
.innerJoin(series.member, member)
|
||||
.innerJoin(series.genre, seriesGenre)
|
||||
.where(where)
|
||||
.groupBy(series.id)
|
||||
.fetch()
|
||||
.size
|
||||
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())
|
||||
}
|
||||
|
||||
return (
|
||||
queryFactory
|
||||
.select(series.id.countDistinct())
|
||||
.from(seriesContent)
|
||||
.innerJoin(seriesContent.series, series)
|
||||
.innerJoin(seriesContent.content, audioContent)
|
||||
.innerJoin(series.member, member)
|
||||
.innerJoin(series.genre, seriesGenre)
|
||||
.where(where)
|
||||
.fetchOne() ?: 0L
|
||||
).toInt()
|
||||
}
|
||||
|
||||
override fun getSeriesByGenreList(
|
||||
override fun getSeriesByGenreListV2(
|
||||
imageHost: String,
|
||||
genreId: Long,
|
||||
isAuth: Boolean,
|
||||
contentType: ContentType,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<Series> {
|
||||
limit: Long,
|
||||
locale: String,
|
||||
memberId: Long?
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
var where = series.isActive.isTrue
|
||||
.and(series.genre.id.eq(genreId))
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (!isAuth) {
|
||||
where = where.and(series.isAdult.isFalse)
|
||||
@@ -257,30 +416,106 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
where = where.and(
|
||||
series.member.isNull.or(
|
||||
series.member.auth.gender.eq(
|
||||
if (contentType == ContentType.MALE) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
if (contentType == ContentType.MALE) 0 else 1
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(series)
|
||||
if (!isAuth) {
|
||||
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)
|
||||
.innerJoin(seriesContent.series, series)
|
||||
.innerJoin(seriesContent.content, audioContent)
|
||||
.innerJoin(series.member, member)
|
||||
.innerJoin(series.genre, seriesGenre)
|
||||
.leftJoin(series.publishedDaysOfWeek, seriesPublishedDay)
|
||||
.leftJoin(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||
.where(where)
|
||||
.groupBy(series.id)
|
||||
.orderBy(audioContent.releaseDate.max().desc(), series.createdAt.asc())
|
||||
.having(contentCount.gt(0))
|
||||
.orderBy(latestReleaseDate.desc(), series.createdAt.asc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.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? {
|
||||
@@ -337,34 +572,120 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
.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
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (!isAuth) {
|
||||
where = where.and(series.isAdult.isFalse)
|
||||
where = where
|
||||
.and(series.isAdult.isFalse)
|
||||
.and(audioContent.isAdult.isFalse)
|
||||
} else {
|
||||
if (contentType != ContentType.ALL) {
|
||||
where = where.and(
|
||||
series.member.isNull.or(
|
||||
series.member.auth.gender.eq(
|
||||
if (contentType == ContentType.MALE) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
if (contentType == ContentType.MALE) 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)
|
||||
.groupBy(series.id)
|
||||
.having(contentCount.gt(0))
|
||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||
.offset(0)
|
||||
.limit(limit)
|
||||
.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(
|
||||
@@ -377,6 +698,7 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
var where = series.isOriginal.isTrue
|
||||
.and(series.isActive.isTrue)
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (!isAdult) {
|
||||
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 sevenDaysAgo = now.minusDays(7)
|
||||
|
||||
val contentCountSubquery = queryFactory
|
||||
.select(seriesContent.id.count())
|
||||
.from(seriesContent)
|
||||
.innerJoin(seriesContent.content, audioContent)
|
||||
.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)
|
||||
)
|
||||
.limit(1)
|
||||
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(
|
||||
@@ -431,15 +748,19 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
member.id,
|
||||
member.nickname,
|
||||
member.profileImage,
|
||||
contentCountSubquery,
|
||||
isNewSubquery.exists(),
|
||||
series
|
||||
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)
|
||||
.having(contentCountSubquery.gt(0))
|
||||
.groupBy(series.id)
|
||||
.having(contentCount.gt(0))
|
||||
.orderBy(series.id.desc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
@@ -455,9 +776,9 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
val creatorId = row.get(member.id)!!
|
||||
val nickname = row.get(member.nickname)!!
|
||||
val profileImage = row.get(member.profileImage)
|
||||
val numberOfContent = row.get(8, Long::class.java) ?: 0L
|
||||
val isNew = row.get(9, Boolean::class.java) ?: false
|
||||
val seriesEntity = row.get(series)!!
|
||||
val numberOfContent = row.get(contentCount) ?: 0L
|
||||
val isNew = (row.get(isNewFlag) ?: 0) > 0
|
||||
val rawDays = parsePublishedDaysOfWeek(row.get(publishedDaysConcat))
|
||||
|
||||
GetSeriesListResponse.SeriesListItem(
|
||||
seriesId = seriesId,
|
||||
@@ -472,7 +793,7 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
),
|
||||
numberOfContent = numberOfContent.toInt(),
|
||||
isNew = isNew,
|
||||
rawPublishedDaysOfWeek = seriesEntity.publishedDaysOfWeek
|
||||
rawPublishedDaysOfWeek = rawDays
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -560,12 +881,14 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun findByCurationId(
|
||||
override fun findByCurationIdV2(
|
||||
imageHost: String,
|
||||
curationId: Long,
|
||||
memberId: Long,
|
||||
isAdult: Boolean,
|
||||
contentType: ContentType
|
||||
): List<Series> {
|
||||
contentType: ContentType,
|
||||
locale: String
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
val blockMemberCondition = blockMember.member.id.eq(member.id)
|
||||
.and(blockMember.isActive.isTrue)
|
||||
.and(blockMember.blockedMember.id.eq(memberId))
|
||||
@@ -576,6 +899,7 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
.and(audioContentCuration.id.eq(curationId))
|
||||
.and(audioContentCurationItem.isActive.isTrue)
|
||||
.and(blockMember.id.isNull)
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (!isAdult) {
|
||||
where = where.and(series.isAdult.isFalse)
|
||||
@@ -584,38 +908,108 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
where = where.and(
|
||||
series.member.isNull.or(
|
||||
series.member.auth.gender.eq(
|
||||
if (contentType == ContentType.MALE) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
if (contentType == ContentType.MALE) 0 else 1
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(series)
|
||||
if (!isAdult) {
|
||||
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)
|
||||
.innerJoin(audioContentCurationItem.curation, audioContentCuration)
|
||||
.innerJoin(audioContentCurationItem.series, series)
|
||||
.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(seriesTranslation).on(series.id.eq(seriesTranslation.seriesId), seriesTranslation.locale.eq(locale))
|
||||
.where(where)
|
||||
.orderBy(audioContentCurationItem.orders.asc())
|
||||
.groupBy(series.id)
|
||||
.orderBy(minCurationOrder.asc(), series.id.asc())
|
||||
.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,
|
||||
contentType: ContentType,
|
||||
isAdult: Boolean,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<Series> {
|
||||
limit: Long,
|
||||
locale: String,
|
||||
memberId: Long?
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
var where = series.isActive.isTrue
|
||||
.and(series.publishedDaysOfWeek.contains(dayOfWeek))
|
||||
.and(audioContent.isActive.isTrue)
|
||||
|
||||
if (!isAdult) {
|
||||
where = where.and(series.isAdult.isFalse)
|
||||
@@ -624,24 +1018,103 @@ class ContentSeriesQueryRepositoryImpl(
|
||||
where = where.and(
|
||||
series.member.isNull.or(
|
||||
series.member.auth.gender.eq(
|
||||
if (contentType == ContentType.MALE) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
if (contentType == ContentType.MALE) 0 else 1
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.selectFrom(series)
|
||||
if (!isAdult) {
|
||||
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)
|
||||
.groupBy(series.id)
|
||||
.having(contentCount.gt(0))
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.orderBy(series.createdAt.desc())
|
||||
.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.TranslatedSeries
|
||||
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.SeriesSortType
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
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.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@@ -160,10 +157,11 @@ class ContentSeriesService(
|
||||
isAuth = isAuth,
|
||||
contentType = contentType,
|
||||
isOriginal = isOriginal,
|
||||
isCompleted = isCompleted
|
||||
isCompleted = isCompleted,
|
||||
memberId = member.id
|
||||
)
|
||||
|
||||
val rawItems = repository.getSeriesList(
|
||||
val items = repository.getSeriesListV2(
|
||||
imageHost = coverImageHost,
|
||||
creatorId = creatorId,
|
||||
isAuth = isAuth,
|
||||
@@ -172,11 +170,14 @@ class ContentSeriesService(
|
||||
isCompleted = isCompleted,
|
||||
orderByRandom = orderByRandom,
|
||||
offset = offset,
|
||||
limit = limit
|
||||
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
|
||||
limit = limit,
|
||||
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, getTranslatedSeriesList(items))
|
||||
return GetSeriesListResponse(totalCount, items)
|
||||
}
|
||||
|
||||
fun getSeriesListByGenre(
|
||||
@@ -192,20 +193,24 @@ class ContentSeriesService(
|
||||
val totalCount = repository.getSeriesByGenreTotalCount(
|
||||
genreId = genreId,
|
||||
isAuth = isAuth,
|
||||
contentType = contentType
|
||||
contentType = contentType,
|
||||
memberId = member.id
|
||||
)
|
||||
|
||||
val rawItems = repository.getSeriesByGenreList(
|
||||
val items = repository.getSeriesByGenreListV2(
|
||||
imageHost = coverImageHost,
|
||||
genreId = genreId,
|
||||
isAuth = isAuth,
|
||||
contentType = contentType,
|
||||
offset = offset,
|
||||
limit = limit
|
||||
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
|
||||
limit = limit,
|
||||
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, getTranslatedSeriesList(items))
|
||||
return GetSeriesListResponse(totalCount, items)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -468,19 +473,16 @@ class ContentSeriesService(
|
||||
member: Member
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
val isAuth = member.auth != null && isAdultContentVisible
|
||||
val seriesList = repository.getRecommendSeriesList(
|
||||
return repository.getRecommendSeriesListV2(
|
||||
imageHost = coverImageHost,
|
||||
isAuth = isAuth,
|
||||
contentType = contentType,
|
||||
limit = 20
|
||||
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
|
||||
|
||||
return getTranslatedSeriesList(
|
||||
seriesToSeriesListItem(
|
||||
seriesList = seriesList,
|
||||
isAdult = isAuth,
|
||||
contentType = contentType
|
||||
)
|
||||
)
|
||||
limit = 20,
|
||||
locale = langContext.lang.code,
|
||||
memberId = member.id
|
||||
).map { item ->
|
||||
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchSeriesByCurationId(
|
||||
@@ -489,13 +491,16 @@ class ContentSeriesService(
|
||||
isAdult: Boolean,
|
||||
contentType: ContentType
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
val seriesList = repository.findByCurationId(
|
||||
return repository.findByCurationIdV2(
|
||||
imageHost = coverImageHost,
|
||||
curationId = curationId,
|
||||
memberId = memberId,
|
||||
isAdult = isAdult,
|
||||
contentType = contentType
|
||||
)
|
||||
return getTranslatedSeriesList(seriesToSeriesListItem(seriesList, isAdult, contentType))
|
||||
contentType = contentType,
|
||||
locale = langContext.lang.code
|
||||
).map { item ->
|
||||
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||
}
|
||||
}
|
||||
|
||||
fun getDayOfWeekSeriesList(
|
||||
@@ -506,72 +511,18 @@ class ContentSeriesService(
|
||||
offset: Long = 0,
|
||||
limit: Long = 10
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
var seriesList = repository.getDayOfWeekSeriesList(
|
||||
return repository.getDayOfWeekSeriesListV2(
|
||||
imageHost = coverImageHost,
|
||||
dayOfWeek = dayOfWeek,
|
||||
contentType = contentType,
|
||||
isAdult = isAdult,
|
||||
offset = offset,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
seriesList = if (memberId != null) {
|
||||
seriesList.filter {
|
||||
!blockMemberRepository.isBlocked(
|
||||
blockedMemberId = memberId,
|
||||
memberId = it.member!!.id!!
|
||||
)
|
||||
}
|
||||
} else {
|
||||
seriesList
|
||||
limit = limit,
|
||||
locale = langContext.lang.code,
|
||||
memberId = memberId
|
||||
).map { item ->
|
||||
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
private fun publishedDaysOfWeekText(publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>): String {
|
||||
@@ -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