캐릭터 챗봇 #338
| @@ -54,12 +54,13 @@ class CharacterCommentController( | ||||
|     fun listComments( | ||||
|         @PathVariable characterId: Long, | ||||
|         @RequestParam(required = false, defaultValue = "20") limit: Int, | ||||
|         @RequestParam(required = false) cursor: Long?, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|         if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") | ||||
|  | ||||
|         val data = service.listComments(imageHost, characterId, limit) | ||||
|         val data = service.listComments(imageHost, characterId, cursor, limit) | ||||
|         ApiResponse.ok(data) | ||||
|     } | ||||
|  | ||||
| @@ -68,13 +69,14 @@ class CharacterCommentController( | ||||
|         @PathVariable characterId: Long, | ||||
|         @PathVariable commentId: Long, | ||||
|         @RequestParam(required = false, defaultValue = "20") limit: Int, | ||||
|         @RequestParam(required = false) cursor: Long?, | ||||
|         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|         if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") | ||||
|  | ||||
|         // characterId는 서비스 내부 검증(원본 댓글과 캐릭터 일치)에서 검증됨 | ||||
|         val data = service.getReplies(imageHost, commentId, limit) | ||||
|         val data = service.getReplies(imageHost, commentId, cursor, limit) | ||||
|         ApiResponse.ok(data) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -41,7 +41,8 @@ data class CharacterReplyResponse( | ||||
|  | ||||
| data class CharacterCommentRepliesResponse( | ||||
|     val original: CharacterCommentResponse, | ||||
|     val replies: List<CharacterReplyResponse> | ||||
|     val replies: List<CharacterReplyResponse>, | ||||
|     val cursor: Long? | ||||
| ) | ||||
|  | ||||
| // 댓글 리스트 조회 Response 컨테이너 | ||||
| @@ -50,5 +51,6 @@ data class CharacterCommentRepliesResponse( | ||||
|  | ||||
| data class CharacterCommentListResponse( | ||||
|     val totalCount: Int, | ||||
|     val comments: List<CharacterCommentResponse> | ||||
|     val comments: List<CharacterCommentResponse>, | ||||
|     val cursor: Long? | ||||
| ) | ||||
|   | ||||
| @@ -9,8 +9,22 @@ interface CharacterCommentRepository : JpaRepository<CharacterComment, Long> { | ||||
|         pageable: Pageable | ||||
|     ): List<CharacterComment> | ||||
|  | ||||
|     fun findByChatCharacter_IdAndIsActiveTrueAndParentIsNullAndIdLessThanOrderByCreatedAtDesc( | ||||
|         chatCharacterId: Long, | ||||
|         id: Long, | ||||
|         pageable: Pageable | ||||
|     ): List<CharacterComment> | ||||
|  | ||||
|     fun countByParent_IdAndIsActiveTrue(parentId: Long): Int | ||||
|  | ||||
|     fun findByParent_IdAndIsActiveTrueOrderByCreatedAtDesc(parentId: Long, pageable: Pageable): List<CharacterComment> | ||||
|  | ||||
|     fun findByParent_IdAndIsActiveTrueAndIdLessThanOrderByCreatedAtDesc( | ||||
|         parentId: Long, | ||||
|         id: Long, | ||||
|         pageable: Pageable | ||||
|     ): List<CharacterComment> | ||||
|  | ||||
|     fun findFirstByChatCharacter_IdAndIsActiveTrueOrderByCreatedAtDesc(chatCharacterId: Long): CharacterComment? | ||||
|  | ||||
|     // 전체(상위+답글) 활성 댓글 총 개수 | ||||
|   | ||||
| @@ -83,31 +83,67 @@ class CharacterCommentService( | ||||
|     } | ||||
|  | ||||
|     @Transactional(readOnly = true) | ||||
|     fun listComments(imageHost: String, characterId: Long, limit: Int = 20): CharacterCommentListResponse { | ||||
|     fun listComments( | ||||
|         imageHost: String, | ||||
|         characterId: Long, | ||||
|         cursor: Long?, | ||||
|         limit: Int = 20 | ||||
|     ): CharacterCommentListResponse { | ||||
|         val pageable = PageRequest.of(0, limit) | ||||
|         val comments = commentRepository.findByChatCharacter_IdAndIsActiveTrueAndParentIsNullOrderByCreatedAtDesc( | ||||
|             characterId, | ||||
|             pageable | ||||
|         ) | ||||
|         val comments = if (cursor == null) { | ||||
|             commentRepository.findByChatCharacter_IdAndIsActiveTrueAndParentIsNullOrderByCreatedAtDesc( | ||||
|                 characterId, | ||||
|                 pageable | ||||
|             ) | ||||
|         } else { | ||||
|             commentRepository.findByChatCharacter_IdAndIsActiveTrueAndParentIsNullAndIdLessThanOrderByCreatedAtDesc( | ||||
|                 characterId, | ||||
|                 cursor, | ||||
|                 pageable | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         val items = comments.map { toCommentResponse(imageHost, it) } | ||||
|         val total = getTotalCommentCount(characterId) | ||||
|         val nextCursor = if (items.size == limit) items.lastOrNull()?.commentId else null | ||||
|  | ||||
|         return CharacterCommentListResponse( | ||||
|             totalCount = total, | ||||
|             comments = items | ||||
|             comments = items, | ||||
|             cursor = nextCursor | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @Transactional(readOnly = true) | ||||
|     fun getReplies(imageHost: String, commentId: Long, limit: Int = 20): CharacterCommentRepliesResponse { | ||||
|         val original = commentRepository.findById(commentId).orElseThrow { SodaException("댓글을 찾을 수 없습니다.") } | ||||
|     fun getReplies( | ||||
|         imageHost: String, | ||||
|         commentId: Long, | ||||
|         cursor: Long?, | ||||
|         limit: Int = 20 | ||||
|     ): CharacterCommentRepliesResponse { | ||||
|         val original = commentRepository.findById(commentId).orElseThrow { | ||||
|             SodaException("댓글을 찾을 수 없습니다.") | ||||
|         } | ||||
|         if (!original.isActive) throw SodaException("비활성화된 댓글입니다.") | ||||
|  | ||||
|         val pageable = PageRequest.of(0, limit) | ||||
|         val replies = commentRepository.findByParent_IdAndIsActiveTrueOrderByCreatedAtDesc(commentId, pageable) | ||||
|         val replies = if (cursor == null) { | ||||
|             commentRepository.findByParent_IdAndIsActiveTrueOrderByCreatedAtDesc(commentId, pageable) | ||||
|         } else { | ||||
|             commentRepository.findByParent_IdAndIsActiveTrueAndIdLessThanOrderByCreatedAtDesc( | ||||
|                 commentId, | ||||
|                 cursor, | ||||
|                 pageable | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         val items = replies.map { toReplyResponse(imageHost, it) } | ||||
|         val nextCursor = if (items.size == limit) items.lastOrNull()?.replyId else null | ||||
|  | ||||
|         return CharacterCommentRepliesResponse( | ||||
|             original = toCommentResponse(imageHost, original, 0), | ||||
|             replies = replies.map { toReplyResponse(imageHost, it) } | ||||
|             replies = items, | ||||
|             cursor = nextCursor | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user