From 1444afaae2c5b7693a968da568a7fedfa73af2f3 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 20 Aug 2025 00:13:13 +0900 Subject: [PATCH] =?UTF-8?q?feat(chat-character-comment):=20=EC=BA=90?= =?UTF-8?q?=EB=A6=AD=ED=84=B0=20=EB=8C=93=EA=B8=80=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=8B=A0=EA=B3=A0=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 삭제 API: 본인 댓글에 대해 soft delete 처리 - 신고 API: 신고 내용을 그대로 저장하는 CharacterCommentReport 엔티티/리포지토리 도입 - Controller: 삭제, 신고 엔드포인트 추가 및 인증/본인인증 체크 - Service: 비즈니스 로직 구현 및 예외 처리 강화 왜: 캐릭터 댓글 관리 기능 요구사항(삭제/신고)을 충족하기 위함 무엇: 엔드포인트, 서비스 로직, DTO 및 JPA 엔티티/리포지토리 추가 --- .../comment/CharacterCommentController.kt | 26 +++++++++++++++++++ .../character/comment/CharacterCommentDto.kt | 5 ++++ .../comment/CharacterCommentReport.kt | 25 ++++++++++++++++++ .../CharacterCommentReportRepository.kt | 5 ++++ .../comment/CharacterCommentService.kt | 26 ++++++++++++++++++- 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentReport.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentReportRepository.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentController.kt index 8b772e6..8036169 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentController.kt @@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.member.Member import org.springframework.beans.factory.annotation.Value import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping @@ -79,4 +80,29 @@ class CharacterCommentController( val data = service.getReplies(imageHost, commentId, cursor, limit) ApiResponse.ok(data) } + + @DeleteMapping("/{characterId}/comments/{commentId}") + fun deleteComment( + @PathVariable characterId: Long, + @PathVariable commentId: Long, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") + service.deleteComment(characterId, commentId, member) + ApiResponse.ok(true, "댓글이 삭제되었습니다.") + } + + @PostMapping("/{characterId}/comments/{commentId}/reports") + fun reportComment( + @PathVariable characterId: Long, + @PathVariable commentId: Long, + @RequestBody request: ReportCharacterCommentRequest, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") + service.reportComment(characterId, commentId, member, request.content) + ApiResponse.ok(true, "신고가 접수되었습니다.") + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentDto.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentDto.kt index fd67785..ba9d66a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentDto.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentDto.kt @@ -54,3 +54,8 @@ data class CharacterCommentListResponse( val comments: List, val cursor: Long? ) + +// 신고 Request +data class ReportCharacterCommentRequest( + val content: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentReport.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentReport.kt new file mode 100644 index 0000000..96c2955 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentReport.kt @@ -0,0 +1,25 @@ +package kr.co.vividnext.sodalive.chat.character.comment + +import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.member.Member +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne +import javax.persistence.Table + +@Entity +@Table(name = "character_comment_report") +data class CharacterCommentReport( + @Column(columnDefinition = "TEXT", nullable = false) + val content: String +) : BaseEntity() { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "comment_id", nullable = false) + var comment: CharacterComment? = null + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + var member: Member? = null +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentReportRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentReportRepository.kt new file mode 100644 index 0000000..229fd6f --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentReportRepository.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.chat.character.comment + +import org.springframework.data.jpa.repository.JpaRepository + +interface CharacterCommentReportRepository : JpaRepository diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentService.kt index ff54dd3..ead1eeb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentService.kt @@ -11,7 +11,8 @@ import java.time.ZoneId @Service class CharacterCommentService( private val chatCharacterRepository: ChatCharacterRepository, - private val commentRepository: CharacterCommentRepository + private val commentRepository: CharacterCommentRepository, + private val reportRepository: CharacterCommentReportRepository ) { private fun profileUrl(imageHost: String, profileImage: String?): String { @@ -157,4 +158,27 @@ class CharacterCommentService( fun getTotalCommentCount(characterId: Long): Int { return commentRepository.countByChatCharacter_IdAndIsActiveTrue(characterId) } + + @Transactional + fun deleteComment(characterId: Long, commentId: Long, member: Member) { + val comment = commentRepository.findById(commentId).orElseThrow { SodaException("댓글을 찾을 수 없습니다.") } + if (comment.chatCharacter?.id != characterId) throw SodaException("잘못된 요청입니다.") + if (!comment.isActive) return + val ownerId = comment.member?.id ?: throw SodaException("유효하지 않은 댓글입니다.") + if (ownerId != member.id) throw SodaException("삭제 권한이 없습니다.") + comment.isActive = false + commentRepository.save(comment) + } + + @Transactional + fun reportComment(characterId: Long, commentId: Long, member: Member, content: String) { + val comment = commentRepository.findById(commentId).orElseThrow { SodaException("댓글을 찾을 수 없습니다.") } + if (comment.chatCharacter?.id != characterId) throw SodaException("잘못된 요청입니다.") + if (content.isBlank()) throw SodaException("신고 내용을 입력해주세요.") + + val report = CharacterCommentReport(content = content) + report.comment = comment + report.member = member + reportRepository.save(report) + } }