feat(home-recommendation): 첫 오디오 제목 번역을 적용한다
This commit is contained in:
+19
-3
@@ -15,6 +15,7 @@ import kr.co.vividnext.sodalive.chat.room.QChatMessage.chatMessage
|
|||||||
import kr.co.vividnext.sodalive.chat.room.QChatParticipant.chatParticipant
|
import kr.co.vividnext.sodalive.chat.room.QChatParticipant.chatParticipant
|
||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
|
import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
|
||||||
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||||
import kr.co.vividnext.sodalive.event.QEvent.event
|
import kr.co.vividnext.sodalive.event.QEvent.event
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
|
||||||
@@ -50,7 +51,8 @@ import javax.persistence.EntityManager
|
|||||||
@Repository
|
@Repository
|
||||||
class DefaultHomeRecommendationQueryRepository(
|
class DefaultHomeRecommendationQueryRepository(
|
||||||
private val queryFactory: JPAQueryFactory,
|
private val queryFactory: JPAQueryFactory,
|
||||||
private val entityManager: EntityManager
|
private val entityManager: EntityManager,
|
||||||
|
private val contentTranslationRepository: ContentTranslationRepository
|
||||||
) : HomeRecommendationQueryRepository {
|
) : HomeRecommendationQueryRepository {
|
||||||
override fun findLiveRecommendations(
|
override fun findLiveRecommendations(
|
||||||
offset: Long,
|
offset: Long,
|
||||||
@@ -367,7 +369,8 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultContents: Boolean
|
includeAdultContents: Boolean,
|
||||||
|
locale: String
|
||||||
): List<HomeFirstAudioContentRecord> {
|
): List<HomeFirstAudioContentRecord> {
|
||||||
val sql = """
|
val sql = """
|
||||||
with creator_debut as (
|
with creator_debut as (
|
||||||
@@ -475,7 +478,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val rows = query.resultList as List<Array<Any?>>
|
val rows = query.resultList as List<Array<Any?>>
|
||||||
|
|
||||||
return rows.map { row ->
|
val firstAudioContents = rows.map { row ->
|
||||||
HomeFirstAudioContentRecord(
|
HomeFirstAudioContentRecord(
|
||||||
contentId = (row[0] as Number).toLong(),
|
contentId = (row[0] as Number).toLong(),
|
||||||
creatorId = (row[1] as Number).toLong(),
|
creatorId = (row[1] as Number).toLong(),
|
||||||
@@ -489,6 +492,19 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
isOriginalSeries = row[9].toNativeBoolean()
|
isOriginalSeries = row[9].toNativeBoolean()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (firstAudioContents.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
val translatedTitleByContentId = contentTranslationRepository
|
||||||
|
.findByContentIdInAndLocale(firstAudioContents.map { it.contentId }, locale)
|
||||||
|
.mapNotNull { translation ->
|
||||||
|
translation.renderedPayload.title
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
?.let { translation.contentId to it }
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
return firstAudioContents.map { audio ->
|
||||||
|
translatedTitleByContentId[audio.contentId]?.let { audio.copy(title = it) } ?: audio
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findAiCharacterSnapshots(
|
override fun findAiCharacterSnapshots(
|
||||||
|
|||||||
+8
-2
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.home.application
|
package kr.co.vividnext.sodalive.v2.api.home.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.i18n.Lang
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
|
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
|
||||||
@@ -26,7 +27,11 @@ import java.time.LocalDateTime
|
|||||||
|
|
||||||
class HomeRecommendationFacadeTest {
|
class HomeRecommendationFacadeTest {
|
||||||
private val queryPort = FakeHomeRecommendationQueryPort()
|
private val queryPort = FakeHomeRecommendationQueryPort()
|
||||||
private val queryService = HomeRecommendationQueryService(queryPort, EmptyRecommendationSnapshotPort())
|
private val queryService = HomeRecommendationQueryService(
|
||||||
|
queryPort,
|
||||||
|
EmptyRecommendationSnapshotPort(),
|
||||||
|
langContext = LangContext()
|
||||||
|
)
|
||||||
private val preferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
private val preferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||||
private val facade = HomeRecommendationFacade(queryService, preferenceService, "https://cdn.test")
|
private val facade = HomeRecommendationFacade(queryService, preferenceService, "https://cdn.test")
|
||||||
|
|
||||||
@@ -135,7 +140,8 @@ class HomeRecommendationFacadeTest {
|
|||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultContents: Boolean
|
includeAdultContents: Boolean,
|
||||||
|
locale: String
|
||||||
): List<HomeFirstAudioContentRecord> = firstAudioContents
|
): List<HomeFirstAudioContentRecord> = firstAudioContents
|
||||||
|
|
||||||
override fun findAiCharacterSnapshots(
|
override fun findAiCharacterSnapshots(
|
||||||
|
|||||||
+132
-13
@@ -21,6 +21,9 @@ import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
|||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
||||||
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
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.content.translation.ContentTranslationRepository
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
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.SeriesContent
|
||||||
import kr.co.vividnext.sodalive.event.Event
|
import kr.co.vividnext.sodalive.event.Event
|
||||||
@@ -63,9 +66,10 @@ import javax.persistence.Query
|
|||||||
@Import(QueryDslConfig::class)
|
@Import(QueryDslConfig::class)
|
||||||
class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
||||||
private val entityManager: EntityManager,
|
private val entityManager: EntityManager,
|
||||||
queryFactory: JPAQueryFactory
|
queryFactory: JPAQueryFactory,
|
||||||
|
contentTranslationRepository: ContentTranslationRepository
|
||||||
) {
|
) {
|
||||||
private val repository = DefaultHomeRecommendationQueryRepository(queryFactory, entityManager)
|
private val repository = DefaultHomeRecommendationQueryRepository(queryFactory, entityManager, contentTranslationRepository)
|
||||||
private val scorePolicy = RecommendationScorePolicy()
|
private val scorePolicy = RecommendationScorePolicy()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1565,12 +1569,76 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
).forEach { (content, createdAt) -> updateCreatedAt("AudioContent", content.id!!, createdAt) }
|
).forEach { (content, createdAt) -> updateCreatedAt("AudioContent", content.id!!, createdAt) }
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val contents = repository.findFirstAudioContents(now, limit = 10)
|
val contents = repository.findFirstAudioContents(now, limit = 10, locale = Lang.KO.code)
|
||||||
|
|
||||||
assertEquals(listOf(eligibleActive.id), contents.map { it.contentId })
|
assertEquals(listOf(eligibleActive.id), contents.map { it.contentId })
|
||||||
assertEquals(12, contents.single().price)
|
assertEquals(12, contents.single().price)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("첫 오디오 native query는 빈 문자열, 공백, 누락, 다른 언어 번역을 원문으로 대체한다")
|
||||||
|
fun shouldTranslateFirstAudioContentsByRequestedLocaleWithFallback() {
|
||||||
|
val now = LocalDateTime.of(2026, 5, 31, 10, 0)
|
||||||
|
val translated = saveAudioContent(
|
||||||
|
saveMember("translated-first-audio", MemberRole.CREATOR),
|
||||||
|
now.minusDays(1),
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
val empty = saveAudioContent(
|
||||||
|
saveMember("empty-first-audio", MemberRole.CREATOR),
|
||||||
|
now.minusDays(2),
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
val whitespace = saveAudioContent(
|
||||||
|
saveMember("whitespace-first-audio", MemberRole.CREATOR),
|
||||||
|
now.minusDays(3),
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
val otherLocaleOnly = saveAudioContent(
|
||||||
|
saveMember("other-locale-first-audio", MemberRole.CREATOR),
|
||||||
|
now.minusDays(4),
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
val missing = saveAudioContent(
|
||||||
|
saveMember("missing-first-audio", MemberRole.CREATOR),
|
||||||
|
now.minusDays(5),
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
saveContentTranslation(translated, "en", "first-audio-en")
|
||||||
|
saveContentTranslation(translated, "ja", "first-audio-ja")
|
||||||
|
saveContentTranslation(empty, "en", "")
|
||||||
|
saveContentTranslation(whitespace, "en", " ")
|
||||||
|
saveContentTranslation(otherLocaleOnly, "en", "other-locale-first-audio-en")
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val englishRecords = repository.findFirstAudioContents(now, limit = 10, locale = Lang.EN.code)
|
||||||
|
val japaneseRecords = repository.findFirstAudioContents(now, limit = 10, locale = Lang.JA.code)
|
||||||
|
|
||||||
|
assertEquals(englishRecords.map { it.contentId }, japaneseRecords.map { it.contentId })
|
||||||
|
assertEquals(
|
||||||
|
"first-audio-en",
|
||||||
|
englishRecords.single { it.contentId == translated.id }.title
|
||||||
|
)
|
||||||
|
assertEquals(empty.title, englishRecords.single { it.contentId == empty.id }.title)
|
||||||
|
assertEquals(whitespace.title, englishRecords.single { it.contentId == whitespace.id }.title)
|
||||||
|
assertEquals(
|
||||||
|
"other-locale-first-audio-en",
|
||||||
|
englishRecords.single { it.contentId == otherLocaleOnly.id }.title
|
||||||
|
)
|
||||||
|
assertEquals(missing.title, englishRecords.single { it.contentId == missing.id }.title)
|
||||||
|
assertEquals(
|
||||||
|
"first-audio-ja",
|
||||||
|
japaneseRecords.single { it.contentId == translated.id }.title
|
||||||
|
)
|
||||||
|
assertEquals(empty.title, japaneseRecords.single { it.contentId == empty.id }.title)
|
||||||
|
assertEquals(whitespace.title, japaneseRecords.single { it.contentId == whitespace.id }.title)
|
||||||
|
assertEquals(
|
||||||
|
otherLocaleOnly.title,
|
||||||
|
japaneseRecords.single { it.contentId == otherLocaleOnly.id }.title
|
||||||
|
)
|
||||||
|
assertEquals(missing.title, japaneseRecords.single { it.contentId == missing.id }.title)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("첫 오디오 콘텐츠는 예약/미공개 콘텐츠를 제외하고 releaseDate 최신성 점수순으로 조회한다")
|
@DisplayName("첫 오디오 콘텐츠는 예약/미공개 콘텐츠를 제외하고 releaseDate 최신성 점수순으로 조회한다")
|
||||||
fun shouldFindFirstAudioContentsOrderedByReleaseDateRecencyScoreExcludingScheduledContents() {
|
fun shouldFindFirstAudioContentsOrderedByReleaseDateRecencyScoreExcludingScheduledContents() {
|
||||||
@@ -1585,7 +1653,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
updateCreatedAt("AudioContent", old.id!!, now.minusDays(20))
|
updateCreatedAt("AudioContent", old.id!!, now.minusDays(20))
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val contents = repository.findFirstAudioContents(now, limit = 10)
|
val contents = repository.findFirstAudioContents(now, limit = 10, locale = Lang.KO.code)
|
||||||
|
|
||||||
assertEquals(listOf(fresh.id, old.id), contents.map { it.contentId })
|
assertEquals(listOf(fresh.id, old.id), contents.map { it.contentId })
|
||||||
}
|
}
|
||||||
@@ -1602,8 +1670,20 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
val oldest = saveAudioContent(oldestCreator, now.minusDays(21), isActive = true, isAdult = false)
|
val oldest = saveAudioContent(oldestCreator, now.minusDays(21), isActive = true, isAdult = false)
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val page0 = repository.findFirstAudioContents(now, offset = 0L, limit = 2, includeAdultContents = false)
|
val page0 = repository.findFirstAudioContents(
|
||||||
val page1 = repository.findFirstAudioContents(now, offset = 1L, limit = 2, includeAdultContents = false)
|
now,
|
||||||
|
offset = 0L,
|
||||||
|
limit = 2,
|
||||||
|
includeAdultContents = false,
|
||||||
|
locale = Lang.KO.code
|
||||||
|
)
|
||||||
|
val page1 = repository.findFirstAudioContents(
|
||||||
|
now,
|
||||||
|
offset = 1L,
|
||||||
|
limit = 2,
|
||||||
|
includeAdultContents = false,
|
||||||
|
locale = Lang.KO.code
|
||||||
|
)
|
||||||
|
|
||||||
assertEquals(listOf(newest.id, oldest.id), page0.map { it.contentId })
|
assertEquals(listOf(newest.id, oldest.id), page0.map { it.contentId })
|
||||||
assertEquals(listOf(oldest.id), page1.map { it.contentId })
|
assertEquals(listOf(oldest.id), page1.map { it.contentId })
|
||||||
@@ -1622,7 +1702,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
updateCreatedAt("AudioContent", content.id!!, now.minusDays(1))
|
updateCreatedAt("AudioContent", content.id!!, now.minusDays(1))
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val contents = repository.findFirstAudioContents(now, limit = 10)
|
val contents = repository.findFirstAudioContents(now, limit = 10, locale = Lang.KO.code)
|
||||||
|
|
||||||
assertEquals(false, contents.single().isAdult)
|
assertEquals(false, contents.single().isAdult)
|
||||||
assertEquals(true, contents.single().isOriginalSeries)
|
assertEquals(true, contents.single().isOriginalSeries)
|
||||||
@@ -1633,12 +1713,16 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
fun shouldMapNumericNativeBooleanFromFirstAudioContentRows() {
|
fun shouldMapNumericNativeBooleanFromFirstAudioContentRows() {
|
||||||
val mockEntityManager = Mockito.mock(EntityManager::class.java)
|
val mockEntityManager = Mockito.mock(EntityManager::class.java)
|
||||||
val mockQuery = Mockito.mock(Query::class.java)
|
val mockQuery = Mockito.mock(Query::class.java)
|
||||||
|
val mockContentTranslationRepository = Mockito.mock(ContentTranslationRepository::class.java)
|
||||||
val repository = DefaultHomeRecommendationQueryRepository(
|
val repository = DefaultHomeRecommendationQueryRepository(
|
||||||
JPAQueryFactory(mockEntityManager),
|
JPAQueryFactory(mockEntityManager),
|
||||||
mockEntityManager
|
mockEntityManager,
|
||||||
|
mockContentTranslationRepository
|
||||||
)
|
)
|
||||||
Mockito.`when`(mockEntityManager.createNativeQuery(anyString())).thenReturn(mockQuery)
|
Mockito.`when`(mockEntityManager.createNativeQuery(anyString())).thenReturn(mockQuery)
|
||||||
Mockito.`when`(mockQuery.setParameter(anyString(), any())).thenReturn(mockQuery)
|
Mockito.`when`(mockQuery.setParameter(anyString(), any())).thenReturn(mockQuery)
|
||||||
|
Mockito.`when`(mockContentTranslationRepository.findByContentIdInAndLocale(listOf(1L), Lang.KO.code))
|
||||||
|
.thenReturn(emptyList())
|
||||||
Mockito.`when`(mockQuery.resultList).thenReturn(
|
Mockito.`when`(mockQuery.resultList).thenReturn(
|
||||||
listOf(
|
listOf(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
@@ -1661,7 +1745,8 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
offset = 0L,
|
offset = 0L,
|
||||||
limit = 1,
|
limit = 1,
|
||||||
memberId = null,
|
memberId = null,
|
||||||
includeAdultContents = false
|
includeAdultContents = false,
|
||||||
|
locale = Lang.KO.code
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(true, contents.single().isOriginalSeries)
|
assertEquals(true, contents.single().isOriginalSeries)
|
||||||
@@ -1682,7 +1767,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
saveBlock(creatorBlockedViewer, viewer)
|
saveBlock(creatorBlockedViewer, viewer)
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val contents = repository.findFirstAudioContents(now, limit = 10, memberId = viewer.id)
|
val contents = repository.findFirstAudioContents(now, limit = 10, memberId = viewer.id, locale = Lang.KO.code)
|
||||||
|
|
||||||
assertEquals(listOf(visibleContent.id), contents.map { it.contentId })
|
assertEquals(listOf(visibleContent.id), contents.map { it.contentId })
|
||||||
}
|
}
|
||||||
@@ -1699,9 +1784,27 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
val content3 = saveAudioContent(creator3, now.minusDays(5), isActive = true)
|
val content3 = saveAudioContent(creator3, now.minusDays(5), isActive = true)
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val page0 = repository.findFirstAudioContents(now, offset = 0L, limit = 1, includeAdultContents = false)
|
val page0 = repository.findFirstAudioContents(
|
||||||
val page1 = repository.findFirstAudioContents(now, offset = 1L, limit = 1, includeAdultContents = false)
|
now,
|
||||||
val page2 = repository.findFirstAudioContents(now, offset = 2L, limit = 1, includeAdultContents = false)
|
offset = 0L,
|
||||||
|
limit = 1,
|
||||||
|
includeAdultContents = false,
|
||||||
|
locale = Lang.KO.code
|
||||||
|
)
|
||||||
|
val page1 = repository.findFirstAudioContents(
|
||||||
|
now,
|
||||||
|
offset = 1L,
|
||||||
|
limit = 1,
|
||||||
|
includeAdultContents = false,
|
||||||
|
locale = Lang.KO.code
|
||||||
|
)
|
||||||
|
val page2 = repository.findFirstAudioContents(
|
||||||
|
now,
|
||||||
|
offset = 2L,
|
||||||
|
limit = 1,
|
||||||
|
includeAdultContents = false,
|
||||||
|
locale = Lang.KO.code
|
||||||
|
)
|
||||||
|
|
||||||
val pagedContentIds = (page0 + page1 + page2).map { it.contentId }
|
val pagedContentIds = (page0 + page1 + page2).map { it.contentId }
|
||||||
assertEquals(setOf(content1.id, content2.id, content3.id), pagedContentIds.toSet())
|
assertEquals(setOf(content1.id, content2.id, content3.id), pagedContentIds.toSet())
|
||||||
@@ -2621,6 +2724,22 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveContentTranslation(content: AudioContent, locale: String, title: String) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
private fun saveTheme(name: String, isActive: Boolean = true): AudioContentTheme {
|
private fun saveTheme(name: String, isActive: Boolean = true): AudioContentTheme {
|
||||||
val theme = AudioContentTheme(
|
val theme = AudioContentTheme(
|
||||||
theme = name,
|
theme = name,
|
||||||
|
|||||||
Reference in New Issue
Block a user