diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/controller/OriginalWorkController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/controller/OriginalWorkController.kt index 4b7dd4b..e8b6301 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/controller/OriginalWorkController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/controller/OriginalWorkController.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.chat.original.controller import kr.co.vividnext.sodalive.chat.character.dto.Character +import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkCharactersPageResponse import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkDetailResponse import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkListItemResponse import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkListResponse @@ -13,6 +14,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController /** @@ -50,6 +52,7 @@ class OriginalWorkController( * - 로그인 및 본인인증 필수 * - 반환: 이미지, 제목, 콘텐츠 타입, 카테고리, 19금 여부, 작품 소개, 원작 링크 * - 해당 원작의 활성 캐릭터 리스트 [imageUrl, name, description] + * - 캐릭터는 페이징 적용: 첫 페이지 20개 */ @GetMapping("/{id}") fun detail( @@ -60,7 +63,8 @@ class OriginalWorkController( if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") val ow = queryService.getOriginalWork(id) - val characters = queryService.getActiveCharacters(id).map { + val pageRes = queryService.getActiveCharactersPage(id, page = 0, size = 20) + val characters = pageRes.content.map { val path = it.imagePath ?: "profile/default-profile.png" Character( characterId = it.id!!, @@ -72,4 +76,37 @@ class OriginalWorkController( val response = OriginalWorkDetailResponse.from(ow, imageHost, characters) ApiResponse.ok(response) } + + /** + * 지정 원작에 속한 활성 캐릭터 목록 조회 (페이징) + * - 로그인 및 본인인증 필수 + * - 기본 페이지 사이즈 20 + */ + @GetMapping("/{id}/characters") + fun listCharacters( + @PathVariable id: Long, + @RequestParam(defaultValue = "0") page: Int, + @RequestParam(defaultValue = "20") size: Int, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") + + val pageRes = queryService.getActiveCharactersPage(id, page, size) + val content = pageRes.content.map { + val path = it.imagePath ?: "profile/default-profile.png" + Character( + characterId = it.id!!, + name = it.name, + description = it.description, + imageUrl = "$imageHost/$path" + ) + } + ApiResponse.ok( + OriginalWorkCharactersPageResponse( + totalCount = pageRes.totalElements, + content = content + ) + ) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/dto/OriginalWorkAppDtos.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/dto/OriginalWorkAppDtos.kt index 9c38622..7520eca 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/dto/OriginalWorkAppDtos.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/dto/OriginalWorkAppDtos.kt @@ -75,3 +75,11 @@ data class OriginalWorkDetailResponse( } } } + +/** + * 앱용: 원작별 활성 캐릭터 페이징 응답 DTO + */ +data class OriginalWorkCharactersPageResponse( + @JsonProperty("totalCount") val totalCount: Long, + @JsonProperty("content") val content: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/service/OriginalWorkQueryService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/service/OriginalWorkQueryService.kt index 1377dab..01dc3e6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/service/OriginalWorkQueryService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/original/service/OriginalWorkQueryService.kt @@ -5,6 +5,9 @@ import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepositor import kr.co.vividnext.sodalive.chat.original.OriginalWork import kr.co.vividnext.sodalive.chat.original.OriginalWorkRepository import kr.co.vividnext.sodalive.common.SodaException +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -36,10 +39,21 @@ class OriginalWorkQueryService( } /** - * 지정 원작에 속한 활성 캐릭터 목록 조회 (최신순) + * 지정 원작에 속한 활성 캐릭터 페이징 조회 (최신순) */ @Transactional(readOnly = true) - fun getActiveCharacters(originalWorkId: Long): List { - return chatCharacterRepository.findByOriginalWorkIdAndIsActiveTrueOrderByCreatedAtDesc(originalWorkId) + fun getActiveCharactersPage(originalWorkId: Long, page: Int = 0, size: Int = 20): Page { + // 원작 존재 및 소프트 삭제 여부 확인 + originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId) + .orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") } + + val safePage = if (page < 0) 0 else page + val safeSize = when { + size <= 0 -> 20 + size > 50 -> 50 + else -> size + } + val pageable = PageRequest.of(safePage, safeSize, Sort.by("createdAt").descending()) + return chatCharacterRepository.findByOriginalWorkIdAndIsActiveTrue(originalWorkId, pageable) } }