Merge pull request 'test' (#63) from test into main

Reviewed-on: #63
This commit is contained in:
klaus 2023-11-02 12:18:29 +00:00
commit cb1dde17bb
10 changed files with 174 additions and 73 deletions

View File

@ -10,6 +10,9 @@ import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.RedisTemplate import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories import org.springframework.data.redis.repository.configuration.EnableRedisRepositories
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.RedisSerializationContext
import org.springframework.data.redis.serializer.StringRedisSerializer
import java.time.Duration import java.time.Duration
@Configuration @Configuration
@ -37,12 +40,30 @@ class RedisConfig(
fun cacheManager(redisConnectionFactory: RedisConnectionFactory): RedisCacheManager { fun cacheManager(redisConnectionFactory: RedisConnectionFactory): RedisCacheManager {
val defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig() val defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) .entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
GenericJackson2JsonRedisSerializer()
)
)
val cacheConfigMap = mutableMapOf<String, RedisCacheConfiguration>() val cacheConfigMap = mutableMapOf<String, RedisCacheConfiguration>()
cacheConfigMap["default"] = RedisCacheConfiguration.defaultCacheConfig() cacheConfigMap["default"] = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) .entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
GenericJackson2JsonRedisSerializer()
)
)
cacheConfigMap["cache_ttl_3_days"] = RedisCacheConfiguration.defaultCacheConfig() cacheConfigMap["cache_ttl_3_days"] = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(3)) .entryTtl(Duration.ofDays(3))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
GenericJackson2JsonRedisSerializer()
)
)
return RedisCacheManager.builder(redisConnectionFactory) return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultCacheConfig) .cacheDefaults(defaultCacheConfig)

View File

@ -19,6 +19,9 @@ import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RequestPart import org.springframework.web.bind.annotation.RequestPart
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile import org.springframework.web.multipart.MultipartFile
import java.time.DayOfWeek
import java.time.LocalDateTime
import java.time.temporal.TemporalAdjusters
@RestController @RestController
@RequestMapping("/audio-content") @RequestMapping("/audio-content")
@ -149,18 +152,41 @@ class AudioContentController(private val service: AudioContentService) {
ApiResponse.ok(service.audioContentLike(request, member)) ApiResponse.ok(service.audioContentLike(request, member))
} }
@GetMapping("/ranking-sort-type")
fun getAudioContentRankingSort(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(service.getContentRankingSortTypeList())
}
@GetMapping("/ranking") @GetMapping("/ranking")
fun getAudioContentRanking( fun getAudioContentRanking(
@RequestParam("sort-type", required = false) sortType: String? = "매출",
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable pageable: Pageable
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val currentDateTime = LocalDateTime.now()
val startDate = currentDateTime
.withHour(15)
.withMinute(0)
.withSecond(0)
.minusWeeks(1)
.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
val endDate = startDate
.plusDays(7)
ApiResponse.ok( ApiResponse.ok(
service.getAudioContentRanking( service.getAudioContentRanking(
member = member, isAdult = member.auth != null,
startDate = startDate,
endDate = endDate,
offset = pageable.offset, offset = pageable.offset,
limit = pageable.pageSize.toLong() limit = pageable.pageSize.toLong(),
sortType = sortType ?: "매출"
) )
) )
} }

View File

