관리자 채팅 메시지 다국어 처리
This commit is contained in:
@@ -13,6 +13,8 @@ import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
|||||||
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterBannerService
|
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterBannerService
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
@@ -35,6 +37,8 @@ class AdminChatBannerController(
|
|||||||
private val bannerService: ChatCharacterBannerService,
|
private val bannerService: ChatCharacterBannerService,
|
||||||
private val adminCharacterService: AdminChatCharacterService,
|
private val adminCharacterService: AdminChatCharacterService,
|
||||||
private val s3Uploader: S3Uploader,
|
private val s3Uploader: S3Uploader,
|
||||||
|
private val langContext: LangContext,
|
||||||
|
private val messageSource: SodaMessageSource,
|
||||||
|
|
||||||
@Value("\${cloud.aws.s3.bucket}")
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
private val s3Bucket: String,
|
private val s3Bucket: String,
|
||||||
@@ -158,8 +162,8 @@ class AdminChatBannerController(
|
|||||||
filePath = "characters/banners/$bannerId/$fileName",
|
filePath = "characters/banners/$bannerId/$fileName",
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
throw SodaException("이미지 저장에 실패했습니다: ${e.message}")
|
throw SodaException(messageKey = "admin.chat.banner.image_save_failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +212,8 @@ class AdminChatBannerController(
|
|||||||
fun deleteBanner(@PathVariable bannerId: Long) = run {
|
fun deleteBanner(@PathVariable bannerId: Long) = run {
|
||||||
bannerService.deleteBanner(bannerId)
|
bannerService.deleteBanner(bannerId)
|
||||||
|
|
||||||
ApiResponse.ok("배너가 성공적으로 삭제되었습니다.")
|
val message = messageSource.getMessage("admin.chat.banner.delete_success", langContext.lang)
|
||||||
|
ApiResponse.ok(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -224,6 +229,7 @@ class AdminChatBannerController(
|
|||||||
) = run {
|
) = run {
|
||||||
bannerService.updateBannerOrders(request.ids)
|
bannerService.updateBannerOrders(request.ids)
|
||||||
|
|
||||||
ApiResponse.ok(null, "배너 정렬 순서가 성공적으로 변경되었습니다.")
|
val message = messageSource.getMessage("admin.chat.banner.reorder_success", langContext.lang)
|
||||||
|
ApiResponse.ok(null, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ class AdminChatCalculateService(
|
|||||||
val todayKst = LocalDate.now(kstZone)
|
val todayKst = LocalDate.now(kstZone)
|
||||||
|
|
||||||
if (endDate.isAfter(todayKst)) {
|
if (endDate.isAfter(todayKst)) {
|
||||||
throw SodaException("끝 날짜는 오늘 날짜까지만 입력 가능합니다.")
|
throw SodaException(messageKey = "admin.chat.calculate.end_date_max_today")
|
||||||
}
|
}
|
||||||
if (startDate.isAfter(endDate)) {
|
if (startDate.isAfter(endDate)) {
|
||||||
throw SodaException("시작 날짜는 끝 날짜보다 이후일 수 없습니다.")
|
throw SodaException(messageKey = "admin.chat.calculate.start_date_after_end")
|
||||||
}
|
}
|
||||||
if (endDate.isAfter(startDate.plusMonths(6))) {
|
if (endDate.isAfter(startDate.plusMonths(6))) {
|
||||||
throw SodaException("조회 가능 기간은 최대 6개월입니다.")
|
throw SodaException(messageKey = "admin.chat.calculate.max_period_6_months")
|
||||||
}
|
}
|
||||||
|
|
||||||
val startUtc = startDateStr.convertLocalDateTime()
|
val startUtc = startDateStr.convertLocalDateTime()
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class AdminChatCharacterController(
|
|||||||
// 외부 API 호출 전 DB에 동일한 이름이 있는지 조회
|
// 외부 API 호출 전 DB에 동일한 이름이 있는지 조회
|
||||||
val existingCharacter = service.findByName(request.name)
|
val existingCharacter = service.findByName(request.name)
|
||||||
if (existingCharacter != null) {
|
if (existingCharacter != null) {
|
||||||
throw SodaException("동일한 이름은 등록이 불가능합니다: ${request.name}")
|
throw SodaException(messageKey = "admin.chat.character.duplicate_name")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 외부 API 호출
|
// 1. 외부 API 호출
|
||||||
@@ -233,14 +233,18 @@ class AdminChatCharacterController(
|
|||||||
|
|
||||||
// success가 false이면 throw
|
// success가 false이면 throw
|
||||||
if (!apiResponse.success) {
|
if (!apiResponse.success) {
|
||||||
throw SodaException(apiResponse.message ?: "등록에 실패했습니다. 다시 시도해 주세요.")
|
val apiMessage = apiResponse.message
|
||||||
|
if (apiMessage.isNullOrBlank()) {
|
||||||
|
throw SodaException(messageKey = "admin.chat.character.register_failed_retry")
|
||||||
|
}
|
||||||
|
throw SodaException(apiMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// success가 true이면 data.id 반환
|
// success가 true이면 data.id 반환
|
||||||
return apiResponse.data?.id ?: throw SodaException("등록에 실패했습니다. 응답에 ID가 없습니다.")
|
return apiResponse.data?.id ?: throw SodaException(messageKey = "admin.chat.character.register_failed_no_id")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
throw SodaException("${e.message}, 등록에 실패했습니다. 다시 시도해 주세요.")
|
throw SodaException(messageKey = "admin.chat.character.register_failed_retry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +261,7 @@ class AdminChatCharacterController(
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw SodaException("이미지 저장에 실패했습니다: ${e.message}")
|
throw SodaException(messageKey = "admin.chat.character.image_save_failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,19 +301,19 @@ class AdminChatCharacterController(
|
|||||||
request.originalWorkId != null
|
request.originalWorkId != null
|
||||||
|
|
||||||
if (!hasChangedData && !hasImage && !hasDbOnlyChanges) {
|
if (!hasChangedData && !hasImage && !hasDbOnlyChanges) {
|
||||||
throw SodaException("변경된 데이터가 없습니다.")
|
throw SodaException(messageKey = "admin.chat.character.no_changes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 외부 API로 전달할 변경이 있을 때만 외부 API 호출(3가지 필드만 변경된 경우는 호출하지 않음)
|
// 외부 API로 전달할 변경이 있을 때만 외부 API 호출(3가지 필드만 변경된 경우는 호출하지 않음)
|
||||||
if (hasChangedData) {
|
if (hasChangedData) {
|
||||||
val chatCharacter = service.findById(request.id)
|
val chatCharacter = service.findById(request.id)
|
||||||
?: throw SodaException("해당 ID의 캐릭터를 찾을 수 없습니다: ${request.id}")
|
?: throw SodaException(messageKey = "admin.chat.character.not_found")
|
||||||
|
|
||||||
// 이름이 수정된 경우 DB에 동일한 이름이 있는지 확인
|
// 이름이 수정된 경우 DB에 동일한 이름이 있는지 확인
|
||||||
if (request.name != null && request.name != chatCharacter.name) {
|
if (request.name != null && request.name != chatCharacter.name) {
|
||||||
val existingCharacter = service.findByName(request.name)
|
val existingCharacter = service.findByName(request.name)
|
||||||
if (existingCharacter != null) {
|
if (existingCharacter != null) {
|
||||||
throw SodaException("동일한 이름은 등록이 불가능합니다: ${request.name}")
|
throw SodaException(messageKey = "admin.chat.character.duplicate_name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,11 +442,15 @@ class AdminChatCharacterController(
|
|||||||
|
|
||||||
// success가 false이면 throw
|
// success가 false이면 throw
|
||||||
if (!apiResponse.success) {
|
if (!apiResponse.success) {
|
||||||
throw SodaException(apiResponse.message ?: "수정에 실패했습니다. 다시 시도해 주세요.")
|
val apiMessage = apiResponse.message
|
||||||
|
if (apiMessage.isNullOrBlank()) {
|
||||||
|
throw SodaException(messageKey = "admin.chat.character.update_failed_retry")
|
||||||
|
}
|
||||||
|
throw SodaException(apiMessage)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
throw SodaException("${e.message} 수정에 실패했습니다. 다시 시도해 주세요.")
|
throw SodaException(messageKey = "admin.chat.character.update_failed_retry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class CharacterCurationAdminController(
|
|||||||
@RequestBody request: CharacterCurationAddCharacterRequest
|
@RequestBody request: CharacterCurationAddCharacterRequest
|
||||||
): ApiResponse<Boolean> {
|
): ApiResponse<Boolean> {
|
||||||
val ids = request.characterIds.filter { it > 0 }.distinct()
|
val ids = request.characterIds.filter { it > 0 }.distinct()
|
||||||
if (ids.isEmpty()) throw SodaException("등록할 캐릭터 ID 리스트가 비어있습니다")
|
if (ids.isEmpty()) throw SodaException(messageKey = "admin.chat.curation.character_ids_empty")
|
||||||
service.addCharacters(curationId, ids)
|
service.addCharacters(curationId, ids)
|
||||||
return ApiResponse.ok(true)
|
return ApiResponse.ok(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class CharacterCurationAdminService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun update(request: CharacterCurationUpdateRequest): CharacterCuration {
|
fun update(request: CharacterCurationUpdateRequest): CharacterCuration {
|
||||||
val curation = curationRepository.findById(request.id)
|
val curation = curationRepository.findById(request.id)
|
||||||
.orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: ${request.id}") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") }
|
||||||
|
|
||||||
request.title?.let { curation.title = it }
|
request.title?.let { curation.title = it }
|
||||||
request.isAdult?.let { curation.isAdult = it }
|
request.isAdult?.let { curation.isAdult = it }
|
||||||
@@ -44,7 +44,7 @@ class CharacterCurationAdminService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun softDelete(curationId: Long) {
|
fun softDelete(curationId: Long) {
|
||||||
val curation = curationRepository.findById(curationId)
|
val curation = curationRepository.findById(curationId)
|
||||||
.orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") }
|
||||||
curation.isActive = false
|
curation.isActive = false
|
||||||
curationRepository.save(curation)
|
curationRepository.save(curation)
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ class CharacterCurationAdminService(
|
|||||||
fun reorder(ids: List<Long>) {
|
fun reorder(ids: List<Long>) {
|
||||||
ids.forEachIndexed { index, id ->
|
ids.forEachIndexed { index, id ->
|
||||||
val curation = curationRepository.findById(id)
|
val curation = curationRepository.findById(id)
|
||||||
.orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $id") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") }
|
||||||
curation.sortOrder = index + 1
|
curation.sortOrder = index + 1
|
||||||
curationRepository.save(curation)
|
curationRepository.save(curation)
|
||||||
}
|
}
|
||||||
@@ -61,14 +61,14 @@ class CharacterCurationAdminService(
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun addCharacters(curationId: Long, characterIds: List<Long>) {
|
fun addCharacters(curationId: Long, characterIds: List<Long>) {
|
||||||
if (characterIds.isEmpty()) throw SodaException("등록할 캐릭터 ID 리스트가 비어있습니다")
|
if (characterIds.isEmpty()) throw SodaException(messageKey = "admin.chat.curation.character_ids_empty")
|
||||||
|
|
||||||
val curation = curationRepository.findById(curationId)
|
val curation = curationRepository.findById(curationId)
|
||||||
.orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") }
|
||||||
if (!curation.isActive) throw SodaException("비활성화된 큐레이션입니다: $curationId")
|
if (!curation.isActive) throw SodaException(messageKey = "admin.chat.curation.inactive")
|
||||||
|
|
||||||
val uniqueIds = characterIds.filter { it > 0 }.distinct()
|
val uniqueIds = characterIds.filter { it > 0 }.distinct()
|
||||||
if (uniqueIds.isEmpty()) throw SodaException("유효한 캐릭터 ID가 없습니다")
|
if (uniqueIds.isEmpty()) throw SodaException(messageKey = "admin.chat.curation.invalid_character_ids")
|
||||||
|
|
||||||
// 활성 캐릭터만 조회 (조회 단계에서 검증 포함)
|
// 활성 캐릭터만 조회 (조회 단계에서 검증 포함)
|
||||||
val characters = characterRepository.findByIdInAndIsActiveTrue(uniqueIds)
|
val characters = characterRepository.findByIdInAndIsActiveTrue(uniqueIds)
|
||||||
@@ -101,23 +101,23 @@ class CharacterCurationAdminService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun removeCharacter(curationId: Long, characterId: Long) {
|
fun removeCharacter(curationId: Long, characterId: Long) {
|
||||||
val curation = curationRepository.findById(curationId)
|
val curation = curationRepository.findById(curationId)
|
||||||
.orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") }
|
||||||
val mappings = mappingRepository.findByCuration(curation)
|
val mappings = mappingRepository.findByCuration(curation)
|
||||||
val target = mappings.firstOrNull { it.chatCharacter.id == characterId }
|
val target = mappings.firstOrNull { it.chatCharacter.id == characterId }
|
||||||
?: throw SodaException("매핑을 찾을 수 없습니다: curation=$curationId, character=$characterId")
|
?: throw SodaException(messageKey = "admin.chat.curation.mapping_not_found")
|
||||||
mappingRepository.delete(target)
|
mappingRepository.delete(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun reorderCharacters(curationId: Long, characterIds: List<Long>) {
|
fun reorderCharacters(curationId: Long, characterIds: List<Long>) {
|
||||||
val curation = curationRepository.findById(curationId)
|
val curation = curationRepository.findById(curationId)
|
||||||
.orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") }
|
||||||
val mappings = mappingRepository.findByCuration(curation)
|
val mappings = mappingRepository.findByCuration(curation)
|
||||||
val mappingByCharacterId = mappings.associateBy { it.chatCharacter.id }
|
val mappingByCharacterId = mappings.associateBy { it.chatCharacter.id }
|
||||||
|
|
||||||
characterIds.forEachIndexed { index, cid ->
|
characterIds.forEachIndexed { index, cid ->
|
||||||
val mapping = mappingByCharacterId[cid]
|
val mapping = mappingByCharacterId[cid]
|
||||||
?: throw SodaException("큐레이션에 포함되지 않은 캐릭터입니다: $cid")
|
?: throw SodaException(messageKey = "admin.chat.curation.character_not_in_curation")
|
||||||
mapping.sortOrder = index + 1
|
mapping.sortOrder = index + 1
|
||||||
mappingRepository.save(mapping)
|
mappingRepository.save(mapping)
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ class CharacterCurationAdminService(
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun listCharacters(curationId: Long): List<ChatCharacter> {
|
fun listCharacters(curationId: Long): List<ChatCharacter> {
|
||||||
val curation = curationRepository.findById(curationId)
|
val curation = curationRepository.findById(curationId)
|
||||||
.orElseThrow { SodaException("큐레이션을 찾을 수 없습니다: $curationId") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.curation.not_found") }
|
||||||
val mappings = mappingRepository.findByCurationWithCharacterOrderBySortOrderAsc(curation)
|
val mappings = mappingRepository.findByCurationWithCharacterOrderBySortOrderAsc(curation)
|
||||||
return mappings.map { it.chatCharacter }
|
return mappings.map { it.chatCharacter }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
|||||||
import kr.co.vividnext.sodalive.chat.character.image.CharacterImageService
|
import kr.co.vividnext.sodalive.chat.character.image.CharacterImageService
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.utils.ImageBlurUtil
|
import kr.co.vividnext.sodalive.utils.ImageBlurUtil
|
||||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
@@ -34,6 +36,8 @@ class AdminCharacterImageController(
|
|||||||
private val imageService: CharacterImageService,
|
private val imageService: CharacterImageService,
|
||||||
private val s3Uploader: S3Uploader,
|
private val s3Uploader: S3Uploader,
|
||||||
private val imageCloudFront: ImageContentCloudFront,
|
private val imageCloudFront: ImageContentCloudFront,
|
||||||
|
private val langContext: LangContext,
|
||||||
|
private val messageSource: SodaMessageSource,
|
||||||
|
|
||||||
@Value("\${cloud.aws.s3.content-bucket}")
|
@Value("\${cloud.aws.s3.content-bucket}")
|
||||||
private val s3Bucket: String,
|
private val s3Bucket: String,
|
||||||
@@ -106,14 +110,18 @@ class AdminCharacterImageController(
|
|||||||
@DeleteMapping("/{imageId}")
|
@DeleteMapping("/{imageId}")
|
||||||
fun delete(@PathVariable imageId: Long) = run {
|
fun delete(@PathVariable imageId: Long) = run {
|
||||||
imageService.deleteImage(imageId)
|
imageService.deleteImage(imageId)
|
||||||
ApiResponse.ok(null, "이미지가 삭제되었습니다.")
|
val message = messageSource.getMessage("admin.chat.character.image_deleted", langContext.lang)
|
||||||
|
ApiResponse.ok(null, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/orders")
|
@PutMapping("/orders")
|
||||||
fun updateOrders(@RequestBody request: UpdateCharacterImageOrdersRequest) = run {
|
fun updateOrders(@RequestBody request: UpdateCharacterImageOrdersRequest) = run {
|
||||||
if (request.characterId == null) throw SodaException("characterId는 필수입니다")
|
if (request.characterId == null) {
|
||||||
|
throw SodaException(messageKey = "admin.chat.character.character_id_required")
|
||||||
|
}
|
||||||
imageService.updateOrders(request.characterId, request.ids)
|
imageService.updateOrders(request.characterId, request.ids)
|
||||||
ApiResponse.ok(null, "정렬 순서가 변경되었습니다.")
|
val message = messageSource.getMessage("admin.chat.character.order_updated", langContext.lang)
|
||||||
|
ApiResponse.ok(null, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildS3Key(characterId: Long): String {
|
private fun buildS3Key(characterId: Long): String {
|
||||||
@@ -132,7 +140,7 @@ class AdminCharacterImageController(
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw SodaException("이미지 저장에 실패했습니다: ${e.message}")
|
throw SodaException(messageKey = "admin.chat.character.image_save_failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +149,7 @@ class AdminCharacterImageController(
|
|||||||
// 멀티파트를 BufferedImage로 읽기
|
// 멀티파트를 BufferedImage로 읽기
|
||||||
val bytes = image.bytes
|
val bytes = image.bytes
|
||||||
val bimg = javax.imageio.ImageIO.read(java.io.ByteArrayInputStream(bytes))
|
val bimg = javax.imageio.ImageIO.read(java.io.ByteArrayInputStream(bytes))
|
||||||
?: throw SodaException("이미지 포맷을 인식할 수 없습니다.")
|
?: throw SodaException(messageKey = "admin.chat.character.image_format_invalid")
|
||||||
val blurred = ImageBlurUtil.blurFast(bimg)
|
val blurred = ImageBlurUtil.blurFast(bimg)
|
||||||
|
|
||||||
// PNG로 저장(알파 유지), JPEG 업로드가 필요하면 포맷 변경 가능
|
// PNG로 저장(알파 유지), JPEG 업로드가 필요하면 포맷 변경 가능
|
||||||
@@ -164,7 +172,7 @@ class AdminCharacterImageController(
|
|||||||
metadata = metadata
|
metadata = metadata
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw SodaException("블러 이미지 저장에 실패했습니다: ${e.message}")
|
throw SodaException(messageKey = "admin.chat.character.blur_image_save_failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class AdminChatCharacterService(
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getChatCharacterDetail(characterId: Long, imageHost: String = ""): ChatCharacterDetailResponse {
|
fun getChatCharacterDetail(characterId: Long, imageHost: String = ""): ChatCharacterDetailResponse {
|
||||||
val chatCharacter = chatCharacterRepository.findById(characterId)
|
val chatCharacter = chatCharacterRepository.findById(characterId)
|
||||||
.orElseThrow { SodaException("해당 ID의 캐릭터를 찾을 수 없습니다: $characterId") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.character.not_found") }
|
||||||
|
|
||||||
return ChatCharacterDetailResponse.from(chatCharacter, imageHost)
|
return ChatCharacterDetailResponse.from(chatCharacter, imageHost)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,8 +192,8 @@ class AdminOriginalWorkController(
|
|||||||
filePath = "originals/$originalWorkId/${generateFileName(prefix = "original")}",
|
filePath = "originals/$originalWorkId/${generateFileName(prefix = "original")}",
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
throw SodaException("이미지 저장에 실패했습니다: ${e.message}")
|
throw SodaException(messageKey = "admin.chat.original.image_save_failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class AdminOriginalWorkService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun createOriginalWork(request: OriginalWorkRegisterRequest): OriginalWork {
|
fun createOriginalWork(request: OriginalWorkRegisterRequest): OriginalWork {
|
||||||
originalWorkRepository.findByTitleAndIsDeletedFalse(request.title)?.let {
|
originalWorkRepository.findByTitleAndIsDeletedFalse(request.title)?.let {
|
||||||
throw SodaException("동일한 제목의 원작이 이미 존재합니다: ${request.title}")
|
throw SodaException(messageKey = "admin.chat.original.duplicate_title")
|
||||||
}
|
}
|
||||||
val entity = OriginalWork(
|
val entity = OriginalWork(
|
||||||
title = request.title,
|
title = request.title,
|
||||||
@@ -107,7 +107,7 @@ class AdminOriginalWorkService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun updateOriginalWork(request: OriginalWorkUpdateRequest, imagePath: String? = null): OriginalWork {
|
fun updateOriginalWork(request: OriginalWorkUpdateRequest, imagePath: String? = null): OriginalWork {
|
||||||
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(request.id)
|
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(request.id)
|
||||||
.orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
|
||||||
|
|
||||||
request.title?.let { ow.title = it }
|
request.title?.let { ow.title = it }
|
||||||
request.contentType?.let { ow.contentType = it }
|
request.contentType?.let { ow.contentType = it }
|
||||||
@@ -177,7 +177,7 @@ class AdminOriginalWorkService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun updateOriginalWorkImage(originalWorkId: Long, imagePath: String): OriginalWork {
|
fun updateOriginalWorkImage(originalWorkId: Long, imagePath: String): OriginalWork {
|
||||||
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
||||||
.orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
|
||||||
ow.imagePath = imagePath
|
ow.imagePath = imagePath
|
||||||
return originalWorkRepository.save(ow)
|
return originalWorkRepository.save(ow)
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ class AdminOriginalWorkService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun deleteOriginalWork(id: Long) {
|
fun deleteOriginalWork(id: Long) {
|
||||||
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(id)
|
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(id)
|
||||||
.orElseThrow { SodaException("해당 원작을 찾을 수 없습니다: $id") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
|
||||||
ow.isDeleted = true
|
ow.isDeleted = true
|
||||||
originalWorkRepository.save(ow)
|
originalWorkRepository.save(ow)
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ class AdminOriginalWorkService(
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getOriginalWork(id: Long): OriginalWork {
|
fun getOriginalWork(id: Long): OriginalWork {
|
||||||
return originalWorkRepository.findByIdAndIsDeletedFalse(id)
|
return originalWorkRepository.findByIdAndIsDeletedFalse(id)
|
||||||
.orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 원작 페이징 조회 */
|
/** 원작 페이징 조회 */
|
||||||
@@ -216,7 +216,7 @@ class AdminOriginalWorkService(
|
|||||||
fun getCharactersOfOriginalWorkPage(originalWorkId: Long, page: Int, size: Int): Page<ChatCharacter> {
|
fun getCharactersOfOriginalWorkPage(originalWorkId: Long, page: Int, size: Int): Page<ChatCharacter> {
|
||||||
// 원작 존재 및 소프트 삭제 여부 확인
|
// 원작 존재 및 소프트 삭제 여부 확인
|
||||||
originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
||||||
.orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
|
||||||
|
|
||||||
val safePage = if (page < 0) 0 else page
|
val safePage = if (page < 0) 0 else page
|
||||||
val safeSize = when {
|
val safeSize = when {
|
||||||
@@ -238,7 +238,7 @@ class AdminOriginalWorkService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun assignCharacters(originalWorkId: Long, characterIds: List<Long>) {
|
fun assignCharacters(originalWorkId: Long, characterIds: List<Long>) {
|
||||||
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
||||||
.orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
|
||||||
if (characterIds.isEmpty()) return
|
if (characterIds.isEmpty()) return
|
||||||
val characters = chatCharacterRepository.findByIdInAndIsActiveTrue(characterIds)
|
val characters = chatCharacterRepository.findByIdInAndIsActiveTrue(characterIds)
|
||||||
characters.forEach { it.originalWork = ow }
|
characters.forEach { it.originalWork = ow }
|
||||||
@@ -250,7 +250,7 @@ class AdminOriginalWorkService(
|
|||||||
fun unassignCharacters(originalWorkId: Long, characterIds: List<Long>) {
|
fun unassignCharacters(originalWorkId: Long, characterIds: List<Long>) {
|
||||||
// 원작 존재 확인 (소프트 삭제 제외)
|
// 원작 존재 확인 (소프트 삭제 제외)
|
||||||
originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
||||||
.orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
|
||||||
if (characterIds.isEmpty()) return
|
if (characterIds.isEmpty()) return
|
||||||
val characters = chatCharacterRepository.findByIdInAndIsActiveTrue(characterIds)
|
val characters = chatCharacterRepository.findByIdInAndIsActiveTrue(characterIds)
|
||||||
characters.forEach { it.originalWork = null }
|
characters.forEach { it.originalWork = null }
|
||||||
@@ -261,13 +261,13 @@ class AdminOriginalWorkService(
|
|||||||
@Transactional
|
@Transactional
|
||||||
fun assignOneCharacter(originalWorkId: Long, characterId: Long) {
|
fun assignOneCharacter(originalWorkId: Long, characterId: Long) {
|
||||||
val character = chatCharacterRepository.findById(characterId)
|
val character = chatCharacterRepository.findById(characterId)
|
||||||
.orElseThrow { SodaException("해당 캐릭터를 찾을 수 없습니다") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.character.not_found") }
|
||||||
|
|
||||||
if (originalWorkId == 0L) {
|
if (originalWorkId == 0L) {
|
||||||
character.originalWork = null
|
character.originalWork = null
|
||||||
} else {
|
} else {
|
||||||
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
val ow = originalWorkRepository.findByIdAndIsDeletedFalse(originalWorkId)
|
||||||
.orElseThrow { SodaException("해당 원작을 찾을 수 없습니다") }
|
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
|
||||||
character.originalWork = ow
|
character.originalWork = ow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,159 @@ class SodaMessageSource {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val adminChatBannerMessages = mapOf(
|
||||||
|
"admin.chat.banner.image_save_failed" to mapOf(
|
||||||
|
Lang.KO to "이미지 저장에 실패했습니다.",
|
||||||
|
Lang.EN to "Failed to save image.",
|
||||||
|
Lang.JA to "画像の保存に失敗しました。"
|
||||||
|
),
|
||||||
|
"admin.chat.banner.delete_success" to mapOf(
|
||||||
|
Lang.KO to "배너가 성공적으로 삭제되었습니다.",
|
||||||
|
Lang.EN to "Banner deleted successfully.",
|
||||||
|
Lang.JA to "バナーが削除されました。"
|
||||||
|
),
|
||||||
|
"admin.chat.banner.reorder_success" to mapOf(
|
||||||
|
Lang.KO to "배너 정렬 순서가 성공적으로 변경되었습니다.",
|
||||||
|
Lang.EN to "Banner order updated successfully.",
|
||||||
|
Lang.JA to "バナーの並び順が変更されました。"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val adminChatCalculateMessages = mapOf(
|
||||||
|
"admin.chat.calculate.end_date_max_today" to mapOf(
|
||||||
|
Lang.KO to "끝 날짜는 오늘 날짜까지만 입력 가능합니다.",
|
||||||
|
Lang.EN to "End date can be at most today.",
|
||||||
|
Lang.JA to "終了日は本日まで指定できます。"
|
||||||
|
),
|
||||||
|
"admin.chat.calculate.start_date_after_end" to mapOf(
|
||||||
|
Lang.KO to "시작 날짜는 끝 날짜보다 이후일 수 없습니다.",
|
||||||
|
Lang.EN to "Start date cannot be after end date.",
|
||||||
|
Lang.JA to "開始日は終了日より後にできません。"
|
||||||
|
),
|
||||||
|
"admin.chat.calculate.max_period_6_months" to mapOf(
|
||||||
|
Lang.KO to "조회 가능 기간은 최대 6개월입니다.",
|
||||||
|
Lang.EN to "Maximum query period is 6 months.",
|
||||||
|
Lang.JA to "照会期間は最大6ヶ月です。"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val adminChatCharacterMessages = mapOf(
|
||||||
|
"admin.chat.character.duplicate_name" to mapOf(
|
||||||
|
Lang.KO to "동일한 이름은 등록이 불가능합니다.",
|
||||||
|
Lang.EN to "A character with the same name already exists.",
|
||||||
|
Lang.JA to "同じ名前は登録できません。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.register_failed_retry" to mapOf(
|
||||||
|
Lang.KO to "등록에 실패했습니다. 다시 시도해 주세요.",
|
||||||
|
Lang.EN to "Registration failed. Please try again.",
|
||||||
|
Lang.JA to "登録に失敗しました。もう一度お試しください。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.register_failed_no_id" to mapOf(
|
||||||
|
Lang.KO to "등록에 실패했습니다. 응답에 ID가 없습니다.",
|
||||||
|
Lang.EN to "Registration failed. No ID in response.",
|
||||||
|
Lang.JA to "登録に失敗しました。応答にIDがありません。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.image_save_failed" to mapOf(
|
||||||
|
Lang.KO to "이미지 저장에 실패했습니다.",
|
||||||
|
Lang.EN to "Failed to save image.",
|
||||||
|
Lang.JA to "画像の保存に失敗しました。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.no_changes" to mapOf(
|
||||||
|
Lang.KO to "변경된 데이터가 없습니다.",
|
||||||
|
Lang.EN to "No changes detected.",
|
||||||
|
Lang.JA to "変更されたデータがありません。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.not_found" to mapOf(
|
||||||
|
Lang.KO to "해당 캐릭터를 찾을 수 없습니다.",
|
||||||
|
Lang.EN to "Character not found.",
|
||||||
|
Lang.JA to "該当キャラクターが見つかりません。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.update_failed_retry" to mapOf(
|
||||||
|
Lang.KO to "수정에 실패했습니다. 다시 시도해 주세요.",
|
||||||
|
Lang.EN to "Update failed. Please try again.",
|
||||||
|
Lang.JA to "更新に失敗しました。もう一度お試しください。"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val adminChatCurationMessages = mapOf(
|
||||||
|
"admin.chat.curation.not_found" to mapOf(
|
||||||
|
Lang.KO to "큐레이션을 찾을 수 없습니다.",
|
||||||
|
Lang.EN to "Curation not found.",
|
||||||
|
Lang.JA to "キュレーションが見つかりません。"
|
||||||
|
),
|
||||||
|
"admin.chat.curation.character_ids_empty" to mapOf(
|
||||||
|
Lang.KO to "등록할 캐릭터 ID 리스트가 비어있습니다",
|
||||||
|
Lang.EN to "Character ID list to register is empty.",
|
||||||
|
Lang.JA to "登録するキャラクターIDリストが空です。"
|
||||||
|
),
|
||||||
|
"admin.chat.curation.inactive" to mapOf(
|
||||||
|
Lang.KO to "비활성화된 큐레이션입니다.",
|
||||||
|
Lang.EN to "Curation is inactive.",
|
||||||
|
Lang.JA to "無効化されたキュレーションです。"
|
||||||
|
),
|
||||||
|
"admin.chat.curation.invalid_character_ids" to mapOf(
|
||||||
|
Lang.KO to "유효한 캐릭터 ID가 없습니다",
|
||||||
|
Lang.EN to "No valid character IDs.",
|
||||||
|
Lang.JA to "有効なキャラクターIDがありません。"
|
||||||
|
),
|
||||||
|
"admin.chat.curation.mapping_not_found" to mapOf(
|
||||||
|
Lang.KO to "매핑을 찾을 수 없습니다.",
|
||||||
|
Lang.EN to "Mapping not found.",
|
||||||
|
Lang.JA to "マッピングが見つかりません。"
|
||||||
|
),
|
||||||
|
"admin.chat.curation.character_not_in_curation" to mapOf(
|
||||||
|
Lang.KO to "큐레이션에 포함되지 않은 캐릭터입니다.",
|
||||||
|
Lang.EN to "Character not included in this curation.",
|
||||||
|
Lang.JA to "このキュレーションに含まれていないキャラクターです。"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val adminChatCharacterImageMessages = mapOf(
|
||||||
|
"admin.chat.character.image_deleted" to mapOf(
|
||||||
|
Lang.KO to "이미지가 삭제되었습니다.",
|
||||||
|
Lang.EN to "Image deleted.",
|
||||||
|
Lang.JA to "画像が削除されました。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.character_id_required" to mapOf(
|
||||||
|
Lang.KO to "characterId는 필수입니다",
|
||||||
|
Lang.EN to "characterId is required.",
|
||||||
|
Lang.JA to "characterIdは必須です。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.order_updated" to mapOf(
|
||||||
|
Lang.KO to "정렬 순서가 변경되었습니다.",
|
||||||
|
Lang.EN to "Order updated.",
|
||||||
|
Lang.JA to "並び順が変更されました。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.image_format_invalid" to mapOf(
|
||||||
|
Lang.KO to "이미지 포맷을 인식할 수 없습니다.",
|
||||||
|
Lang.EN to "Unsupported image format.",
|
||||||
|
Lang.JA to "画像形式を認識できません。"
|
||||||
|
),
|
||||||
|
"admin.chat.character.blur_image_save_failed" to mapOf(
|
||||||
|
Lang.KO to "블러 이미지 저장에 실패했습니다.",
|
||||||
|
Lang.EN to "Failed to save blurred image.",
|
||||||
|
Lang.JA to "ぼかし画像の保存に失敗しました。"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val adminChatOriginalWorkMessages = mapOf(
|
||||||
|
"admin.chat.original.image_save_failed" to mapOf(
|
||||||
|
Lang.KO to "이미지 저장에 실패했습니다.",
|
||||||
|
Lang.EN to "Failed to save image.",
|
||||||
|
Lang.JA to "画像の保存に失敗しました。"
|
||||||
|
),
|
||||||
|
"admin.chat.original.duplicate_title" to mapOf(
|
||||||
|
Lang.KO to "동일한 제목의 원작이 이미 존재합니다.",
|
||||||
|
Lang.EN to "An original work with the same title already exists.",
|
||||||
|
Lang.JA to "同じタイトルの原作が既に存在します。"
|
||||||
|
),
|
||||||
|
"admin.chat.original.not_found" to mapOf(
|
||||||
|
Lang.KO to "해당 원작을 찾을 수 없습니다.",
|
||||||
|
Lang.EN to "Original work not found.",
|
||||||
|
Lang.JA to "該当の原作が見つかりません。"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
fun getMessage(key: String, lang: Lang): String? {
|
fun getMessage(key: String, lang: Lang): String? {
|
||||||
val messageGroups = listOf(
|
val messageGroups = listOf(
|
||||||
commonMessages,
|
commonMessages,
|
||||||
@@ -158,7 +311,13 @@ class SodaMessageSource {
|
|||||||
auditionNotificationMessages,
|
auditionNotificationMessages,
|
||||||
auditionRoleMessages,
|
auditionRoleMessages,
|
||||||
settlementRatioMessages,
|
settlementRatioMessages,
|
||||||
adminCanMessages
|
adminCanMessages,
|
||||||
|
adminChatBannerMessages,
|
||||||
|
adminChatCalculateMessages,
|
||||||
|
adminChatCharacterMessages,
|
||||||
|
adminChatCurationMessages,
|
||||||
|
adminChatCharacterImageMessages,
|
||||||
|
adminChatOriginalWorkMessages
|
||||||
)
|
)
|
||||||
for (messages in messageGroups) {
|
for (messages in messageGroups) {
|
||||||
val translations = messages[key] ?: continue
|
val translations = messages[key] ?: continue
|
||||||
|
|||||||
Reference in New Issue
Block a user