From 2335050834c225117d8f618039fa8b93f469748d Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 7 Aug 2025 12:30:19 +0900 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=BA=90=EB=A6=AD=ED=84=B0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../character/AdminChatCharacterController.kt | 16 ++++ .../dto/ChatCharacterDetailResponse.kt | 83 +++++++++++++++++++ .../service/AdminChatCharacterService.kt | 18 ++++ 3 files changed, 117 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/dto/ChatCharacterDetailResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt index 7f5bf84..9c05dca 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt @@ -21,6 +21,7 @@ import org.springframework.retry.annotation.Backoff import org.springframework.retry.annotation.Retryable import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestMapping @@ -68,6 +69,21 @@ class AdminChatCharacterController( ApiResponse.ok(response) } + /** + * 캐릭터 상세 정보 조회 API + * + * @param characterId 캐릭터 ID + * @return 캐릭터 상세 정보 + */ + @GetMapping("/{characterId}") + fun getCharacterDetail( + @PathVariable characterId: Long + ) = run { + val response = adminService.getChatCharacterDetail(characterId, imageHost) + + ApiResponse.ok(response) + } + @PostMapping("/register") @Retryable( value = [Exception::class], diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/dto/ChatCharacterDetailResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/dto/ChatCharacterDetailResponse.kt new file mode 100644 index 0000000..e71b9fe --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/dto/ChatCharacterDetailResponse.kt @@ -0,0 +1,83 @@ +package kr.co.vividnext.sodalive.admin.chat.character.dto + +import kr.co.vividnext.sodalive.chat.character.ChatCharacter + +data class ChatCharacterDetailResponse( + val id: Long, + val characterUUID: String, + val name: String, + val imageUrl: String?, + val description: String, + val systemPrompt: String, + val age: Int?, + val gender: String?, + val mbti: String?, + val speechPattern: String?, + val speechStyle: String?, + val appearance: String?, + val isActive: Boolean, + val tags: List, + val hobbies: List, + val values: List, + val goals: List, + val relationships: List, + val personalities: List, + val backgrounds: List, + val memories: List +) { + companion object { + fun from(chatCharacter: ChatCharacter, imageHost: String = ""): ChatCharacterDetailResponse { + val fullImagePath = if (chatCharacter.imagePath != null && imageHost.isNotEmpty()) { + "$imageHost/${chatCharacter.imagePath}" + } else { + chatCharacter.imagePath + } + + return ChatCharacterDetailResponse( + id = chatCharacter.id!!, + characterUUID = chatCharacter.characterUUID, + name = chatCharacter.name, + imageUrl = fullImagePath, + description = chatCharacter.description, + systemPrompt = chatCharacter.systemPrompt, + age = chatCharacter.age, + gender = chatCharacter.gender, + mbti = chatCharacter.mbti, + speechPattern = chatCharacter.speechPattern, + speechStyle = chatCharacter.speechStyle, + appearance = chatCharacter.appearance, + isActive = chatCharacter.isActive, + tags = chatCharacter.tagMappings.map { it.tag.tag }, + hobbies = chatCharacter.hobbyMappings.map { it.hobby.hobby }, + values = chatCharacter.valueMappings.map { it.value.value }, + goals = chatCharacter.goalMappings.map { it.goal.goal }, + relationships = chatCharacter.relationships.map { it.relationShip }, + personalities = chatCharacter.personalities.map { + PersonalityResponse(it.trait, it.description) + }, + backgrounds = chatCharacter.backgrounds.map { + BackgroundResponse(it.topic, it.description) + }, + memories = chatCharacter.memories.map { + MemoryResponse(it.title, it.content, it.emotion) + } + ) + } + } +} + +data class PersonalityResponse( + val trait: String, + val description: String +) + +data class BackgroundResponse( + val topic: String, + val description: String +) + +data class MemoryResponse( + val title: String, + val content: String, + val emotion: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/service/AdminChatCharacterService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/service/AdminChatCharacterService.kt index 25433c2..b48c66e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/service/AdminChatCharacterService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/service/AdminChatCharacterService.kt @@ -1,8 +1,10 @@ package kr.co.vividnext.sodalive.admin.chat.character.service +import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterDetailResponse import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterListPageResponse import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterListResponse import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository +import kr.co.vividnext.sodalive.common.SodaException import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort @@ -43,4 +45,20 @@ class AdminChatCharacterService( fun createDefaultPageRequest(page: Int = 0, size: Int = 20): PageRequest { return PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")) } + + /** + * 캐릭터 상세 정보 조회 + * + * @param characterId 캐릭터 ID + * @param imageHost 이미지 호스트 URL + * @return 캐릭터 상세 정보 + * @throws SodaException 캐릭터를 찾을 수 없는 경우 + */ + @Transactional(readOnly = true) + fun getChatCharacterDetail(characterId: Long, imageHost: String = ""): ChatCharacterDetailResponse { + val chatCharacter = chatCharacterRepository.findById(characterId) + .orElseThrow { SodaException("해당 ID의 캐릭터를 찾을 수 없습니다: $characterId") } + + return ChatCharacterDetailResponse.from(chatCharacter, imageHost) + } }