diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt index aaefbce..388fc86 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt @@ -22,6 +22,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 @RestController @@ -67,16 +68,11 @@ class ChatCharacterController( // 인기 캐릭터 조회 val popularCharacters = service.getPopularCharacters() - // 최신 캐릭터 조회 (최대 10개) - val newCharacters = service.getNewCharacters(50) - .map { - Character( - characterId = it.id!!, - name = it.name, - description = it.description, - imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}" - ) - } + // 최근 등록된 캐릭터 리스트 조회 + val newCharacters = service.getRecentCharacters( + page = 0, + size = 50 + ) // 큐레이션 섹션 (활성화된 큐레이션 + 캐릭터) val curationSections = curationQueryService.getActiveCurationsWithCharacters() @@ -182,4 +178,19 @@ class ChatCharacterController( ) ) } + + /** + * 최근 등록된 캐릭터 전체보기 + * - 기준: 2주 이내 등록된 캐릭터만 페이징 조회 + * - 예외: 2주 이내 캐릭터가 0개인 경우, 최근 등록한 캐릭터 20개만 제공 + */ + @GetMapping("/recent") + fun getRecentCharacters(@RequestParam("page", required = false) page: Int?) = run { + ApiResponse.ok( + service.getRecentCharacters( + page = page ?: 0, + size = 20 + ) + ) + } } 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 d03ee4f..20b74b9 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 @@ -10,14 +10,25 @@ import org.springframework.stereotype.Repository @Repository interface ChatCharacterRepository : JpaRepository { - fun findByCharacterUUID(characterUUID: String): ChatCharacter? fun findByName(name: String): ChatCharacter? fun findByIsActiveTrue(pageable: Pageable): Page /** - * 활성화된 캐릭터를 생성일 기준 내림차순으로 조회 + * 2주 이내(파라미터 since 이상) 활성 캐릭터 페이징 조회 */ - fun findByIsActiveTrueOrderByCreatedAtDesc(pageable: Pageable): List + @Query( + """ + SELECT c FROM ChatCharacter c + WHERE c.isActive = true AND c.createdAt >= :since + ORDER BY c.createdAt DESC + """ + ) + fun findRecentSince(@Param("since") since: java.time.LocalDateTime, pageable: Pageable): Page + + /** + * 2주 이내(파라미터 since 이상) 활성 캐릭터 개수 + */ + fun countByIsActiveTrueAndCreatedAtGreaterThanEqual(since: java.time.LocalDateTime): Long /** * 이름, 설명, MBTI, 태그로 캐릭터 검색 diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterService.kt index c72c17a..064d29b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterService.kt @@ -20,8 +20,10 @@ import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterValueRepo import org.springframework.beans.factory.annotation.Value import org.springframework.cache.annotation.Cacheable import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime @Service class ChatCharacterService( @@ -66,11 +68,51 @@ class ChatCharacterService( } /** - * 최근 등록된 캐릭터 목록 조회 (최대 10개) + * 최근 등록된 캐릭터 전체보기 (페이징) + * - 기준: 현재 시각 기준 2주 이내 생성된 활성 캐릭터 + * - 2주 이내 캐릭터가 0개라면: 최근 등록한 캐릭터 20개 반환(페이지 무시) */ @Transactional(readOnly = true) - fun getNewCharacters(limit: Int = 10): List { - return chatCharacterRepository.findByIsActiveTrueOrderByCreatedAtDesc(PageRequest.of(0, limit)) + fun getRecentCharacters(page: Int = 0, size: Int = 20): List { + val safePage = if (page < 0) 0 else page + val safeSize = when { + size <= 0 -> 20 + size > 50 -> 50 // 과도한 page size 방지 + else -> size + } + val since = LocalDateTime.now().minusWeeks(2) + + val totalRecent = chatCharacterRepository.countByIsActiveTrueAndCreatedAtGreaterThanEqual(since) + if (totalRecent == 0L) { + if (safePage > 0) { + return emptyList() + } + + val fallback = chatCharacterRepository.findByIsActiveTrue( + PageRequest.of(0, 20, Sort.by("createdAt").descending()) + ) + return fallback.content.map { + Character( + characterId = it.id!!, + name = it.name, + description = it.description, + imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}" + ) + } + } + + val pageResult = chatCharacterRepository.findRecentSince( + since, + PageRequest.of(safePage, safeSize) + ) + return pageResult.content.map { + Character( + characterId = it.id!!, + name = it.name, + description = it.description, + imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}" + ) + } } /**