diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/AdminOriginalWorkController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/AdminOriginalWorkController.kt index a6f90a4..71b14c8 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/AdminOriginalWorkController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/AdminOriginalWorkController.kt @@ -2,11 +2,14 @@ package kr.co.vividnext.sodalive.admin.chat.original import com.amazonaws.services.s3.model.ObjectMetadata import com.fasterxml.jackson.databind.ObjectMapper +import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterSearchListPageResponse +import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterSearchResponse import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkAssignCharactersRequest import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkPageResponse import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkRegisterRequest import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkResponse import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkUpdateRequest +import kr.co.vividnext.sodalive.admin.chat.original.service.AdminOriginalWorkService import kr.co.vividnext.sodalive.aws.s3.S3Uploader import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException @@ -34,7 +37,7 @@ import org.springframework.web.multipart.MultipartFile @RequestMapping("/admin/chat/original") @PreAuthorize("hasRole('ADMIN')") class AdminOriginalWorkController( - private val originalWorkService: kr.co.vividnext.sodalive.admin.chat.original.service.AdminOriginalWorkService, + private val originalWorkService: AdminOriginalWorkService, private val s3Uploader: S3Uploader, @Value("\${cloud.aws.s3.bucket}") private val s3Bucket: String, @@ -157,6 +160,27 @@ class AdminOriginalWorkController( ApiResponse.ok(null) } + /** + * 관리자용: 지정 원작에 속한 캐릭터 목록 페이징 조회 + * - 활성 캐릭터만 포함 + * - 응답 항목: 캐릭터 이미지(URL), 이름 + */ + @GetMapping("/{id}/characters") + fun listCharactersOfOriginal( + @PathVariable id: Long, + @RequestParam(defaultValue = "0") page: Int, + @RequestParam(defaultValue = "20") size: Int + ) = run { + val pageRes = originalWorkService.getCharactersOfOriginalWorkPage(id, page, size) + val content = pageRes.content.map { ChatCharacterSearchResponse.from(it, imageHost) } + ApiResponse.ok( + ChatCharacterSearchListPageResponse( + totalCount = pageRes.totalElements, + content = content + ) + ) + } + /** 이미지 업로드 공통 처리 */ private fun uploadImage(originalWorkId: Long, image: MultipartFile): String { try { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/service/AdminOriginalWorkService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/service/AdminOriginalWorkService.kt index 212e7b9..288ddf5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/service/AdminOriginalWorkService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/original/service/AdminOriginalWorkService.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.admin.chat.original.service import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkRegisterRequest import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkUpdateRequest +import kr.co.vividnext.sodalive.chat.character.ChatCharacter import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository import kr.co.vividnext.sodalive.chat.original.OriginalWork import kr.co.vividnext.sodalive.chat.original.OriginalWorkRepository @@ -85,10 +86,33 @@ class AdminOriginalWorkService( /** 원작 페이징 조회 */ @Transactional(readOnly = true) fun getOriginalWorkPage(page: Int, size: Int): Page { - val pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()) + val safePage = if (page < 0) 0 else page + val safeSize = when { + size <= 0 -> 20 + size > 100 -> 100 + else -> size + } + val pageable = PageRequest.of(safePage, safeSize, Sort.by("createdAt").descending()) return originalWorkRepository.findByIsDeletedFalse(pageable) } + /** 지정 원작에 속한 활성 캐릭터 페이징 조회 (최신순) */ + @Transactional(readOnly = true) + fun getCharactersOfOriginalWorkPage(originalWorkId: Long, page: Int, size: Int): Page { + // 원작 존재 및 소프트 삭제 여부 확인 + originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId) + .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + + val safePage = if (page < 0) 0 else page + val safeSize = when { + size <= 0 -> 20 + size > 100 -> 100 + else -> size + } + val pageable = PageRequest.of(safePage, safeSize, Sort.by("createdAt").descending()) + return chatCharacterRepository.findByOriginalWorkIdAndIsActiveTrue(originalWorkId, pageable) + } + /** 원작 검색 (제목/콘텐츠타입/카테고리, 소프트 삭제 제외) - 무페이징 */ @Transactional(readOnly = true) fun searchOriginalWorksAll(searchTerm: String): List { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt index 9daacec..2269123 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt @@ -13,6 +13,7 @@ interface ChatCharacterRepository : JpaRepository { fun findByName(name: String): ChatCharacter? fun findByIsActiveTrue(pageable: Pageable): Page fun findByOriginalWorkIdAndIsActiveTrueOrderByCreatedAtDesc(originalWorkId: Long): List + fun findByOriginalWorkIdAndIsActiveTrue(originalWorkId: Long, pageable: Pageable): Page /** * 2주 이내(파라미터 since 이상) 활성 캐릭터 페이징 조회