feat(banner): 정렬 순서 추가
This commit is contained in:
parent
81f972edc1
commit
ef8458c7a3
|
@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.admin.chat.dto.ChatCharacterBannerListPageRespon
|
||||||
import kr.co.vividnext.sodalive.admin.chat.dto.ChatCharacterBannerRegisterRequest
|
import kr.co.vividnext.sodalive.admin.chat.dto.ChatCharacterBannerRegisterRequest
|
||||||
import kr.co.vividnext.sodalive.admin.chat.dto.ChatCharacterBannerResponse
|
import kr.co.vividnext.sodalive.admin.chat.dto.ChatCharacterBannerResponse
|
||||||
import kr.co.vividnext.sodalive.admin.chat.dto.ChatCharacterBannerUpdateRequest
|
import kr.co.vividnext.sodalive.admin.chat.dto.ChatCharacterBannerUpdateRequest
|
||||||
|
import kr.co.vividnext.sodalive.admin.chat.dto.UpdateBannerOrdersRequest
|
||||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
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
|
||||||
|
@ -18,6 +19,7 @@ import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.PutMapping
|
import org.springframework.web.bind.annotation.PutMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RequestPart
|
import org.springframework.web.bind.annotation.RequestPart
|
||||||
|
@ -98,7 +100,7 @@ class AdminChatBannerController(
|
||||||
* 배너 등록 API
|
* 배너 등록 API
|
||||||
*
|
*
|
||||||
* @param image 배너 이미지
|
* @param image 배너 이미지
|
||||||
* @param request 배너 등록 요청 정보
|
* @param request 배너 등록 요청 정보 (캐릭터 ID와 선택적으로 정렬 순서 포함)
|
||||||
* @return 등록된 배너 정보
|
* @return 등록된 배너 정보
|
||||||
*/
|
*/
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
|
@ -106,8 +108,11 @@ class AdminChatBannerController(
|
||||||
@RequestPart("image") image: MultipartFile,
|
@RequestPart("image") image: MultipartFile,
|
||||||
@RequestPart("request") request: ChatCharacterBannerRegisterRequest
|
@RequestPart("request") request: ChatCharacterBannerRegisterRequest
|
||||||
) = run {
|
) = run {
|
||||||
// 1. 먼저 빈 이미지 경로로 배너 등록
|
// 1. 먼저 빈 이미지 경로로 배너 등록 (정렬 순서 포함)
|
||||||
val banner = bannerService.registerBanner(request.characterId, "")
|
val banner = bannerService.registerBanner(
|
||||||
|
characterId = request.characterId,
|
||||||
|
imagePath = ""
|
||||||
|
)
|
||||||
|
|
||||||
// 2. 배너 ID를 사용하여 이미지 업로드
|
// 2. 배너 ID를 사용하여 이미지 업로드
|
||||||
val imagePath = saveImage(banner.id!!, image)
|
val imagePath = saveImage(banner.id!!, image)
|
||||||
|
@ -188,4 +193,20 @@ class AdminChatBannerController(
|
||||||
|
|
||||||
ApiResponse.ok("배너가 성공적으로 삭제되었습니다.")
|
ApiResponse.ok("배너가 성공적으로 삭제되었습니다.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배너 정렬 순서 일괄 변경 API
|
||||||
|
* ID 목록의 순서대로 정렬 순서를 1부터 순차적으로 설정합니다.
|
||||||
|
*
|
||||||
|
* @param request 정렬 순서 일괄 변경 요청 정보 (배너 ID 목록)
|
||||||
|
* @return 성공 메시지
|
||||||
|
*/
|
||||||
|
@PutMapping("/orders")
|
||||||
|
fun updateBannerOrders(
|
||||||
|
@RequestBody request: UpdateBannerOrdersRequest
|
||||||
|
) = run {
|
||||||
|
bannerService.updateBannerOrders(request.ids)
|
||||||
|
|
||||||
|
ApiResponse.ok(null, "배너 정렬 순서가 성공적으로 변경되었습니다.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,3 +18,11 @@ data class ChatCharacterBannerUpdateRequest(
|
||||||
// 캐릭터 ID (변경할 캐릭터)
|
// 캐릭터 ID (변경할 캐릭터)
|
||||||
val characterId: Long? = null
|
val characterId: Long? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 캐릭터 배너 정렬 순서 일괄 변경 요청 DTO
|
||||||
|
*/
|
||||||
|
data class UpdateBannerOrdersRequest(
|
||||||
|
// 배너 ID 목록 (순서대로 정렬됨)
|
||||||
|
val ids: List<Long>
|
||||||
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import javax.persistence.ManyToOne
|
||||||
/**
|
/**
|
||||||
* 캐릭터 배너 엔티티
|
* 캐릭터 배너 엔티티
|
||||||
* 이미지와 캐릭터 ID를 가지며, 소프트 삭제(isActive = false)를 지원합니다.
|
* 이미지와 캐릭터 ID를 가지며, 소프트 삭제(isActive = false)를 지원합니다.
|
||||||
|
* 정렬 순서(sortOrder)를 통해 배너의 표시 순서를 결정합니다.
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
class ChatCharacterBanner(
|
class ChatCharacterBanner(
|
||||||
|
@ -20,6 +21,9 @@ class ChatCharacterBanner(
|
||||||
@JoinColumn(name = "character_id")
|
@JoinColumn(name = "character_id")
|
||||||
var chatCharacter: ChatCharacter,
|
var chatCharacter: ChatCharacter,
|
||||||
|
|
||||||
|
// 정렬 순서 (낮을수록 먼저 표시)
|
||||||
|
var sortOrder: Int = 0,
|
||||||
|
|
||||||
// 활성화 여부 (소프트 삭제용)
|
// 활성화 여부 (소프트 삭제용)
|
||||||
var isActive: Boolean = true
|
var isActive: Boolean = true
|
||||||
) : BaseEntity()
|
) : BaseEntity()
|
||||||
|
|
|
@ -4,10 +4,15 @@ import kr.co.vividnext.sodalive.chat.character.ChatCharacterBanner
|
||||||
import org.springframework.data.domain.Page
|
import org.springframework.data.domain.Page
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.data.jpa.repository.Query
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface ChatCharacterBannerRepository : JpaRepository<ChatCharacterBanner, Long> {
|
interface ChatCharacterBannerRepository : JpaRepository<ChatCharacterBanner, Long> {
|
||||||
// 활성화된 배너 목록 조회
|
// 활성화된 배너 목록 조회 (정렬 순서대로)
|
||||||
fun findByActiveTrue(pageable: Pageable): Page<ChatCharacterBanner>
|
fun findByActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page<ChatCharacterBanner>
|
||||||
|
|
||||||
|
// 활성화된 배너 중 최대 정렬 순서 값 조회
|
||||||
|
@Query("SELECT MAX(b.sortOrder) FROM ChatCharacterBanner b WHERE b.isActive = true")
|
||||||
|
fun findMaxSortOrder(): Int?
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,10 @@ class ChatCharacterBannerService(
|
||||||
private val characterRepository: ChatCharacterRepository
|
private val characterRepository: ChatCharacterRepository
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* 활성화된 모든 배너 조회
|
* 활성화된 모든 배너 조회 (정렬 순서대로)
|
||||||
*/
|
*/
|
||||||
fun getActiveBanners(pageable: Pageable): Page<ChatCharacterBanner> {
|
fun getActiveBanners(pageable: Pageable): Page<ChatCharacterBanner> {
|
||||||
return bannerRepository.findByActiveTrue(pageable)
|
return bannerRepository.findByActiveTrueOrderBySortOrderAsc(pageable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +31,10 @@ class ChatCharacterBannerService(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 배너 등록
|
* 배너 등록
|
||||||
|
*
|
||||||
|
* @param characterId 캐릭터 ID
|
||||||
|
* @param imagePath 이미지 경로
|
||||||
|
* @return 등록된 배너
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
fun registerBanner(characterId: Long, imagePath: String): ChatCharacterBanner {
|
fun registerBanner(characterId: Long, imagePath: String): ChatCharacterBanner {
|
||||||
|
@ -41,9 +45,13 @@ class ChatCharacterBannerService(
|
||||||
throw SodaException("비활성화된 캐릭터에는 배너를 등록할 수 없습니다: $characterId")
|
throw SodaException("비활성화된 캐릭터에는 배너를 등록할 수 없습니다: $characterId")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 정렬 순서가 지정되지 않은 경우 가장 마지막 순서로 설정
|
||||||
|
val finalSortOrder = (bannerRepository.findMaxSortOrder() ?: 0) + 1
|
||||||
|
|
||||||
val banner = ChatCharacterBanner(
|
val banner = ChatCharacterBanner(
|
||||||
imagePath = imagePath,
|
imagePath = imagePath,
|
||||||
chatCharacter = character
|
chatCharacter = character,
|
||||||
|
sortOrder = finalSortOrder
|
||||||
)
|
)
|
||||||
|
|
||||||
return bannerRepository.save(banner)
|
return bannerRepository.save(banner)
|
||||||
|
@ -97,4 +105,30 @@ class ChatCharacterBannerService(
|
||||||
banner.isActive = false
|
banner.isActive = false
|
||||||
bannerRepository.save(banner)
|
bannerRepository.save(banner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배너 정렬 순서 일괄 변경
|
||||||
|
* ID 목록의 순서대로 정렬 순서를 1부터 순차적으로 설정합니다.
|
||||||
|
*
|
||||||
|
* @param ids 배너 ID 목록 (순서대로 정렬됨)
|
||||||
|
* @return 수정된 배너 목록
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
fun updateBannerOrders(ids: List<Long>): List<ChatCharacterBanner> {
|
||||||
|
val updatedBanners = mutableListOf<ChatCharacterBanner>()
|
||||||
|
|
||||||
|
for (index in ids.indices) {
|
||||||
|
val banner = bannerRepository.findById(ids[index])
|
||||||
|
.orElseThrow { SodaException("배너를 찾을 수 없습니다: ${ids[index]}") }
|
||||||
|
|
||||||
|
if (!banner.isActive) {
|
||||||
|
throw SodaException("비활성화된 배너는 수정할 수 없습니다: ${ids[index]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
banner.sortOrder = index + 1
|
||||||
|
updatedBanners.add(bannerRepository.save(banner))
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedBanners
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue