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

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

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` 를 통해 신고자, 신고된 게시글, 신고된 댓글과의 관계를 표현했다. `EReportType`과 `EReason` 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
}
반응형

댓글