test #426
@@ -0,0 +1,436 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence
|
||||||
|
|
||||||
|
import com.querydsl.core.Tuple
|
||||||
|
import com.querydsl.core.types.Expression
|
||||||
|
import com.querydsl.core.types.dsl.BooleanExpression
|
||||||
|
import com.querydsl.core.types.dsl.CaseBuilder
|
||||||
|
import com.querydsl.jpa.JPAExpressions
|
||||||
|
import com.querydsl.jpa.impl.JPAQuery
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||||
|
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.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
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
|
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.common.domain.toCdnUrl
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllAudio
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllSeries
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class DefaultMainContentAllQueryRepository(
|
||||||
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) : MainContentAllQueryRepository {
|
||||||
|
override fun countAudios(
|
||||||
|
memberId: Long?,
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
now: LocalDateTime,
|
||||||
|
onlyFree: Boolean,
|
||||||
|
onlyPointAvailable: Boolean
|
||||||
|
): Int {
|
||||||
|
return queryFactory
|
||||||
|
.select(audioContent.id.count())
|
||||||
|
.from(audioContent)
|
||||||
|
.join(audioContent.member, member)
|
||||||
|
.join(audioContent.theme, audioContentTheme)
|
||||||
|
.where(audioCondition(memberId, canViewAdultContent, now, onlyFree, onlyPointAvailable))
|
||||||
|
.fetchOne()
|
||||||
|
?.toInt()
|
||||||
|
?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAudios(
|
||||||
|
memberId: Long?,
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
now: LocalDateTime,
|
||||||
|
sort: ContentSort,
|
||||||
|
offset: Long,
|
||||||
|
limit: Int,
|
||||||
|
onlyFree: Boolean,
|
||||||
|
onlyPointAvailable: Boolean
|
||||||
|
): List<MainContentAllAudio> {
|
||||||
|
val rows = findAudioRows(memberId, canViewAdultContent, now, sort, offset, limit, onlyFree, onlyPointAvailable)
|
||||||
|
if (rows.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
val contentIds = rows.map { it.get(audioContent.id)!! }
|
||||||
|
val creatorIds = rows.map { it.get(audioContent.member.id)!! }.distinct()
|
||||||
|
val firstContentIdByCreatorId = firstAudioContentIds(creatorIds, now, canViewAdultContent)
|
||||||
|
val originalSeriesByContentId = originalSeriesFlags(contentIds)
|
||||||
|
|
||||||
|
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)!!,
|
||||||
|
imageUrl = row.get(audioContent.coverImage).toCdnUrl(cloudFrontHost),
|
||||||
|
price = row.get(audioContent.price)!!,
|
||||||
|
isAdult = row.get(audioContent.isAdult)!!,
|
||||||
|
isPointAvailable = row.get(audioContent.isPointAvailable)!!,
|
||||||
|
isFirstContent = firstContentIdByCreatorId[creatorId] == contentId,
|
||||||
|
isOriginalSeries = originalSeriesByContentId[contentId] ?: false,
|
||||||
|
creatorNickname = row.get(member.nickname)!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun countSeries(
|
||||||
|
memberId: Long?,
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
now: LocalDateTime,
|
||||||
|
onlyOriginal: Boolean,
|
||||||
|
dayOfWeek: SeriesPublishedDaysOfWeek?
|
||||||
|
): Int {
|
||||||
|
return queryFactory
|
||||||
|
.select(series.id.count())
|
||||||
|
.from(series)
|
||||||
|
.join(series.member, member)
|
||||||
|
.where(seriesCondition(memberId, canViewAdultContent, onlyOriginal, dayOfWeek))
|
||||||
|
.fetchOne()
|
||||||
|
?.toInt()
|
||||||
|
?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findSeries(
|
||||||
|
memberId: Long?,
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
now: LocalDateTime,
|
||||||
|
sort: ContentSort,
|
||||||
|
offset: Long,
|
||||||
|
limit: Int,
|
||||||
|
onlyOriginal: Boolean,
|
||||||
|
dayOfWeek: SeriesPublishedDaysOfWeek?,
|
||||||
|
locale: String
|
||||||
|
): List<MainContentAllSeries> {
|
||||||
|
val seriesIds = findSeriesIds(memberId, canViewAdultContent, now, sort, offset, limit, onlyOriginal, dayOfWeek)
|
||||||
|
if (seriesIds.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
val seriesTranslation = QSeriesTranslation("mainContentAllSeriesTranslation")
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
series.id,
|
||||||
|
series.title,
|
||||||
|
seriesTranslation.renderedPayload,
|
||||||
|
series.coverImage,
|
||||||
|
member.nickname,
|
||||||
|
series.isOriginal,
|
||||||
|
series.isAdult
|
||||||
|
)
|
||||||
|
.from(series)
|
||||||
|
.join(series.member, member)
|
||||||
|
.leftJoin(seriesTranslation)
|
||||||
|
.on(
|
||||||
|
seriesTranslation.seriesId.eq(series.id),
|
||||||
|
seriesTranslation.locale.eq(locale)
|
||||||
|
)
|
||||||
|
.where(series.id.`in`(seriesIds))
|
||||||
|
.fetch()
|
||||||
|
.sortedBy { seriesIds.indexOf(it.get(series.id)!!) }
|
||||||
|
.map { row ->
|
||||||
|
val translatedTitle = row.get(seriesTranslation.renderedPayload)?.title
|
||||||
|
MainContentAllSeries(
|
||||||
|
seriesId = row.get(series.id)!!,
|
||||||
|
title = translatedTitle.takeUnless(String?::isNullOrBlank) ?: row.get(series.title)!!,
|
||||||
|
coverImageUrl = row.get(series.coverImage).toCdnUrl(cloudFrontHost),
|
||||||
|
creatorNickname = row.get(member.nickname)!!,
|
||||||
|
isOriginal = row.get(series.isOriginal)!!,
|
||||||
|
isAdult = row.get(series.isAdult)!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findAudioRows(
|
||||||
|
memberId: Long?,
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
now: LocalDateTime,
|
||||||
|
sort: ContentSort,
|
||||||
|
offset: Long,
|
||||||
|
limit: Int,
|
||||||
|
onlyFree: Boolean,
|
||||||
|
onlyPointAvailable: Boolean
|
||||||
|
): List<Tuple> {
|
||||||
|
val query = queryFactory
|
||||||
|
.select(
|
||||||
|
audioContent.id,
|
||||||
|
audioContent.title,
|
||||||
|
audioContent.coverImage,
|
||||||
|
audioContent.price,
|
||||||
|
audioContent.isAdult,
|
||||||
|
audioContent.isPointAvailable,
|
||||||
|
audioContent.member.id,
|
||||||
|
member.nickname,
|
||||||
|
audioContent.releaseDate
|
||||||
|
)
|
||||||
|
.from(audioContent)
|
||||||
|
.join(audioContent.member, member)
|
||||||
|
.join(audioContent.theme, audioContentTheme)
|
||||||
|
.where(audioCondition(memberId, canViewAdultContent, now, onlyFree, onlyPointAvailable))
|
||||||
|
|
||||||
|
when (sort) {
|
||||||
|
ContentSort.POPULAR -> {
|
||||||
|
val revenueOrder = QOrder("mainContentAllAudioRevenueOrder")
|
||||||
|
query
|
||||||
|
.leftJoin(revenueOrder)
|
||||||
|
.on(
|
||||||
|
revenueOrder.audioContent.id.eq(audioContent.id),
|
||||||
|
revenueOrder.isActive.isTrue
|
||||||
|
)
|
||||||
|
.groupByAudioRow()
|
||||||
|
.orderBy(
|
||||||
|
revenueOrder.can.sum().coalesce(0).desc(),
|
||||||
|
audioContent.releaseDate.desc(),
|
||||||
|
audioContent.id.desc()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ContentSort.PRICE_HIGH -> query.orderBy(
|
||||||
|
audioContent.price.desc(),
|
||||||
|
audioContent.releaseDate.desc(),
|
||||||
|
audioContent.id.desc()
|
||||||
|
)
|
||||||
|
ContentSort.PRICE_LOW -> query.orderBy(
|
||||||
|
audioContent.price.asc(),
|
||||||
|
audioContent.releaseDate.desc(),
|
||||||
|
audioContent.id.desc()
|
||||||
|
)
|
||||||
|
ContentSort.LATEST,
|
||||||
|
ContentSort.OWNED -> query.orderBy(
|
||||||
|
audioContent.releaseDate.desc(),
|
||||||
|
audioContent.id.desc()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.offset(offset).limit(limit.toLong()).fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSeriesIds(
|
||||||
|
memberId: Long?,
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
now: LocalDateTime,
|
||||||
|
sort: ContentSort,
|
||||||
|
offset: Long,
|
||||||
|
limit: Int,
|
||||||
|
onlyOriginal: Boolean,
|
||||||
|
dayOfWeek: SeriesPublishedDaysOfWeek?
|
||||||
|
): List<Long> {
|
||||||
|
val audioCreator = QMember("mainContentAllSeriesAudioCreator")
|
||||||
|
val audioTheme = QAudioContentTheme("mainContentAllSeriesAudioTheme")
|
||||||
|
val revenueOrder = QOrder("mainContentAllSeriesRevenueOrder")
|
||||||
|
val publicSeriesAudioCondition = publicSeriesAudioCondition(canViewAdultContent, now, audioCreator, audioTheme)
|
||||||
|
val latestReleaseDate = CaseBuilder()
|
||||||
|
.`when`(publicSeriesAudioCondition)
|
||||||
|
.then(audioContent.releaseDate)
|
||||||
|
.otherwise(null as LocalDateTime?)
|
||||||
|
.max()
|
||||||
|
val highestPrice = CaseBuilder()
|
||||||
|
.`when`(publicSeriesAudioCondition)
|
||||||
|
.then(audioContent.price)
|
||||||
|
.otherwise(null as Int?)
|
||||||
|
.max()
|
||||||
|
val lowestPrice = CaseBuilder()
|
||||||
|
.`when`(publicSeriesAudioCondition)
|
||||||
|
.then(audioContent.price)
|
||||||
|
.otherwise(null as Int?)
|
||||||
|
.min()
|
||||||
|
val revenue = CaseBuilder()
|
||||||
|
.`when`(publicSeriesAudioCondition)
|
||||||
|
.then(revenueOrder.can)
|
||||||
|
.otherwise(0)
|
||||||
|
.sum()
|
||||||
|
.coalesce(0)
|
||||||
|
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)
|
||||||
|
.join(series.member, member)
|
||||||
|
.leftJoin(seriesContent).on(seriesContent.series.id.eq(series.id))
|
||||||
|
.leftJoin(audioContent).on(seriesContent.content.id.eq(audioContent.id))
|
||||||
|
.leftJoin(audioContent.member, audioCreator)
|
||||||
|
.leftJoin(audioContent.theme, audioTheme)
|
||||||
|
.where(seriesCondition(memberId, canViewAdultContent, onlyOriginal, dayOfWeek))
|
||||||
|
.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.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()
|
||||||
|
)
|
||||||
|
ContentSort.LATEST,
|
||||||
|
ContentSort.OWNED -> query.orderBy(
|
||||||
|
latestReleaseDateNullLast.asc(),
|
||||||
|
latestReleaseDate.desc(),
|
||||||
|
series.id.desc()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.offset(offset).limit(limit.toLong()).fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JPAQuery<Tuple>.groupByAudioRow(): JPAQuery<Tuple> {
|
||||||
|
return groupBy(
|
||||||
|
audioContent.id,
|
||||||
|
audioContent.title,
|
||||||
|
audioContent.coverImage,
|
||||||
|
audioContent.price,
|
||||||
|
audioContent.isAdult,
|
||||||
|
audioContent.isPointAvailable,
|
||||||
|
audioContent.member.id,
|
||||||
|
member.nickname,
|
||||||
|
audioContent.releaseDate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun firstAudioContentIds(
|
||||||
|
creatorIds: List<Long>,
|
||||||
|
now: LocalDateTime,
|
||||||
|
canViewAdultContent: Boolean
|
||||||
|
): Map<Long, Long> {
|
||||||
|
return creatorIds.associateWith { creatorId ->
|
||||||
|
queryFactory
|
||||||
|
.select(audioContent.id)
|
||||||
|
.from(audioContent)
|
||||||
|
.join(audioContent.member, member)
|
||||||
|
.join(audioContent.theme, audioContentTheme)
|
||||||
|
.where(
|
||||||
|
audioContent.member.id.eq(creatorId),
|
||||||
|
publicAudioCondition(canViewAdultContent, now)
|
||||||
|
)
|
||||||
|
.orderBy(audioContent.releaseDate.asc(), audioContent.id.asc())
|
||||||
|
.fetchFirst()
|
||||||
|
}.filterValues { it != null }.mapValues { it.value!! }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun originalSeriesFlags(contentIds: List<Long>): Map<Long, Boolean> {
|
||||||
|
if (contentIds.isEmpty()) return emptyMap()
|
||||||
|
return queryFactory
|
||||||
|
.select(seriesContent.content.id, series.isOriginal)
|
||||||
|
.from(seriesContent)
|
||||||
|
.join(seriesContent.series, series)
|
||||||
|
.where(seriesContent.content.id.`in`(contentIds))
|
||||||
|
.fetch()
|
||||||
|
.associate { it.get(seriesContent.content.id)!! to it.get(series.isOriginal)!! }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun audioCondition(
|
||||||
|
memberId: Long?,
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
now: LocalDateTime,
|
||||||
|
onlyFree: Boolean,
|
||||||
|
onlyPointAvailable: Boolean
|
||||||
|
): BooleanExpression {
|
||||||
|
return publicAudioCondition(canViewAdultContent, now)
|
||||||
|
.and(optionalAudioFreeCondition(onlyFree))
|
||||||
|
.and(optionalAudioPointCondition(onlyPointAvailable))
|
||||||
|
.withOptionalAnd(notBlockedCreatorCondition(memberId, audioContent.member.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun publicAudioCondition(canViewAdultContent: Boolean, now: LocalDateTime): BooleanExpression {
|
||||||
|
return audioContent.isActive.isTrue
|
||||||
|
.and(audioContent.duration.isNotNull)
|
||||||
|
.and(audioContent.releaseDate.isNotNull)
|
||||||
|
.and(audioContent.releaseDate.loe(now))
|
||||||
|
.and(audioContent.member.isActive.isTrue)
|
||||||
|
.and(audioContentTheme.isActive.isTrue)
|
||||||
|
.withOptionalAnd(adultAudioCondition(canViewAdultContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun publicSeriesAudioCondition(
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
now: LocalDateTime,
|
||||||
|
audioCreator: QMember,
|
||||||
|
audioTheme: QAudioContentTheme
|
||||||
|
): BooleanExpression {
|
||||||
|
return audioContent.isActive.isTrue
|
||||||
|
.and(audioContent.duration.isNotNull)
|
||||||
|
.and(audioContent.releaseDate.isNotNull)
|
||||||
|
.and(audioContent.releaseDate.loe(now))
|
||||||
|
.and(audioCreator.isActive.isTrue)
|
||||||
|
.and(audioTheme.isActive.isTrue)
|
||||||
|
.withOptionalAnd(adultAudioCondition(canViewAdultContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun seriesCondition(
|
||||||
|
memberId: Long?,
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
onlyOriginal: Boolean,
|
||||||
|
dayOfWeek: SeriesPublishedDaysOfWeek?
|
||||||
|
): BooleanExpression {
|
||||||
|
return series.isActive.isTrue
|
||||||
|
.and(member.isActive.isTrue)
|
||||||
|
.and(optionalOriginalCondition(onlyOriginal))
|
||||||
|
.withOptionalAnd(dayOfWeekCondition(dayOfWeek))
|
||||||
|
.withOptionalAnd(adultSeriesCondition(canViewAdultContent))
|
||||||
|
.withOptionalAnd(notBlockedCreatorCondition(memberId, series.member.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optionalAudioFreeCondition(onlyFree: Boolean): BooleanExpression? {
|
||||||
|
return if (onlyFree) audioContent.price.eq(0) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optionalAudioPointCondition(onlyPointAvailable: Boolean): BooleanExpression? {
|
||||||
|
return if (onlyPointAvailable) audioContent.isPointAvailable.isTrue else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optionalOriginalCondition(onlyOriginal: Boolean): BooleanExpression? {
|
||||||
|
return if (onlyOriginal) series.isOriginal.isTrue else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dayOfWeekCondition(dayOfWeek: SeriesPublishedDaysOfWeek?): BooleanExpression? {
|
||||||
|
return dayOfWeek?.let { series.publishedDaysOfWeek.contains(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun adultAudioCondition(canViewAdultContent: Boolean): BooleanExpression? {
|
||||||
|
return if (canViewAdultContent) null else audioContent.isAdult.isFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun adultSeriesCondition(canViewAdultContent: Boolean): BooleanExpression? {
|
||||||
|
return if (canViewAdultContent) null else series.isAdult.isFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notBlockedCreatorCondition(memberId: Long?, creatorIdPath: Expression<Long>): BooleanExpression? {
|
||||||
|
if (memberId == null) return null
|
||||||
|
val blockMember = QBlockMember("mainContentAllBlockMember")
|
||||||
|
return JPAExpressions
|
||||||
|
.selectOne()
|
||||||
|
.from(blockMember)
|
||||||
|
.where(
|
||||||
|
blockMember.isActive.isTrue,
|
||||||
|
blockMember.member.id.eq(memberId).and(blockMember.blockedMember.id.eq(creatorIdPath))
|
||||||
|
.or(blockMember.member.id.eq(creatorIdPath).and(blockMember.blockedMember.id.eq(memberId)))
|
||||||
|
)
|
||||||
|
.notExists()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BooleanExpression.withOptionalAnd(condition: BooleanExpression?): BooleanExpression {
|
||||||
|
return if (condition == null) this else and(condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.all.port.out.MainContentAllQueryPort
|
||||||
|
|
||||||
|
interface MainContentAllQueryRepository : MainContentAllQueryPort
|
||||||
@@ -0,0 +1,383 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.content.all.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.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.assertFalse
|
||||||
|
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 DefaultMainContentAllQueryRepositoryTest @Autowired constructor(
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
queryFactory: JPAQueryFactory
|
||||||
|
) {
|
||||||
|
private val repository = DefaultMainContentAllQueryRepository(queryFactory, "https://cdn.test")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("오디오는 공개 조건, 성인 노출 정책, 차단 관계, 무료/포인트 필터를 반영한다")
|
||||||
|
fun shouldFindPublicAudiosWithVisibilityAndTypeFilters() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 25, 12, 0)
|
||||||
|
val viewer = saveMember("audio-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("audio-creator", MemberRole.CREATOR)
|
||||||
|
val blockedCreator = saveMember("blocked-audio-creator", MemberRole.CREATOR)
|
||||||
|
val inactiveCreator = saveMember("inactive-audio-creator", MemberRole.CREATOR, isActive = false)
|
||||||
|
val theme = saveTheme("audio-theme")
|
||||||
|
val inactiveTheme = saveTheme("inactive-audio-theme", isActive = false)
|
||||||
|
val free = saveAudioContent(creator, theme, now.minusDays(3), isAdult = false, price = 0)
|
||||||
|
val point = saveAudioContent(creator, theme, now.minusDays(2), isAdult = false, price = 100, isPointAvailable = true)
|
||||||
|
saveAudioContent(creator, theme, now.minusDays(1), isAdult = true, price = 200)
|
||||||
|
saveAudioContent(blockedCreator, theme, now.minusDays(1), isAdult = false, price = 100)
|
||||||
|
saveAudioContent(inactiveCreator, theme, now.minusDays(1), isAdult = false, price = 100)
|
||||||
|
saveAudioContent(creator, inactiveTheme, now.minusDays(1), isAdult = false, price = 100)
|
||||||
|
saveAudioContent(creator, theme, now.plusDays(1), isAdult = false, price = 100)
|
||||||
|
saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100).duration = null
|
||||||
|
saveBlock(viewer, blockedCreator)
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val visible = repository.findAudios(viewer.id, canViewAdultContent = false, now, ContentSort.LATEST, 0, 20)
|
||||||
|
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)
|
||||||
|
|
||||||
|
assertEquals(2, repository.countAudios(viewer.id, canViewAdultContent = false, now))
|
||||||
|
assertEquals(listOf(point.id, free.id), visible.map { it.audioContentId })
|
||||||
|
assertEquals(listOf(free.id), freeAudios.map { it.audioContentId })
|
||||||
|
assertEquals(listOf(point.id), pointAudios.map { it.audioContentId })
|
||||||
|
assertEquals("https://cdn.test/audio.png", visible.first().imageUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("오디오 목록은 가격순과 인기순 can 매출 정렬, 첫 콘텐츠, 오리지널 시리즈 여부를 반환한다")
|
||||||
|
fun shouldSortAudiosAndReturnEnrichedFields() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 25, 12, 0)
|
||||||
|
val buyer = saveMember("audio-buyer", MemberRole.USER)
|
||||||
|
val creator = saveMember("audio-sort-creator", MemberRole.CREATOR)
|
||||||
|
val theme = saveTheme("audio-sort-theme")
|
||||||
|
val first = saveAudioContent(creator, theme, now.minusDays(10), isAdult = false, price = 100)
|
||||||
|
val low = saveAudioContent(creator, theme, now.minusDays(3), isAdult = false, price = 100)
|
||||||
|
val high = saveAudioContent(creator, theme, now.minusDays(2), isAdult = false, price = 300)
|
||||||
|
val middle = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 200)
|
||||||
|
val original = saveSeries("original-audio-series", creator, isOriginal = true)
|
||||||
|
saveSeriesContent(original, high)
|
||||||
|
saveOrder(buyer, creator, low, can = 500, point = 9000)
|
||||||
|
saveOrder(buyer, creator, high, can = 100, point = 9999)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 })
|
||||||
|
assertEquals(listOf(low.id, high.id, middle.id, first.id), popular.map { it.audioContentId })
|
||||||
|
assertTrue(priceHigh.last().isFirstContent)
|
||||||
|
assertTrue(priceHigh.first().isOriginalSeries)
|
||||||
|
assertFalse(priceHigh[1].isOriginalSeries)
|
||||||
|
assertEquals("audio-sort-creator", priceHigh.first().creatorNickname)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("오디오 최신순은 동일 공개일에서 가격이 아니라 id desc로 정렬한다")
|
||||||
|
fun shouldSortAudiosByLatestReleaseDateAndIdOnly() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 25, 12, 0)
|
||||||
|
val creator = saveMember("audio-latest-creator", MemberRole.CREATOR)
|
||||||
|
val theme = saveTheme("audio-latest-theme")
|
||||||
|
val sameDateHighPrice = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 500)
|
||||||
|
val sameDateLowPrice = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100)
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val latest = repository.findAudios(null, false, now, ContentSort.LATEST, 0, 20)
|
||||||
|
|
||||||
|
assertEquals(listOf(sameDateLowPrice.id, sameDateHighPrice.id), latest.map { it.audioContentId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈는 활성 creator, 성인 노출 정책, 차단 관계, 요일과 오리지널 필터를 반영한다")
|
||||||
|
fun shouldFindSeriesWithVisibilityDayOfWeekAndOriginalFilters() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 25, 12, 0)
|
||||||
|
val viewer = saveMember("series-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("series-creator", MemberRole.CREATOR)
|
||||||
|
val blockedCreator = saveMember("blocked-series-creator", MemberRole.CREATOR)
|
||||||
|
val inactiveCreator = saveMember("inactive-series-creator", MemberRole.CREATOR, isActive = false)
|
||||||
|
val mon = saveSeries("mon-series", creator).apply { publishedDaysOfWeek.add(SeriesPublishedDaysOfWeek.MON) }
|
||||||
|
val random = saveSeries("random-series", creator).apply { publishedDaysOfWeek.add(SeriesPublishedDaysOfWeek.RANDOM) }
|
||||||
|
val original = saveSeries("original-series", creator, isOriginal = true).apply {
|
||||||
|
publishedDaysOfWeek.add(SeriesPublishedDaysOfWeek.TUE)
|
||||||
|
}
|
||||||
|
saveSeries("adult-series", creator, isAdult = true).publishedDaysOfWeek.add(SeriesPublishedDaysOfWeek.MON)
|
||||||
|
saveSeries("blocked-series", blockedCreator).publishedDaysOfWeek.add(SeriesPublishedDaysOfWeek.MON)
|
||||||
|
saveSeries("inactive-creator-series", inactiveCreator).publishedDaysOfWeek.add(SeriesPublishedDaysOfWeek.MON)
|
||||||
|
saveSeries("inactive-series", creator).apply {
|
||||||
|
isActive = false
|
||||||
|
publishedDaysOfWeek.add(SeriesPublishedDaysOfWeek.MON)
|
||||||
|
}
|
||||||
|
saveBlock(viewer, blockedCreator)
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val monSeries = repository.findSeries(
|
||||||
|
viewer.id,
|
||||||
|
false,
|
||||||
|
now,
|
||||||
|
ContentSort.LATEST,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
dayOfWeek = SeriesPublishedDaysOfWeek.MON,
|
||||||
|
locale = "ko"
|
||||||
|
)
|
||||||
|
val randomSeries = repository.findSeries(
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
now,
|
||||||
|
ContentSort.LATEST,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
dayOfWeek = SeriesPublishedDaysOfWeek.RANDOM,
|
||||||
|
locale = "ko"
|
||||||
|
)
|
||||||
|
val originalSeries = repository.findSeries(
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
now,
|
||||||
|
ContentSort.LATEST,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
onlyOriginal = true,
|
||||||
|
dayOfWeek = null,
|
||||||
|
locale = "ko"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(1, repository.countSeries(viewer.id, false, now, dayOfWeek = SeriesPublishedDaysOfWeek.MON))
|
||||||
|
assertEquals(listOf(mon.id), monSeries.map { it.seriesId })
|
||||||
|
assertEquals(listOf(random.id), randomSeries.map { it.seriesId })
|
||||||
|
assertEquals(listOf(original.id), originalSeries.map { it.seriesId })
|
||||||
|
assertEquals("https://cdn.test/mon-series.png", monSeries.first().coverImageUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 제목은 locale 번역값을 사용하고 blank 번역은 원문으로 fallback한다")
|
||||||
|
fun shouldFindSeriesWithTranslatedTitleFallback() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 25, 12, 0)
|
||||||
|
val creator = saveMember("series-translation-creator", MemberRole.CREATOR)
|
||||||
|
val translated = saveSeries("origin-translated-series", creator)
|
||||||
|
val blankTranslated = saveSeries("origin-blank-series", creator)
|
||||||
|
saveSeriesTranslation(translated, "en", "Translated Series")
|
||||||
|
saveSeriesTranslation(blankTranslated, "en", " ")
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val records = repository.findSeries(null, false, now, ContentSort.LATEST, 0, 20, locale = "en")
|
||||||
|
|
||||||
|
assertEquals("Translated Series", records.first { it.seriesId == translated.id }.title)
|
||||||
|
assertEquals("origin-blank-series", records.first { it.seriesId == blankTranslated.id }.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 목록은 공개 오디오 대표값으로 최신순, 가격순, 인기순 can 매출 정렬을 적용한다")
|
||||||
|
fun shouldSortSeriesByPublicAudioRepresentatives() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 25, 12, 0)
|
||||||
|
val buyer = saveMember("series-buyer", MemberRole.USER)
|
||||||
|
val creator = saveMember("series-sort-creator", MemberRole.CREATOR)
|
||||||
|
val inactiveAudioCreator = saveMember("inactive-audio-creator-for-series", MemberRole.CREATOR, isActive = false)
|
||||||
|
val theme = saveTheme("series-sort-theme")
|
||||||
|
val inactiveTheme = saveTheme("inactive-series-sort-theme", isActive = false)
|
||||||
|
val oldHigh = saveSeries("old-high", creator)
|
||||||
|
val recentLow = saveSeries("recent-low", creator)
|
||||||
|
val sameDateHigh = saveSeries("same-date-high", creator)
|
||||||
|
val sameDateLow = saveSeries("same-date-low", creator)
|
||||||
|
val popular = saveSeries("popular", creator)
|
||||||
|
val inactiveRevenue = saveSeries("inactive-revenue", creator)
|
||||||
|
val inactiveThemeOnly = saveSeries("inactive-theme-only", creator)
|
||||||
|
val inactiveCreatorOnly = saveSeries("inactive-creator-only", creator)
|
||||||
|
val oldHighAudio = saveAudioContent(creator, theme, now.minusDays(5), isAdult = false, price = 500)
|
||||||
|
val recentLowAudio = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 100)
|
||||||
|
val sameDateHighAudio = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 300)
|
||||||
|
val sameDateLowAudio = saveAudioContent(creator, theme, now.minusDays(1), isAdult = false, price = 50)
|
||||||
|
val popularAudio = saveAudioContent(creator, theme, now.minusDays(4), isAdult = false, price = 100)
|
||||||
|
val inactiveRevenueAudio = saveAudioContent(creator, theme, now.minusDays(3), isAdult = false, price = 100)
|
||||||
|
val inactiveThemeAudio = saveAudioContent(creator, inactiveTheme, now, isAdult = false, price = 1000)
|
||||||
|
val inactiveCreatorAudio = saveAudioContent(inactiveAudioCreator, theme, now, isAdult = false, price = 1000)
|
||||||
|
saveSeriesContent(oldHigh, oldHighAudio)
|
||||||
|
saveSeriesContent(recentLow, recentLowAudio)
|
||||||
|
saveSeriesContent(sameDateHigh, sameDateHighAudio)
|
||||||
|
saveSeriesContent(sameDateLow, sameDateLowAudio)
|
||||||
|
saveSeriesContent(popular, popularAudio)
|
||||||
|
saveSeriesContent(inactiveRevenue, inactiveRevenueAudio)
|
||||||
|
saveSeriesContent(inactiveThemeOnly, inactiveThemeAudio)
|
||||||
|
saveSeriesContent(inactiveCreatorOnly, inactiveCreatorAudio)
|
||||||
|
saveOrder(buyer, creator, popularAudio, can = 900)
|
||||||
|
saveOrder(buyer, creator, inactiveThemeAudio, can = 5000)
|
||||||
|
saveOrder(buyer, inactiveAudioCreator, inactiveCreatorAudio, can = 5000)
|
||||||
|
saveOrder(buyer, creator, inactiveRevenueAudio, can = 1000, isActive = false)
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val latest = findSeriesIds(now, ContentSort.LATEST)
|
||||||
|
val priceHigh = findSeriesIds(now, ContentSort.PRICE_HIGH)
|
||||||
|
val priceLow = findSeriesIds(now, ContentSort.PRICE_LOW)
|
||||||
|
val popularSorted = findSeriesIds(now, ContentSort.POPULAR)
|
||||||
|
|
||||||
|
assertEquals(listOf(sameDateLow.id, sameDateHigh.id, recentLow.id), latest.take(3))
|
||||||
|
assertEquals(oldHigh.id, priceHigh.first())
|
||||||
|
assertEquals(sameDateLow.id, priceLow.first())
|
||||||
|
assertEquals(popular.id, popularSorted.first())
|
||||||
|
assertEquals(listOf(inactiveCreatorOnly.id, inactiveThemeOnly.id), latest.takeLast(2))
|
||||||
|
assertEquals(listOf(inactiveCreatorOnly.id, inactiveThemeOnly.id), priceHigh.takeLast(2))
|
||||||
|
assertEquals(listOf(inactiveCreatorOnly.id, inactiveThemeOnly.id), popularSorted.takeLast(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSeriesIds(now: LocalDateTime, sort: ContentSort): List<Long> {
|
||||||
|
return repository.findSeries(null, false, now, sort, 0, 20, locale = "ko").map { it.seriesId }
|
||||||
|
}
|
||||||
|
|
||||||
|
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, isActive: Boolean = true): AudioContentTheme {
|
||||||
|
val theme = AudioContentTheme(theme = name, image = "$name.png", isActive = isActive)
|
||||||
|
entityManager.persist(theme)
|
||||||
|
return theme
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveAudioContent(
|
||||||
|
creator: Member,
|
||||||
|
theme: AudioContentTheme,
|
||||||
|
releaseDate: LocalDateTime,
|
||||||
|
isAdult: Boolean,
|
||||||
|
price: Int,
|
||||||
|
isPointAvailable: Boolean = false
|
||||||
|
): AudioContent {
|
||||||
|
val content = AudioContent(
|
||||||
|
title = "audio-${creator.nickname}-$releaseDate",
|
||||||
|
detail = "detail",
|
||||||
|
languageCode = "ko",
|
||||||
|
releaseDate = releaseDate,
|
||||||
|
isAdult = isAdult,
|
||||||
|
price = price,
|
||||||
|
isPointAvailable = isPointAvailable
|
||||||
|
)
|
||||||
|
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
|
||||||
|
): Series {
|
||||||
|
val series = Series(
|
||||||
|
title = title,
|
||||||
|
introduction = "introduction",
|
||||||
|
languageCode = "ko",
|
||||||
|
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,
|
||||||
|
can: Int,
|
||||||
|
point: Int = 0,
|
||||||
|
isActive: Boolean = true
|
||||||
|
): Order {
|
||||||
|
val order = Order(type = OrderType.KEEP, isActive = isActive)
|
||||||
|
order.member = member
|
||||||
|
order.creator = creator
|
||||||
|
order.audioContent = content
|
||||||
|
order.can = can
|
||||||
|
order.point = point
|
||||||
|
entityManager.persist(order)
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flushAndClear() {
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user