feat(character): 캐릭터 수정 API 구현

- ChatCharacterUpdateRequest 클래스 추가 (모든 필드 nullable)
- ChatCharacter 엔티티의 필드를 var로 변경하여 수정 가능하게 함
- 이미지 포함/제외 수정 API를 하나로 통합
- 변경된 데이터만 업데이트하도록 구현
- isActive가 false인 경우 특별 처리 추가
This commit is contained in:
2025-08-06 21:59:16 +09:00
parent de6642b675
commit 5132a6b9fa
4 changed files with 297 additions and 10 deletions

View File

@@ -12,37 +12,37 @@ class ChatCharacter(
val characterUUID: String,
// 캐릭터 이름 (API 키 내에서 유일해야 함)
val name: String,
var name: String,
// 캐릭터 설명
@Column(columnDefinition = "TEXT", nullable = false)
val description: String,
var description: String,
// AI 시스템 프롬프트
@Column(columnDefinition = "TEXT", nullable = false)
val systemPrompt: String,
var systemPrompt: String,
// 나이
val age: Int? = null,
var age: Int? = null,
// 성별
val gender: String? = null,
var gender: String? = null,
// mbti
val mbti: String? = null,
var mbti: String? = null,
// 말투 패턴 설명
@Column(columnDefinition = "TEXT")
val speechPattern: String? = null,
var speechPattern: String? = null,
// 대화 스타일
val speechStyle: String? = null,
var speechStyle: String? = null,
// 외모 설명
@Column(columnDefinition = "TEXT")
val appearance: String? = null,
var appearance: String? = null,
val isActive: Boolean = true
var isActive: Boolean = true
) : BaseEntity() {
var imagePath: String? = null

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.chat.character.service
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterUpdateRequest
import kr.co.vividnext.sodalive.chat.character.ChatCharacter
import kr.co.vividnext.sodalive.chat.character.ChatCharacterGoal
import kr.co.vividnext.sodalive.chat.character.ChatCharacterHobby
@@ -134,6 +135,14 @@ class ChatCharacterService(
return chatCharacterRepository.findAll()
}
/**
* ID로 캐릭터 조회
*/
@Transactional(readOnly = true)
fun findById(id: Long): ChatCharacter? {
return chatCharacterRepository.findById(id).orElse(null)
}
/**
* 캐릭터 생성 및 관련 엔티티 연결
*/
@@ -260,4 +269,100 @@ class ChatCharacterService(
return saveChatCharacter(chatCharacter)
}
/**
* 캐릭터 수정 시 기본 정보와 함께 추가 정보도 설정
* 이름은 변경할 수 없으므로 이름은 변경하지 않음
* 변경된 데이터만 업데이트
*
* @param imagePath 이미지 경로 (null이면 이미지 변경 없음)
* @param request 수정 요청 데이터 (id를 제외한 모든 필드는 null 가능)
* @return 수정된 ChatCharacter 객체
* @throws SodaException 캐릭터를 찾을 수 없는 경우
*/
@Transactional
fun updateChatCharacterWithDetails(
imagePath: String? = null,
request: ChatCharacterUpdateRequest
): ChatCharacter {
// 캐릭터 조회
val chatCharacter = findById(request.id)
?: throw kr.co.vividnext.sodalive.common.SodaException("해당 ID의 캐릭터를 찾을 수 없습니다: ${request.id}")
// isActive가 false이면 isActive = false, name = "inactive_$name"으로 변경하고 나머지는 반영하지 않는다.
if (request.isActive != null && !request.isActive) {
chatCharacter.isActive = false
chatCharacter.name = "inactive_${chatCharacter.name}"
return saveChatCharacter(chatCharacter)
}
// 이미지 경로가 있으면 설정
if (imagePath != null) {
chatCharacter.imagePath = imagePath
}
// 기본 필드 업데이트 - 변경된 데이터만 업데이트
request.name?.let { chatCharacter.name = it }
request.systemPrompt?.let { chatCharacter.systemPrompt = it }
request.description?.let { chatCharacter.description = it }
request.age?.toIntOrNull()?.let { chatCharacter.age = it }
request.gender?.let { chatCharacter.gender = it }
request.mbti?.let { chatCharacter.mbti = it }
request.speechPattern?.let { chatCharacter.speechPattern = it }
request.speechStyle?.let { chatCharacter.speechStyle = it }
request.appearance?.let { chatCharacter.appearance = it }
// request에서 변경된 데이터만 업데이트
if (request.tags != null) {
chatCharacter.tagMappings.clear()
addTagsToCharacter(chatCharacter, request.tags)
}
if (request.values != null) {
chatCharacter.valueMappings.clear()
addValuesToCharacter(chatCharacter, request.values)
}
if (request.hobbies != null) {
chatCharacter.hobbyMappings.clear()
addHobbiesToCharacter(chatCharacter, request.hobbies)
}
if (request.goals != null) {
chatCharacter.goalMappings.clear()
addGoalsToCharacter(chatCharacter, request.goals)
}
// 추가 정보 설정 - 변경된 데이터만 업데이트
if (request.memories != null) {
chatCharacter.memories.clear()
request.memories.forEach { memory ->
chatCharacter.addMemory(memory.title, memory.content, memory.emotion)
}
}
if (request.personalities != null) {
chatCharacter.personalities.clear()
request.personalities.forEach { personality ->
chatCharacter.addPersonality(personality.trait, personality.description)
}
}
if (request.backgrounds != null) {
chatCharacter.backgrounds.clear()
request.backgrounds.forEach { background ->
chatCharacter.addBackground(background.topic, background.description)
}
}
if (request.relationships != null) {
chatCharacter.relationships.clear()
request.relationships.forEach { relationship ->
chatCharacter.addRelationship(relationship)
}
}
return saveChatCharacter(chatCharacter)
}
}