@ -2,8 +2,11 @@ package kr.co.vividnext.sodalive.content
import com.querydsl.core.types.dsl.Expressions import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.impl.JPAQueryFactory import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
import kr.co.vividnext.sodalive.content.QBundleAudioContent.bundleAudioContent import kr.co.vividnext.sodalive.content.QBundleAudioContent.bundleAudioContent
import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.GetNewContentUploadCreator import kr.co.vividnext.sodalive.content.main.GetNewContentUploadCreator
@ -88,7 +91,8 @@ interface AudioContentQueryRepository {
startDate: LocalDateTime, startDate: LocalDateTime,
endDate: LocalDateTime, endDate: LocalDateTime,
offset: Long = 0, offset: Long = 0,
limit: Long = 12 limit: Long = 12,
sortType: String = "매출"
): List<GetAudioContentRankingItem> ): List<GetAudioContentRankingItem>
} }
@ -443,11 +447,11 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
startDate: LocalDateTime, startDate: LocalDateTime,
endDate: LocalDateTime, endDate: LocalDateTime,
offset: Long, offset: Long,
limit: Long limit: Long,
sortType: String
): List<GetAudioContentRankingItem> { ): List<GetAudioContentRankingItem> {
var where = order.createdAt.goe(startDate) var where = audioContent.isActive.isTrue
.and(order.createdAt.lt(endDate)) .and(audioContent.member.id.ne(648))
.and(audioContent.isActive.isTrue)
.and(audioContent.member.isNotNull) .and(audioContent.member.isNotNull)
.and(audioContent.duration.isNotNull) .and(audioContent.duration.isNotNull)
.and(audioContent.member.isActive.isTrue) .and(audioContent.member.isActive.isTrue)
@ -457,7 +461,7 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
where = where.and(audioContent.isAdult.isFalse) where = where.and(audioContent.isAdult.isFalse)
} }
return queryFactory var select = queryFactory
.select( .select(
QGetAudioContentRankingItem( QGetAudioContentRankingItem(
audioContent.id, audioContent.id,
@ -470,13 +474,70 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
member.nickname member.nickname
) )
) )
.from(order)
.innerJoin(order.audioContent, audioContent) select = when (sortType) {
.innerJoin(audioContent.member, member) "후원" -> {
.innerJoin(audioContent.theme, audioContentTheme) select
.where(where) .from(useCan)
.groupBy(audioContent.id) .innerJoin(useCan.audioContent, audioContent)
.orderBy(order.can.sum().desc(), audioContent.createdAt.asc()) .innerJoin(audioContent.member, member)
.innerJoin(audioContent.theme, audioContentTheme)
.where(
where
.and(useCan.createdAt.goe(startDate))
.and(useCan.createdAt.lt(endDate))
)
.groupBy(audioContent.id)
.orderBy(useCan.can.add(useCan.rewardCan).sum().desc(), audioContent.createdAt.asc())
}
"댓글" -> {
select
.from(audioContentComment)
.innerJoin(audioContentComment.audioContent, audioContent)
.innerJoin(audioContentComment.audioContent.member, member)
.innerJoin(audioContentComment.audioContent.theme, audioContentTheme)
.where(
where
.and(audioContentComment.createdAt.goe(startDate))
.and(audioContentComment.createdAt.lt(endDate))
)
.groupBy(audioContentComment.audioContent.id)
.orderBy(audioContentComment.id.count().desc(), audioContent.createdAt.asc())
}
"좋아요" -> {
select
.from(audioContentLike)
.innerJoin(audioContentLike.audioContent, audioContent)
.innerJoin(audioContentLike.audioContent.member, member)
.innerJoin(audioContentLike.audioContent.theme, audioContentTheme)
.where(
where
.and(audioContentLike.createdAt.goe(startDate))
.and(audioContentLike.createdAt.lt(endDate))
)
.groupBy(audioContentLike.audioContent.id)
.orderBy(audioContentLike.id.count().desc(), audioContent.createdAt.asc())
}
else -> {
select
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(audioContent.theme, audioContentTheme)
.where(
where
.and(order.createdAt.goe(startDate))
.and(order.createdAt.lt(endDate))
)
.groupBy(audioContent.id)
.orderBy(order.can.sum().desc(), audioContent.createdAt.asc())
}
}
return select
.offset(offset) .offset(offset)
.limit(limit) .limit(limit)
.fetch() .fetch()

View File

@ -24,17 +24,16 @@ import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import kr.co.vividnext.sodalive.utils.generateFileName import kr.co.vividnext.sodalive.utils.generateFileName
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.Cacheable
import org.springframework.context.ApplicationEventPublisher import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile import org.springframework.web.multipart.MultipartFile
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.time.DayOfWeek
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters
import java.util.Locale import java.util.Locale
@Service @Service
@ -72,11 +71,10 @@ class AudioContentService(
) )
if (audioContentLike == null) { if (audioContentLike == null) {
audioContentLike = AudioContentLike( audioContentLike = AudioContentLike(memberId = member.id!!)
memberId = member.id!!,
contentId = request.contentId
)
val audioContent = repository.findByIdAndActive(request.contentId)
audioContentLike.audioContent = audioContent
audioContentLikeRepository.save(audioContentLike) audioContentLikeRepository.save(audioContentLike)
} else { } else {
audioContentLike.isActive = !audioContentLike.isActive audioContentLike.isActive = !audioContentLike.isActive
@ -579,21 +577,19 @@ class AudioContentService(
} }
} }
@Cacheable(
cacheNames = ["cache_ttl_3_days"],
key = "'contentRanking:' + ':' +" +
"#isAdult + ':' + #startDate + ':' + #endDate + ':' + #sortType + ':' + #offset + ':' + #limit"
)
fun getAudioContentRanking( fun getAudioContentRanking(
member: Member, isAdult: Boolean,
startDate: LocalDateTime,
endDate: LocalDateTime,
offset: Long, offset: Long,
limit: Long limit: Long,
sortType: String = "매출"
): GetAudioContentRanking { ): GetAudioContentRanking {
val currentDateTime = LocalDateTime.now()
val startDate = currentDateTime
.withHour(15)
.withMinute(0)
.withSecond(0)
.minusWeeks(1)
.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
val endDate = startDate
.plusDays(7)
val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")
val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일") val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일")
@ -602,9 +598,10 @@ class AudioContentService(
cloudfrontHost = coverImageHost, cloudfrontHost = coverImageHost,
startDate = startDate.minusDays(1), startDate = startDate.minusDays(1),
endDate = endDate.minusDays(1), endDate = endDate.minusDays(1),
isAdult = member.auth != null, isAdult = isAdult,
offset = offset, offset = offset,
limit = limit limit = limit,
sortType = sortType
) )
return GetAudioContentRanking( return GetAudioContentRanking(
@ -613,4 +610,8 @@ class AudioContentService(
items = contentRankingItemList items = contentRankingItemList
) )
} }
fun getContentRankingSortTypeList(): List<String> {
return listOf("매출", "후원", "댓글", "좋아요")
}
} }

