본문 바로가기
Projects/청하-청년을 위한 커뮤니티 서비스

[청하] 게시글 신고 기능 구현

by Lpromotion 2024. 10. 15.

게시글 CRUD 기능이 완성되었기 때문에 커뮤니티의 완성도를 위해 신고 기능을 추가하게 되었다.

게시글 상세조회 페이지에서 이 기능을 통해 사용자들이 부적절하다고 생각하는 게시글을 신고할 수 있다.

 

1. 프로젝트 구조

src/main/java/com/example/withpeace/
│
├── domain/                 # 도메인 모델 (엔티티)
│   ├── Post.java           # 게시글 엔티티
│   └── Report.java         # 신고 엔티티
│
├── repository/                   # 데이터 접근 계층
│   └── PostRepository.java       # 게시글 레포지토리
│   └── ReportRepository.java     # 신고 레포지토리
│
├── dto/                                    # 데이터 전송 객체
│   └── request/
│       ├── ReportRegisterRequestDto.java   # 신고 생성 요청 DTO
│
├── controller/             # 컨트롤러 계층
│   └── PostController.java # 게시글 관련 API 엔드포인트
│
├── service/                # 비즈니스 로직 계층
│   ├── PostService.java    # 게시글 관련 비즈니스 로직
│
└── type/                   # 열거형 및 상수
    ├── EReportType.java    # 신고 유형 열거형
    └── EReason.java        # 신고 이유 열거형

 

2. 도메인 모델 구현

Report

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicUpdate
@Table(name = "reports")
public class Report {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false, updatable = false, unique = true)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "writer_id", nullable = false)
    private User writer; // 신고자

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post; // 신고된 게시글

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "comment_id")
    private Comment comment; // 신고된 댓글

    @Column(name = "type", nullable = false)
    @Enumerated(EnumType.STRING)
    private EReportType type; // 신고 유형 (게시글 or 댓글)

    @Column(name = "reason", nullable = false)
    @Enumerated(EnumType.STRING)
    private EReason reason; // 신고 이유

    @Column(name = "create_date", nullable = false)
    private LocalDateTime createDate; // 신고 생성 일시

    @Builder
    public Report(User writer, Post post, Comment comment, EReportType type, EReason reason) {
        this.writer = writer;
        this.post = post;
        this.comment = comment;
        this.type = type;
        this.reason = reason;
        this.createDate = LocalDateTime.now();
    }
}

Report 엔티티는 신고 정보를 담는다. 신고자, 신고된 게시글, 신고 유형, 신고 유형 등이 포함된다.

@ManyToOne 를 통해 신고자, 신고된 게시글, 신고된 댓글과의 관계를 표현했다. EReportTypeEReason Enum을 사용해 신고 유형과 신고 이유를 구현했다.

 

3. 레포지토리 구현

ReportRepository

@Repository
public interface ReportRepository extends JpaRepository<Report, Long> {

    boolean existsByWriterAndPostAndType(User user, Post post, EReportType type);
}

existsByWriterAndPostAndType 는 중복 신고를 방지하기 위한 메서드이다. 동일한 사용자가 같은 게시글에 대해 이미 신고한 적이 있는지 확인한다.

 

4. DTO 구현

ReportRegisterRequestDto

public record ReportRegisterRequestDto(
        @NotNull @JsonProperty("reason") @Schema(description = "신고이유") EReason reason){
}

신고 요청을 위한 DTO 이다.

  • @NotNull: 필드가 null이 아니어야 한다.
  • @JsonProperty: JSON 직렬화/역직렬화 시 사용할 속성 이름을 지정한다.
  • @Schema: Swagger 에서 API 문서를 생성할 때 사용하는 어노테이션이다.

reason은 반드시 입력해야 하는 값이므로 @NotNull 어노테이션을 설정했다.

 

5. 컨트롤러 구현

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/posts")
public class PostController {
    // ... (기존 코드 생략)

    // 게시글 신고
    @PostMapping("/{postId}/reports")
    public ResponseDto<?> reportPost(@UserId Long userId, // 사용자 ID
                                     @PathVariable Long postId, // 게시글 ID
                                     @Valid @RequestBody ReportRegisterRequestDto reason // 신고 이유
                                     ) {
        return ResponseDto.ok(postService.reportPost(userId, postId, reason.reason()));
    }
}

reportPost 메서드에서 게시글 신고 요청을 처리한다. 인증된 사용자 ID, 게시글 ID, 신고 이유를 입력받아 서비스로 전달한다.

 

6. 서비스 구현

@Service
@RequiredArgsConstructor
public class PostService {
		// ... (기존 코드 생략)

    @Transactional
    public Boolean reportPost(Long userId, Long postId, EReason reason) {
        // 사용자 및 게시글 존재 여부 확인
        User user = getUserById(userId);
        Post post = getPostById(postId);

        // 해당 게시글 중복 신고 확인
        boolean alreadyReported = reportRepository.existsByWriterAndPostAndType(user, post, EReportType.POST);
        if(alreadyReported) { throw new CommonException(ErrorCode.POST_ALREADY_REPORTED);}

        try {
            reportRepository.save(Report.builder()
                    .writer(user)
                    .post(post)
                    .type(EReportType.POST)
                    .reason(reason)
                    .build());

            return true;
        } catch (Exception e) {
            throw new CommonException(ErrorCode.POST_ERROR);
        }
    }
}
  1. 사용자와 게시글을 조회한다. (존재 여부 확인)
  2. 사용자가 이미 해당 게시글을 신고했는지 확인한다. (existsByWriterAndPostAndType)
  3. 중복 신고인 경우 예외를 반환한다.
  4. 중복 신고가 아닌 경우 새로운 Report 엔티티를 생성하고 저장한다.

 

7. Enum 구현

신고 이유와 신고 유형을 Enum을 사용해 구현한다.

Enum 을 사용하면 잘못된 값이 할당되는 것을 컴파일 시점에 방지할 수 있고, 데이터베이스에 저장될 때 일관된 값을 보장한다.

 

7.1. EReason

public enum EReason {
    DUPLICATE,     // 중복
    ADVERTISEMENT, // 광고
    INAPPROPRIATE, // 부적절
    PROFANITY,     // 욕설
    OBSCENITY      // 음란물
}

 

7.2. EReportType

public enum EReportType {
    POST,    // 게시글 신고
    COMMENT  // 댓글 신고
}

 

8. API 응답 예시

요청 URL

[POST] http://cheongha.site/api/v1/posts/5/reportPost

 

Response Body

{
  "data": true,
  "error": null
}
반응형

댓글