diff --git a/jihoonkang/src/main/java/com/example/blog/domain/comment/controller/CommentController.java b/jihoonkang/src/main/java/com/example/blog/domain/comment/controller/CommentController.java index 6e91e421..ba28b32c 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/comment/controller/CommentController.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/comment/controller/CommentController.java @@ -4,6 +4,9 @@ import com.example.blog.domain.comment.dto.CommentResponse; import com.example.blog.domain.comment.dto.CommentUpdateRequest; import com.example.blog.domain.comment.service.CommentService; +import com.example.blog.domain.report.dto.ReportCommentRequest; +import com.example.blog.domain.report.dto.ReportResponse; +import com.example.blog.domain.report.service.ReportService; import com.example.blog.global.response.ApiResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -17,6 +20,7 @@ public class CommentController { private final CommentService commentService; + private final ReportService reportService; @PostMapping("/api/v1/posts/{postId}/comments") @ResponseStatus(HttpStatus.CREATED) @@ -50,4 +54,23 @@ public void delete( ) { commentService.delete(userId, commentId); } + + @PostMapping("/api/v1/posts/{postId}/comments/{commentId}/accept") + public ApiResponse accept( + @RequestHeader("X-User-Id") Long userId, + @PathVariable Long postId, + @PathVariable Long commentId + ) { + return ApiResponse.success(commentService.acceptComment(userId, postId, commentId)); + } + + @PostMapping("/api/v1/comments/{commentId}/reports") + @ResponseStatus(HttpStatus.CREATED) + public ApiResponse reportComment( + @RequestHeader("X-User-Id") Long reporterId, + @PathVariable Long commentId, + @RequestBody @Valid ReportCommentRequest request + ) { + return ApiResponse.success(reportService.reportComment(reporterId, commentId, request)); + } } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/comment/dto/CommentResponse.java b/jihoonkang/src/main/java/com/example/blog/domain/comment/dto/CommentResponse.java index d0ffa3eb..b6b81ec5 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/comment/dto/CommentResponse.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/comment/dto/CommentResponse.java @@ -11,6 +11,7 @@ public record CommentResponse( Long userId, String username, String content, + boolean accepted, Long parentCommentId, List replies, LocalDateTime createdAt @@ -26,6 +27,7 @@ public static CommentResponse from(Comment comment) { comment.getUser().getId(), comment.getUser().getUsername(), comment.getContent(), + comment.isAccepted(), comment.getParentComment() != null ? comment.getParentComment().getId() : null, replies, comment.getCreatedAt() diff --git a/jihoonkang/src/main/java/com/example/blog/domain/comment/entity/Comment.java b/jihoonkang/src/main/java/com/example/blog/domain/comment/entity/Comment.java index 6e4d2184..1290884e 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/comment/entity/Comment.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/comment/entity/Comment.java @@ -40,16 +40,28 @@ public class Comment extends BaseEntity { @OneToMany(mappedBy = "parentComment") private List replies = new ArrayList<>(); + @Column(nullable = false) + private boolean accepted; + public static Comment of(User user, Post post, String content, Comment parentComment) { Comment comment = new Comment(); comment.user = user; comment.post = post; comment.content = content; comment.parentComment = parentComment; + comment.accepted = false; return comment; } public void update(String content) { this.content = content; } + + public void accept() { + this.accepted = true; + } + + public void unaccept() { + this.accepted = false; + } } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/comment/repository/CommentRepository.java b/jihoonkang/src/main/java/com/example/blog/domain/comment/repository/CommentRepository.java index 791682e6..c17cdb5e 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/comment/repository/CommentRepository.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/comment/repository/CommentRepository.java @@ -4,8 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; public interface CommentRepository extends JpaRepository { List findByPost_IdAndParentCommentIsNull(Long postId); + + Optional findByPost_IdAndAcceptedTrue(Long postId); } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/comment/service/CommentService.java b/jihoonkang/src/main/java/com/example/blog/domain/comment/service/CommentService.java index 8295636b..577868bd 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/comment/service/CommentService.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/comment/service/CommentService.java @@ -38,10 +38,10 @@ public CommentResponse create(Long userId, Long postId, CommentCreateRequest req parentComment = commentRepository.findById(request.parentCommentId()) .orElseThrow(() -> new NotFoundException(ErrorCode.COMMENT_NOT_FOUND)); if (!parentComment.getPost().getId().equals(postId)) { - throw new BusinessException(ErrorCode.INVALID_REPORT_TARGET); + throw new BusinessException(ErrorCode.INVALID_PARENT_COMMENT); } if (parentComment.getParentComment() != null) { - throw new BusinessException(ErrorCode.INVALID_REPORT_TARGET); + throw new BusinessException(ErrorCode.INVALID_PARENT_COMMENT); } } @@ -76,4 +76,28 @@ public void delete(Long userId, Long commentId) { } commentRepository.delete(comment); } + + public CommentResponse acceptComment(Long userId, Long postId, Long commentId) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(ErrorCode.POST_NOT_FOUND)); + if (!post.getUser().getId().equals(userId)) { + throw new BusinessException(ErrorCode.FORBIDDEN_ACCESS); + } + + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new NotFoundException(ErrorCode.COMMENT_NOT_FOUND)); + if (!comment.getPost().getId().equals(postId)) { + throw new BusinessException(ErrorCode.INVALID_PARENT_COMMENT); + } + + if (comment.isAccepted()) { + return CommentResponse.from(comment); + } + + commentRepository.findByPost_IdAndAcceptedTrue(postId) + .ifPresent(Comment::unaccept); + + comment.accept(); + return CommentResponse.from(comment); + } } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/post/controller/PostController.java b/jihoonkang/src/main/java/com/example/blog/domain/post/controller/PostController.java index be6fd78c..83279c02 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/post/controller/PostController.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/post/controller/PostController.java @@ -5,6 +5,9 @@ import com.example.blog.domain.post.dto.PostResponse; import com.example.blog.domain.post.dto.PostUpdateRequest; import com.example.blog.domain.post.service.PostService; +import com.example.blog.domain.report.dto.ReportPostRequest; +import com.example.blog.domain.report.dto.ReportResponse; +import com.example.blog.domain.report.service.ReportService; import com.example.blog.global.response.ApiResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -17,6 +20,7 @@ public class PostController { private final PostService postService; + private final ReportService reportService; @PostMapping @ResponseStatus(HttpStatus.CREATED) @@ -58,4 +62,22 @@ public void delete( ) { postService.delete(userId, postId); } + + @PostMapping("/{postId}/hide") + public ApiResponse hide( + @RequestHeader("X-User-Id") Long userId, + @PathVariable Long postId + ) { + return ApiResponse.success(postService.hidePost(userId, postId)); + } + + @PostMapping("/{postId}/reports") + @ResponseStatus(HttpStatus.CREATED) + public ApiResponse reportPost( + @RequestHeader("X-User-Id") Long reporterId, + @PathVariable Long postId, + @RequestBody @Valid ReportPostRequest request + ) { + return ApiResponse.success(reportService.reportPost(reporterId, postId, request)); + } } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/post/dto/PostResponse.java b/jihoonkang/src/main/java/com/example/blog/domain/post/dto/PostResponse.java index 868cb32c..701dea8a 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/post/dto/PostResponse.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/post/dto/PostResponse.java @@ -22,7 +22,7 @@ public static PostResponse from(Post post) { post.getUser().getUsername(), post.getTitle(), post.getContent(), - post.getStatus(), + post.getStatus().name(), post.getCreatedAt(), post.getUpdatedAt() ); diff --git a/jihoonkang/src/main/java/com/example/blog/domain/post/entity/Post.java b/jihoonkang/src/main/java/com/example/blog/domain/post/entity/Post.java index 81dd3057..a668e88d 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/post/entity/Post.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/post/entity/Post.java @@ -3,6 +3,8 @@ import com.example.blog.domain.comment.entity.Comment; import com.example.blog.domain.user.entity.User; import com.example.blog.global.entity.BaseEntity; +import com.example.blog.global.exception.BusinessException; +import com.example.blog.global.exception.ErrorCode; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -32,22 +34,23 @@ public class Post extends BaseEntity { @Column(columnDefinition = "TEXT") private String content; + @Enumerated(EnumType.STRING) @Column(length = 20) - private String status; + private PostStatus status; @OneToMany(mappedBy = "post") private List comments = new ArrayList<>(); - public static Post of(User user, String title, String content, String status) { + public static Post of(User user, String title, String content, PostStatus status) { Post post = new Post(); post.user = user; post.title = title; post.content = content; - post.status = status != null ? status : "PUBLISHED"; + post.status = status != null ? status : PostStatus.PUBLISHED; return post; } - public void update(String title, String content, String status) { + public void update(String title, String content, PostStatus status) { if (title != null) { this.title = title; } @@ -58,4 +61,11 @@ public void update(String title, String content, String status) { this.status = status; } } + + public void hide() { + if (this.status == PostStatus.HIDDEN) { + throw new BusinessException(ErrorCode.POST_ALREADY_HIDDEN); + } + this.status = PostStatus.HIDDEN; + } } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/post/entity/PostStatus.java b/jihoonkang/src/main/java/com/example/blog/domain/post/entity/PostStatus.java new file mode 100644 index 00000000..245596b4 --- /dev/null +++ b/jihoonkang/src/main/java/com/example/blog/domain/post/entity/PostStatus.java @@ -0,0 +1,5 @@ +package com.example.blog.domain.post.entity; + +public enum PostStatus { + PUBLISHED, HIDDEN +} diff --git a/jihoonkang/src/main/java/com/example/blog/domain/post/repository/PostRepository.java b/jihoonkang/src/main/java/com/example/blog/domain/post/repository/PostRepository.java index d7789a94..3b39216e 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/post/repository/PostRepository.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/post/repository/PostRepository.java @@ -1,11 +1,12 @@ package com.example.blog.domain.post.repository; import com.example.blog.domain.post.entity.Post; +import com.example.blog.domain.post.entity.PostStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface PostRepository extends JpaRepository { - Page findByStatus(String status, Pageable pageable); + Page findByStatus(PostStatus status, Pageable pageable); } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/post/service/PostService.java b/jihoonkang/src/main/java/com/example/blog/domain/post/service/PostService.java index 3260e958..1f1b193c 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/post/service/PostService.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/post/service/PostService.java @@ -5,6 +5,7 @@ import com.example.blog.domain.post.dto.PostResponse; import com.example.blog.domain.post.dto.PostUpdateRequest; import com.example.blog.domain.post.entity.Post; +import com.example.blog.domain.post.entity.PostStatus; import com.example.blog.domain.post.repository.PostRepository; import com.example.blog.domain.user.entity.User; import com.example.blog.domain.user.repository.UserRepository; @@ -32,7 +33,8 @@ public class PostService { public PostResponse create(Long userId, PostCreateRequest request) { User user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); - Post post = Post.of(user, request.title(), request.content(), request.status()); + PostStatus status = parseStatus(request.status()); + Post post = Post.of(user, request.title(), request.content(), status); return PostResponse.from(postRepository.save(post)); } @@ -41,7 +43,7 @@ public PostListResponse findAll(String status, int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); Page posts; if (status != null && !status.isBlank()) { - posts = postRepository.findByStatus(status, pageable); + posts = postRepository.findByStatus(PostStatus.valueOf(status.toUpperCase()), pageable); } else { posts = postRepository.findAll(pageable); } @@ -64,7 +66,8 @@ public PostResponse update(Long userId, Long postId, PostUpdateRequest request) if (!post.getUser().getId().equals(userId)) { throw new BusinessException(ErrorCode.FORBIDDEN_ACCESS); } - post.update(request.title(), request.content(), request.status()); + PostStatus status = parseStatus(request.status()); + post.update(request.title(), request.content(), status); return PostResponse.from(post); } @@ -76,4 +79,21 @@ public void delete(Long userId, Long postId) { } postRepository.delete(post); } + + public PostResponse hidePost(Long userId, Long postId) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(ErrorCode.POST_NOT_FOUND)); + if (!post.getUser().getId().equals(userId)) { + throw new BusinessException(ErrorCode.FORBIDDEN_ACCESS); + } + post.hide(); + return PostResponse.from(post); + } + + private PostStatus parseStatus(String status) { + if (status == null || status.isBlank()) { + return null; + } + return PostStatus.valueOf(status.toUpperCase()); + } } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/controller/ReportController.java b/jihoonkang/src/main/java/com/example/blog/domain/report/controller/ReportController.java index 5b2c7bf1..ac816581 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/report/controller/ReportController.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/report/controller/ReportController.java @@ -1,12 +1,9 @@ package com.example.blog.domain.report.controller; -import com.example.blog.domain.report.dto.ReportCreateRequest; import com.example.blog.domain.report.dto.ReportResponse; import com.example.blog.domain.report.service.ReportService; import com.example.blog.global.response.ApiResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -18,13 +15,12 @@ public class ReportController { private final ReportService reportService; - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public ApiResponse create( - @RequestHeader("X-User-Id") Long reporterId, - @RequestBody @Valid ReportCreateRequest request + @PostMapping("/{reportId}/resolve") + public ApiResponse resolve( + @RequestHeader("X-User-Id") Long handlerId, + @PathVariable Long reportId ) { - return ApiResponse.success(reportService.create(reporterId, request)); + return ApiResponse.success(reportService.resolveReport(handlerId, reportId)); } @GetMapping diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportCommentRequest.java b/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportCommentRequest.java new file mode 100644 index 00000000..818bb6f6 --- /dev/null +++ b/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportCommentRequest.java @@ -0,0 +1,8 @@ +package com.example.blog.domain.report.dto; + +import jakarta.validation.constraints.NotBlank; + +public record ReportCommentRequest( + @NotBlank(message = "신고 사유는 필수입니다.") + String reason +) {} diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportCreateRequest.java b/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportCreateRequest.java deleted file mode 100644 index a8aaaa07..00000000 --- a/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportCreateRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.blog.domain.report.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -public record ReportCreateRequest( - - @NotBlank(message = "신고 대상 유형은 필수입니다.") - String targetType, - - @NotNull(message = "신고 대상 ID는 필수입니다.") - Long targetId, - - @NotBlank(message = "신고 사유는 필수입니다.") - String reason - -) {} diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportPostRequest.java b/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportPostRequest.java new file mode 100644 index 00000000..03ebfcfe --- /dev/null +++ b/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportPostRequest.java @@ -0,0 +1,8 @@ +package com.example.blog.domain.report.dto; + +import jakarta.validation.constraints.NotBlank; + +public record ReportPostRequest( + @NotBlank(message = "신고 사유는 필수입니다.") + String reason +) {} diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportResponse.java b/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportResponse.java index 79a20dd1..bc2ec7b7 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportResponse.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/report/dto/ReportResponse.java @@ -1,6 +1,7 @@ package com.example.blog.domain.report.dto; import com.example.blog.domain.report.entity.Report; +import com.example.blog.domain.report.entity.ReportStatus; import java.time.LocalDateTime; @@ -11,6 +12,8 @@ public record ReportResponse( String targetType, Long targetId, String reason, + ReportStatus status, + LocalDateTime resolvedAt, LocalDateTime createdAt ) { @@ -26,6 +29,8 @@ public static ReportResponse from(Report report) { targetType, targetId, report.getReason(), + report.getStatus(), + report.getResolvedAt(), report.getCreatedAt() ); } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/entity/Report.java b/jihoonkang/src/main/java/com/example/blog/domain/report/entity/Report.java index 2f313f00..42eb3745 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/report/entity/Report.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/report/entity/Report.java @@ -4,13 +4,23 @@ import com.example.blog.domain.post.entity.Post; import com.example.blog.domain.user.entity.User; import com.example.blog.global.entity.BaseEntity; +import com.example.blog.global.exception.BusinessException; +import com.example.blog.global.exception.ErrorCode; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + @Entity -@Table(name = "reports") +@Table( + name = "reports", + uniqueConstraints = { + @UniqueConstraint(columnNames = {"reporter_id", "post_id"}), + @UniqueConstraint(columnNames = {"reporter_id", "comment_id"}) + } +) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Report extends BaseEntity { @@ -35,12 +45,41 @@ public class Report extends BaseEntity { @Column(nullable = false) private String reason; - public static Report of(User reporter, Post post, Comment comment, String reason) { + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private ReportStatus status; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "resolved_by") + private User resolvedBy; + + @Column + private LocalDateTime resolvedAt; + + public static Report ofPost(User reporter, Post post, String reason) { Report report = new Report(); report.reporter = reporter; report.post = post; + report.reason = reason; + report.status = ReportStatus.PENDING; + return report; + } + + public static Report ofComment(User reporter, Comment comment, String reason) { + Report report = new Report(); + report.reporter = reporter; report.comment = comment; report.reason = reason; + report.status = ReportStatus.PENDING; return report; } + + public void resolve(User handler) { + if (this.status == ReportStatus.RESOLVED) { + throw new BusinessException(ErrorCode.REPORT_ALREADY_RESOLVED); + } + this.status = ReportStatus.RESOLVED; + this.resolvedBy = handler; + this.resolvedAt = LocalDateTime.now(); + } } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/entity/ReportStatus.java b/jihoonkang/src/main/java/com/example/blog/domain/report/entity/ReportStatus.java new file mode 100644 index 00000000..3f469d39 --- /dev/null +++ b/jihoonkang/src/main/java/com/example/blog/domain/report/entity/ReportStatus.java @@ -0,0 +1,5 @@ +package com.example.blog.domain.report.entity; + +public enum ReportStatus { + PENDING, RESOLVED +} diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/repository/ReportRepository.java b/jihoonkang/src/main/java/com/example/blog/domain/report/repository/ReportRepository.java index 14887600..787be8aa 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/report/repository/ReportRepository.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/report/repository/ReportRepository.java @@ -4,4 +4,8 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface ReportRepository extends JpaRepository { + + boolean existsByReporter_IdAndPost_Id(Long reporterId, Long postId); + + boolean existsByReporter_IdAndComment_Id(Long reporterId, Long commentId); } diff --git a/jihoonkang/src/main/java/com/example/blog/domain/report/service/ReportService.java b/jihoonkang/src/main/java/com/example/blog/domain/report/service/ReportService.java index 9b8ca855..41f3648e 100644 --- a/jihoonkang/src/main/java/com/example/blog/domain/report/service/ReportService.java +++ b/jihoonkang/src/main/java/com/example/blog/domain/report/service/ReportService.java @@ -4,7 +4,8 @@ import com.example.blog.domain.comment.repository.CommentRepository; import com.example.blog.domain.post.entity.Post; import com.example.blog.domain.post.repository.PostRepository; -import com.example.blog.domain.report.dto.ReportCreateRequest; +import com.example.blog.domain.report.dto.ReportCommentRequest; +import com.example.blog.domain.report.dto.ReportPostRequest; import com.example.blog.domain.report.dto.ReportResponse; import com.example.blog.domain.report.entity.Report; import com.example.blog.domain.report.repository.ReportRepository; @@ -29,27 +30,50 @@ public class ReportService { private final PostRepository postRepository; private final CommentRepository commentRepository; - public ReportResponse create(Long reporterId, ReportCreateRequest request) { + public ReportResponse reportPost(Long reporterId, Long postId, ReportPostRequest request) { User reporter = userRepository.findById(reporterId) .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); + Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(ErrorCode.POST_NOT_FOUND)); - Post post = null; - Comment comment = null; - - if ("POST".equalsIgnoreCase(request.targetType())) { - post = postRepository.findById(request.targetId()) - .orElseThrow(() -> new NotFoundException(ErrorCode.POST_NOT_FOUND)); - } else if ("COMMENT".equalsIgnoreCase(request.targetType())) { - comment = commentRepository.findById(request.targetId()) - .orElseThrow(() -> new NotFoundException(ErrorCode.COMMENT_NOT_FOUND)); - } else { - throw new BusinessException(ErrorCode.INVALID_REPORT_TARGET); + if (post.getUser().getId().equals(reporterId)) { + throw new BusinessException(ErrorCode.CANNOT_REPORT_SELF); } + if (reportRepository.existsByReporter_IdAndPost_Id(reporterId, postId)) { + throw new BusinessException(ErrorCode.ALREADY_REPORTED); + } + + Report report = Report.ofPost(reporter, post, request.reason()); + return ReportResponse.from(reportRepository.save(report)); + } + + public ReportResponse reportComment(Long reporterId, Long commentId, ReportCommentRequest request) { + User reporter = userRepository.findById(reporterId) + .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new NotFoundException(ErrorCode.COMMENT_NOT_FOUND)); - Report report = Report.of(reporter, post, comment, request.reason()); + if (comment.getUser().getId().equals(reporterId)) { + throw new BusinessException(ErrorCode.CANNOT_REPORT_SELF); + } + if (reportRepository.existsByReporter_IdAndComment_Id(reporterId, commentId)) { + throw new BusinessException(ErrorCode.ALREADY_REPORTED); + } + + Report report = Report.ofComment(reporter, comment, request.reason()); return ReportResponse.from(reportRepository.save(report)); } + public ReportResponse resolveReport(Long handlerId, Long reportId) { + User handler = userRepository.findById(handlerId) + .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); + Report report = reportRepository.findById(reportId) + .orElseThrow(() -> new NotFoundException(ErrorCode.INVALID_REPORT_TARGET)); + + report.resolve(handler); + return ReportResponse.from(report); + } + @Transactional(readOnly = true) public List findAll() { return reportRepository.findAll().stream() diff --git a/jihoonkang/src/main/java/com/example/blog/global/exception/ErrorCode.java b/jihoonkang/src/main/java/com/example/blog/global/exception/ErrorCode.java index 061aaff1..a3de2104 100644 --- a/jihoonkang/src/main/java/com/example/blog/global/exception/ErrorCode.java +++ b/jihoonkang/src/main/java/com/example/blog/global/exception/ErrorCode.java @@ -17,7 +17,14 @@ public enum ErrorCode { FORBIDDEN_ACCESS(HttpStatus.FORBIDDEN, "A001", "접근 권한이 없습니다."), - INVALID_REPORT_TARGET(HttpStatus.BAD_REQUEST, "R001", "유효하지 않은 신고 대상입니다."); + INVALID_REPORT_TARGET(HttpStatus.BAD_REQUEST, "R001", "유효하지 않은 신고 대상입니다."), + ALREADY_REPORTED(HttpStatus.CONFLICT, "R002", "이미 신고한 대상입니다."), + CANNOT_REPORT_SELF(HttpStatus.BAD_REQUEST, "R003", "자신의 게시물/댓글은 신고할 수 없습니다."), + REPORT_ALREADY_RESOLVED(HttpStatus.CONFLICT, "R004", "이미 처리된 신고입니다."), + + POST_ALREADY_HIDDEN(HttpStatus.CONFLICT, "P002", "이미 숨김 처리된 게시물입니다."), + + INVALID_PARENT_COMMENT(HttpStatus.BAD_REQUEST, "C002", "유효하지 않은 부모 댓글입니다."); private final HttpStatus httpStatus; private final String code;