feat(character): 캐릭터 메인 API 추가
This commit is contained in:
parent
b0a6fc6498
commit
a1533c8e98
|
@ -0,0 +1,88 @@
|
||||||
|
package kr.co.vividnext.sodalive.chat.character.controller
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.dto.Character
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.dto.CharacterBannerResponse
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.dto.CharacterMainResponse
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.dto.CurationSection
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.dto.RecentCharacter
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterBannerService
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/chat/character")
|
||||||
|
class ChatCharacterController(
|
||||||
|
private val service: ChatCharacterService,
|
||||||
|
private val bannerService: ChatCharacterBannerService,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val imageHost: String
|
||||||
|
) {
|
||||||
|
@GetMapping("/main")
|
||||||
|
fun getCharacterMain(
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
): ApiResponse<CharacterMainResponse> = run {
|
||||||
|
// 배너 조회 (최대 10개)
|
||||||
|
val banners = bannerService.getActiveBanners(PageRequest.of(0, 10))
|
||||||
|
.content
|
||||||
|
.map {
|
||||||
|
CharacterBannerResponse(
|
||||||
|
characterId = it.chatCharacter.id!!,
|
||||||
|
imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 최근 대화한 캐릭터 조회 (현재는 빈 리스트)
|
||||||
|
val recentCharacters = service.getRecentCharacters()
|
||||||
|
.map {
|
||||||
|
RecentCharacter(
|
||||||
|
characterId = it.id!!,
|
||||||
|
name = it.name,
|
||||||
|
imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 인기 캐릭터 조회 (현재는 빈 리스트)
|
||||||
|
val popularCharacters = service.getPopularCharacters()
|
||||||
|
.map {
|
||||||
|
Character(
|
||||||
|
characterId = it.id!!,
|
||||||
|
name = it.name,
|
||||||
|
description = it.description,
|
||||||
|
imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 최신 캐릭터 조회 (최대 10개)
|
||||||
|
val newCharacters = service.getNewCharacters(10)
|
||||||
|
.map {
|
||||||
|
Character(
|
||||||
|
characterId = it.id!!,
|
||||||
|
name = it.name,
|
||||||
|
description = it.description,
|
||||||
|
imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 큐레이션 섹션 (현재는 빈 리스트)
|
||||||
|
val curationSections = emptyList<CurationSection>()
|
||||||
|
|
||||||
|
// 응답 생성
|
||||||
|
ApiResponse.ok(
|
||||||
|
CharacterMainResponse(
|
||||||
|
banners = banners,
|
||||||
|
recentCharacters = recentCharacters,
|
||||||
|
popularCharacters = popularCharacters,
|
||||||
|
newCharacters = newCharacters,
|
||||||
|
curationSections = curationSections
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package kr.co.vividnext.sodalive.chat.character.dto
|
||||||
|
|
||||||
|
data class CharacterMainResponse(
|
||||||
|
val banners: List<CharacterBannerResponse>,
|
||||||
|
val recentCharacters: List<RecentCharacter>,
|
||||||
|
val popularCharacters: List<Character>,
|
||||||
|
val newCharacters: List<Character>,
|
||||||
|
val curationSections: List<CurationSection>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CurationSection(
|
||||||
|
val characterCurationId: Long,
|
||||||
|
val title: String,
|
||||||
|
val characters: List<Character>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Character(
|
||||||
|
val characterId: Long,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val imageUrl: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RecentCharacter(
|
||||||
|
val characterId: Long,
|
||||||
|
val name: String,
|
||||||
|
val imageUrl: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CharacterBannerResponse(
|
||||||
|
val characterId: Long,
|
||||||
|
val imageUrl: String
|
||||||
|
)
|
|
@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository
|
||||||
@Repository
|
@Repository
|
||||||
interface ChatCharacterBannerRepository : JpaRepository<ChatCharacterBanner, Long> {
|
interface ChatCharacterBannerRepository : JpaRepository<ChatCharacterBanner, Long> {
|
||||||
// 활성화된 배너 목록 조회 (정렬 순서대로)
|
// 활성화된 배너 목록 조회 (정렬 순서대로)
|
||||||
fun findByActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page<ChatCharacterBanner>
|
fun findByIsActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page<ChatCharacterBanner>
|
||||||
|
|
||||||
// 활성화된 배너 중 최대 정렬 순서 값 조회
|
// 활성화된 배너 중 최대 정렬 순서 값 조회
|
||||||
@Query("SELECT MAX(b.sortOrder) FROM ChatCharacterBanner b WHERE b.isActive = true")
|
@Query("SELECT MAX(b.sortOrder) FROM ChatCharacterBanner b WHERE b.isActive = true")
|
||||||
|
|
|
@ -18,7 +18,7 @@ class ChatCharacterBannerService(
|
||||||
* 활성화된 모든 배너 조회 (정렬 순서대로)
|
* 활성화된 모든 배너 조회 (정렬 순서대로)
|
||||||
*/
|
*/
|
||||||
fun getActiveBanners(pageable: Pageable): Page<ChatCharacterBanner> {
|
fun getActiveBanners(pageable: Pageable): Page<ChatCharacterBanner> {
|
||||||
return bannerRepository.findByActiveTrueOrderBySortOrderAsc(pageable)
|
return bannerRepository.findByIsActiveTrueOrderBySortOrderAsc(pageable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,6 +23,37 @@ class ChatCharacterService(
|
||||||
private val goalRepository: ChatCharacterGoalRepository
|
private val goalRepository: ChatCharacterGoalRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 최근에 대화한 캐릭터 목록 조회
|
||||||
|
* 현재는 채팅방 구현 전이므로 빈 리스트 반환
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getRecentCharacters(): List<ChatCharacter> {
|
||||||
|
// 채팅방 구현 전이므로 빈 리스트 반환
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 일주일간 대화가 가장 많은 인기 캐릭터 목록 조회
|
||||||
|
* 현재는 채팅방 구현 전이므로 빈 리스트 반환
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getPopularCharacters(): List<ChatCharacter> {
|
||||||
|
// 채팅방 구현 전이므로 빈 리스트 반환
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 최근 등록된 캐릭터 목록 조회 (최대 10개)
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun getNewCharacters(limit: Int = 10): List<ChatCharacter> {
|
||||||
|
return chatCharacterRepository.findAll()
|
||||||
|
.filter { it.isActive }
|
||||||
|
.sortedByDescending { it.createdAt }
|
||||||
|
.take(limit)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 태그를 찾거나 생성하여 캐릭터에 연결
|
* 태그를 찾거나 생성하여 캐릭터에 연결
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -93,6 +93,7 @@ class SecurityConfig(
|
||||||
.antMatchers(HttpMethod.GET, "/live/recommend").permitAll()
|
.antMatchers(HttpMethod.GET, "/live/recommend").permitAll()
|
||||||
.antMatchers("/ad-tracking/app-launch").permitAll()
|
.antMatchers("/ad-tracking/app-launch").permitAll()
|
||||||
.antMatchers(HttpMethod.GET, "/notice/latest").permitAll()
|
.antMatchers(HttpMethod.GET, "/notice/latest").permitAll()
|
||||||
|
.antMatchers(HttpMethod.GET, "/api/chat/character/main").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
.and()
|
.and()
|
||||||
.build()
|
.build()
|
||||||
|
|
Loading…
Reference in New Issue