캐릭터 챗봇 #338
|
@ -1,6 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.admin.chat.character.curation
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
|
@ -60,7 +61,12 @@ class CharacterCurationAdminController(
|
|||
fun addCharacter(
|
||||
@PathVariable curationId: Long,
|
||||
@RequestBody request: CharacterCurationAddCharacterRequest
|
||||
) = ApiResponse.ok(service.addCharacter(curationId, request.characterId))
|
||||
): ApiResponse<Boolean> {
|
||||
val ids = request.characterIds.filter { it > 0 }.distinct()
|
||||
if (ids.isEmpty()) throw SodaException("등록할 캐릭터 ID 리스트가 비어있습니다")
|
||||
service.addCharacters(curationId, ids)
|
||||
return ApiResponse.ok(true)
|
||||
}
|
||||
|
||||
@DeleteMapping("/{curationId}/characters/{characterId}")
|
||||
fun removeCharacter(
|
||||
|
|
|
@ -18,7 +18,7 @@ data class CharacterCurationOrderUpdateRequest(
|
|||
)
|
||||
|
||||
data class CharacterCurationAddCharacterRequest(
|
||||
val characterId: Long
|
||||
val characterIds: List<Long>
|
||||
)
|
||||
|
||||
data class CharacterCurationReorderCharactersRequest(
|
||||
|
|
|
@ -60,26 +60,42 @@ class CharacterCurationAdminService(
|
|||
}
|
||||
|
||||
@Transactional
|
||||
fun addCharacter(curationId: Long, characterId: Long) {
|
||||
fun addCharacters(curationId: Long, characterIds: List<Long>) {
|
||||
if (characterIds.isEmpty()) throw SodaException("등록할 캐릭터 ID 리스트가 비어있습니다")
|
||||
|
||||
val curation = curationRepository.findById(curationId)
|
||||
.orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") }
|
||||
if (!curation.isActive) throw SodaException("비활성화된 큐레이션입니다: $curationId")
|
||||
|
||||
val character = characterRepository.findById(characterId)
|
||||
.orElseThrow { SodaException("캐릭터를 찾을 수 없습니다: $characterId") }
|
||||
if (!character.isActive) throw SodaException("비활성화된 캐릭터는 추가할 수 없습니다: $characterId")
|
||||
val uniqueIds = characterIds.filter { it > 0 }.distinct()
|
||||
if (uniqueIds.isEmpty()) throw SodaException("유효한 캐릭터 ID가 없습니다")
|
||||
|
||||
val existing = mappingRepository.findByCuration(curation)
|
||||
.firstOrNull { it.chatCharacter.id == characterId }
|
||||
if (existing != null) return // 이미 존재하면 무시
|
||||
// 활성 캐릭터만 조회 (조회 단계에서 검증 포함)
|
||||
val characters = characterRepository.findByIdInAndIsActiveTrue(uniqueIds)
|
||||
val characterMap = characters.associateBy { it.id!! }
|
||||
|
||||
val nextOrder = (mappingRepository.findByCuration(curation).maxOfOrNull { it.sortOrder } ?: 0) + 1
|
||||
val mapping = CharacterCurationMapping(
|
||||
curation = curation,
|
||||
chatCharacter = character,
|
||||
sortOrder = nextOrder
|
||||
)
|
||||
mappingRepository.save(mapping)
|
||||
// 조회 결과에 존재하는 캐릭터만 유효
|
||||
val validIds = uniqueIds.filter { id -> characterMap.containsKey(id) }
|
||||
|
||||
val existingMappings = mappingRepository.findByCuration(curation)
|
||||
val existingCharacterIds = existingMappings.mapNotNull { it.chatCharacter.id }.toSet()
|
||||
var nextOrder = (existingMappings.maxOfOrNull { it.sortOrder } ?: 0) + 1
|
||||
|
||||
val toSave = mutableListOf<CharacterCurationMapping>()
|
||||
validIds.forEach { id ->
|
||||
if (!existingCharacterIds.contains(id)) {
|
||||
val character = characterMap[id] ?: return@forEach
|
||||
toSave += CharacterCurationMapping(
|
||||
curation = curation,
|
||||
chatCharacter = character,
|
||||
sortOrder = nextOrder++
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (toSave.isNotEmpty()) {
|
||||
mappingRepository.saveAll(toSave)
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
|
|
@ -61,4 +61,6 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
|
|||
@Param("characterId") characterId: Long,
|
||||
pageable: Pageable
|
||||
): List<ChatCharacter>
|
||||
|
||||
fun findByIdInAndIsActiveTrue(ids: List<Long>): List<ChatCharacter>
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue