Skip to content

[4주차] 송유경/feat 게시물·댓글·신고 도메인 상태 전이 및 중복 방지 API 구현#138

Open
yukyoungs wants to merge 4 commits intoLeets-Official:송유경/mainfrom
yukyoungs:송유경/4주차

Hidden character warning

The head ref may contain hidden characters: "\uc1a1\uc720\uacbd/4\uc8fc\ucc28"
Open

[4주차] 송유경/feat 게시물·댓글·신고 도메인 상태 전이 및 중복 방지 API 구현#138
yukyoungs wants to merge 4 commits intoLeets-Official:송유경/mainfrom
yukyoungs:송유경/4주차

Conversation

@yukyoungs
Copy link
Copy Markdown

@yukyoungs yukyoungs commented Apr 28, 2026

1. 과제 요구사항 중 구현한 내용

  • 게시물/댓글/신고 도메인 활용
  • 단순 CRUD를 넘는 기능 설계 및 구현
  • API 3개 이상 구현
  • 도메인 상태(State) 변화 반영
  • 동일 요청 중복 처리 방지 로직 반영

구현 API(주요):

  • POST /api/posts/{postId}/comments 댓글 작성
  • PATCH /api/comments/{commentId}/accept 댓글 채택 (게시글당 1개 제한)
  • POST /api/posts/{postId}/reports 게시물 신고
  • POST /api/comments/{commentId}/reports 댓글 신고
  • PATCH /api/reports/{reportId}/resolve 신고 처리 완료 (PENDING -> RESOLVED)
  • PATCH /api/posts/{id}/hide, PATCH /api/posts/{id}/activate 게시물 상태 전이

2. 핵심 변경 사항

  • 인증/유저 => 회원가입/로그인/내 정보 API 구현@CurrentUser + CurrentUserArgumentResolverX-USER-ID 인증 주입
  • 게시물 => PostStatusACTIVE/HIDDEN으로 변경
  • 댓글 => CommentStatus 추가(ACTIVE/HIDDEN)
  • 신고 => Report, ReportStatus(PENDING/RESOLVED), ReportTargetType(POST/COMMENT) 도입, 동일 유저의 동일 대상 신고 중복 방지

3. 실행 및 검증 결과

  • 실행 결과:
    • ./gradlew test 통과
    • Swagger에서 Authorize -> X-USER-ID 설정 후 API 호출 정상
  • 인증/작성자 검증:
    • POST /api/posts 성공 시 authorId가 로그인 유저 ID로 저장됨
    • 비작성자가 게시물 수정/삭제/상태변경 시 권한 제한 동작 확인
  • 상태 전이 검증:
    • 게시물 ACTIVE <-> HIDDEN 정상
    • 신고 PENDING -> RESOLVED 정상
    • 댓글 채택 중복 시 차단 정상
  • 중복 처리 검증:
    • 동일 유저가 동일 대상 재신고 시 충돌 처리 동작 확인
  • 요청/응답 예시(신고 생성):
    • 요청: POST /api/posts/{postId}/reports
    • 응답(성공): 201 Created, status: PENDING
스크린샷 2026-04-28 오후 1 25 07 스크린샷 2026-04-28 오후 1 25 14 스크린샷 2026-04-28 오후 3 11 31

4. 완료 사항

  1. 유저 인증 최소셋(회원가입/로그인/현재유저 주입) 구현
  2. 게시물/댓글/신고 상태 모델 및 상태 전이 로직 구현
  3. 과제 요구 기능 API(댓글 채택, 신고 등록/처리, 게시물 상태 전이) 구현 및 Swagger 테스트 가능 상태로 정리

5. 추가 사항

  • 관련 이슈: closed #133

현재 인증은 과제 진행용 X-USER-ID 헤더 기반 최소 구현입니다.
시드 데이터는 서버 부팅 시 실행되며, 조건 기반으로 중복 생성을 방지합니다.

제출 체크리스트

  • PR 제목이 규칙에 맞다
  • base가 {이름}/main 브랜치다
  • compare가 {이름}/{숫자}주차 브랜치다
  • 프로젝트가 정상 실행된다
  • 본인을 Assignee로 지정했다
  • 파트 담당 Reviewer를 지정했다
  • 리뷰 피드백을 반영한 뒤 머지/PR close를 진행한다

Reviewer 참고

@yukyoungs yukyoungs requested a review from a team April 28, 2026 06:12
@yukyoungs yukyoungs self-assigned this Apr 28, 2026
public PostResponse create(PostRequest.Create request) {
public PostResponse create(AuthUser authUser, PostRequest.Create request) {
User user = userRepository.findById(authUser.getUserId())
.orElseThrow(() -> new IllegalArgumentException(ErrorCode.USER_NOT_FOUND.getMessage()));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostService 전반에서 ErrorCode를 IllegalArgumentException에 message로 풀어 던지고 계신데요, 같은 PR의 CommentService/ReportService는 BusinessException(ErrorCode.XXX)를 던지고 있어서 전역 예외 처리기에서 받는 방식이 도메인마다 갈라질 것 같습니다. PostService에서도 BusinessException으로 통일해주시면 응답 포맷과 HTTP 상태 코드가 일관되게 내려갈 것 같아요! (USER_NOT_FOUND, POST_NOT_FOUND, FORBIDDEN 모두 동일하게요)

@Component
@RequiredArgsConstructor
public class SeedDataInitializer implements CommandLineRunner {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Component + CommandLineRunner 조합이라 환경과 관계없이 부팅 시 항상 실행되는 구조인데요, admin/user1/user2 같은 고정 비밀번호 계정이 운영 환경에서도 자동으로 생성될 여지가 있어 보입니다. @Profile("local") 또는 @ConditionalOnProperty로 dev/local 프로파일에서만 동작하도록 가드를 한 겹 두시면 실수로 운영에 시드가 새는 사고를 막을 수 있을 것 같아요!

return ResponseEntity.ok(BaseResponse.ok(response));
}

@GetMapping("/me")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/me에서 AuthUser를 그대로 응답으로 내려주고 계신데요, AuthUser는 인증 컨텍스트용 내부 객체라 응답 모델로 노출되면 향후 필드를 추가할 때 의도치 않게 API 스펙이 같이 바뀔 수 있을 것 같습니다. 다른 엔드포인트처럼 AuthResponse.UserInfo 같은 응답 DTO로 매핑해서 내려주시면 응답 계약을 분리해서 관리하기 좋을 것 같아요!

return ResponseEntity.ok(BaseResponse.ok(authUser));
}

@GetMapping("/users/{userId}")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RequestMapping("/api/auth") 아래에 /users/{userId} 조회가 들어와 있는데요, 인증 흐름(signup/login/me)과 사용자 단건 조회는 책임이 조금 달라 보여서 경로상 /api/auth/users/{userId}가 약간 어색하게 느껴집니다. UserController로 분리하고 /api/users/{userId}로 빼주시면 도메인별 라우팅이 더 명확해질 것 같아요!

private final String email;
private final String nickname;
private final UserRole role;
private final String authHeaderName;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그인 응답에 authHeaderName("X-USER-ID")이 포함되어 있는데요, 헤더 이름은 API 스펙(혹은 Swagger 문서)에서 클라이언트가 알게 되는 정보라 응답 페이로드마다 같이 내려보내는 건 다소 중복일 수 있습니다. 임시 인증 방식이라는 의도가 있다면 PR 본문/Swagger 설명에서 안내하는 쪽으로 옮기시고 응답에서는 빼주셔도 좋을 것 같아요!


@Transactional
public ReportResponse resolve(AuthUser authUser, Long reportId) {
if (authUser.getRole() != UserRole.ADMIN) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolve() 안에서도 admin 검증을 직접 하고 계신데, 아래 findAll/findByIdvalidateAdmin 헬퍼를 쓰고 있어요. resolve()validateAdmin(authUser)로 통일해주시면 권한 체크 로직이 한 곳에 모여서 나중에 정책이 바뀔 때 한 군데만 손대면 될 것 같습니다!

PostResponse response = postService.activate(authUser, id);
return ResponseEntity.ok(BaseResponse.ok(response));
}
} No newline at end of file
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

파일 끝 개행이 빠져 있네요. SwaggerConfig.java, PostService.java도 동일한 상태로 보여서 함께 줄바꿈 한 줄 추가해주시면 깔끔할 것 같아요!

Copy link
Copy Markdown
Member

@theSnackOverflow theSnackOverflow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4주차 분량이 많은데 도메인 상태 전이를 엔티티 메서드로 깔끔하게 캡슐화하고, 신고 중복 방지를 DB 유니크 제약까지 걸어 막아주신 점 인상 깊었어요. @CurrentUser 리졸버 분리와 readOnly 트랜잭션 패턴도 일관되게 잡혀 있어 보기 좋았습니다. 몇 가지 확인해보시면 좋을 것 같은 부분 남겨봅니다!

@daekyochung
Copy link
Copy Markdown

게시물과 댓글 신고를 POST /api/posts/{postId}/reports와 POST /api/comments/{commentId}/reports로 깔끔하게 분리한 점이 좋은것 같습니다! PATCH /api/reports/{reportId}/resolve로 신고 상태를 관리하는 로직도 체계적인 거같아서 잘 구현하신것 같네요. 고생하셨습니다!

@sky-0131
Copy link
Copy Markdown

@currentuser와 ArgumentResolver를 활용해 인증 로직을 깔끔하게 분리하 게시글·댓글·신고 시스템의 상태 관리 체계를 명확히 구축한 점이 인상적입니다. 각 API가 비즈니스 목적에만 집중할 수 있는 순수한 계층 분리를 구현하신 것 같습니다. 오늘도 배워갑니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants