feat(character): 최근 등록된 캐릭터 전체보기 API 추가

This commit is contained in:
Klaus 2025-09-12 18:37:25 +09:00
parent 27a3f450ef
commit 88e287067b
3 changed files with 80 additions and 16 deletions

View File

@ -22,6 +22,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -67,16 +68,11 @@ class ChatCharacterController(
// 인기 캐릭터 조회 // 인기 캐릭터 조회
val popularCharacters = service.getPopularCharacters() val popularCharacters = service.getPopularCharacters()
// 최신 캐릭터 조회 (최대 10개) // 최근 등록된 캐릭터 리스트 조회
val newCharacters = service.getNewCharacters(50) val newCharacters = service.getRecentCharacters(
.map { page = 0,
Character( size = 50
characterId = it.id!!, )
name = it.name,
description = it.description,
imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}"
)
}
// 큐레이션 섹션 (활성화된 큐레이션 + 캐릭터) // 큐레이션 섹션 (활성화된 큐레이션 + 캐릭터)
val curationSections = curationQueryService.getActiveCurationsWithCharacters() 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
)
)
}
} }

View File

@ -10,14 +10,25 @@ import org.springframework.stereotype.Repository
@Repository @Repository
interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> { interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
fun findByCharacterUUID(characterUUID: String): ChatCharacter?
fun findByName(name: String): ChatCharacter? fun findByName(name: String): ChatCharacter?
fun findByIsActiveTrue(pageable: Pageable): Page<ChatCharacter> fun findByIsActiveTrue(pageable: Pageable): Page<ChatCharacter>
/** /**
* 활성화된 캐릭터를 생성일 기준 내림차순으로 조회 * 2 이내(파라미터 since 이상) 활성 캐릭터 페이징 조회
*/ */
fun findByIsActiveTrueOrderByCreatedAtDesc(pageable: Pageable): List<ChatCharacter> @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<ChatCharacter>
/**
* 2 이내(파라미터 since 이상) 활성 캐릭터 개수
*/
fun countByIsActiveTrueAndCreatedAtGreaterThanEqual(since: java.time.LocalDateTime): Long
/** /**
* 이름, 설명, MBTI, 태그로 캐릭터 검색 * 이름, 설명, MBTI, 태그로 캐릭터 검색

View File

@ -20,8 +20,10 @@ import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterValueRepo
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.Cacheable import org.springframework.cache.annotation.Cacheable
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Service @Service
class ChatCharacterService( class ChatCharacterService(
@ -66,11 +68,51 @@ class ChatCharacterService(
} }
/** /**
* 최근 등록된 캐릭터 목록 조회 (최대 10) * 최근 등록된 캐릭터 전체보기 (페이징)
* - 기준: 현재 시각 기준 2 이내 생성된 활성 캐릭터
* - 2 이내 캐릭터가 0개라면: 최근 등록한 캐릭터 20 반환(페이지 무시)
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun getNewCharacters(limit: Int = 10): List<ChatCharacter> { fun getRecentCharacters(page: Int = 0, size: Int = 20): List<Character> {
return chatCharacterRepository.findByIsActiveTrueOrderByCreatedAtDesc(PageRequest.of(0, limit)) 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"}"
)
}
} }
/** /**