From a1533c8e980071ac74b326d43b8eee3ca27d68bc Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 7 Aug 2025 22:33:29 +0900 Subject: [PATCH] =?UTF-8?q?feat(character):=20=EC=BA=90=EB=A6=AD=ED=84=B0?= =?UTF-8?q?=20=EB=A9=94=EC=9D=B8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatCharacterController.kt | 88 +++++++++++++++++++ .../character/dto/CharacterHomeResponse.kt | 33 +++++++ .../ChatCharacterBannerRepository.kt | 2 +- .../service/ChatCharacterBannerService.kt | 2 +- .../character/service/ChatCharacterService.kt | 31 +++++++ .../sodalive/configs/SecurityConfig.kt | 1 + 6 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/chat/character/dto/CharacterHomeResponse.kt 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 new file mode 100644 index 0000000..5b024e4 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt @@ -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 = 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() + + // 응답 생성 + ApiResponse.ok( + CharacterMainResponse( + banners = banners, + recentCharacters = recentCharacters, + popularCharacters = popularCharacters, + newCharacters = newCharacters, + curationSections = curationSections + ) + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/dto/CharacterHomeResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/dto/CharacterHomeResponse.kt new file mode 100644 index 0000000..b471315 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/dto/CharacterHomeResponse.kt @@ -0,0 +1,33 @@ +package kr.co.vividnext.sodalive.chat.character.dto + +data class CharacterMainResponse( + val banners: List, + val recentCharacters: List, + val popularCharacters: List, + val newCharacters: List, + val curationSections: List +) + +data class CurationSection( + val characterCurationId: Long, + val title: String, + val characters: List +) + +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 +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterBannerRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterBannerRepository.kt index 8b7ef53..2de9020 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterBannerRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterBannerRepository.kt @@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository @Repository interface ChatCharacterBannerRepository : JpaRepository { // 활성화된 배너 목록 조회 (정렬 순서대로) - fun findByActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page + fun findByIsActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page // 활성화된 배너 중 최대 정렬 순서 값 조회 @Query("SELECT MAX(b.sortOrder) FROM ChatCharacterBanner b WHERE b.isActive = true") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterBannerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterBannerService.kt index 6abc5c6..1eeaadb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterBannerService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterBannerService.kt @@ -18,7 +18,7 @@ class ChatCharacterBannerService( * 활성화된 모든 배너 조회 (정렬 순서대로) */ fun getActiveBanners(pageable: Pageable): Page { - return bannerRepository.findByActiveTrueOrderBySortOrderAsc(pageable) + return bannerRepository.findByIsActiveTrueOrderBySortOrderAsc(pageable) } /** 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 ab4254a..ba958cc 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 @@ -23,6 +23,37 @@ class ChatCharacterService( private val goalRepository: ChatCharacterGoalRepository ) { + /** + * 최근에 대화한 캐릭터 목록 조회 + * 현재는 채팅방 구현 전이므로 빈 리스트 반환 + */ + @Transactional(readOnly = true) + fun getRecentCharacters(): List { + // 채팅방 구현 전이므로 빈 리스트 반환 + return emptyList() + } + + /** + * 일주일간 대화가 가장 많은 인기 캐릭터 목록 조회 + * 현재는 채팅방 구현 전이므로 빈 리스트 반환 + */ + @Transactional(readOnly = true) + fun getPopularCharacters(): List { + // 채팅방 구현 전이므로 빈 리스트 반환 + return emptyList() + } + + /** + * 최근 등록된 캐릭터 목록 조회 (최대 10개) + */ + @Transactional(readOnly = true) + fun getNewCharacters(limit: Int = 10): List { + return chatCharacterRepository.findAll() + .filter { it.isActive } + .sortedByDescending { it.createdAt } + .take(limit) + } + /** * 태그를 찾거나 생성하여 캐릭터에 연결 */ diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt index 0f01436..78fb478 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt @@ -93,6 +93,7 @@ class SecurityConfig( .antMatchers(HttpMethod.GET, "/live/recommend").permitAll() .antMatchers("/ad-tracking/app-launch").permitAll() .antMatchers(HttpMethod.GET, "/notice/latest").permitAll() + .antMatchers(HttpMethod.GET, "/api/chat/character/main").permitAll() .anyRequest().authenticated() .and() .build()