feat(ai-character): 캐릭터 관리자 레거시 계약을 보강한다
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character
|
||||
|
||||
import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkResponse
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.aicharacter.error.AiCharacterAdminApiException
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
@@ -11,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RequestPart
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/admin/ai-characters")
|
||||
@@ -19,11 +24,18 @@ class AiCharacterAdminCharacterController(
|
||||
) {
|
||||
@GetMapping
|
||||
fun list(
|
||||
@RequestParam(required = false) search: String?,
|
||||
@RequestParam(required = false) searchTerm: String?,
|
||||
@RequestParam(defaultValue = "0") page: Int,
|
||||
@RequestParam(defaultValue = "20") size: Int
|
||||
): ApiResponse<AiCharacterAdminListResponse> {
|
||||
return ApiResponse.ok(facade.list(search, page, size))
|
||||
return ApiResponse.ok(facade.list(searchTerm, page, size))
|
||||
}
|
||||
|
||||
@GetMapping("/original-works/search")
|
||||
fun searchOriginalWorks(
|
||||
@RequestParam("searchTerm") searchTerm: String
|
||||
): ApiResponse<List<OriginalWorkResponse>> {
|
||||
return ApiResponse.ok(facade.searchOriginalWorks(searchTerm))
|
||||
}
|
||||
|
||||
@GetMapping("/{characterId:[0-9]+}")
|
||||
@@ -33,18 +45,46 @@ class AiCharacterAdminCharacterController(
|
||||
|
||||
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
fun create(
|
||||
@RequestPart(value = "image", required = false) image: MultipartFile?,
|
||||
@RequestPart("request") request: String
|
||||
): ApiResponse<AiCharacterAdminCharacterResponse> {
|
||||
return ApiResponse.ok(facade.create(image, request))
|
||||
@RequestPart("image") image: MultipartFile,
|
||||
@RequestPart("request") request: String,
|
||||
multipartRequest: MultipartHttpServletRequest
|
||||
): ApiResponse<Nothing> {
|
||||
requireAllowedMultipartParts(multipartRequest)
|
||||
requireJsonRequestPart(multipartRequest)
|
||||
facade.create(image, request)
|
||||
return ApiResponse.ok(null)
|
||||
}
|
||||
|
||||
@PutMapping("/{characterId:[0-9]+}", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
fun update(
|
||||
@PathVariable characterId: Long,
|
||||
@RequestPart(value = "image", required = false) image: MultipartFile?,
|
||||
@RequestPart("request") request: String
|
||||
): ApiResponse<AiCharacterAdminCharacterResponse> {
|
||||
return ApiResponse.ok(facade.update(characterId, image, request))
|
||||
@RequestPart("request") request: String,
|
||||
multipartRequest: MultipartHttpServletRequest
|
||||
): ApiResponse<Nothing> {
|
||||
requireAllowedMultipartParts(multipartRequest)
|
||||
requireJsonRequestPart(multipartRequest)
|
||||
facade.update(characterId, image, request)
|
||||
return ApiResponse.ok(null)
|
||||
}
|
||||
|
||||
private fun requireAllowedMultipartParts(multipartRequest: MultipartHttpServletRequest) {
|
||||
if (multipartRequest.fileMap.keys.any { it !in CHARACTER_MULTIPART_PARTS } ||
|
||||
multipartRequest.parts.any { it.name !in CHARACTER_MULTIPART_PARTS }
|
||||
) {
|
||||
throw AiCharacterAdminApiException(HttpStatus.BAD_REQUEST, "common.error.invalid_request")
|
||||
}
|
||||
}
|
||||
|
||||
private fun requireJsonRequestPart(multipartRequest: MultipartHttpServletRequest) {
|
||||
val contentType = multipartRequest.getMultipartHeaders("request")?.contentType
|
||||
?: multipartRequest.getPart("request")?.contentType?.let(MediaType::parseMediaType)
|
||||
if (contentType == null || !MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
|
||||
throw HttpMediaTypeNotSupportedException(contentType, listOf(MediaType.APPLICATION_JSON))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val CHARACTER_MULTIPART_PARTS = setOf("image", "request")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,23 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character
|
||||
|
||||
data class AiCharacterAdminListResponse(
|
||||
val totalCount: Long,
|
||||
val items: List<AiCharacterAdminCharacterListItemResponse>,
|
||||
val page: Int,
|
||||
val size: Int,
|
||||
val hasNext: Boolean
|
||||
)
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterBackgroundRequest
|
||||
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.admin.chat.character.dto.ChatCharacterMemoryRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterPersonalityRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterRegisterRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterRelationshipRequest
|
||||
|
||||
data class AiCharacterAdminCharacterListItemResponse(
|
||||
val characterId: Long,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val imageUrl: String,
|
||||
val creatorMemberId: Long,
|
||||
val creatorNickname: String,
|
||||
val originalWorkId: Long?,
|
||||
val externalCharacterId: String,
|
||||
val isActive: Boolean,
|
||||
val createdAtUtc: String?
|
||||
)
|
||||
|
||||
data class AiCharacterAdminCharacterResponse(
|
||||
val characterId: Long,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val imageUrl: String,
|
||||
val creatorMemberId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorIntroduce: String?,
|
||||
val originalWorkId: Long?,
|
||||
val externalCharacterId: String,
|
||||
val isActive: Boolean,
|
||||
val createdAtUtc: String?,
|
||||
val updatedAtUtc: String?
|
||||
)
|
||||
|
||||
data class AiCharacterAdminCharacterCreateRequest(
|
||||
val name: String,
|
||||
val systemPrompt: String,
|
||||
val description: String,
|
||||
val externalCharacterId: String? = null,
|
||||
val isActive: Boolean? = null,
|
||||
val age: String? = null,
|
||||
val gender: String? = null,
|
||||
val mbti: String? = null,
|
||||
val speechPattern: String? = null,
|
||||
val speechStyle: String? = null,
|
||||
val appearance: String? = null,
|
||||
val region: String = "KR",
|
||||
val originalTitle: String? = null,
|
||||
val originalLink: String? = null,
|
||||
val originalWorkId: Long? = null,
|
||||
val characterType: String? = null,
|
||||
val tags: List<String> = emptyList(),
|
||||
val hobbies: List<String> = emptyList(),
|
||||
val values: List<String> = emptyList(),
|
||||
val goals: List<String> = emptyList(),
|
||||
val relationships: List<AiCharacterAdminCharacterRelationshipRequest> = emptyList(),
|
||||
val personalities: List<AiCharacterAdminCharacterPersonalityRequest> = emptyList(),
|
||||
val backgrounds: List<AiCharacterAdminCharacterBackgroundRequest> = emptyList(),
|
||||
val memories: List<AiCharacterAdminCharacterMemoryRequest> = emptyList()
|
||||
)
|
||||
typealias AiCharacterAdminListResponse = ChatCharacterListPageResponse
|
||||
typealias AiCharacterAdminCharacterListItemResponse = ChatCharacterListResponse
|
||||
typealias AiCharacterAdminCharacterResponse = ChatCharacterDetailResponse
|
||||
typealias AiCharacterAdminCharacterCreateRequest = ChatCharacterRegisterRequest
|
||||
|
||||
data class AiCharacterAdminCharacterUpdateRequest(
|
||||
val name: String? = null,
|
||||
val systemPrompt: String? = null,
|
||||
val description: String? = null,
|
||||
val externalCharacterId: String? = null,
|
||||
val age: String? = null,
|
||||
val gender: String? = null,
|
||||
val mbti: String? = null,
|
||||
@@ -84,33 +33,8 @@ data class AiCharacterAdminCharacterUpdateRequest(
|
||||
val hobbies: List<String>? = null,
|
||||
val values: List<String>? = null,
|
||||
val goals: List<String>? = null,
|
||||
val relationships: List<AiCharacterAdminCharacterRelationshipRequest>? = null,
|
||||
val personalities: List<AiCharacterAdminCharacterPersonalityRequest>? = null,
|
||||
val backgrounds: List<AiCharacterAdminCharacterBackgroundRequest>? = null,
|
||||
val memories: List<AiCharacterAdminCharacterMemoryRequest>? = null
|
||||
)
|
||||
|
||||
data class AiCharacterAdminCharacterRelationshipRequest(
|
||||
val personName: String,
|
||||
val relationshipName: String,
|
||||
val description: String,
|
||||
val importance: Int,
|
||||
val relationshipType: String,
|
||||
val currentStatus: String
|
||||
)
|
||||
|
||||
data class AiCharacterAdminCharacterPersonalityRequest(
|
||||
val trait: String,
|
||||
val description: String
|
||||
)
|
||||
|
||||
data class AiCharacterAdminCharacterBackgroundRequest(
|
||||
val topic: String,
|
||||
val description: String
|
||||
)
|
||||
|
||||
data class AiCharacterAdminCharacterMemoryRequest(
|
||||
val title: String,
|
||||
val content: String,
|
||||
val emotion: String
|
||||
val relationships: List<ChatCharacterRelationshipRequest>? = null,
|
||||
val personalities: List<ChatCharacterPersonalityRequest>? = null,
|
||||
val backgrounds: List<ChatCharacterBackgroundRequest>? = null,
|
||||
val memories: List<ChatCharacterMemoryRequest>? = null
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkResponse
|
||||
import kr.co.vividnext.sodalive.admin.chat.original.service.AdminOriginalWorkService
|
||||
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository
|
||||
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterCreatorMemberService
|
||||
@@ -14,6 +16,7 @@ import kr.co.vividnext.sodalive.member.MemberKind
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.aicharacter.application.AiCharacterAdminTargetResolver
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.aicharacter.error.AiCharacterAdminApiException
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Sort
|
||||
@@ -33,14 +36,16 @@ class AiCharacterAdminCharacterFacade(
|
||||
private val characterMapper: AiCharacterAdminCharacterMapper,
|
||||
private val originalWorkService: AdminOriginalWorkService,
|
||||
private val creatorMemberService: ChatCharacterCreatorMemberService,
|
||||
private val applicationEventPublisher: ApplicationEventPublisher
|
||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val imageHost: String
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun list(search: String?, page: Int, size: Int): AiCharacterAdminListResponse {
|
||||
fun list(searchTerm: String?, page: Int, size: Int): AiCharacterAdminListResponse {
|
||||
val normalizedPage = page.coerceAtLeast(0)
|
||||
val normalizedSize = size.coerceIn(MINIMUM_PAGE_SIZE, MAXIMUM_PAGE_SIZE)
|
||||
val normalizedSize = size.coerceAtLeast(MINIMUM_PAGE_SIZE)
|
||||
val characters = chatCharacterRepository.searchAiCharacters(
|
||||
searchTerm = search?.trim().orEmpty(),
|
||||
searchTerm = searchTerm?.trim().orEmpty(),
|
||||
role = MemberRole.CREATOR,
|
||||
memberKind = MemberKind.AI_CHARACTER,
|
||||
pageable = PageRequest.of(normalizedPage, normalizedSize, Sort.by(Sort.Direction.DESC, "createdAt"))
|
||||
@@ -48,10 +53,7 @@ class AiCharacterAdminCharacterFacade(
|
||||
|
||||
return AiCharacterAdminListResponse(
|
||||
totalCount = characters.totalElements,
|
||||
items = characters.content.map(characterMapper::toListItemResponse),
|
||||
page = normalizedPage,
|
||||
size = normalizedSize,
|
||||
hasNext = characters.hasNext()
|
||||
content = characters.content.map(characterMapper::toListItemResponse)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -60,12 +62,16 @@ class AiCharacterAdminCharacterFacade(
|
||||
return characterMapper.toResponse(targetResolver.resolve(characterId).chatCharacter)
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun searchOriginalWorks(searchTerm: String): List<OriginalWorkResponse> {
|
||||
return originalWorkService.searchOriginalWorksAll(searchTerm)
|
||||
.map { OriginalWorkResponse.from(it, imageHost) }
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun create(image: MultipartFile?, requestString: String): AiCharacterAdminCharacterResponse {
|
||||
fun create(image: MultipartFile?, requestString: String) {
|
||||
if (image?.isEmpty == true) throw invalidRequest()
|
||||
val request = readRequest(requestString, AiCharacterAdminCharacterCreateRequest::class.java)
|
||||
if (request.externalCharacterId != null || request.isActive != null) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
val characterType = characterMapper.toCharacterType(request)
|
||||
if (chatCharacterService.findByName(request.name) != null) {
|
||||
throw invalidRequest()
|
||||
@@ -94,7 +100,7 @@ class AiCharacterAdminCharacterFacade(
|
||||
memories = request.memories.map { Triple(it.title, it.content, it.emotion) },
|
||||
personalities = request.personalities.map { Pair(it.trait, it.description) },
|
||||
backgrounds = request.backgrounds.map { Pair(it.topic, it.description) },
|
||||
relationships = request.relationships.map(characterMapper::toLegacyRequest)
|
||||
relationships = request.relationships
|
||||
)
|
||||
|
||||
if (image?.isEmpty == false) {
|
||||
@@ -113,50 +119,47 @@ class AiCharacterAdminCharacterFacade(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return characterMapper.toResponse(chatCharacter)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun update(characterId: Long, image: MultipartFile?, requestString: String): AiCharacterAdminCharacterResponse {
|
||||
fun update(characterId: Long, image: MultipartFile?, requestString: String) {
|
||||
val target = targetResolver.resolve(characterId)
|
||||
val request = readRequest(requestString, AiCharacterAdminCharacterUpdateRequest::class.java)
|
||||
if (request.externalCharacterId != null) {
|
||||
if (request.isActive == false && image?.isEmpty == false) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
characterMapper.validateCharacterType(request)
|
||||
if (
|
||||
request.isActive == false &&
|
||||
(characterMapper.hasNonSoftDeleteChanges(request) || image?.isEmpty == false)
|
||||
) {
|
||||
throw invalidRequest()
|
||||
val effectiveRequest = if (request.isActive == false) {
|
||||
AiCharacterAdminCharacterUpdateRequest(isActive = false)
|
||||
} else {
|
||||
request
|
||||
}
|
||||
characterMapper.validateCharacterType(effectiveRequest)
|
||||
if (
|
||||
!characterMapper.hasExternalChanges(request) &&
|
||||
!characterMapper.hasDbOnlyChanges(request) &&
|
||||
!characterMapper.hasExternalChanges(effectiveRequest) &&
|
||||
!characterMapper.hasDbOnlyChanges(effectiveRequest) &&
|
||||
image?.isEmpty != false
|
||||
) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
if (
|
||||
request.name != null && request.name != target.chatCharacter.name &&
|
||||
chatCharacterService.findByName(request.name) != null
|
||||
effectiveRequest.name != null && effectiveRequest.name != target.chatCharacter.name &&
|
||||
chatCharacterService.findByName(effectiveRequest.name) != null
|
||||
) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
request.originalWorkId?.takeIf { it != 0L }?.let(originalWorkService::getOriginalWork)
|
||||
effectiveRequest.originalWorkId?.takeIf { it != 0L }?.let(originalWorkService::getOriginalWork)
|
||||
|
||||
val requestName = if (request.isActive == false) request.name ?: target.chatCharacter.name else request.name
|
||||
if (characterMapper.hasExternalChanges(request)) {
|
||||
externalApiClient.update(target.chatCharacter, request, requestName)
|
||||
val requestName = if (effectiveRequest.isActive == false) target.chatCharacter.name else effectiveRequest.name
|
||||
if (characterMapper.hasExternalChanges(effectiveRequest)) {
|
||||
externalApiClient.update(target.chatCharacter, effectiveRequest, requestName)
|
||||
}
|
||||
|
||||
val imagePath = if (image?.isEmpty == false) imageStorage.upload(characterId, image) else null
|
||||
val updatedCharacter = chatCharacterService.updateChatCharacterWithDetails(
|
||||
chatCharacterService.updateChatCharacterWithDetails(
|
||||
imagePath = imagePath,
|
||||
request = characterMapper.toLegacyRequest(request, characterId, requestName)
|
||||
request = characterMapper.toLegacyRequest(effectiveRequest, characterId, requestName)
|
||||
)
|
||||
request.originalWorkId?.let { originalWorkService.assignOneCharacter(it, characterId) }
|
||||
effectiveRequest.originalWorkId?.let { originalWorkService.assignOneCharacter(it, characterId) }
|
||||
chatCharacterRepository.flush()
|
||||
applicationEventPublisher.publishEvent(
|
||||
LanguageTranslationEvent(
|
||||
@@ -165,13 +168,14 @@ class AiCharacterAdminCharacterFacade(
|
||||
waitTransactionCommit = true
|
||||
)
|
||||
)
|
||||
|
||||
return characterMapper.toResponse(updatedCharacter)
|
||||
}
|
||||
|
||||
private fun <T> readRequest(requestString: String, requestClass: Class<T>): T {
|
||||
return try {
|
||||
objectMapper.readValue(requestString, requestClass)
|
||||
objectMapper.readerFor(requestClass)
|
||||
.with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
.with(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
|
||||
.readValue(requestString)
|
||||
} catch (_: JsonProcessingException) {
|
||||
throw invalidRequest()
|
||||
}
|
||||
@@ -182,7 +186,6 @@ class AiCharacterAdminCharacterFacade(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MINIMUM_PAGE_SIZE = 20
|
||||
private const val MAXIMUM_PAGE_SIZE = 50
|
||||
private const val MINIMUM_PAGE_SIZE = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character
|
||||
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterBackgroundRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterMemoryRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterPersonalityRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterRelationshipRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterDetailResponse
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterListResponse
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterUpdateRequest
|
||||
import kr.co.vividnext.sodalive.chat.character.CharacterType
|
||||
import kr.co.vividnext.sodalive.chat.character.ChatCharacter
|
||||
import kr.co.vividnext.sodalive.extensions.toUtcIso
|
||||
import kr.co.vividnext.sodalive.v2.api.admin.aicharacter.error.AiCharacterAdminApiException
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.http.HttpStatus
|
||||
@@ -18,38 +15,11 @@ class AiCharacterAdminCharacterMapper(
|
||||
@Value("\${cloud.aws.cloud-front.host}") private val imageHost: String
|
||||
) {
|
||||
fun toListItemResponse(chatCharacter: ChatCharacter): AiCharacterAdminCharacterListItemResponse {
|
||||
val creatorMember = chatCharacter.creatorMember ?: throw invalidRequest()
|
||||
return AiCharacterAdminCharacterListItemResponse(
|
||||
characterId = chatCharacter.id ?: throw invalidRequest(),
|
||||
name = chatCharacter.name,
|
||||
description = chatCharacter.description,
|
||||
imageUrl = "$imageHost/${chatCharacter.imagePath ?: "profile/default-profile.png"}",
|
||||
creatorMemberId = creatorMember.id ?: throw invalidRequest(),
|
||||
creatorNickname = creatorMember.nickname,
|
||||
originalWorkId = chatCharacter.originalWork?.id,
|
||||
externalCharacterId = chatCharacter.characterUUID,
|
||||
isActive = chatCharacter.isActive,
|
||||
createdAtUtc = chatCharacter.createdAt?.toUtcIso()
|
||||
)
|
||||
return ChatCharacterListResponse.from(chatCharacter, imageHost)
|
||||
}
|
||||
|
||||
fun toResponse(chatCharacter: ChatCharacter): AiCharacterAdminCharacterResponse {
|
||||
val creatorMember = chatCharacter.creatorMember ?: throw invalidRequest()
|
||||
return AiCharacterAdminCharacterResponse(
|
||||
characterId = chatCharacter.id ?: throw invalidRequest(),
|
||||
name = chatCharacter.name,
|
||||
description = chatCharacter.description,
|
||||
imageUrl = "$imageHost/${chatCharacter.imagePath ?: "profile/default-profile.png"}",
|
||||
creatorMemberId = creatorMember.id ?: throw invalidRequest(),
|
||||
creatorNickname = creatorMember.nickname,
|
||||
creatorProfileImageUrl = "$imageHost/${creatorMember.profileImage ?: "profile/default-profile.png"}",
|
||||
creatorIntroduce = creatorMember.introduce,
|
||||
originalWorkId = chatCharacter.originalWork?.id,
|
||||
externalCharacterId = chatCharacter.characterUUID,
|
||||
isActive = chatCharacter.isActive,
|
||||
createdAtUtc = chatCharacter.createdAt?.toUtcIso(),
|
||||
updatedAtUtc = chatCharacter.updatedAt?.toUtcIso()
|
||||
)
|
||||
return ChatCharacterDetailResponse.from(chatCharacter, imageHost)
|
||||
}
|
||||
|
||||
fun toCharacterType(request: AiCharacterAdminCharacterCreateRequest): CharacterType {
|
||||
@@ -61,11 +31,7 @@ class AiCharacterAdminCharacterMapper(
|
||||
}
|
||||
|
||||
fun hasExternalChanges(request: AiCharacterAdminCharacterUpdateRequest): Boolean {
|
||||
return hasRegularExternalChanges(request) || request.isActive == false
|
||||
}
|
||||
|
||||
fun hasNonSoftDeleteChanges(request: AiCharacterAdminCharacterUpdateRequest): Boolean {
|
||||
return hasRegularExternalChanges(request) || hasDbOnlyChanges(request)
|
||||
return hasRegularExternalChanges(request) || request.isActive != null
|
||||
}
|
||||
|
||||
private fun hasRegularExternalChanges(request: AiCharacterAdminCharacterUpdateRequest): Boolean {
|
||||
@@ -106,21 +72,10 @@ class AiCharacterAdminCharacterMapper(
|
||||
hobbies = request.hobbies,
|
||||
values = request.values,
|
||||
goals = request.goals,
|
||||
relationships = request.relationships?.map(::toLegacyRequest),
|
||||
personalities = request.personalities?.map { ChatCharacterPersonalityRequest(it.trait, it.description) },
|
||||
backgrounds = request.backgrounds?.map { ChatCharacterBackgroundRequest(it.topic, it.description) },
|
||||
memories = request.memories?.map { ChatCharacterMemoryRequest(it.title, it.content, it.emotion) }
|
||||
)
|
||||
}
|
||||
|
||||
fun toLegacyRequest(request: AiCharacterAdminCharacterRelationshipRequest): ChatCharacterRelationshipRequest {
|
||||
return ChatCharacterRelationshipRequest(
|
||||
personName = request.personName,
|
||||
relationshipName = request.relationshipName,
|
||||
description = request.description,
|
||||
importance = request.importance,
|
||||
relationshipType = request.relationshipType,
|
||||
currentStatus = request.currentStatus
|
||||
relationships = request.relationships,
|
||||
personalities = request.personalities,
|
||||
backgrounds = request.backgrounds,
|
||||
memories = request.memories
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user