test #426

Merged
klaus merged 415 commits from test into main 2026-06-27 00:35:30 +00:00
3 changed files with 647 additions and 0 deletions
Showing only changes of commit 67fe0ec497 - Show all commits

View File

@@ -0,0 +1,5 @@
package kr.co.vividnext.sodalive.v2.creator.channel.series.adapter.out.persistence
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesQueryPort
interface CreatorChannelSeriesQueryRepository : CreatorChannelSeriesQueryPort

View File

@@ -0,0 +1,288 @@
package kr.co.vividnext.sodalive.v2.creator.channel.series.adapter.out.persistence
import com.querydsl.core.Tuple
import com.querydsl.core.types.Projections
import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.core.types.dsl.CaseBuilder
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
import kr.co.vividnext.sodalive.content.order.OrderType
import kr.co.vividnext.sodalive.content.order.QOrder
import kr.co.vividnext.sodalive.content.series.translation.QSeriesTranslation
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.member.QMember.member
import kr.co.vividnext.sodalive.member.block.QBlockMember
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesCreatorRecord
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesRecord
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@Repository
class DefaultCreatorChannelSeriesQueryRepository(
private val queryFactory: JPAQueryFactory
) : CreatorChannelSeriesQueryRepository {
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelSeriesCreatorRecord? {
return queryFactory
.select(
Projections.constructor(
CreatorChannelSeriesCreatorRecord::class.java,
member.id,
member.role,
member.nickname
)
)
.from(member)
.where(
member.id.eq(creatorId),
member.isActive.isTrue
)
.fetchFirst()
}
override fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean {
val blockMember = QBlockMember("creatorChannelSeriesBlockMember")
return queryFactory
.select(blockMember.id)
.from(blockMember)
.where(
blockMember.isActive.isTrue,
blockMember.member.id.eq(viewerId).and(blockMember.blockedMember.id.eq(creatorId))
.or(blockMember.member.id.eq(creatorId).and(blockMember.blockedMember.id.eq(viewerId)))
)
.fetchFirst() != null
}
override fun countSeries(creatorId: Long, now: LocalDateTime, canViewAdultContent: Boolean): Int {
return queryFactory
.select(series.id.count())
.from(series)
.where(seriesCondition(creatorId, canViewAdultContent))
.fetchOne()
?.toInt()
?: 0
}
override fun findSeries(
creatorId: Long,
viewerId: Long,
now: LocalDateTime,
canViewAdultContent: Boolean,
sort: ContentSort,
locale: String,
offset: Long,
limit: Int
): List<CreatorChannelSeriesRecord> {
val seriesIds = findSeriesIds(creatorId, viewerId, now, canViewAdultContent, sort, offset, limit)
if (seriesIds.isEmpty()) return emptyList()
val seriesTranslation = QSeriesTranslation("creatorChannelSeriesTranslation")
val rows = findSeriesRows(seriesIds, locale, seriesTranslation)
val contentStats = contentStatsBySeriesIds(seriesIds, now, canViewAdultContent)
val purchaseStats = purchaseStatsBySeriesIds(seriesIds, viewerId, now, canViewAdultContent)
return rows.sortedBy { seriesIds.indexOf(it.get(series)!!.id!!) }
.map { row ->
val targetSeries = row.get(series)!!
val translatedTitle = row.get(seriesTranslation)
?.renderedPayload
?.title
val contentStat = contentStats[targetSeries.id] ?: SeriesContentStats()
CreatorChannelSeriesRecord(
seriesId = targetSeries.id!!,
title = translatedTitle.takeUnless(String?::isNullOrBlank) ?: targetSeries.title,
coverImagePath = targetSeries.coverImage,
publishedDaysOfWeek = targetSeries.publishedDaysOfWeek,
isOriginal = targetSeries.isOriginal,
isAdult = targetSeries.isAdult,
state = targetSeries.state,
contentCount = contentStat.contentCount,
purchasedContentCount = purchaseStats[targetSeries.id] ?: 0,
paidContentCount = contentStat.paidContentCount
)
}
}
private fun findSeriesIds(
creatorId: Long,
viewerId: Long,
now: LocalDateTime,
canViewAdultContent: Boolean,
sort: ContentSort,
offset: Long,
limit: Int
): List<Long> {
val revenueOrder = QOrder("seriesRevenueOrder")
val ownedOrder = QOrder("seriesOwnedOrder")
val latestReleaseDate = audioContent.releaseDate.max()
val highestPrice = audioContent.price.max()
val lowestPrice = audioContent.price.min()
val revenue = revenueOrder.can.sum().coalesce(0)
val ownedCount = ownedOrder.audioContent.id.countDistinct()
val latestReleaseDateNullLast = CaseBuilder().`when`(latestReleaseDate.isNull).then(1).otherwise(0)
val highestPriceNullLast = CaseBuilder().`when`(highestPrice.isNull).then(1).otherwise(0)
val lowestPriceNullLast = CaseBuilder().`when`(lowestPrice.isNull).then(1).otherwise(0)
val query = queryFactory
.select(series.id)
.from(series)
.leftJoin(seriesContent).on(seriesContent.series.id.eq(series.id))
.leftJoin(audioContent).on(
seriesContent.content.id.eq(audioContent.id),
publicAudioContentCondition(now, canViewAdultContent)
)
.where(seriesCondition(creatorId, canViewAdultContent))
.groupBy(series.id)
when (sort) {
ContentSort.POPULAR -> {
query
.leftJoin(revenueOrder)
.on(
revenueOrder.audioContent.id.eq(audioContent.id),
revenueOrder.isActive.isTrue
)
.orderBy(revenue.desc(), latestReleaseDate.desc(), series.id.desc())
}
ContentSort.OWNED -> {
query
.leftJoin(ownedOrder)
.on(
ownedOrder.audioContent.id.eq(audioContent.id),
ownedOrder.member.id.eq(viewerId),
ownedOrder.isActive.isTrue,
validPurchasedOrderCondition(ownedOrder, now)
)
.orderBy(ownedCount.desc(), latestReleaseDate.desc(), series.id.desc())
}
ContentSort.LATEST -> query.orderBy(
latestReleaseDateNullLast.asc(),
latestReleaseDate.desc(),
highestPrice.desc(),
series.id.desc()
)
ContentSort.PRICE_HIGH -> query.orderBy(
highestPriceNullLast.asc(),
highestPrice.desc(),
latestReleaseDate.desc(),
series.id.desc()
)
ContentSort.PRICE_LOW -> query.orderBy(
lowestPriceNullLast.asc(),
lowestPrice.asc(),
latestReleaseDate.desc(),
series.id.desc()
)
}
return query.offset(offset).limit(limit.toLong()).fetch()
}
private fun findSeriesRows(
seriesIds: List<Long>,
locale: String,
seriesTranslation: QSeriesTranslation
): List<Tuple> {
return queryFactory
.select(series, seriesTranslation)
.from(series)
.leftJoin(seriesTranslation)
.on(
seriesTranslation.seriesId.eq(series.id),
seriesTranslation.locale.eq(locale)
)
.where(series.id.`in`(seriesIds))
.fetch()
}
private fun contentStatsBySeriesIds(
seriesIds: List<Long>,
now: LocalDateTime,
canViewAdultContent: Boolean
): Map<Long, SeriesContentStats> {
val paidContentCount = CaseBuilder()
.`when`(audioContent.price.gt(0))
.then(audioContent.id)
.otherwise(null as Long?)
.countDistinct()
return queryFactory
.select(
seriesContent.series.id,
audioContent.id.countDistinct(),
paidContentCount
)
.from(seriesContent)
.innerJoin(seriesContent.content, audioContent)
.where(
seriesContent.series.id.`in`(seriesIds),
publicAudioContentCondition(now, canViewAdultContent)
)
.groupBy(seriesContent.series.id)
.fetch()
.associate {
it.get(seriesContent.series.id)!! to SeriesContentStats(
contentCount = it.get(audioContent.id.countDistinct())?.toInt() ?: 0,
paidContentCount = it.get(paidContentCount)?.toInt() ?: 0
)
}
}
private fun purchaseStatsBySeriesIds(
seriesIds: List<Long>,
viewerId: Long,
now: LocalDateTime,
canViewAdultContent: Boolean
): Map<Long, Int> {
val purchasedOrder = QOrder("seriesPurchasedOrder")
return queryFactory
.select(seriesContent.series.id, audioContent.id.countDistinct())
.from(seriesContent)
.innerJoin(seriesContent.content, audioContent)
.innerJoin(purchasedOrder)
.on(purchasedOrder.audioContent.id.eq(audioContent.id))
.where(
seriesContent.series.id.`in`(seriesIds),
publicAudioContentCondition(now, canViewAdultContent),
audioContent.price.gt(0),
purchasedOrder.member.id.eq(viewerId),
purchasedOrder.isActive.isTrue,
validPurchasedOrderCondition(purchasedOrder, now)
)
.groupBy(seriesContent.series.id)
.fetch()
.associate { it.get(seriesContent.series.id)!! to (it.get(audioContent.id.countDistinct())?.toInt() ?: 0) }
}
private fun seriesCondition(creatorId: Long, canViewAdultContent: Boolean): BooleanExpression {
return series.member.id.eq(creatorId)
.and(series.isActive.isTrue)
.and(adultSeriesCondition(canViewAdultContent))
}
private fun adultSeriesCondition(canViewAdultContent: Boolean): BooleanExpression? {
return if (canViewAdultContent) null else series.isAdult.isFalse
}
private fun publicAudioContentCondition(now: LocalDateTime, canViewAdultContent: Boolean): BooleanExpression {
return audioContent.isActive.isTrue
.and(audioContent.duration.isNotNull)
.and(audioContent.releaseDate.isNotNull)
.and(audioContent.releaseDate.loe(now))
.and(adultAudioCondition(canViewAdultContent))
}
private fun adultAudioCondition(canViewAdultContent: Boolean): BooleanExpression? {
return if (canViewAdultContent) null else audioContent.isAdult.isFalse
}
private fun validPurchasedOrderCondition(targetOrder: QOrder, now: LocalDateTime): BooleanExpression {
return targetOrder.type.eq(OrderType.KEEP)
.or(targetOrder.type.eq(OrderType.RENTAL).and(targetOrder.endDate.after(now)))
}
private data class SeriesContentStats(
val contentCount: Int = 0,
val paidContentCount: Int = 0
)
}

View File

@@ -0,0 +1,354 @@
package kr.co.vividnext.sodalive.v2.creator.channel.series.adapter.out.persistence
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.content.AudioContent
import kr.co.vividnext.sodalive.content.order.Order
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.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
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.block.BlockMember
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import java.time.LocalDateTime
import javax.persistence.EntityManager
@DataJpaTest(
properties = [
"spring.cache.type=none",
"spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;NON_KEYWORDS=VALUE"
]
)
@Import(QueryDslConfig::class)
class DefaultCreatorChannelSeriesQueryRepositoryTest @Autowired constructor(
private val entityManager: EntityManager,
queryFactory: JPAQueryFactory
) {
private val repository = DefaultCreatorChannelSeriesQueryRepository(queryFactory)
@Test
@DisplayName("활성 creator와 양방향 차단 관계를 조회한다")
fun shouldFindCreatorAndBlockedRelationship() {
val viewer = saveMember("series-viewer", MemberRole.USER)
val creator = saveMember("series-creator", MemberRole.CREATOR)
val inactiveCreator = saveMember("inactive-series-creator", MemberRole.CREATOR, isActive = false)
saveBlock(creator, viewer)
flushAndClear()
val record = repository.findCreator(creator.id!!, viewer.id!!)
val inactiveRecord = repository.findCreator(inactiveCreator.id!!, viewer.id!!)
assertEquals(creator.id, record!!.creatorId)
assertEquals(MemberRole.CREATOR, record.role)
assertEquals("series-creator", record.nickname)
assertNull(inactiveRecord)
assertTrue(repository.existsBlockedBetween(viewer.id!!, creator.id!!))
}
@Test
@DisplayName("시리즈 count는 활성 시리즈, creator, 성인 노출 정책을 반영한다")
fun shouldCountSeriesWithCreatorAndAdultVisibilityFilters() {
val now = LocalDateTime.of(2026, 6, 20, 12, 0)
val creator = saveMember("count-series-creator", MemberRole.CREATOR)
val otherCreator = saveMember("count-series-other-creator", MemberRole.CREATOR)
saveSeries("public-series", creator, isAdult = false)
saveSeries("adult-series", creator, isAdult = true)
saveSeries("inactive-series", creator, isAdult = false).isActive = false
saveSeries("other-creator-series", otherCreator, isAdult = false)
flushAndClear()
assertEquals(1, repository.countSeries(creator.id!!, now, canViewAdultContent = false))
assertEquals(2, repository.countSeries(creator.id!!, now, canViewAdultContent = true))
}
@Test
@DisplayName("목록은 시리즈 필드, 번역 fallback, 공개 콘텐츠 통계와 구매 통계를 반환한다")
fun shouldFindSeriesWithFieldsTranslationsAndStats() {
val now = LocalDateTime.of(2026, 6, 20, 12, 0)
val viewer = saveMember("field-series-viewer", MemberRole.USER)
val creator = saveMember("field-series-creator", MemberRole.CREATOR)
val theme = saveTheme("field-theme")
val translated = saveSeries("translated-series", creator, isOriginal = true, state = SeriesState.PROCEEDING).apply {
publishedDaysOfWeek.addAll(setOf(SeriesPublishedDaysOfWeek.MON, SeriesPublishedDaysOfWeek.THU))
}
val blankTranslated = saveSeries("blank-fallback-series", creator, state = SeriesState.COMPLETE).apply {
publishedDaysOfWeek.add(SeriesPublishedDaysOfWeek.RANDOM)
}
val publicPaid = saveAudioContent(creator, theme, now.minusDays(3), isAdult = false, price = 300)
val publicFree = saveAudioContent(creator, theme, now.minusDays(2), isAdult = false, price = 0)
val future = saveAudioContent(creator, theme, now.plusDays(1), isAdult = false, price = 100)
val nullRelease = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100).apply {
releaseDate = null
}
val noDuration = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100).apply {
duration = null
}
val adultContent = saveAudioContent(creator, theme, now.minusDays(1), isAdult = true, price = 100)
saveSeriesContent(translated, publicPaid)
saveSeriesContent(translated, publicFree)
saveSeriesContent(translated, future)
saveSeriesContent(translated, nullRelease)
saveSeriesContent(translated, noDuration)
saveSeriesContent(translated, adultContent)
saveSeriesContent(blankTranslated, saveAudioContent(creator, theme, now.minusDays(4), isAdult = false, price = 100))
saveSeriesTranslation(translated, "en", "Translated Series")
saveSeriesTranslation(blankTranslated, "en", " ")
saveOrder(viewer, creator, publicPaid, OrderType.KEEP)
saveOrder(viewer, creator, publicPaid, OrderType.RENTAL, endDate = now.plusDays(1))
saveOrder(viewer, creator, future, OrderType.KEEP)
flushAndClear()
val records = repository.findSeries(
creator.id!!,
viewer.id!!,
now,
canViewAdultContent = false,
ContentSort.LATEST,
"en",
offset = 0,
limit = 20
)
val translatedRecord = records.first { it.seriesId == translated.id }
val blankRecord = records.first { it.seriesId == blankTranslated.id }
assertEquals("Translated Series", translatedRecord.title)
assertEquals("translated-series.png", translatedRecord.coverImagePath)
assertEquals(setOf(SeriesPublishedDaysOfWeek.MON, SeriesPublishedDaysOfWeek.THU), translatedRecord.publishedDaysOfWeek)
assertEquals(true, translatedRecord.isOriginal)
assertEquals(false, translatedRecord.isAdult)
assertEquals(SeriesState.PROCEEDING, translatedRecord.state)
assertEquals(2, translatedRecord.contentCount)
assertEquals(1, translatedRecord.paidContentCount)
assertEquals(1, translatedRecord.purchasedContentCount)
assertEquals("blank-fallback-series", blankRecord.title)
assertEquals(1, blankRecord.contentCount)
}
@Test
@DisplayName("목록은 최신순과 가격순 대표값 정렬을 적용한다")
fun shouldSortSeriesByLatestAndPriceRepresentatives() {
val now = LocalDateTime.of(2026, 6, 20, 12, 0)
val viewer = saveMember("sort-representative-viewer", MemberRole.USER)
val creator = saveMember("sort-representative-creator", MemberRole.CREATOR)
val theme = saveTheme("sort-representative-theme")
val oldHigh = saveSeries("old-high", creator)
val recentLow = saveSeries("recent-low", creator)
val sameDateHigh = saveSeries("same-date-high", creator)
saveSeriesContent(oldHigh, saveAudioContent(creator, theme, now.minusDays(5), isAdult = false, price = 500))
saveSeriesContent(recentLow, saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100))
saveSeriesContent(sameDateHigh, saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 300))
flushAndClear()
val latest = findSortedSeriesIds(creator.id!!, viewer.id!!, now, ContentSort.LATEST)
val priceHigh = findSortedSeriesIds(creator.id!!, viewer.id!!, now, ContentSort.PRICE_HIGH)
val priceLow = findSortedSeriesIds(creator.id!!, viewer.id!!, now, ContentSort.PRICE_LOW)
assertEquals(listOf(sameDateHigh.id, recentLow.id, oldHigh.id), latest)
assertEquals(listOf(oldHigh.id, sameDateHigh.id, recentLow.id), priceHigh)
assertEquals(listOf(recentLow.id, sameDateHigh.id, oldHigh.id), priceLow)
}
@Test
@DisplayName("목록은 인기순 can 합계와 소장순 유효 구매 개수 정렬을 적용한다")
fun shouldSortSeriesByPopularRevenueAndOwnedCount() {
val now = LocalDateTime.of(2026, 6, 20, 12, 0)
val viewer = saveMember("sort-order-viewer", MemberRole.USER)
val creator = saveMember("sort-order-creator", MemberRole.CREATOR)
val theme = saveTheme("sort-order-theme")
val popular = saveSeries("popular-series", creator)
val owned = saveSeries("owned-series", creator)
val manyUnowned = saveSeries("many-unowned-series", creator)
val inactiveRevenue = saveSeries("inactive-revenue-series", creator)
val popularContent = saveAudioContent(creator, theme, now.minusDays(3), isAdult = false, price = 100)
val ownedKeep = saveAudioContent(creator, theme, now.minusDays(2), isAdult = false, price = 100)
val ownedRental = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100)
val expiredRental = saveAudioContent(creator, theme, now.minusDays(4), isAdult = false, price = 100)
val inactiveRevenueContent = saveAudioContent(creator, theme, now.minusDays(5), isAdult = false, price = 100)
saveSeriesContent(popular, popularContent)
saveSeriesContent(owned, ownedKeep)
saveSeriesContent(owned, ownedRental)
saveSeriesContent(owned, expiredRental)
saveSeriesContent(manyUnowned, saveAudioContent(creator, theme, now.minusHours(1), isAdult = false, price = 100))
saveSeriesContent(manyUnowned, saveAudioContent(creator, theme, now.minusHours(2), isAdult = false, price = 100))
saveSeriesContent(manyUnowned, saveAudioContent(creator, theme, now.minusHours(3), isAdult = false, price = 100))
saveSeriesContent(manyUnowned, saveAudioContent(creator, theme, now.minusHours(4), isAdult = false, price = 100))
saveSeriesContent(inactiveRevenue, inactiveRevenueContent)
saveOrder(viewer, creator, popularContent, OrderType.KEEP, can = 900)
saveOrder(viewer, creator, inactiveRevenueContent, OrderType.KEEP, isActive = false, can = 1000)
saveOrder(viewer, creator, ownedKeep, OrderType.KEEP)
saveOrder(viewer, creator, ownedRental, OrderType.RENTAL, endDate = now.plusDays(1))
saveOrder(viewer, creator, expiredRental, OrderType.RENTAL, endDate = now.minusDays(1))
flushAndClear()
val popularSorted = findSortedSeriesIds(creator.id!!, viewer.id!!, now, ContentSort.POPULAR)
val ownedSorted = findSortedSeriesIds(creator.id!!, viewer.id!!, now, ContentSort.OWNED)
assertEquals(popular.id, popularSorted.first())
assertEquals(listOf(owned.id, popular.id, manyUnowned.id, inactiveRevenue.id), ownedSorted)
assertEquals(inactiveRevenue.id, popularSorted.last())
}
private fun saveMember(nickname: String, role: MemberRole, isActive: Boolean = true): Member {
val member = Member(
email = "$nickname@test.com",
password = "password",
nickname = nickname,
profileImage = "$nickname.png",
role = role,
isActive = isActive
)
entityManager.persist(member)
return member
}
private fun saveBlock(member: Member, blockedMember: Member): BlockMember {
val block = BlockMember(isActive = true)
block.member = member
block.blockedMember = blockedMember
entityManager.persist(block)
return block
}
private fun saveTheme(name: String): AudioContentTheme {
val theme = AudioContentTheme(theme = name, image = "$name.png", isActive = true, orders = 1)
entityManager.persist(theme)
return theme
}
private fun saveAudioContent(
creator: Member,
theme: AudioContentTheme,
releaseDate: LocalDateTime,
isAdult: Boolean,
price: Int = 0
): AudioContent {
val content = AudioContent(
title = "audio-${creator.nickname}-$releaseDate",
detail = "detail",
languageCode = "ko",
releaseDate = releaseDate,
isAdult = isAdult,
price = price
)
content.member = creator
content.theme = theme
content.isActive = true
content.coverImage = "audio.png"
content.duration = "00:10:00"
entityManager.persist(content)
return content
}
private fun saveSeries(
title: String,
creator: Member,
isAdult: Boolean = false,
isOriginal: Boolean = false,
state: SeriesState = SeriesState.PROCEEDING
): Series {
val series = Series(
title = title,
introduction = "introduction",
languageCode = "ko",
state = state,
isAdult = isAdult,
isOriginal = isOriginal
)
series.member = creator
series.genre = saveSeriesGenre(title)
series.coverImage = "$title.png"
entityManager.persist(series)
return series
}
private fun saveSeriesGenre(name: String): SeriesGenre {
val genre = SeriesGenre(genre = "genre-$name", isAdult = false, isActive = true)
entityManager.persist(genre)
return genre
}
private fun saveSeriesContent(series: Series, content: AudioContent): SeriesContent {
val seriesContent = SeriesContent()
seriesContent.series = series
seriesContent.content = content
entityManager.persist(seriesContent)
return seriesContent
}
private fun saveSeriesTranslation(series: Series, locale: String, title: String): SeriesTranslation {
val translation = SeriesTranslation(
seriesId = series.id!!,
locale = locale,
renderedPayload = SeriesTranslationPayload(title = title, introduction = "", keywords = emptyList())
)
entityManager.persist(translation)
entityManager.flush()
val payload = "{\"title\":\"$title\",\"introduction\":\"\",\"keywords\":[]}"
entityManager.createNativeQuery(
"update series_translation set rendered_payload = '$payload' format json where id = :id"
)
.setParameter("id", translation.id)
.executeUpdate()
return translation
}
private fun saveOrder(
member: Member,
creator: Member,
content: AudioContent,
type: OrderType,
isActive: Boolean = true,
endDate: LocalDateTime? = null,
can: Int? = null
): Order {
val order = Order(type = type, isActive = isActive)
order.member = member
order.creator = creator
order.audioContent = content
can?.let { order.can = it }
entityManager.persist(order)
if (endDate != null) {
entityManager.flush()
order.endDate = endDate
}
return order
}
private fun findSortedSeriesIds(
creatorId: Long,
viewerId: Long,
now: LocalDateTime,
sort: ContentSort
): List<Long> {
return repository.findSeries(
creatorId,
viewerId,
now,
canViewAdultContent = false,
sort,
"ko",
offset = 0,
limit = 20
).map { it.seriesId }
}
private fun flushAndClear() {
entityManager.flush()
entityManager.clear()
}
}