feat(ai-character): 시리즈 관리자 API를 추가한다
This commit is contained in:
@@ -7,8 +7,19 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seri
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.content.QSearchContentNotInSeriesResponse
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.content.SearchContentNotInSeriesResponse
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Lock
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import javax.persistence.LockModeType
|
||||
|
||||
interface CreatorAdminContentSeriesRepository : JpaRepository<Series, Long>, CreatorAdminContentSeriesQueryRepository
|
||||
interface CreatorAdminContentSeriesRepository : JpaRepository<Series, Long>, CreatorAdminContentSeriesQueryRepository {
|
||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||
@Query("select s from Series s where s.member.id = :creatorId and s.id in :ids and s.isActive = true order by s.id asc")
|
||||
fun findActiveByCreatorIdAndIdInForUpdate(
|
||||
@Param("creatorId") creatorId: Long,
|
||||
@Param("ids") ids: List<Long>
|
||||
): List<Series>
|
||||
}
|
||||
|
||||
interface CreatorAdminContentSeriesQueryRepository {
|
||||
fun findByIdAndCreatorId(id: Long, creatorId: Long): Series?
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.series
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.aicharacter.error.AiCharacterAdminApiException
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RequestPart
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/admin/ai-characters/{characterId:[0-9]+}/series")
|
||||
class AiCharacterAdminSeriesController(
|
||||
private val facade: AiCharacterAdminSeriesFacade
|
||||
) {
|
||||
@GetMapping
|
||||
fun list(
|
||||
@PathVariable characterId: Long,
|
||||
@RequestParam(defaultValue = "0") page: Int,
|
||||
@RequestParam(defaultValue = "20") size: Int
|
||||
): ApiResponse<AiCharacterAdminSeriesListResponse> {
|
||||
return ApiResponse.ok(facade.list(characterId, page, size))
|
||||
}
|
||||
|
||||
@GetMapping("/{seriesId:[0-9]+}")
|
||||
fun detail(
|
||||
@PathVariable characterId: Long,
|
||||
@PathVariable seriesId: Long
|
||||
): ApiResponse<AiCharacterAdminSeriesDetailResponse> {
|
||||
return ApiResponse.ok(facade.detail(characterId, seriesId))
|
||||
}
|
||||
|
||||
@GetMapping("/{seriesId:[0-9]+}/contents")
|
||||
fun contents(
|
||||
@PathVariable characterId: Long,
|
||||
@PathVariable seriesId: Long,
|
||||
@RequestParam(defaultValue = "0") page: Int,
|
||||
@RequestParam(defaultValue = "20") size: Int
|
||||
) = ApiResponse.ok(facade.contents(characterId, seriesId, page, size))
|
||||
|
||||
@GetMapping("/{seriesId:[0-9]+}/contents/search")
|
||||
fun searchContents(
|
||||
@PathVariable characterId: Long,
|
||||
@PathVariable seriesId: Long,
|
||||
@RequestParam("search_word") searchWord: String
|
||||
) = ApiResponse.ok(facade.searchContents(characterId, seriesId, searchWord))
|
||||
|
||||
@PostMapping("/{seriesId:[0-9]+}/contents", consumes = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun addContents(
|
||||
@PathVariable characterId: Long,
|
||||
@PathVariable seriesId: Long,
|
||||
@RequestBody request: String
|
||||
): ApiResponse<Nothing> {
|
||||
facade.addContents(characterId, seriesId, request)
|
||||
return ApiResponse.ok(null)
|
||||
}
|
||||
|
||||
@DeleteMapping("/{seriesId:[0-9]+}/contents/{contentId:[0-9]+}")
|
||||
fun removeContent(
|
||||
@PathVariable characterId: Long,
|
||||
@PathVariable seriesId: Long,
|
||||
@PathVariable contentId: Long
|
||||
): ApiResponse<Nothing> {
|
||||
facade.removeContent(characterId, seriesId, contentId)
|
||||
return ApiResponse.ok(null)
|
||||
}
|
||||
|
||||
@PutMapping("/orders", consumes = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun updateOrders(
|
||||
@PathVariable characterId: Long,
|
||||
@RequestBody request: String
|
||||
): ApiResponse<Nothing> {
|
||||
facade.updateOrders(characterId, request)
|
||||
return ApiResponse.ok(null)
|
||||
}
|
||||
|
||||
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
fun create(
|
||||
@PathVariable characterId: Long,
|
||||
@RequestPart("image") image: MultipartFile,
|
||||
@RequestPart("request") request: String,
|
||||
multipartRequest: MultipartHttpServletRequest
|
||||
): ApiResponse<Nothing> {
|
||||
requireAllowedMultipartParts(multipartRequest)
|
||||
requireJsonRequestPart(multipartRequest)
|
||||
facade.create(characterId, image, request)
|
||||
return ApiResponse.ok(null)
|
||||
}
|
||||
|
||||
@PutMapping("/{seriesId:[0-9]+}", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
fun update(
|
||||
@PathVariable characterId: Long,
|
||||
@PathVariable seriesId: Long,
|
||||
@RequestPart(value = "image", required = false) image: MultipartFile?,
|
||||
@RequestPart("request") request: String,
|
||||
multipartRequest: MultipartHttpServletRequest
|
||||
): ApiResponse<Nothing> {
|
||||
requireAllowedMultipartParts(multipartRequest)
|
||||
requireJsonRequestPart(multipartRequest)
|
||||
facade.update(characterId, seriesId, image, request)
|
||||
return ApiResponse.ok(null)
|
||||
}
|
||||
|
||||
private fun requireAllowedMultipartParts(multipartRequest: MultipartHttpServletRequest) {
|
||||
if (multipartRequest.fileMap.keys.any { it !in SERIES_MULTIPART_PARTS } ||
|
||||
multipartRequest.parts.any { it.name !in SERIES_MULTIPART_PARTS }
|
||||
) {
|
||||
throw AiCharacterAdminApiException(HttpStatus.BAD_REQUEST, "common.error.invalid_request")
|
||||
}
|
||||
}
|
||||
|
||||
private fun requireJsonRequestPart(multipartRequest: MultipartHttpServletRequest) {
|
||||
val contentType = multipartRequest.getMultipartHeaders("request")?.contentType
|
||||
?: multipartRequest.getPart("request")?.contentType?.let(MediaType::parseMediaType)
|
||||
if (contentType == null || !MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
|
||||
throw HttpMediaTypeNotSupportedException(contentType, listOf(MediaType.APPLICATION_JSON))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val SERIES_MULTIPART_PARTS = setOf("image", "request")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.series
|
||||
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.CreateSeriesRequest
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.GetCreatorAdminContentSeriesListItem
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.GetCreatorAdminContentSeriesListResponse
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
||||
|
||||
typealias AiCharacterAdminSeriesListResponse = GetCreatorAdminContentSeriesListResponse
|
||||
typealias AiCharacterAdminSeriesListItem = GetCreatorAdminContentSeriesListItem
|
||||
typealias AiCharacterAdminSeriesDetailResponse = GetCreatorAdminContentSeriesListItem
|
||||
typealias AiCharacterAdminSeriesCreateRequest = CreateSeriesRequest
|
||||
|
||||
data class AiCharacterAdminSeriesUpdateRequest(
|
||||
val title: String? = null,
|
||||
val introduction: String? = null,
|
||||
val publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>? = null,
|
||||
val genreId: Long? = null,
|
||||
val isAdult: Boolean? = null,
|
||||
val state: SeriesState? = null,
|
||||
val isActive: Boolean? = null,
|
||||
val writer: String? = null,
|
||||
val studio: String? = null
|
||||
)
|
||||
|
||||
data class AiCharacterAdminSeriesContentAddRequest(
|
||||
val contentIdList: List<Long>?
|
||||
)
|
||||
@@ -0,0 +1,220 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.series
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.admin.content.series.genre.AdminContentSeriesGenreService
|
||||
import kr.co.vividnext.sodalive.admin.content.series.genre.GetSeriesGenreListResponse
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.CreatorAdminContentSeriesService
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.GetCreatorAdminContentSeriesContentResponse
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.GetCreatorAdminContentSeriesListItem
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.ModifySeriesRequest
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.UpdateOrdersRequest
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.content.AddingContentToTheSeriesRequest
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.content.RemoveContentToTheSeriesRequest
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.content.SearchContentNotInSeriesResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.aicharacter.application.AiCharacterAdminTarget
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.aicharacter.application.AiCharacterAdminTargetResolver
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.aicharacter.error.AiCharacterAdminApiException
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@Service
|
||||
class AiCharacterAdminSeriesFacade(
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val targetResolver: AiCharacterAdminTargetResolver,
|
||||
private val repository: AiCharacterAdminSeriesRepository,
|
||||
private val legacyService: CreatorAdminContentSeriesService,
|
||||
private val genreService: AdminContentSeriesGenreService,
|
||||
@Value("\${cloud.aws.cloud-front.host}") private val coverImageHost: String
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun genres(): List<GetSeriesGenreListResponse> {
|
||||
return genreService.getSeriesGenreList()
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun list(characterId: Long, page: Int, size: Int): AiCharacterAdminSeriesListResponse {
|
||||
val target = resolveActiveTarget(characterId)
|
||||
if (page < 0 || size < 1) throw invalidRequest()
|
||||
|
||||
val pageable = PageRequest.of(page, size)
|
||||
return legacyService.getSeriesList(
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong(),
|
||||
creatorId = target.creatorMember.id ?: throw invalidRequest()
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun detail(characterId: Long, seriesId: Long): AiCharacterAdminSeriesDetailResponse {
|
||||
val target = resolveActiveTarget(characterId)
|
||||
val creatorMemberId = target.creatorMember.id ?: throw invalidRequest()
|
||||
val series = repository.findActiveByIdAndCreatorMemberId(seriesId, creatorMemberId)
|
||||
?: throw invalidRequest()
|
||||
return GetCreatorAdminContentSeriesListItem(
|
||||
seriesId = series.id!!,
|
||||
title = series.title,
|
||||
introduction = series.introduction,
|
||||
coverImageUrl = "$coverImageHost/${series.coverImage!!}",
|
||||
publishedDaysOfWeek = series.publishedDaysOfWeek.toList(),
|
||||
genreId = series.genre!!.id!!,
|
||||
isAdult = series.isAdult,
|
||||
state = series.state,
|
||||
isActive = series.isActive,
|
||||
writer = series.writer,
|
||||
studio = series.studio
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun contents(
|
||||
characterId: Long,
|
||||
seriesId: Long,
|
||||
page: Int,
|
||||
size: Int
|
||||
): GetCreatorAdminContentSeriesContentResponse {
|
||||
val target = resolveOwnedActiveSeries(characterId, seriesId)
|
||||
if (page < 0 || size < 1) throw invalidRequest()
|
||||
|
||||
val pageable = PageRequest.of(page, size)
|
||||
return legacyService.getSeriesContent(
|
||||
seriesId = seriesId,
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong(),
|
||||
creatorId = target.creatorMember.id ?: throw invalidRequest()
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun searchContents(characterId: Long, seriesId: Long, searchWord: String): List<SearchContentNotInSeriesResponse> {
|
||||
val target = resolveOwnedActiveSeries(characterId, seriesId)
|
||||
return legacyService.searchContentNotInSeries(
|
||||
seriesId = seriesId,
|
||||
searchWord = searchWord,
|
||||
memberId = target.creatorMember.id ?: throw invalidRequest()
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun addContents(characterId: Long, seriesId: Long, requestString: String) {
|
||||
val request = readRequest(requestString, AiCharacterAdminSeriesContentAddRequest::class.java)
|
||||
val target = resolveOwnedActiveSeries(characterId, seriesId)
|
||||
val creatorMemberId = target.creatorMember.id ?: throw invalidRequest()
|
||||
val contentIds = request.contentIdList ?: throw invalidRequest()
|
||||
if (contentIds.isEmpty()) {
|
||||
legacyService.addingContentToTheSeries(
|
||||
AddingContentToTheSeriesRequest(seriesId, contentIds),
|
||||
creatorMemberId
|
||||
)
|
||||
return
|
||||
}
|
||||
if (contentIds.size != contentIds.toSet().size) throw invalidRequest()
|
||||
|
||||
val series = repository.findActiveByIdAndCreatorMemberId(seriesId, creatorMemberId) ?: throw invalidRequest()
|
||||
val linkedContentIds = series.contentList.mapNotNull { it.content?.id }.toSet()
|
||||
contentIds.forEach { contentId ->
|
||||
if (contentId in linkedContentIds ||
|
||||
repository.findEligibleContentByIdAndCreatorMemberId(contentId, creatorMemberId) == null
|
||||
) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
}
|
||||
legacyService.addingContentToTheSeries(AddingContentToTheSeriesRequest(seriesId, contentIds), creatorMemberId)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun removeContent(characterId: Long, seriesId: Long, contentId: Long?) {
|
||||
val target = resolveOwnedActiveSeries(characterId, seriesId)
|
||||
val creatorMemberId = target.creatorMember.id ?: throw invalidRequest()
|
||||
val resolvedContentId = contentId ?: throw invalidRequest()
|
||||
val series = repository.findActiveByIdAndCreatorMemberId(seriesId, creatorMemberId) ?: throw invalidRequest()
|
||||
if (series.contentList.none { it.content?.id == resolvedContentId && it.content?.member?.id == creatorMemberId }) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
legacyService.removeContentInTheSeries(RemoveContentToTheSeriesRequest(seriesId, resolvedContentId), creatorMemberId)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateOrders(characterId: Long, requestString: String) {
|
||||
val request = readRequest(requestString, UpdateOrdersRequest::class.java)
|
||||
val target = resolveActiveTarget(characterId)
|
||||
val creatorMemberId = target.creatorMember.id ?: throw invalidRequest()
|
||||
val ids = request.ids
|
||||
if (ids.isEmpty() || ids.size != ids.toSet().size) throw invalidRequest()
|
||||
val lockedIds = repository.findActiveByCreatorMemberIdAndIdsForUpdate(creatorMemberId, ids).map { it.id }.toSet()
|
||||
if (lockedIds != ids.toSet()) throw invalidRequest()
|
||||
legacyService.updateSeriesOrders(ids)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun create(characterId: Long, image: MultipartFile, requestString: String) {
|
||||
val target = resolveActiveTarget(characterId)
|
||||
if (image.isEmpty) throw invalidRequest()
|
||||
val request = readRequest(requestString, AiCharacterAdminSeriesCreateRequest::class.java)
|
||||
rejectMissingActiveGenre(request.genreId)
|
||||
legacyService.createSeries(image, requestString, target.creatorMember)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun update(characterId: Long, seriesId: Long, image: MultipartFile?, requestString: String) {
|
||||
val target = resolveOwnedActiveSeries(characterId, seriesId)
|
||||
val normalizedImage = image?.takeUnless { it.isEmpty }
|
||||
val request = readRequest(requestString, AiCharacterAdminSeriesUpdateRequest::class.java)
|
||||
request.genreId?.let(::rejectMissingActiveGenre)
|
||||
legacyService.modifySeries(
|
||||
coverImage = normalizedImage,
|
||||
requestString = objectMapper.writeValueAsString(
|
||||
ModifySeriesRequest(
|
||||
seriesId = seriesId,
|
||||
title = request.title,
|
||||
introduction = request.introduction,
|
||||
publishedDaysOfWeek = request.publishedDaysOfWeek,
|
||||
genreId = request.genreId,
|
||||
isAdult = request.isAdult,
|
||||
state = request.state,
|
||||
isActive = request.isActive,
|
||||
writer = request.writer,
|
||||
studio = request.studio
|
||||
)
|
||||
),
|
||||
member = target.creatorMember
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveOwnedActiveSeries(characterId: Long, seriesId: Long): AiCharacterAdminTarget {
|
||||
val target = resolveActiveTarget(characterId)
|
||||
val creatorMemberId = target.creatorMember.id ?: throw invalidRequest()
|
||||
repository.findActiveByIdAndCreatorMemberId(seriesId, creatorMemberId) ?: throw invalidRequest()
|
||||
return target
|
||||
}
|
||||
|
||||
private fun <T> readRequest(requestString: String, requestClass: Class<T>): T {
|
||||
return try {
|
||||
objectMapper.readerFor(requestClass)
|
||||
.with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
.with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
|
||||
.readValue(requestString)
|
||||
} catch (_: JsonProcessingException) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveActiveTarget(characterId: Long): AiCharacterAdminTarget {
|
||||
val target = targetResolver.resolve(characterId)
|
||||
if (!target.chatCharacter.isActive) throw invalidRequest()
|
||||
return target
|
||||
}
|
||||
|
||||
private fun rejectMissingActiveGenre(genreId: Long) {
|
||||
if (genreId <= 0 || !repository.existsActiveGenre(genreId)) throw invalidRequest()
|
||||
}
|
||||
|
||||
private fun invalidRequest(): AiCharacterAdminApiException {
|
||||
return AiCharacterAdminApiException(HttpStatus.BAD_REQUEST, "common.error.invalid_request")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.series
|
||||
|
||||
import kr.co.vividnext.sodalive.admin.content.series.genre.GetSeriesGenreListResponse
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/admin/ai-characters")
|
||||
class AiCharacterAdminSeriesReferenceController(
|
||||
private val facade: AiCharacterAdminSeriesFacade
|
||||
) {
|
||||
@GetMapping("/series-genres")
|
||||
fun genres(): ApiResponse<List<GetSeriesGenreListResponse>> {
|
||||
return ApiResponse.ok(facade.genres())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.series
|
||||
|
||||
import kr.co.vividnext.sodalive.content.AudioContent
|
||||
import kr.co.vividnext.sodalive.content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.CreatorAdminContentSeriesRepository
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.genre.CreatorAdminContentSeriesGenreRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
class AiCharacterAdminSeriesRepository(
|
||||
private val repository: CreatorAdminContentSeriesRepository,
|
||||
private val genreRepository: CreatorAdminContentSeriesGenreRepository,
|
||||
private val audioContentRepository: AudioContentRepository
|
||||
) {
|
||||
fun findActiveByIdAndCreatorMemberId(seriesId: Long, creatorMemberId: Long): Series? {
|
||||
return repository.findByIdAndCreatorId(seriesId, creatorMemberId)?.takeIf { it.isActive }
|
||||
}
|
||||
|
||||
fun findActiveByCreatorMemberIdAndIdsForUpdate(creatorMemberId: Long, ids: List<Long>): List<Series> {
|
||||
return repository.findActiveByCreatorIdAndIdInForUpdate(creatorMemberId, ids.sorted())
|
||||
}
|
||||
|
||||
fun existsActiveGenre(genreId: Long): Boolean {
|
||||
return genreRepository.findById(genreId).orElse(null)?.isActive == true
|
||||
}
|
||||
|
||||
fun findEligibleContentByIdAndCreatorMemberId(contentId: Long, creatorMemberId: Long): AudioContent? {
|
||||
return audioContentRepository.findByIdAndCreatorId(contentId, creatorMemberId)
|
||||
?.takeIf { it.duration != null && (it.isActive || it.releaseDate != null) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user