Conversation
- 회의 중 기획이 변경된 것과 같이 신고 객체에 신고 당시 시점의 데이터를 스냅샷할 수 있도록 컬럼 추가했습니다. - 기획 상 요구되는 신고 게시글 바로가기를 위한 postId, 신고 게시판을 위한 postType을 추가했습니다. Post 객체를 업로드하는 것은 Post나 comment가 삭제되더라도 신고 기록을 남겨야 하기에 FK 설정을 하지 않기 위해 postId와 postType을 기록하는 형식으로 구현했습니다.
…ent-report Refactor/#195 post comment report
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive MyPage feature, refactors the reporting system to support both posts and comments, and implements profile switching functionality. Key changes include the addition of MyPage-related controllers and services, updates to the Report entity and its associated logic, and repository enhancements for optimized data fetching. Feedback identifies a critical security risk regarding the database DDL configuration in the application properties and points out code duplication in report handling within the Post entity. Additionally, recommendations are provided to maintain API response consistency in the MyPage controller, implement missing self-report validations for comments, and remove commented-out legacy files to improve code maintainability.
| //package org.example.tackit.domain.admin.controller; | ||
| // | ||
| //import lombok.RequiredArgsConstructor; | ||
| //import org.example.tackit.domain.admin.dto.ReportedPostDTO; | ||
| //import org.example.tackit.domain.admin.service.ReportedPostService; | ||
| //import org.example.tackit.domain.entity.postType; | ||
| //import org.example.tackit.common.dto.PageResponseDTO; | ||
| //import org.springframework.data.domain.Page; | ||
| //import org.springframework.data.domain.Pageable; | ||
| //import org.springframework.data.domain.Sort; | ||
| //import org.springframework.data.web.PageableDefault; | ||
| //import org.springframework.http.ResponseEntity; | ||
| //import org.springframework.web.bind.annotation.*; | ||
| // | ||
| //import java.util.Map; | ||
| // | ||
| //@RestController | ||
| //@RequestMapping("/api/admin/report") | ||
| //@RequiredArgsConstructor | ||
| //public class AdminPostController { | ||
| // private final Map<Post, ReportedPostService> reportedPostServices; | ||
| // | ||
| // // 신고된 게시글 조회 | ||
| // @GetMapping("/{postType}/posts") | ||
| // public ResponseEntity<PageResponseDTO<ReportedPostDTO>> getReportedPosts( | ||
| // @PathVariable("postType") Post postType, | ||
| // @PageableDefault(size = 5, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { | ||
| // ReportedPostService service = reportedPostServices.get(postType); | ||
| // | ||
| // if (service == null) { | ||
| // throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType); | ||
| // } | ||
| // | ||
| // Page<ReportedPostDTO> posts = service.getDeletedPosts(pageable); | ||
| // return ResponseEntity.ok(PageResponseDTO.from(posts)); | ||
| // } | ||
| // | ||
| // // 게시글 완전 삭제 | ||
| // @DeleteMapping("/{postType}/posts/{postId}") | ||
| // public ResponseEntity<Void> deleteReportedPost( | ||
| // @PathVariable("postType") Post postType, | ||
| // @PathVariable("postId") Long postId) { | ||
| // | ||
| // ReportedPostService service = reportedPostServices.get(postType); | ||
| // if (service == null) { | ||
| // throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType); | ||
| // } | ||
| // | ||
| // service.deletePost(postId); | ||
| // return ResponseEntity.noContent().build(); | ||
| // } | ||
| // | ||
| // // 게시글 활성화 | ||
| // @PatchMapping("/{postType}/posts/{postId}/activate") | ||
| // public ResponseEntity<String> activateReportedPost( | ||
| // @PathVariable("postType") Post postType, | ||
| // @PathVariable("postId") Long postId) { | ||
| // | ||
| // ReportedPostService service = reportedPostServices.get(postType); | ||
| // if (service == null) { | ||
| // throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType); | ||
| // } | ||
| // | ||
| // service.activatePost(postId); | ||
| // return ResponseEntity.ok("게시글이 활성화되었습니다."); | ||
| // } | ||
| // | ||
| //} |
| public void receiveReport() { | ||
| this.reportCnt++; | ||
|
|
||
| if (this.reportCnt >= 3) { | ||
| this.activeStatus = ActiveStatus.DELETED; | ||
| } | ||
| } |
| public ResponseEntity<SignInResponse> switchProfile() { | ||
| return ResponseEntity.ok(authService.switchProfile()); | ||
| } |
There was a problem hiding this comment.
해당 컨트롤러의 다른 API 엔드포인트들은 ApiResponse 래퍼를 사용하여 일관된 응답 형식을 반환하고 있으나, switchProfile 메서드만 SignInResponse를 직접 반환하고 있습니다. 클라이언트에서 공통된 응답 처리를 할 수 있도록 ApiResponse.success()를 사용하여 형식을 맞추는 것이 좋습니다.
| public ResponseEntity<SignInResponse> switchProfile() { | |
| return ResponseEntity.ok(authService.switchProfile()); | |
| } | |
| @PostMapping("/switch") | |
| public ResponseEntity<ApiResponse<SignInResponse>> switchProfile() { | |
| return ApiResponse.success(HttpStatus.OK, "프로필 전환 성공", authService.switchProfile()); | |
| } |
| public void reportComment(ReportReqDto reqDto, Long requesterMemberOrgId, Long commentId) { | ||
| MemberOrg reporter = memberOrgValidator.validateActiveMembership(requesterMemberOrgId); | ||
|
|
||
| Comment comment = commentRepository.findByIdWithPost(commentId) | ||
| .orElseThrow(() -> new IllegalArgumentException("해당 댓글을 찾을 수 없습니다.")); | ||
|
|
||
| Post post = comment.getPost(); | ||
|
|
||
| Report report = Report.builder() | ||
| .reporter(reporter) | ||
| .targetMember(comment.getWriter()) | ||
| .targetId(comment.getId()) | ||
| .targetType(TargetType.COMMENT) | ||
| .postId(post.getId()) | ||
| .postType(post.getPostType()) | ||
| .reportedPostTitle(post.getTitle()) | ||
| .reportedContent(comment.getContent()) | ||
| .reportReason(reqDto.getReportReason()) | ||
| .detailReason(reqDto.getDetailReason()) | ||
| .build(); | ||
|
|
||
| comment.receiveReport(); | ||
| reportRepository.save(report); | ||
| } |
There was a problem hiding this comment.
reportPost 메서드(36행)에서는 본인이 작성한 게시글을 신고할 수 없도록 검증하는 로직이 포함되어 있으나, reportComment 메서드에는 해당 검증이 누락되어 있습니다. 일관성 및 서비스 오남용 방지를 위해 본인이 작성한 댓글에 대한 신고 차단 로직을 추가해 주세요. 이때, 해당 검증 로직은 MemberOrgValidator와 같은 별도의 검증 컴포넌트에서 중앙 집중식으로 관리하여 재사용성을 높이는 것을 권장합니다.
References
- Centralize ownership validation logic in a dedicated validator component (e.g., MemberOrgValidator) for reusability, rather than implementing it inline within service methods.
이슈 번호
작업 내용