feat(character): 최근 등록된 캐릭터 전체보기 API 추가
This commit is contained in:
parent
27a3f450ef
commit
88e287067b
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, 태그로 캐릭터 검색
|
||||||
|
|
|
@ -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"}"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue