캐릭터 챗봇 #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> | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user