feat(original): 원작 도메인과 캐릭터를 연결하기 위해 각각 검색 API 추가
This commit is contained in:
		| @@ -70,6 +70,19 @@ class AdminChatCharacterController( | |||||||
|         ApiResponse.ok(response) |         ApiResponse.ok(response) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 캐릭터 검색(관리자) | ||||||
|  |      * - 이름/설명/MBTI/태그 기준 부분 검색, 활성 캐릭터만 대상 | ||||||
|  |      * - 페이징 제거: 전체 목록 반환 | ||||||
|  |      */ | ||||||
|  |     @GetMapping("/search") | ||||||
|  |     fun searchCharacters( | ||||||
|  |         @RequestParam("searchTerm") searchTerm: String | ||||||
|  |     ) = run { | ||||||
|  |         val list = adminService.searchCharactersAll(searchTerm, imageHost) | ||||||
|  |         ApiResponse.ok(list) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 캐릭터 상세 정보 조회 API |      * 캐릭터 상세 정보 조회 API | ||||||
|      * |      * | ||||||
|   | |||||||
| @@ -65,12 +65,7 @@ class AdminChatCharacterService( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 캐릭터 검색 (이름, 설명, MBTI, 태그 기반) |      * 캐릭터 검색 (이름, 설명, MBTI, 태그 기반) - 페이징 (기존 사용처 호환용) | ||||||
|      * |  | ||||||
|      * @param searchTerm 검색어 |  | ||||||
|      * @param pageable 페이징 정보 |  | ||||||
|      * @param imageHost 이미지 호스트 URL |  | ||||||
|      * @return 검색된 캐릭터 목록 (페이징) |  | ||||||
|      */ |      */ | ||||||
|     @Transactional(readOnly = true) |     @Transactional(readOnly = true) | ||||||
|     fun searchCharacters( |     fun searchCharacters( | ||||||
| @@ -81,4 +76,20 @@ class AdminChatCharacterService( | |||||||
|         val characters = chatCharacterRepository.searchCharacters(searchTerm, pageable) |         val characters = chatCharacterRepository.searchCharacters(searchTerm, pageable) | ||||||
|         return characters.map { ChatCharacterSearchResponse.from(it, imageHost) } |         return characters.map { ChatCharacterSearchResponse.from(it, imageHost) } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 캐릭터 검색 (이름, 설명, MBTI, 태그 기반) - 무페이징 | ||||||
|  |      * | ||||||
|  |      * @param searchTerm 검색어 | ||||||
|  |      * @param imageHost 이미지 호스트 URL | ||||||
|  |      * @return 검색된 캐릭터 목록 (전체) | ||||||
|  |      */ | ||||||
|  |     @Transactional(readOnly = true) | ||||||
|  |     fun searchCharactersAll( | ||||||
|  |         searchTerm: String, | ||||||
|  |         imageHost: String = "" | ||||||
|  |     ): List<ChatCharacterSearchResponse> { | ||||||
|  |         val characters = chatCharacterRepository.searchCharactersNoPaging(searchTerm) | ||||||
|  |         return characters.map { ChatCharacterSearchResponse.from(it, imageHost) } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -109,6 +109,20 @@ class AdminOriginalWorkController( | |||||||
|         ApiResponse.ok(OriginalWorkPageResponse(totalCount = pageRes.totalElements, content = content)) |         ApiResponse.ok(OriginalWorkPageResponse(totalCount = pageRes.totalElements, content = content)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 원작 검색(관리자) | ||||||
|  |      * - 제목/콘텐츠타입/카테고리 기준 부분 검색, 소프트 삭제 제외 | ||||||
|  |      * - 페이징 제거: 전체 목록 반환 | ||||||
|  |      */ | ||||||
|  |     @GetMapping("/search") | ||||||
|  |     fun search( | ||||||
|  |         @RequestParam("searchTerm") searchTerm: String | ||||||
|  |     ) = run { | ||||||
|  |         val list = originalWorkService.searchOriginalWorksAll(searchTerm) | ||||||
|  |         val content = list.map { OriginalWorkResponse.from(it, imageHost) } | ||||||
|  |         ApiResponse.ok(content) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 원작 상세 |      * 원작 상세 | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -89,6 +89,12 @@ class AdminOriginalWorkService( | |||||||
|         return originalWorkRepository.findByIsDeletedFalse(pageable) |         return originalWorkRepository.findByIsDeletedFalse(pageable) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** 원작 검색 (제목/콘텐츠타입/카테고리, 소프트 삭제 제외) - 무페이징 */ | ||||||
|  |     @Transactional(readOnly = true) | ||||||
|  |     fun searchOriginalWorksAll(searchTerm: String): List<OriginalWork> { | ||||||
|  |         return originalWorkRepository.searchNoPaging(searchTerm) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** 원작에 기존 캐릭터들을 배정 */ |     /** 원작에 기존 캐릭터들을 배정 */ | ||||||
|     @Transactional |     @Transactional | ||||||
|     fun assignCharacters(originalWorkId: Long, characterIds: List<Long>) { |     fun assignCharacters(originalWorkId: Long, characterIds: List<Long>) { | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> { | |||||||
|     fun countByIsActiveTrueAndCreatedAtGreaterThanEqual(since: java.time.LocalDateTime): Long |     fun countByIsActiveTrueAndCreatedAtGreaterThanEqual(since: java.time.LocalDateTime): Long | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 이름, 설명, MBTI, 태그로 캐릭터 검색 |      * 이름, 설명, MBTI, 태그로 캐릭터 검색 - 페이징 | ||||||
|      */ |      */ | ||||||
|     @Query( |     @Query( | ||||||
|         """ |         """ | ||||||
| @@ -52,6 +52,28 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> { | |||||||
|         pageable: Pageable |         pageable: Pageable | ||||||
|     ): Page<ChatCharacter> |     ): Page<ChatCharacter> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 이름, 설명, MBTI, 태그로 캐릭터 검색 - 무페이징 전체 목록 | ||||||
|  |      */ | ||||||
|  |     @Query( | ||||||
|  |         """ | ||||||
|  |         SELECT DISTINCT c FROM ChatCharacter c | ||||||
|  |         LEFT JOIN c.tagMappings tm | ||||||
|  |         LEFT JOIN tm.tag t | ||||||
|  |         WHERE c.isActive = true AND | ||||||
|  |         ( | ||||||
|  |             LOWER(c.name) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR | ||||||
|  |             LOWER(c.description) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR | ||||||
|  |             (c.mbti IS NOT NULL AND LOWER(c.mbti) LIKE LOWER(CONCAT('%', :searchTerm, '%'))) OR | ||||||
|  |             (t.tag IS NOT NULL AND LOWER(t.tag) LIKE LOWER(CONCAT('%', :searchTerm, '%'))) | ||||||
|  |         ) | ||||||
|  |         ORDER BY c.createdAt DESC | ||||||
|  |         """ | ||||||
|  |     ) | ||||||
|  |     fun searchCharactersNoPaging( | ||||||
|  |         @Param("searchTerm") searchTerm: String | ||||||
|  |     ): List<ChatCharacter> | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 특정 캐릭터와 태그를 공유하는 다른 캐릭터를 무작위로 조회 (현재 캐릭터 제외) |      * 특정 캐릭터와 태그를 공유하는 다른 캐릭터를 무작위로 조회 (현재 캐릭터 제외) | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.chat.original | |||||||
| 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.data.repository.query.Param | ||||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||||
| import java.util.Optional | import java.util.Optional | ||||||
|  |  | ||||||
| @@ -11,4 +13,22 @@ interface OriginalWorkRepository : JpaRepository<OriginalWork, Long> { | |||||||
|     fun findByTitleAndIsDeletedFalse(title: String): OriginalWork? |     fun findByTitleAndIsDeletedFalse(title: String): OriginalWork? | ||||||
|     fun findByIdAndIsDeletedFalse(id: Long): Optional<OriginalWork> |     fun findByIdAndIsDeletedFalse(id: Long): Optional<OriginalWork> | ||||||
|     fun findByIsDeletedFalse(pageable: Pageable): Page<OriginalWork> |     fun findByIsDeletedFalse(pageable: Pageable): Page<OriginalWork> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 제목/콘텐츠타입/카테고리 기준 부분 검색 (소프트 삭제 제외) - 무페이징 전체 목록 | ||||||
|  |      */ | ||||||
|  |     @Query( | ||||||
|  |         """ | ||||||
|  |         SELECT ow FROM OriginalWork ow | ||||||
|  |         WHERE ow.isDeleted = false AND ( | ||||||
|  |             LOWER(ow.title) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR | ||||||
|  |             LOWER(ow.contentType) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR | ||||||
|  |             LOWER(ow.category) LIKE LOWER(CONCAT('%', :searchTerm, '%')) | ||||||
|  |         ) | ||||||
|  |         ORDER BY ow.createdAt DESC | ||||||
|  |         """ | ||||||
|  |     ) | ||||||
|  |     fun searchNoPaging( | ||||||
|  |         @Param("searchTerm") searchTerm: String | ||||||
|  |     ): List<OriginalWork> | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user