View File

@ -1,20 +1,21 @@
package kr.co.vividnext.sodalive.content.like package kr.co.vividnext.sodalive.content.like
import kr.co.vividnext.sodalive.content.AudioContent
import java.time.LocalDateTime import java.time.LocalDateTime
import javax.persistence.Entity import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.GeneratedValue import javax.persistence.GeneratedValue
import javax.persistence.GenerationType import javax.persistence.GenerationType
import javax.persistence.Id import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.PrePersist import javax.persistence.PrePersist
import javax.persistence.PreUpdate import javax.persistence.PreUpdate
import javax.persistence.Table import javax.persistence.Table
@Entity @Entity
@Table(name = "content_like") @Table(name = "content_like")
data class AudioContentLike( data class AudioContentLike(val memberId: Long) {
val memberId: Long,
val contentId: Long
) {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null var id: Long? = null
@ -34,4 +35,8 @@ data class AudioContentLike(
} }
var isActive = true var isActive = true
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "content_id", nullable = false)
var audioContent: AudioContent? = null
} }

View File

@ -20,7 +20,7 @@ class AudioContentLikeQueryRepositoryImpl(private val queryFactory: JPAQueryFact
.selectFrom(audioContentLike) .selectFrom(audioContentLike)
.where( .where(
audioContentLike.memberId.eq(memberId) audioContentLike.memberId.eq(memberId)
.and(audioContentLike.contentId.eq(contentId)) .and(audioContentLike.audioContent.id.eq(contentId))
) )
.fetchFirst() .fetchFirst()
} }
@ -30,7 +30,7 @@ class AudioContentLikeQueryRepositoryImpl(private val queryFactory: JPAQueryFact
.select(audioContentLike.id) .select(audioContentLike.id)
.from(audioContentLike) .from(audioContentLike)
.where( .where(
audioContentLike.contentId.eq(contentId) audioContentLike.audioContent.id.eq(contentId)
.and(audioContentLike.isActive.isTrue) .and(audioContentLike.isActive.isTrue)
) )
.fetch() .fetch()

View File

@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.content.main package kr.co.vividnext.sodalive.content.main
import kr.co.vividnext.sodalive.content.AudioContentRepository import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.AudioContentService
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.GetAudioContentBannerResponse import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse
import kr.co.vividnext.sodalive.content.main.curation.GetAudioContentCurationResponse import kr.co.vividnext.sodalive.content.main.curation.GetAudioContentCurationResponse
@ -15,12 +16,12 @@ import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters import java.time.temporal.TemporalAdjusters
@Service @Service
class AudioContentMainService( class AudioContentMainService(
private val repository: AudioContentRepository, private val repository: AudioContentRepository,
private val audioContentService: AudioContentService,
private val blockMemberRepository: BlockMemberRepository, private val blockMemberRepository: BlockMemberRepository,
private val orderService: OrderService, private val orderService: OrderService,
private val audioContentThemeRepository: AudioContentThemeQueryRepository, private val audioContentThemeRepository: AudioContentThemeQueryRepository,
@ -66,7 +67,14 @@ class AudioContentMainService(
.withSecond(0) .withSecond(0)
val endDate = startDate.plusDays(7) val endDate = startDate.plusDays(7)
val contentRanking = getContentRanking(isAdult = isAdult, startDate = startDate, endDate = endDate) val contentRankingSortTypeList = audioContentService.getContentRankingSortTypeList()
val contentRanking = audioContentService.getAudioContentRanking(
isAdult = isAdult,
startDate = startDate,
endDate = endDate,
offset = 0,
limit = 12
)
return GetAudioContentMainResponse( return GetAudioContentMainResponse(
newContentUploadCreatorList = newContentUploadCreatorList, newContentUploadCreatorList = newContentUploadCreatorList,
@ -75,6 +83,7 @@ class AudioContentMainService(
themeList = themeList, themeList = themeList,
newContentList = newContentList, newContentList = newContentList,
curationList = curationList, curationList = curationList,
contentRankingSortTypeList = contentRankingSortTypeList,
contentRanking = contentRanking contentRanking = contentRanking
) )
} }
@ -109,7 +118,7 @@ class AudioContentMainService(
return GetNewContentAllResponse(totalCount, items) return GetNewContentAllResponse(totalCount, items)
} }
@Cacheable(cacheNames = ["default"], key = "'getNewContentUploadCreatorList:' + #memberId + ':' + #isAdult") @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult")
fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List<GetNewContentUploadCreator> { fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List<GetNewContentUploadCreator> {
return repository.getNewContentUploadCreatorList( return repository.getNewContentUploadCreatorList(
cloudfrontHost = imageHost, cloudfrontHost = imageHost,
@ -120,7 +129,7 @@ class AudioContentMainService(
.toList() .toList()
} }
@Cacheable(cacheNames = ["default"], key = "'getAudioContentMainBannerList:' + #memberId + ':' + #isAdult") @Cacheable(cacheNames = ["default"], key = "'contentMainBannerList:' + #memberId + ':' + #isAdult")
fun getAudioContentMainBannerList(memberId: Long, isAdult: Boolean) = fun getAudioContentMainBannerList(memberId: Long, isAdult: Boolean) =
repository.getAudioContentMainBannerList(isAdult = isAdult) repository.getAudioContentMainBannerList(isAdult = isAdult)
.asSequence() .asSequence()
@ -169,7 +178,7 @@ class AudioContentMainService(
} }
.toList() .toList()
@Cacheable(cacheNames = ["default"], key = "'getAudioContentCurationList:' + #memberId + ':' + #isAdult") @Cacheable(cacheNames = ["default"], key = "'contentCurationList:' + #memberId + ':' + #isAdult")
fun getAudioContentCurationList(memberId: Long, isAdult: Boolean) = fun getAudioContentCurationList(memberId: Long, isAdult: Boolean) =
repository.getAudioContentCurations(isAdult = isAdult) repository.getAudioContentCurations(isAdult = isAdult)
.asSequence() .asSequence()
@ -192,27 +201,4 @@ class AudioContentMainService(
} }
.filter { it.contents.isNotEmpty() } .filter { it.contents.isNotEmpty() }
.toList() .toList()
@Cacheable(
cacheNames = ["cache_ttl_3_days"],
key = "'getAudioContentCurationList:' + ':' + #isAdult + ':' + #startDate + ':' + #endDate"
)
fun getContentRanking(isAdult: Boolean, startDate: LocalDateTime, endDate: LocalDateTime): GetAudioContentRanking {
val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")
val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일")
val contentRankingItemList = repository
.getAudioContentRanking(
cloudfrontHost = imageHost,
startDate = startDate.minusDays(1),
endDate = endDate.minusDays(1),
isAdult = isAdult
)
return GetAudioContentRanking(
startDate = startDate.format(startDateFormatter),
endDate = endDate.minusDays(1).format(endDateFormatter),
contentRankingItemList
)
}
} }

View File

@ -10,5 +10,6 @@ data class GetAudioContentMainResponse(
val themeList: List<String>, val themeList: List<String>,
val newContentList: List<GetAudioContentMainItem>, val newContentList: List<GetAudioContentMainItem>,
val curationList: List<GetAudioContentCurationResponse>, val curationList: List<GetAudioContentCurationResponse>,
val contentRankingSortTypeList: List<String>,
val contentRanking: GetAudioContentRanking val contentRanking: GetAudioContentRanking
) )

View File

@ -4,9 +4,9 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.querydsl.core.annotations.QueryProjection import com.querydsl.core.annotations.QueryProjection
data class GetAudioContentRanking( data class GetAudioContentRanking(
val startDate: String, @JsonProperty("startDate") val startDate: String,
val endDate: String, @JsonProperty("endDate") val endDate: String,
val items: List<GetAudioContentRankingItem> @JsonProperty("items") val items: List<GetAudioContentRankingItem>
) )
data class GetAudioContentRankingItem @QueryProjection constructor( data class GetAudioContentRankingItem @QueryProjection constructor(

View File

@ -28,7 +28,7 @@ class AudioContentThemeQueryRepository(
.fetch() .fetch()
} }
@Cacheable(cacheNames = ["default"], key = "'getActiveThemeOfContent:' + ':' + #isAdult") @Cacheable(cacheNames = ["default"], key = "'activeThemeOfContent:' + ':' + #isAdult")
fun getActiveThemeOfContent(isAdult: Boolean = false): List<String> { fun getActiveThemeOfContent(isAdult: Boolean = false): List<String> {
var where = audioContent.isActive.isTrue var where = audioContent.isActive.isTrue
.and(audioContentTheme.isActive.isTrue) .and(audioContentTheme.isActive.isTrue)