feat(content-all): 전체 목록 제목 번역을 적용한다
This commit is contained in:
+22
-1
@@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.content.order.QOrder
|
||||
import kr.co.vividnext.sodalive.content.series.translation.QSeriesTranslation
|
||||
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme
|
||||
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
||||
import kr.co.vividnext.sodalive.content.translation.QContentTranslation
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
@@ -58,6 +59,7 @@ class DefaultMainContentAllQueryRepository(
|
||||
sort: ContentSort,
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
locale: String,
|
||||
onlyPaid: Boolean,
|
||||
onlyFree: Boolean,
|
||||
onlyPointAvailable: Boolean
|
||||
@@ -69,13 +71,14 @@ class DefaultMainContentAllQueryRepository(
|
||||
val creatorIds = rows.map { it.get(audioContent.member.id)!! }.distinct()
|
||||
val firstContentIdByCreatorId = firstAudioContentIds(creatorIds, now, canViewAdultContent)
|
||||
val originalSeriesByContentId = originalSeriesFlags(contentIds)
|
||||
val titlesByContentId = audioTitlesByContentIds(contentIds, locale)
|
||||
|
||||
return rows.map { row ->
|
||||
val contentId = row.get(audioContent.id)!!
|
||||
val creatorId = row.get(audioContent.member.id)!!
|
||||
MainContentAllAudio(
|
||||
audioContentId = contentId,
|
||||
title = row.get(audioContent.title)!!,
|
||||
title = titlesByContentId[contentId] ?: row.get(audioContent.title)!!,
|
||||
imageUrl = row.get(audioContent.coverImage).toCdnUrl(cloudFrontHost),
|
||||
price = row.get(audioContent.price)!!,
|
||||
isAdult = row.get(audioContent.isAdult)!!,
|
||||
@@ -343,6 +346,24 @@ class DefaultMainContentAllQueryRepository(
|
||||
.associate { it.get(seriesContent.content.id)!! to it.get(series.isOriginal)!! }
|
||||
}
|
||||
|
||||
private fun audioTitlesByContentIds(contentIds: List<Long>, locale: String): Map<Long, String> {
|
||||
if (contentIds.isEmpty()) return emptyMap()
|
||||
val contentTranslation = QContentTranslation("mainContentAllContentTranslation")
|
||||
return queryFactory
|
||||
.select(contentTranslation.contentId, contentTranslation.renderedPayload)
|
||||
.from(contentTranslation)
|
||||
.where(
|
||||
contentTranslation.contentId.`in`(contentIds),
|
||||
contentTranslation.locale.eq(locale)
|
||||
)
|
||||
.fetch()
|
||||
.mapNotNull {
|
||||
val title = it.get(contentTranslation.renderedPayload)?.title.takeUnless(String?::isNullOrBlank)
|
||||
title?.let { translatedTitle -> it.get(contentTranslation.contentId)!! to translatedTitle }
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
private fun audioCondition(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
|
||||
+69
-8
@@ -9,6 +9,8 @@ import kr.co.vividnext.sodalive.content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslation
|
||||
import kr.co.vividnext.sodalive.content.series.translation.SeriesTranslationPayload
|
||||
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslation
|
||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslationPayload
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
@@ -62,10 +64,28 @@ class DefaultMainContentAllQueryRepositoryTest @Autowired constructor(
|
||||
saveBlock(viewer, blockedCreator)
|
||||
flushAndClear()
|
||||
|
||||
val visible = repository.findAudios(viewer.id, canViewAdultContent = false, now, ContentSort.LATEST, 0, 20)
|
||||
val paidAudios = repository.findAudios(viewer.id, false, now, ContentSort.LATEST, 0, 20, onlyPaid = true)
|
||||
val freeAudios = repository.findAudios(null, false, now, ContentSort.LATEST, 0, 20, onlyFree = true)
|
||||
val pointAudios = repository.findAudios(null, false, now, ContentSort.LATEST, 0, 20, onlyPointAvailable = true)
|
||||
val visible = repository.findAudios(viewer.id, canViewAdultContent = false, now, ContentSort.LATEST, 0, 20, locale = "ko")
|
||||
val paidAudios = repository.findAudios(
|
||||
viewer.id,
|
||||
false,
|
||||
now,
|
||||
ContentSort.LATEST,
|
||||
0,
|
||||
20,
|
||||
locale = "ko",
|
||||
onlyPaid = true
|
||||
)
|
||||
val freeAudios = repository.findAudios(null, false, now, ContentSort.LATEST, 0, 20, locale = "ko", onlyFree = true)
|
||||
val pointAudios = repository.findAudios(
|
||||
null,
|
||||
false,
|
||||
now,
|
||||
ContentSort.LATEST,
|
||||
0,
|
||||
20,
|
||||
locale = "ko",
|
||||
onlyPointAvailable = true
|
||||
)
|
||||
|
||||
assertEquals(3, repository.countAudios(viewer.id, canViewAdultContent = false, now))
|
||||
assertEquals(1, repository.countAudios(viewer.id, false, now, onlyPaid = true))
|
||||
@@ -95,9 +115,9 @@ class DefaultMainContentAllQueryRepositoryTest @Autowired constructor(
|
||||
saveOrder(buyer, creator, middle, can = 1000, isActive = false)
|
||||
flushAndClear()
|
||||
|
||||
val priceHigh = repository.findAudios(null, false, now, ContentSort.PRICE_HIGH, 0, 20)
|
||||
val priceLow = repository.findAudios(null, false, now, ContentSort.PRICE_LOW, 0, 20)
|
||||
val popular = repository.findAudios(null, false, now, ContentSort.POPULAR, 0, 20)
|
||||
val priceHigh = repository.findAudios(null, false, now, ContentSort.PRICE_HIGH, 0, 20, locale = "ko")
|
||||
val priceLow = repository.findAudios(null, false, now, ContentSort.PRICE_LOW, 0, 20, locale = "ko")
|
||||
val popular = repository.findAudios(null, false, now, ContentSort.POPULAR, 0, 20, locale = "ko")
|
||||
|
||||
assertEquals(listOf(high.id, middle.id, low.id, first.id), priceHigh.map { it.audioContentId })
|
||||
assertEquals(listOf(low.id, first.id, middle.id, high.id), priceLow.map { it.audioContentId })
|
||||
@@ -118,11 +138,35 @@ class DefaultMainContentAllQueryRepositoryTest @Autowired constructor(
|
||||
val sameDateLowPrice = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100)
|
||||
flushAndClear()
|
||||
|
||||
val latest = repository.findAudios(null, false, now, ContentSort.LATEST, 0, 20)
|
||||
val latest = repository.findAudios(null, false, now, ContentSort.LATEST, 0, 20, locale = "ko")
|
||||
|
||||
assertEquals(listOf(sameDateLowPrice.id, sameDateHighPrice.id), latest.map { it.audioContentId })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("오디오 제목은 locale 번역값을 사용하고 blank 번역은 원문으로 fallback한다")
|
||||
fun shouldFindAudiosWithTranslatedTitleFallback() {
|
||||
val now = LocalDateTime.of(2026, 6, 25, 12, 0)
|
||||
val creator = saveMember("audio-translation-creator", MemberRole.CREATOR)
|
||||
val theme = saveTheme("audio-translation-theme")
|
||||
val fallback = saveAudioContent(creator, theme, now.minusDays(2), isAdult = false, price = 100)
|
||||
val translated = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100)
|
||||
saveContentTranslation(translated, "en", "Translated Main Audio")
|
||||
saveContentTranslation(translated, "ja", "翻訳メインオーディオ")
|
||||
saveContentTranslation(fallback, "en", " ")
|
||||
flushAndClear()
|
||||
|
||||
val enRecords = repository.findAudios(null, false, now, ContentSort.LATEST, 0, 20, locale = "en")
|
||||
val jaRecords = repository.findAudios(null, false, now, ContentSort.LATEST, 0, 20, locale = "ja")
|
||||
|
||||
assertEquals(enRecords.map { it.audioContentId }, jaRecords.map { it.audioContentId })
|
||||
assertEquals(listOf(translated.id, fallback.id), enRecords.map { it.audioContentId })
|
||||
assertEquals("Translated Main Audio", enRecords.first().title)
|
||||
assertEquals("翻訳メインオーディオ", jaRecords.first().title)
|
||||
assertEquals(fallback.title, enRecords.last().title)
|
||||
assertEquals(fallback.title, jaRecords.last().title)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("시리즈는 활성 creator, 성인 노출 정책, 차단 관계, 요일과 오리지널 필터를 반영한다")
|
||||
fun shouldFindSeriesWithVisibilityDayOfWeekAndOriginalFilters() {
|
||||
@@ -363,6 +407,23 @@ class DefaultMainContentAllQueryRepositoryTest @Autowired constructor(
|
||||
return translation
|
||||
}
|
||||
|
||||
private fun saveContentTranslation(content: AudioContent, locale: String, title: String): ContentTranslation {
|
||||
val translation = ContentTranslation(
|
||||
contentId = content.id!!,
|
||||
locale = locale,
|
||||
renderedPayload = ContentTranslationPayload(title = title, detail = "", tags = "")
|
||||
)
|
||||
entityManager.persist(translation)
|
||||
entityManager.flush()
|
||||
val payload = "{\"title\":\"$title\",\"detail\":\"\",\"tags\":\"\"}"
|
||||
entityManager.createNativeQuery(
|
||||
"update content_translation set rendered_payload = '$payload' format json where id = :id"
|
||||
)
|
||||
.setParameter("id", translation.id)
|
||||
.executeUpdate()
|
||||
return translation
|
||||
}
|
||||
|
||||
private fun saveOrder(
|
||||
member: Member,
|
||||
creator: Member,
|
||||
|
||||
Reference in New Issue
Block a user