Compare commits
4 Commits
46b0989795
...
ec077d23f0
| Author | SHA1 | Date | |
|---|---|---|---|
| ec077d23f0 | |||
| 01a1a05d77 | |||
| ac0def6187 | |||
| 341f24c643 |
@@ -1,7 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.api.home
|
||||
|
||||
import kr.co.vividnext.sodalive.audition.AuditionService
|
||||
import kr.co.vividnext.sodalive.chat.character.dto.Character
|
||||
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
|
||||
import kr.co.vividnext.sodalive.chat.character.translate.AiCharacterTranslationRepository
|
||||
import kr.co.vividnext.sodalive.content.AudioContentMainItem
|
||||
@@ -12,9 +11,7 @@ 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.content.translation.ContentTranslationRepository
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
import kr.co.vividnext.sodalive.event.GetEventResponse
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
||||
@@ -50,9 +47,7 @@ class HomeService(
|
||||
private val rankingRepository: RankingRepository,
|
||||
private val explorerQueryRepository: ExplorerQueryRepository,
|
||||
|
||||
private val contentTranslationRepository: ContentTranslationRepository,
|
||||
private val aiCharacterTranslationRepository: AiCharacterTranslationRepository,
|
||||
private val seriesTranslationRepository: SeriesTranslationRepository,
|
||||
|
||||
private val langContext: LangContext,
|
||||
|
||||
@@ -122,8 +117,6 @@ class HomeService(
|
||||
isAdult = isAdult
|
||||
)
|
||||
|
||||
val translatedLatestContentList = getTranslatedContentList(contentList = latestContentList)
|
||||
|
||||
val eventBannerList = GetEventResponse(
|
||||
totalCount = 0,
|
||||
eventList = emptyList()
|
||||
@@ -135,28 +128,23 @@ class HomeService(
|
||||
isAdult = isAdult
|
||||
)
|
||||
|
||||
// 오직 보이스온에서만
|
||||
val originalAudioDramaList = seriesService.getOriginalAudioDramaList(
|
||||
isAdult = isAdult,
|
||||
contentType = contentType,
|
||||
orderByRandom = true
|
||||
contentType = contentType
|
||||
)
|
||||
|
||||
val translatedOriginalAudioDramaList = getTranslatedSeriesList(seriesList = originalAudioDramaList)
|
||||
|
||||
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())
|
||||
val translatedPopularCharacters = characterService.getPopularCharacters(locale = langContext.lang.code)
|
||||
|
||||
val currentDateTime = LocalDateTime.now()
|
||||
val startDate = currentDateTime
|
||||
@@ -177,64 +165,12 @@ class HomeService(
|
||||
sort = ContentRankingSortType.REVENUE
|
||||
)
|
||||
|
||||
val contentRankingContentIds = contentRanking.map { it.contentId }
|
||||
val translatedContentRanking = if (contentRankingContentIds.isNotEmpty()) {
|
||||
val translations = contentTranslationRepository
|
||||
.findByContentIdInAndLocale(contentIds = contentRankingContentIds, locale = langContext.lang.code)
|
||||
.associateBy { it.contentId }
|
||||
|
||||
contentRanking.map { item ->
|
||||
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
|
||||
if (translatedTitle.isNullOrBlank()) {
|
||||
item
|
||||
} else {
|
||||
item.copy(title = translatedTitle)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
contentRanking
|
||||
}
|
||||
|
||||
val recommendChannelList = recommendChannelService.getRecommendChannel(
|
||||
memberId = memberId,
|
||||
isAdult = isAdult,
|
||||
contentType = contentType
|
||||
)
|
||||
|
||||
/**
|
||||
* recommendChannelList의 콘텐츠 번역 데이터 조회
|
||||
*
|
||||
* languageCode != null
|
||||
* contentTranslationRepository를 이용해 번역 콘텐츠를 조회한다. - contentId, locale
|
||||
*
|
||||
* 한 번에 조회하고 contentId를 매핑하여 recommendChannelList의 콘텐츠 title을 번역 데이터로 변경한다
|
||||
*/
|
||||
val channelContentIds = recommendChannelList
|
||||
.flatMap { it.contentList }
|
||||
.map { it.contentId }
|
||||
.distinct()
|
||||
|
||||
val translatedRecommendChannelList = if (channelContentIds.isNotEmpty()) {
|
||||
val translations = contentTranslationRepository
|
||||
.findByContentIdInAndLocale(contentIds = channelContentIds, locale = langContext.lang.code)
|
||||
.associateBy { it.contentId }
|
||||
|
||||
recommendChannelList.map { channel ->
|
||||
val translatedContentList = channel.contentList.map { item ->
|
||||
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
|
||||
if (translatedTitle.isNullOrBlank()) {
|
||||
item
|
||||
} else {
|
||||
item.copy(title = translatedTitle)
|
||||
}
|
||||
}
|
||||
|
||||
channel.copy(contentList = translatedContentList)
|
||||
}
|
||||
} else {
|
||||
recommendChannelList
|
||||
}
|
||||
|
||||
val freeContentList = getRandomizedContentList(
|
||||
memberId = memberId,
|
||||
isAdult = isAdult,
|
||||
@@ -248,8 +184,6 @@ class HomeService(
|
||||
isPointAvailableOnly = false
|
||||
)
|
||||
|
||||
val translatedFreeContentList = getTranslatedContentList(contentList = freeContentList)
|
||||
|
||||
// 포인트 사용가능 콘텐츠 리스트 - 랜덤으로 가져오기 (DB에서 isPointAvailable 조건 적용)
|
||||
val pointAvailableContentList = getRandomizedContentList(
|
||||
memberId = memberId,
|
||||
@@ -260,28 +194,26 @@ class HomeService(
|
||||
isPointAvailableOnly = true
|
||||
)
|
||||
|
||||
val translatedPointAvailableContentList = getTranslatedContentList(contentList = pointAvailableContentList)
|
||||
|
||||
val excludeContentIds = (
|
||||
translatedLatestContentList.map { it.contentId } +
|
||||
translatedContentRanking.map { it.contentId }
|
||||
latestContentList.map { it.contentId } +
|
||||
contentRanking.map { it.contentId }
|
||||
).distinct()
|
||||
|
||||
return GetHomeResponse(
|
||||
liveList = liveList,
|
||||
creatorRanking = creatorRanking,
|
||||
latestContentThemeList = latestContentThemeList,
|
||||
latestContentList = translatedLatestContentList,
|
||||
latestContentList = latestContentList,
|
||||
bannerList = bannerList,
|
||||
eventBannerList = eventBannerList,
|
||||
originalAudioDramaList = translatedOriginalAudioDramaList,
|
||||
originalAudioDramaList = originalAudioDramaList,
|
||||
auditionList = auditionList,
|
||||
dayOfWeekSeriesList = translatedDayOfWeekSeriesList,
|
||||
popularCharacters = translatedPopularCharacters,
|
||||
contentRanking = translatedContentRanking,
|
||||
recommendChannelList = translatedRecommendChannelList,
|
||||
freeContentList = translatedFreeContentList,
|
||||
pointAvailableContentList = translatedPointAvailableContentList,
|
||||
contentRanking = contentRanking,
|
||||
recommendChannelList = recommendChannelList,
|
||||
freeContentList = freeContentList,
|
||||
pointAvailableContentList = pointAvailableContentList,
|
||||
recommendContentList = getRecommendContentList(
|
||||
isAdultContentVisible = isAdultContentVisible,
|
||||
contentType = contentType,
|
||||
@@ -311,15 +243,13 @@ class HomeService(
|
||||
listOf(theme)
|
||||
}
|
||||
|
||||
val contentList = contentService.getLatestContentByTheme(
|
||||
return contentService.getLatestContentByTheme(
|
||||
memberId = memberId,
|
||||
theme = themeList,
|
||||
contentType = contentType,
|
||||
isFree = false,
|
||||
isAdult = isAdult
|
||||
)
|
||||
|
||||
return getTranslatedContentList(contentList = contentList)
|
||||
}
|
||||
|
||||
fun getDayOfWeekSeriesList(
|
||||
@@ -331,14 +261,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(
|
||||
@@ -445,7 +373,7 @@ class HomeService(
|
||||
}
|
||||
}
|
||||
|
||||
return getTranslatedContentList(contentList = result.take(RECOMMEND_TARGET_SIZE).shuffled())
|
||||
return result.take(RECOMMEND_TARGET_SIZE).shuffled()
|
||||
}
|
||||
|
||||
private fun pickByTimeDecay(
|
||||
@@ -542,114 +470,6 @@ class HomeService(
|
||||
}
|
||||
}
|
||||
|
||||
return getTranslatedContentList(contentList = result.take(targetSize).shuffled())
|
||||
}
|
||||
|
||||
/**
|
||||
* 콘텐츠 리스트의 제목을 현재 언어(locale)에 맞춰 일괄 번역한다.
|
||||
*
|
||||
* 처리 절차:
|
||||
* - 입력된 콘텐츠들의 contentId 집합을 만들고, 요청 언어 코드(langContext.lang.code)로
|
||||
* contentTranslationRepository에서 번역 데이터를 한 번에 조회한다.
|
||||
* - 각 항목에 대해 번역된 제목이 존재하고 비어있지 않으면 title만 번역 값으로 교체한다.
|
||||
* - 번역이 없거나 공백이면 원본 항목을 그대로 반환한다.
|
||||
*
|
||||
* 성능:
|
||||
* - N건의 항목을 1회의 조회로 해결하기 위해 IN 쿼리를 사용한다.
|
||||
*
|
||||
* @param contentList 번역 대상 AudioContentMainItem 목록
|
||||
* @return 제목이 가능한 항목은 번역된 목록(불변 사본), 그 외는 원본 항목 유지
|
||||
*/
|
||||
private fun getTranslatedContentList(contentList: List<AudioContentMainItem>): List<AudioContentMainItem> {
|
||||
val contentIds = contentList.map { it.contentId }
|
||||
|
||||
return if (contentIds.isNotEmpty()) {
|
||||
val translations = contentTranslationRepository
|
||||
.findByContentIdInAndLocale(contentIds = contentIds, locale = langContext.lang.code)
|
||||
.associateBy { it.contentId }
|
||||
|
||||
contentList.map { item ->
|
||||
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
|
||||
if (translatedTitle.isNullOrBlank()) {
|
||||
item
|
||||
} else {
|
||||
item.copy(title = translatedTitle)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
contentList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 시리즈 리스트의 제목을 현재 언어(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)에 맞춰 일괄 번역한다.
|
||||
*
|
||||
* 처리 절차:
|
||||
* - characterId 목록을 추출하고, 요청 언어 코드로 aiCharacterTranslationRepository에서
|
||||
* 번역 데이터를 한 번에 조회한다.
|
||||
* - 각 캐릭터에 대해 name과 description 모두 번역 값이 존재하고 비어있지 않을 때에만
|
||||
* 해당 필드를 교체한다. 둘 중 하나라도 없으면 원본 캐릭터를 그대로 유지한다.
|
||||
*
|
||||
* @param aiCharacterList 번역 대상 캐릭터 목록
|
||||
* @return 가능한 경우 name/description이 번역된 캐릭터 목록, 그 외는 원본 유지
|
||||
*/
|
||||
private fun getTranslatedAiCharacterList(aiCharacterList: List<Character>): List<Character> {
|
||||
val characterIds = aiCharacterList.map { it.characterId }
|
||||
|
||||
return if (characterIds.isNotEmpty()) {
|
||||
val translations = aiCharacterTranslationRepository
|
||||
.findByCharacterIdInAndLocale(characterIds = characterIds, locale = langContext.lang.code)
|
||||
.associateBy { it.characterId }
|
||||
|
||||
aiCharacterList.map { character ->
|
||||
val translatedName = translations[character.characterId]?.renderedPayload?.name
|
||||
val translatedDesc = translations[character.characterId]?.renderedPayload?.description
|
||||
if (translatedName.isNullOrBlank() || translatedDesc.isNullOrBlank()) {
|
||||
character
|
||||
} else {
|
||||
character.copy(name = translatedName, description = translatedDesc)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
aiCharacterList
|
||||
}
|
||||
return result.take(targetSize).shuffled()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ class ChatCharacterController(
|
||||
}
|
||||
|
||||
// 인기 캐릭터 조회
|
||||
val popularCharacters = service.getPopularCharacters()
|
||||
val popularCharacters = service.getPopularCharacters(locale = langContext.lang.code)
|
||||
|
||||
// 최근 등록된 캐릭터 리스트 조회
|
||||
val newCharacters = service.getRecentCharactersPage(
|
||||
@@ -138,7 +138,7 @@ class ChatCharacterController(
|
||||
CharacterMainResponse(
|
||||
banners = banners,
|
||||
recentCharacters = translatedRecentCharacters,
|
||||
popularCharacters = getTranslatedAiCharacterList(popularCharacters),
|
||||
popularCharacters = popularCharacters,
|
||||
newCharacters = getTranslatedAiCharacterList(newCharacters),
|
||||
recommendCharacters = getTranslatedAiCharacterList(recommendCharacters),
|
||||
curationSections = curationSections
|
||||
|
||||
@@ -77,18 +77,23 @@ class ChatCharacterService(
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
@Cacheable(
|
||||
cacheNames = ["popularCharacters_24h"],
|
||||
key = "T(kr.co.vividnext.sodalive.chat.character.service.RankingWindowCalculator).now('popular-character').cacheKey"
|
||||
cacheNames = ["popularCharacters_24h_locale"],
|
||||
key = "T(kr.co.vividnext.sodalive.chat.character.service.RankingWindowCalculator)" +
|
||||
".now('popular-character').cacheKey + '-' + #locale"
|
||||
)
|
||||
fun getPopularCharacters(limit: Long = 20): List<Character> {
|
||||
fun getPopularCharacters(locale: String, limit: Long = 20): List<Character> {
|
||||
val window = RankingWindowCalculator.now("popular-character")
|
||||
val topIds = popularCharacterQuery.findPopularCharacterIds(window.windowStart, window.nextBoundary, limit)
|
||||
val list = loadCharactersInOrder(topIds)
|
||||
val results = popularCharacterQuery.findPopularCharactersWithTranslation(
|
||||
window.windowStart,
|
||||
window.nextBoundary,
|
||||
limit,
|
||||
locale
|
||||
)
|
||||
|
||||
val recentSet = if (list.isNotEmpty()) {
|
||||
val recentSet = if (results.isNotEmpty()) {
|
||||
imageRepository
|
||||
.findCharacterIdsWithRecentImages(
|
||||
list.map { it.id!! },
|
||||
results.map { it.id },
|
||||
LocalDateTime.now().minusDays(3)
|
||||
)
|
||||
.toSet()
|
||||
@@ -96,11 +101,11 @@ class ChatCharacterService(
|
||||
emptySet()
|
||||
}
|
||||
|
||||
return list.map {
|
||||
return results.map {
|
||||
Character(
|
||||
characterId = it.id!!,
|
||||
name = it.name,
|
||||
description = it.description,
|
||||
characterId = it.id,
|
||||
name = it.translatedPayload?.name.takeIf { name -> !name.isNullOrBlank() } ?: it.name,
|
||||
description = it.translatedPayload?.description.takeIf { desc -> !desc.isNullOrBlank() } ?: it.description,
|
||||
imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}",
|
||||
new = recentSet.contains(it.id)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.chat.character.service
|
||||
|
||||
import com.querydsl.core.types.Projections
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.chat.character.QChatCharacter
|
||||
import kr.co.vividnext.sodalive.chat.character.translate.AiCharacterTranslationRenderedPayload
|
||||
import kr.co.vividnext.sodalive.chat.character.translate.QAiCharacterTranslation
|
||||
import kr.co.vividnext.sodalive.chat.room.ParticipantType
|
||||
import kr.co.vividnext.sodalive.chat.room.QChatMessage
|
||||
import kr.co.vividnext.sodalive.chat.room.QChatParticipant
|
||||
@@ -51,4 +54,56 @@ class PopularCharacterQuery(
|
||||
.limit(limit)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
data class PopularCharacterQueryResult(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val imagePath: String?,
|
||||
val translatedPayload: AiCharacterTranslationRenderedPayload?
|
||||
)
|
||||
|
||||
fun findPopularCharactersWithTranslation(
|
||||
windowStart: Instant,
|
||||
endExclusive: Instant,
|
||||
limit: Long,
|
||||
locale: String
|
||||
): List<PopularCharacterQueryResult> {
|
||||
val m = QChatMessage.chatMessage
|
||||
val p = QChatParticipant.chatParticipant
|
||||
val c = QChatCharacter.chatCharacter
|
||||
val t = QAiCharacterTranslation.aiCharacterTranslation
|
||||
|
||||
val start = LocalDateTime.ofInstant(windowStart, ZoneOffset.UTC)
|
||||
val end = LocalDateTime.ofInstant(endExclusive, ZoneOffset.UTC)
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
PopularCharacterQueryResult::class.java,
|
||||
c.id,
|
||||
c.name,
|
||||
c.description,
|
||||
c.imagePath,
|
||||
t.renderedPayload
|
||||
)
|
||||
)
|
||||
.from(m)
|
||||
.join(p).on(
|
||||
p.chatRoom.id.eq(m.chatRoom.id)
|
||||
.and(p.participantType.eq(ParticipantType.CHARACTER))
|
||||
)
|
||||
.join(c).on(c.id.eq(p.character.id))
|
||||
.leftJoin(t).on(t.characterId.eq(c.id).and(t.locale.eq(locale)))
|
||||
.where(
|
||||
m.createdAt.goe(start)
|
||||
.and(m.createdAt.lt(end))
|
||||
.and(m.isActive.isTrue)
|
||||
.and(c.isActive.isTrue)
|
||||
)
|
||||
.groupBy(c.id, c.name, c.description, c.imagePath, t.id, t.renderedPayload)
|
||||
.orderBy(m.id.count().desc())
|
||||
.limit(limit)
|
||||
.fetch()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent
|
||||
import kr.co.vividnext.sodalive.content.playlist.AudioContentPlaylistContent
|
||||
import kr.co.vividnext.sodalive.content.playlist.QAudioContentPlaylistContent
|
||||
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
||||
import kr.co.vividnext.sodalive.content.translation.QContentTranslation.contentTranslation
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||
import kr.co.vividnext.sodalive.event.QEvent.event
|
||||
@@ -188,7 +189,8 @@ interface AudioContentQueryRepository {
|
||||
isAdult: Boolean,
|
||||
orderByRandom: Boolean = false,
|
||||
isPointAvailableOnly: Boolean = false,
|
||||
excludeContentIds: List<Long> = emptyList()
|
||||
excludeContentIds: List<Long> = emptyList(),
|
||||
locale: String? = null
|
||||
): List<AudioContentMainItem>
|
||||
|
||||
fun findContentByCurationId(
|
||||
@@ -1331,7 +1333,8 @@ class AudioContentQueryRepositoryImpl(
|
||||
isAdult: Boolean,
|
||||
orderByRandom: Boolean,
|
||||
isPointAvailableOnly: Boolean,
|
||||
excludeContentIds: List<Long>
|
||||
excludeContentIds: List<Long>,
|
||||
locale: String?
|
||||
): List<AudioContentMainItem> {
|
||||
val blockMemberCondition = if (memberId != null) {
|
||||
blockMember.member.id.eq(member.id)
|
||||
@@ -1382,12 +1385,27 @@ class AudioContentQueryRepositoryImpl(
|
||||
where = where.and(audioContent.id.notIn(excludeContentIds))
|
||||
}
|
||||
|
||||
val titleExpression = if (locale != null) {
|
||||
val translatedTitle = Expressions.stringTemplate(
|
||||
"JSON_EXTRACT({0}, '$.title')",
|
||||
contentTranslation.renderedPayload
|
||||
)
|
||||
val coalesceTitle = Expressions.stringTemplate(
|
||||
"COALESCE(NULLIF({0}, ''), {1})",
|
||||
translatedTitle,
|
||||
audioContent.title
|
||||
)
|
||||
coalesceTitle
|
||||
} else {
|
||||
audioContent.title
|
||||
}
|
||||
|
||||
var select = queryFactory
|
||||
.select(
|
||||
QAudioContentMainItem(
|
||||
audioContent.id,
|
||||
member.id,
|
||||
audioContent.title,
|
||||
titleExpression,
|
||||
audioContent.coverImage.prepend("/").prepend(imageHost),
|
||||
member.nickname,
|
||||
audioContent.isPointAvailable
|
||||
@@ -1397,6 +1415,11 @@ class AudioContentQueryRepositoryImpl(
|
||||
.innerJoin(audioContent.member, member)
|
||||
.innerJoin(audioContent.theme, audioContentTheme)
|
||||
|
||||
if (locale != null) {
|
||||
select = select.leftJoin(contentTranslation)
|
||||
.on(contentTranslation.contentId.eq(audioContent.id).and(contentTranslation.locale.eq(locale)))
|
||||
}
|
||||
|
||||
if (memberId != null) {
|
||||
where = where.and(blockMember.id.isNull)
|
||||
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
||||
|
||||
@@ -1222,7 +1222,7 @@ class AudioContentService(
|
||||
isPointAvailableOnly = isPointAvailableOnly
|
||||
)
|
||||
|
||||
val contentList = repository.getLatestContentByTheme(
|
||||
return repository.getLatestContentByTheme(
|
||||
memberId = memberId,
|
||||
theme = normalizedTheme,
|
||||
contentType = contentType,
|
||||
@@ -1233,26 +1233,9 @@ class AudioContentService(
|
||||
isAdult = isAdult,
|
||||
orderByRandom = orderByRandom,
|
||||
isPointAvailableOnly = isPointAvailableOnly,
|
||||
excludeContentIds = excludeContentIds
|
||||
excludeContentIds = excludeContentIds,
|
||||
locale = langContext.lang.code
|
||||
)
|
||||
|
||||
val contentIds = contentList.map { it.contentId }
|
||||
return if (contentIds.isNotEmpty()) {
|
||||
val translations = contentTranslationRepository
|
||||
.findByContentIdInAndLocale(contentIds = contentIds, locale = langContext.lang.code)
|
||||
.associateBy { it.contentId }
|
||||
|
||||
contentList.map { item ->
|
||||
val translatedTitle = translations[item.contentId]?.renderedPayload?.title
|
||||
if (translatedTitle.isNullOrBlank()) {
|
||||
item
|
||||
} else {
|
||||
item.copy(title = translatedTitle)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
contentList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -55,12 +52,21 @@ class ContentSeriesService(
|
||||
fun getOriginalAudioDramaList(
|
||||
isAdult: Boolean,
|
||||
contentType: ContentType,
|
||||
orderByRandom: Boolean = false,
|
||||
offset: Long = 0,
|
||||
limit: Long = 20
|
||||
): List<GetSeriesListResponse.SeriesListItem> {
|
||||
val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, contentType, orderByRandom, offset, limit)
|
||||
return getTranslatedSeriesList(seriesToSeriesListItem(originalAudioDramaList, isAdult, contentType))
|
||||
val originalAudioDramaList = repository.getOriginalAudioDramaList(
|
||||
imageHost = coverImageHost,
|
||||
isAdult = isAdult,
|
||||
contentType = contentType,
|
||||
locale = langContext.lang.code,
|
||||
offset = offset,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
return originalAudioDramaList.map { item ->
|
||||
item.copy(publishedDaysOfWeek = publishedDaysOfWeekText(item.rawPublishedDaysOfWeek))
|
||||
}
|
||||
}
|
||||
|
||||
fun getGenreList(memberId: Long, isAdult: Boolean, contentType: ContentType): List<GetSeriesGenreListResponse> {
|
||||
@@ -151,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,
|
||||
@@ -163,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(
|
||||
@@ -183,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
|
||||
@@ -459,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(
|
||||
@@ -480,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(
|
||||
@@ -497,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 {
|
||||
@@ -632,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package kr.co.vividnext.sodalive.content.series
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
|
||||
data class GetSeriesListResponse(
|
||||
val totalCount: Int,
|
||||
val items: List<SeriesListItem>
|
||||
) {
|
||||
data class SeriesListItem(
|
||||
data class SeriesListItem @QueryProjection constructor(
|
||||
val seriesId: Long,
|
||||
val title: String,
|
||||
val coverImage: String,
|
||||
@@ -13,10 +16,11 @@ data class GetSeriesListResponse(
|
||||
val creator: SeriesListItemCreator,
|
||||
var numberOfContent: Int = 0,
|
||||
var isNew: Boolean = false,
|
||||
var isPopular: Boolean = false
|
||||
var isPopular: Boolean = false,
|
||||
val rawPublishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek> = emptySet()
|
||||
)
|
||||
|
||||
data class SeriesListItemCreator(
|
||||
data class SeriesListItemCreator @QueryProjection constructor(
|
||||
val creatorId: Long,
|
||||
val nickname: String,
|
||||
val profileImage: String
|
||||
|
||||
@@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.content.ContentType
|
||||
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||
import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
|
||||
import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike
|
||||
import kr.co.vividnext.sodalive.content.translation.QContentTranslation.contentTranslation
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import kr.co.vividnext.sodalive.member.auth.QAuth.auth
|
||||
@@ -83,7 +84,8 @@ class RecommendChannelQueryRepository(
|
||||
fun getContentsByCreatorIdLikeDesc(
|
||||
creatorId: Long,
|
||||
memberId: Long?,
|
||||
isAdult: Boolean
|
||||
isAdult: Boolean,
|
||||
locale: String? = null
|
||||
): List<RecommendChannelContentItem> {
|
||||
val blockMemberCondition = if (memberId != null) {
|
||||
blockMember.member.id.eq(audioContent.member.id)
|
||||
@@ -99,11 +101,26 @@ class RecommendChannelQueryRepository(
|
||||
where = where.and(audioContent.isAdult.isFalse)
|
||||
}
|
||||
|
||||
val titleExpression = if (locale != null) {
|
||||
val translatedTitle = Expressions.stringTemplate(
|
||||
"JSON_EXTRACT({0}, '$.title')",
|
||||
contentTranslation.renderedPayload
|
||||
)
|
||||
val coalesceTitle = Expressions.stringTemplate(
|
||||
"COALESCE(NULLIF({0}, ''), {1})",
|
||||
translatedTitle,
|
||||
audioContent.title
|
||||
)
|
||||
coalesceTitle
|
||||
} else {
|
||||
audioContent.title
|
||||
}
|
||||
|
||||
var select = queryFactory
|
||||
.select(
|
||||
QRecommendChannelContentItem(
|
||||
audioContent.id,
|
||||
audioContent.title,
|
||||
titleExpression,
|
||||
audioContent.coverImage.prepend("/").prepend(imageHost),
|
||||
audioContentLike.id.countDistinct(),
|
||||
audioContentComment.id.countDistinct()
|
||||
@@ -121,6 +138,14 @@ class RecommendChannelQueryRepository(
|
||||
.and(audioContentComment.isActive.isTrue)
|
||||
)
|
||||
|
||||
if (locale != null) {
|
||||
select = select.leftJoin(contentTranslation)
|
||||
.on(
|
||||
contentTranslation.contentId.eq(audioContent.id)
|
||||
.and(contentTranslation.locale.eq(locale))
|
||||
)
|
||||
}
|
||||
|
||||
if (memberId != null) {
|
||||
where = where.and(blockMember.id.isNull)
|
||||
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package kr.co.vividnext.sodalive.query.recommend
|
||||
|
||||
import kr.co.vividnext.sodalive.content.ContentType
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import org.springframework.cache.annotation.Cacheable
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class RecommendChannelQueryService(private val repository: RecommendChannelQueryRepository) {
|
||||
class RecommendChannelQueryService(
|
||||
private val repository: RecommendChannelQueryRepository,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@Cacheable(
|
||||
cacheNames = ["default"],
|
||||
key = "'recommendChannel:' + (#memberId ?: 'guest') + ':' + #isAdult + ':' + #contentType"
|
||||
@@ -27,7 +31,8 @@ class RecommendChannelQueryService(private val repository: RecommendChannelQuery
|
||||
it.contentList = repository.getContentsByCreatorIdLikeDesc(
|
||||
creatorId = it.channelId,
|
||||
memberId = memberId,
|
||||
isAdult = isAdult
|
||||
isAdult = isAdult,
|
||||
locale = langContext.lang.code
|
||||
)
|
||||
|
||||
it
|
||||
|
||||
@@ -69,7 +69,8 @@ class RankingRepository(
|
||||
offset: Long,
|
||||
limit: Long,
|
||||
sortType: String,
|
||||
theme: String = ""
|
||||
theme: String = "",
|
||||
locale: String? = null
|
||||
): List<GetAudioContentRankingItem> {
|
||||
val blockMemberCondition = if (memberId != null) {
|
||||
blockMember.member.id.eq(member.id)
|
||||
@@ -79,6 +80,8 @@ class RankingRepository(
|
||||
null
|
||||
}
|
||||
|
||||
val contentTranslation = kr.co.vividnext.sodalive.content.translation.QContentTranslation.contentTranslation
|
||||
|
||||
var where = audioContent.isActive.isTrue
|
||||
.and(audioContent.member.isActive.isTrue)
|
||||
.and(audioContent.member.isNotNull)
|
||||
@@ -109,11 +112,26 @@ class RankingRepository(
|
||||
where = where.and(audioContentTheme.theme.eq(theme))
|
||||
}
|
||||
|
||||
val titleExpression = if (locale != null) {
|
||||
val translatedTitle = Expressions.stringTemplate(
|
||||
"JSON_EXTRACT({0}, '$.title')",
|
||||
contentTranslation.renderedPayload
|
||||
)
|
||||
val coalesceTitle = Expressions.stringTemplate(
|
||||
"COALESCE(NULLIF({0}, ''), {1})",
|
||||
translatedTitle,
|
||||
audioContent.title
|
||||
)
|
||||
coalesceTitle
|
||||
} else {
|
||||
audioContent.title
|
||||
}
|
||||
|
||||
var select = queryFactory
|
||||
.select(
|
||||
QGetAudioContentRankingItem(
|
||||
audioContent.id,
|
||||
audioContent.title,
|
||||
titleExpression,
|
||||
audioContent.coverImage.prepend("/").prepend(imageHost),
|
||||
audioContentTheme.theme,
|
||||
audioContent.price,
|
||||
@@ -167,6 +185,11 @@ class RankingRepository(
|
||||
}
|
||||
}
|
||||
|
||||
if (locale != null) {
|
||||
select = select.leftJoin(contentTranslation)
|
||||
.on(contentTranslation.contentId.eq(audioContent.id).and(contentTranslation.locale.eq(locale)))
|
||||
}
|
||||
|
||||
if (memberId != null) {
|
||||
where = where.and(blockMember.id.isNull)
|
||||
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.SeriesState
|
||||
import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.LocalDateTime
|
||||
@@ -17,6 +18,7 @@ import java.time.LocalDateTime
|
||||
class RankingService(
|
||||
private val repository: RankingRepository,
|
||||
private val seriesContentRepository: ContentSeriesContentRepository,
|
||||
private val langContext: LangContext,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val imageHost: String
|
||||
@@ -59,7 +61,8 @@ class RankingService(
|
||||
offset = offset,
|
||||
limit = limit,
|
||||
sortType = sortType,
|
||||
theme = theme
|
||||
theme = theme,
|
||||
locale = langContext.lang.code
|
||||
)
|
||||
loopCount++
|
||||
} while (contentList.size < 5 && loopCount < 5)
|
||||
|
||||
Reference in New Issue
Block a user