Skip to content

Sisc1 151 be 게시물목록조회#88

Merged
discipline24 merged 11 commits intomainfrom
SISC1-151-BE-게시물목록조회
Nov 9, 2025

Hidden character warning

The head ref may contain hidden characters: "SISC1-151-BE-\uac8c\uc2dc\ubb3c\ubaa9\ub85d\uc870\ud68c"
Merged

Sisc1 151 be 게시물목록조회#88
discipline24 merged 11 commits intomainfrom
SISC1-151-BE-게시물목록조회

Conversation

@nayoung04
Copy link
Contributor

@nayoung04 nayoung04 commented Nov 7, 2025

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 게시글 작성, 수정, 삭제 기능 추가
    • 댓글 작성, 수정, 삭제 기능 추가
    • 게시글 검색 기능 추가
    • 게시글 좋아요 및 북마크 토글 기능 추가
    • 게시글 파일 첨부 기능 추가
    • 다양한 게시판 타입(일반, 금융IT, 자산관리) 지원
  • 테스트

    • 파일 업로드 서비스 테스트 추가
    • 게시글 서비스 테스트 추가

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Walkthrough

새로운 게시판 기능을 추가합니다. REST 컨트롤러, JPA 엔티티, DTO, 저장소, 서비스 구현으로 구성되어 게시물 CRUD, 댓글, 좋아요, 북마크, 파일 업로드 기능을 지원합니다.

Changes

집단 / 파일 변경 요약
컨트롤러 계층
backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java
게시물 및 댓글 관리를 위한 REST 엔드포인트 11개 추가: 게시물 생성/수정/삭제, 조회/검색/상세보기, 좋아요/북마크 토글, 댓글 생성/수정/삭제
DTO 계층
backend/src/main/java/org/sejongisc/backend/board/dto/{PostRequest,PostResponse,PostSummaryResponse,PostAttachmentResponse,CommentRequest,CommentResponse}.java
6개의 데이터 전송 객체 추가: 게시물 요청/응답, 게시물 요약, 첨부파일 응답, 댓글 요청/응답
도메인 엔티티
backend/src/main/java/org/sejongisc/backend/board/entity/{Post,Comment,PostLike,PostBookmark,PostAttachment}.java
backend/src/main/java/org/sejongisc/backend/board/entity/{BoardType,PostType}.java
5개 JPA 엔티티 및 2개 열거형 추가; Post에 낙관적 락(version) 필드 추가
저장소 계층
backend/src/main/java/org/sejongisc/backend/board/repository/{PostRepository,CommentRepository,PostLikeRepository,PostBookmarkRepository,PostAttachmentRepository}.java
5개 Spring Data JPA 저장소 추가: 검색, 필터링, 페이지네이션, 일괄 삭제 쿼리 메서드
서비스 계층
backend/src/main/java/org/sejongisc/backend/board/service/PostService.java
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java
서비스 인터페이스 및 구현체 추가: 게시물/댓글 관리, 좋아요/북마크 토글, 첨부파일 처리, 낙관적 락 재시도
파일 업로드
backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java
파일 저장, 삭제, 검증 기능 제공
구성 및 예외 처리
backend/src/main/java/org/sejongisc/backend/common/config/PrimaryDataSourceConfig.java
backend/src/main/java/org/sejongisc/backend/common/exception/ErrorCode.java
backend/src/main/java/org/sejongisc/backend/user/entity/User.java
EntityManager에 board 엔티티 패키지 추가; 4개의 새로운 에러 코드 추가(POST_NOT_FOUND, INVALID_POST_OWNER, COMMENT_NOT_FOUND, INVALID_COMMENT_OWNER); User에 JsonIgnoreProperties 추가
테스트
backend/src/test/java/org/sejongisc/backend/board/service/{FileUploadServiceTest,PostServiceImplTest}.java
FileUploadService와 PostServiceImpl에 대한 단위 테스트 추가

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Controller as PostController
    participant Service as PostServiceImpl
    participant Repo as Repositories
    participant FileService as FileUploadService
    participant DB as Database

    User->>Controller: 파일 포함 게시물 생성<br/>(multipart/form-data)
    Controller->>Service: savePost(request, userId)
    Service->>Repo: findById(userId)
    Repo-->>Service: User entity
    Service->>FileService: store(file)
    FileService->>FileService: UUID 접두사 생성<br/>파일명 정규화
    FileService-->>Service: 저장된 파일명
    Service->>Repo: save(Post)
    Service->>Repo: save(PostAttachment)
    Repo->>DB: INSERT
    DB-->>Repo: 성공
    Service-->>Controller: void
    Controller-->>User: 200 OK
Loading
sequenceDiagram
    actor User as User (Non-Owner)
    participant Controller as PostController
    participant Service as PostServiceImpl
    participant Repo as PostRepository
    participant DB as Database

    User->>Controller: updatePost(postId)
    Controller->>Service: updatePost(request, postId, userId)
    Service->>Repo: findById(postId)
    Repo->>DB: SELECT
    DB-->>Repo: Post
    Repo-->>Service: Post
    
    alt 소유자 불일치
        Service-->>Controller: CustomException<br/>(INVALID_POST_OWNER)
        Controller-->>User: 403 Forbidden
    else 소유자 일치
        Service->>Service: 필드 업데이트
        Service->>Repo: save(Post)
        Repo->>DB: UPDATE
        DB-->>Service: 성공
        Service-->>Controller: void
        Controller-->>User: 200 OK
    end
Loading
sequenceDiagram
    actor User
    participant Controller as PostController
    participant Service as PostServiceImpl
    participant Repo as PostLikeRepository<br/>& PostRepository
    participant DB as Database

    User->>Controller: toggleLike(postId, userId)
    Controller->>Service: toggleLike(postId, userId)
    Service->>Repo: existsByUserUserIdAndPostPostId()
    Repo->>DB: SELECT
    
    alt 좋아요 미등록
        DB-->>Repo: false
        Repo-->>Service: false
        Service->>Repo: save(PostLike)
        Service->>Repo: 좋아요 수 + 1
        Service->>Repo: save(Post)
    else 좋아요 등록됨
        DB-->>Repo: true
        Repo-->>Service: true
        Service->>Repo: delete(PostLike)
        Service->>Repo: 좋아요 수 - 1
        Service->>Repo: save(Post)
    end
    
    Repo->>DB: INSERT/UPDATE/DELETE
    DB-->>Service: 성공
    Service-->>Controller: void
    Controller-->>User: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

추가 검토 필요 영역:

  • PostServiceImpl: 낙관적 락 재시도 메커니즘, 소유권 검증 로직, 첨부파일 관리(기존 파일 삭제 및 신규 파일 저장) 등 복잡한 비즈니스 로직
  • PostController와 PostServiceImpl 상호작용: 다중 엔드포인트에서 페이지네이션, 정렬, 필터링 처리의 일관성
  • 파일 업로드 보안: FileUploadService의 경로 검증 및 파일명 정규화 로직
  • 데이터베이스 스키마: 다대다 관계(Post-User, Comment-Post, PostLike, PostBookmark, PostAttachment) 설정의 정확성
  • 테스트 커버리지: PostServiceImplTest의 모킹 전략과 예외 시나리오 검증
  • 동시성: 낙관적 락 구현과 retry 로직이 race condition 처리에 충분한지 확인

Possibly related PRs

Suggested reviewers

  • discipline24

Poem

🐰 게시판의 마법을 펼치네,
댓글과 좋아요가 춤을 춘다네!
파일도 모아 저장하고,
낙관적 락으로 안전하게 ✨
새로운 기능, 토끼도 환영하네! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목은 게시물 목록 조회 기능 개발과 관련이 있지만, 실제 변경 사항은 게시물 CRUD 전체, 댓글 관리, 좋아요/북마크 토글, 파일 업로드 등 훨씬 더 광범위한 기능을 포함하고 있습니다. PR 제목을 변경사항의 전체 범위를 더 잘 반영하도록 수정하세요. 예: 'BE: 게시물 및 댓글 관리 기능 구현' 또는 '게시물 API 엔드포인트 전체 구현 (CRUD, 댓글, 좋아요, 북마크)'
Docstring Coverage ⚠️ Warning Docstring coverage is 48.31% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SISC1-151-BE-게시물목록조회

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (6)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (3)

6-6: 사용되지 않는 import를 제거하세요.

GenericGenerator가 코드에서 사용되지 않고 있습니다. GenerationType.UUID를 사용하고 있어 필요하지 않은 import입니다.

-import org.hibernate.annotations.GenericGenerator;

26-28: @joincolumn 명시를 권장합니다.

User 관계에 @JoinColumn을 명시하지 않으면 JPA가 기본 네이밍을 사용합니다. 명확성을 위해 조인 컬럼을 명시적으로 지정하는 것이 좋습니다.

 // 작성자
 @ManyToOne(fetch = FetchType.LAZY)
+@JoinColumn(name = "user_id")
 private User user;

30-44: enum 필드에 nullable 제약 조건을 명시하세요.

boardTypepostType 필드에 nullable 속성이 명시되어 있지 않습니다. 이 필드들이 필수인지 선택적인지 명확하게 표시하는 것이 좋습니다.

 // 게시판 타입
 @Enumerated(EnumType.STRING)
+@Column(nullable = false)
 private BoardType boardType;

 // 제목
 @Column(nullable = false)
 private String title;

 // 내용
 @Column(columnDefinition = "TEXT", nullable = false)
 private String content;

 // 게시글 타입
 @Enumerated(EnumType.STRING)
+@Column(nullable = false)
 private PostType postType;
backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java (2)

52-59: 반환 타입 일관성을 개선하세요.

deletePost 메서드가 void를 직접 반환하고 있지만, 같은 컨트롤러의 다른 변경 메서드들(createPost, updatePost, toggleLike, toggleBookmark)은 ResponseEntity<Void>를 반환합니다. API 일관성을 위해 동일한 반환 타입을 사용하는 것이 좋습니다.

 // 게시글 삭제
 @DeleteMapping("/{postId}")
-public void deletePost(
+public ResponseEntity<Void> deletePost(
     @PathVariable UUID postId,
     @AuthenticationPrincipal CustomUserDetails customUserDetails) {
   UUID userId = customUserDetails.getUserId();
   postService.deletePost(postId, userId);
+  return ResponseEntity.ok().build();
 }

119-127: 반환 타입 일관성을 개선하세요.

updateCommentdeleteComment 메서드들이 void를 직접 반환하고 있지만, createCommentResponseEntity<Void>를 반환합니다. 일관성을 위해 모든 변경 메서드가 동일한 반환 타입을 사용하도록 수정하세요.

 // 댓글 수정
 @PutMapping("/comment/{commentId}")
-public void updateComment(
+public ResponseEntity<Void> updateComment(
     @PathVariable UUID commentId,
     @RequestBody CommentRequest request,
     @AuthenticationPrincipal CustomUserDetails customUserDetails) {
   UUID userId = customUserDetails.getUserId();
   postService.updateComment(request, commentId, userId);
+  return ResponseEntity.ok().build();
 }

 // 댓글 삭제
 @DeleteMapping("/comment/{commentId}")
-public void deleteComment(
+public ResponseEntity<Void> deleteComment(
     @PathVariable UUID commentId,
     @AuthenticationPrincipal CustomUserDetails customUserDetails) {
   UUID userId = customUserDetails.getUserId();
   postService.deleteComment(commentId, userId);
+  return ResponseEntity.ok().build();
 }
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)

139-142: JPA cascade 설정을 사용하여 관련 엔티티 삭제를 자동화하세요.

댓글, 좋아요, 북마크를 수동으로 삭제하고 있습니다. JPA의 cascade 설정을 활용하면 코드가 더 간결해지고 실수를 줄일 수 있습니다.

Post 엔티티에 다음과 같은 관계를 추가하세요:

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<Comment> comments = new ArrayList<>();

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostLike> likes = new ArrayList<>();

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostBookmark> bookmarks = new ArrayList<>();

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostAttachment> attachments = new ArrayList<>();

그러면 deletePost 메서드에서 수동 삭제 코드를 제거할 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e122e12 and dbbaa9e.

📒 Files selected for processing (28)
  • backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/CommentRequest.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/PostRequest.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/PostSummaryResponse.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/BoardType.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/PostType.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/CommentRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostAttachmentRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostBookmarkRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostLikeRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/service/PostService.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/common/config/PrimaryDataSourceConfig.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/common/exception/ErrorCode.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/user/entity/User.java (2 hunks)
  • backend/src/test/java/org/sejongisc/backend/backtest/controller/BacktestControllerTest.java (1 hunks)
  • backend/src/test/java/org/sejongisc/backend/board/service/FileUploadServiceTest.java (1 hunks)
  • backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (2)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
  • Entity (12-57)
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)
  • Entity (10-27)
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (3)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)
  • Entity (11-31)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
  • Entity (12-57)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)
  • Entity (10-27)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (4)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
  • Entity (12-57)
backend/src/main/java/org/sejongisc/backend/user/entity/User.java (1)
  • Entity (13-67)
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)
  • Entity (10-27)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)
  • Entity (10-27)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (2)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)
  • Getter (10-26)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1)
  • ToString (14-36)
backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (5)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)
  • Entity (11-31)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
  • Entity (12-57)
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)
  • Entity (10-27)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)
  • Entity (10-27)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)
  • Getter (10-26)
backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java (1)
backend/src/test/java/org/sejongisc/backend/board/service/FileUploadServiceTest.java (1)
  • ExtendWith (20-98)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentRequest.java (3)
backend/src/main/java/org/sejongisc/backend/board/dto/PostRequest.java (1)
  • ToString (16-37)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • ToString (19-40)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1)
  • ToString (14-36)
backend/src/main/java/org/sejongisc/backend/board/dto/PostRequest.java (2)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)
  • Getter (10-26)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • ToString (19-40)
backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java (1)
backend/src/main/java/org/sejongisc/backend/common/auth/springsecurity/CustomUserDetailsService.java (1)
  • RequiredArgsConstructor (16-38)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)
backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java (1)
  • Service (16-92)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (2)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentRequest.java (1)
  • ToString (13-26)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • ToString (19-40)
backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java (1)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)
  • Service (24-368)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (5)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)
  • Entity (11-31)
backend/src/main/java/org/sejongisc/backend/user/entity/User.java (1)
  • Entity (13-67)
backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (1)
  • Entity (10-33)
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)
  • Entity (10-27)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)
  • Entity (10-27)
backend/src/test/java/org/sejongisc/backend/board/service/FileUploadServiceTest.java (1)
backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java (1)
  • ExtendWith (31-288)
🔇 Additional comments (19)
backend/src/test/java/org/sejongisc/backend/backtest/controller/BacktestControllerTest.java (9)

1-207: PR 요약과 파일 내용 간의 불일치

PR 제목과 요약에서는 "게시물목록조회" 및 Post/Comment 엔티티를 포함한 게시판 기능 구현을 언급하고 있으나, 이 파일은 BacktestController 테스트로 게시판 기능과 전혀 무관합니다. 잘못된 브랜치에 커밋되었거나 PR 설명이 잘못된 것으로 보입니다.


43-56: 테스트 클래스 설정이 적절합니다

@WebMvcTest와 SecurityConfig import, 필요한 MockBean 선언이 올바르게 구성되어 있습니다.


59-68: 인증 토큰 헬퍼 메서드가 잘 구현되었습니다

테스트 전반에서 재사용 가능한 인증 토큰 생성 로직을 헬퍼 메서드로 추출하여 중복을 제거한 점이 좋습니다.


96-117: 상세 조회 테스트가 적절합니다

서비스 모킹과 응답 검증이 올바르게 구현되어 있습니다.


119-141: 백테스트 실행 테스트가 잘 작성되었습니다

요청 본문 직렬화, 인증 헤더 설정, 응답 검증이 모두 적절합니다.


143-155: 삭제 테스트가 올바르게 구현되었습니다

void 메서드에 대한 doNothing() 사용과 204 상태 코드 검증이 적절합니다.


157-177: 템플릿 저장 테스트가 적절합니다

PATCH 엔드포인트 테스트가 올바르게 구현되어 있습니다.


179-198: 템플릿 삭제 테스트가 적절합니다

서비스 메서드 모킹과 204 응답 검증이 올바릅니다.


200-206: 테스트 설정이 올바릅니다

JPA Auditing을 위한 고정된 auditor 제공자 구현이 적절합니다.

backend/src/main/java/org/sejongisc/backend/common/config/PrimaryDataSourceConfig.java (1)

66-66: LGTM!

board 엔티티 패키지를 JPA 스캔 대상에 올바르게 추가했습니다.

backend/src/main/java/org/sejongisc/backend/board/dto/PostSummaryResponse.java (1)

9-17: LGTM!

게시물 요약 정보를 담는 DTO 구조가 간결하고 적절합니다.

backend/src/main/java/org/sejongisc/backend/board/entity/PostType.java (1)

3-6: LGTM!

게시글 타입을 나타내는 enum이 명확하게 정의되었습니다.

backend/src/main/java/org/sejongisc/backend/user/entity/User.java (1)

3-3: LGTM!

Hibernate 지연 로딩 프록시의 JSON 직렬화 문제를 올바르게 처리했습니다. board 기능에서 User 엔티티가 응답 DTO에 포함될 때 발생할 수 있는 직렬화 오류를 방지합니다.

Also applies to: 20-20

backend/src/main/java/org/sejongisc/backend/board/entity/BoardType.java (1)

3-7: LGTM!

게시판 타입을 나타내는 enum이 명확하게 정의되었습니다.

backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)

10-26: LGTM!

정적 팩토리 메서드를 사용한 DTO 변환 패턴이 깔끔하고 적절합니다.

backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (4)

37-70: 게시물 저장 로직이 잘 구현되었습니다.

게시물을 먼저 저장하여 ID를 생성한 후 첨부파일을 연결하는 로직이 올바르게 구현되어 있습니다. null 체크도 적절히 수행되고 있습니다.


148-179: 게시물 조회 및 검색 로직이 잘 구현되었습니다.

페이징, 정렬, DTO 매핑이 올바르게 구현되어 있습니다.


274-282: 관리자 권한 확인 로직이 잘 구현되었습니다.

관리자(PRESIDENT, VICE_PRESIDENT)가 다른 사용자의 댓글을 삭제할 수 있도록 하는 로직이 적절히 구현되어 있습니다.


247-248: 댓글 수 증가 시 동시성 문제를 명확히 해야 합니다.

commentCount를 수동으로 증가시킬 때 동시성 제어가 없습니다. @Transactional 어노테이션은 각 메서드 호출별로 별도의 트랜잭션을 생성하기 때문에, 다음과 같은 레이스 컨디션이 발생합니다:

  • 스레드 A: post.getCommentCount() = 5 읽음
  • 스레드 B: post.getCommentCount() = 5 읽음
  • 스레드 A: 6으로 설정 후 커밋
  • 스레드 B: 6으로 설정 후 커밋 (예상값: 7)

동일한 문제가 deleteComment()에서도 발생합니다(라인 288). Post 엔티티에 @Version 필드나 데이터베이스 레벨의 원자적 업데이트(UPDATE ... SET count = count + 1)가 없으므로, 여러 사용자가 동시에 댓글을 작성/삭제할 때 정확한 카운트를 보장할 수 없습니다.

동시에 여러 댓글이 생성되는 시나리오에 대한 통합 테스트를 수행하여 카운트 정합성을 검증하고, 데이터베이스 레벨의 원자적 작업 또는 낙관적 잠금(@Version) 도입을 고려하세요.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (3)
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)

11-28: 중복 북마크 방지를 위한 유니크 제약 조건 추가 필요

이전 리뷰에서 지적된 내용과 동일하게, (post, user) 조합에 대한 데이터베이스 레벨의 유니크 제약 조건이 필요합니다. 동시 토글 요청 시 중복 레코드가 생성되어 bookmarkCount 집계가 틀어질 수 있습니다.

다음과 같이 수정하세요:

-@Entity
-@Getter
-@Setter
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-public class PostBookmark extends BasePostgresEntity {
+@Entity
+@Table(
+    name = "post_bookmark",
+    uniqueConstraints = @UniqueConstraint(
+        name = "uk_post_bookmark_post_user",
+        columnNames = {"post_id", "user_id"}))
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class PostBookmark extends BasePostgresEntity {
   
   @Id
   @GeneratedValue(strategy = GenerationType.UUID)
   private UUID postBookmarkId;
   
-  @ManyToOne(fetch = FetchType.LAZY)
+  @ManyToOne(fetch = FetchType.LAZY)
+  @JoinColumn(name = "post_id")
   private Post post;
   
-  @ManyToOne(fetch = FetchType.LAZY)
+  @ManyToOne(fetch = FetchType.LAZY)
+  @JoinColumn(name = "user_id")
   private User user;
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (2)

90-96: 이미 지적된 첨부파일 업데이트 문제입니다.

기존 리뷰에서 이미 첨부파일을 전체 삭제 후 재업로드하는 파괴적 로직이 지적되었습니다. 선택적 삭제 및 추가 방식으로 개선이 필요합니다.


303-303: 이미 지적된 댓글 수 감소 시 동시성 문제입니다.

기존 리뷰에서 이미 commentCount 감소 시 race condition 문제가 지적되었습니다. createComment와 동일한 방식으로 원자적 업데이트로 개선이 필요합니다.

🧹 Nitpick comments (8)
backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (1)

5-9: 사용되지 않는 import 제거

CreationTimestamp, LocalDateTime, BasePostgresEntity, User 등의 import가 실제로 사용되지 않습니다.

backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)

5-9: 사용되지 않는 import 제거

CreationTimestamp, LocalDateTime import가 실제로 사용되지 않습니다.

backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)

5-9: 사용되지 않는 import 제거

CreationTimestamp, LocalDateTime import가 실제로 사용되지 않습니다.

backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)

5-9: 사용되지 않는 import 제거

CreationTimestamp, LocalDateTime import가 실제로 사용되지 않습니다.

backend/src/main/java/org/sejongisc/backend/board/repository/PostLikeRepository.java (1)

13-15: 메서드 파라미터 순서 불일치

Line 13의 existsByUserUserIdAndPostPostId(UUID userId, UUID postId)와 Line 15의 findByPostPostIdAndUserUserId(UUID postId, UUID userId)에서 파라미터 순서가 다릅니다. 메서드 이름의 순서와 파라미터 순서를 일치시키는 것이 좋지만, 호출하는 측에서 일관된 순서를 사용하도록 주의가 필요합니다.

backend/src/main/java/org/sejongisc/backend/board/repository/PostBookmarkRepository.java (1)

13-15: 메서드 파라미터 순서 불일치

PostLikeRepository와 동일하게, Line 13의 existsByUserUserIdAndPostPostId(UUID userId, UUID postId)와 Line 15의 findByPostPostIdAndUserUserId(UUID postId, UUID userId)에서 파라미터 순서가 다릅니다. 일관성을 위해 동일한 순서를 사용하는 것을 권장합니다.

backend/src/main/java/org/sejongisc/backend/board/repository/CommentRepository.java (1)

14-16: 중복된 메서드 정의

findByPostPostIdfindAllByPostPostId는 Spring Data JPA에서 동일한 쿼리를 생성합니다. findByfindAllBy는 List 반환 타입에서 차이가 없으므로, 둘 중 하나만 유지하는 것이 좋습니다.

다음과 같이 하나의 메서드만 유지하세요:

-  List<Comment> findByPostPostId(UUID postId);
-
   List<Comment> findAllByPostPostId(UUID postId);
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)

27-30: 클래스 레벨 @Transactional과 메서드 레벨 @Transactional이 중복됩니다.

클래스 레벨에 @Transactional을 선언하면 모든 public 메서드가 기본적으로 트랜잭션을 갖게 됩니다. 일부 메서드(예: savePost, updatePost, deletePost)에서 메서드 레벨 @Transactional을 중복으로 선언하고 있어 불필요합니다.

다음 중 하나를 선택하는 것을 권장합니다:

  • 클래스 레벨 @Transactional을 제거하고 각 메서드에 명시적으로 선언
  • 메서드 레벨의 중복 @Transactional 제거 (단, readOnly = true는 유지)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbbaa9e and 38761dd.

📒 Files selected for processing (12)
  • backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/CommentRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostAttachmentRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostBookmarkRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostLikeRepository.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1 hunks)
  • backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/src/main/java/org/sejongisc/backend/board/entity/Post.java
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostAttachmentRepository.java
  • backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java
🧰 Additional context used
🧬 Code graph analysis (6)
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (4)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)
  • Entity (11-31)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
  • Entity (12-60)
backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (1)
  • Entity (11-34)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)
  • Entity (11-28)
backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (2)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
  • Entity (12-60)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)
  • Getter (10-26)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (2)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentRequest.java (1)
  • ToString (13-26)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • ToString (19-40)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)
backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java (1)
  • Service (16-92)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (3)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)
  • Entity (11-31)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
  • Entity (12-60)
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)
  • Entity (11-28)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (2)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
  • Entity (12-60)
backend/src/main/java/org/sejongisc/backend/user/entity/User.java (1)
  • Entity (13-67)
🔇 Additional comments (9)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1)

27-35: Null 참조 가능성 검토 필요

Line 30에서 comment.getPost().getPostId()를 호출할 때, post가 null이면 NullPointerException이 발생할 수 있습니다. Comment 엔티티의 post 필드가 항상 non-null임을 보장하려면 @ManyToOne(optional = false)를 사용하거나, 여기서 null 체크를 추가하는 것을 고려하세요.

Comment 엔티티의 post 관계가 필수인지 확인하세요.

backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)

23-27: JPA 관계 매핑이 올바르게 구현됨

Post와 User 모두에 대해 @ManyToOne 관계를 사용하여 데이터베이스 참조 무결성이 보장되며, 다른 엔티티들과 일관된 패턴을 유지하고 있습니다.

backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (7)

1-26: 임포트가 적절합니다.

필요한 Spring Data JPA, 트랜잭션, 재시도 관련 의존성이 올바르게 임포트되어 있습니다.


40-73: 게시물 작성 로직이 올바릅니다.

Post 엔티티 생성, 파일 업로드, PostAttachment 저장 흐름이 적절하게 구현되어 있습니다. 파일 null 체크도 잘 되어 있습니다.


151-165: 게시물 목록 조회 로직이 올바릅니다.

페이징, 정렬, 게시판 타입별 필터링이 적절하게 구현되어 있습니다.


167-182: 게시물 검색 로직이 올바릅니다.

제목과 내용을 대소문자 구분 없이 검색하는 로직이 적절하게 구현되어 있습니다.


184-227: 게시물 상세 조회 로직이 올바릅니다.

게시물, 댓글 페이징, 첨부파일 조회가 적절하게 구현되어 있으며, DTO 변환도 올바르게 처리되고 있습니다.


259-274: 댓글 수정 로직이 올바릅니다.

작성자 확인 및 내용 업데이트가 적절하게 구현되어 있습니다.


379-393: 매핑 로직이 올바릅니다.

이전 리뷰에서 언급된 N+1 문제와 관련하여: 현재 mapToPostResponse 메서드는 첨부파일을 조회하지 않고 Post 엔티티의 필드만 사용하여 PostResponse를 생성합니다. 첨부파일은 getPostDetail 메서드에서만 별도로 조회되며, 이는 단일 게시물 조회이므로 N+1 문제가 발생하지 않습니다.

현재 구현은 올바릅니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (7)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (7)

77-77: 클래스 레벨의 @transactional과 중복됩니다.

29번 라인의 클래스 레벨 어노테이션으로 이미 트랜잭션이 적용되므로 중복입니다.


123-123: 클래스 레벨의 @transactional과 중복됩니다.

29번 라인의 클래스 레벨 어노테이션으로 이미 트랜잭션이 적용되므로 중복입니다.


234-234: 클래스 레벨의 @transactional과 중복됩니다.

29번 라인의 클래스 레벨 어노테이션으로 이미 트랜잭션이 적용되므로 중복입니다.


264-264: 클래스 레벨의 @transactional과 중복됩니다.

29번 라인의 클래스 레벨 어노테이션으로 이미 트랜잭션이 적용되므로 중복입니다.


281-281: 클래스 레벨의 @transactional과 중복됩니다.

29번 라인의 클래스 레벨 어노테이션으로 이미 트랜잭션이 적용되므로 중복입니다.


314-314: 클래스 레벨의 @transactional과 중복됩니다.

29번 라인의 클래스 레벨 어노테이션으로 이미 트랜잭션이 적용되므로 중복입니다.


349-349: 클래스 레벨의 @transactional과 중복됩니다.

29번 라인의 클래스 레벨 어노테이션으로 이미 트랜잭션이 적용되므로 중복입니다.

🧹 Nitpick comments (3)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (3)

42-42: 클래스 레벨의 @transactional과 중복됩니다.

29번 라인에서 이미 클래스 전체에 @Transactional이 선언되어 있으므로, 이 메서드의 @Transactional 어노테이션은 불필요합니다. 다른 설정(예: readOnly, propagation)이 필요하지 않다면 제거하는 것이 좋습니다.


382-396: 목록 조회 시 첨부파일이 포함되지 않습니다.

이 메서드는 PostResponse를 반환하지만 attachments 필드를 설정하지 않습니다. getPostDetail 메서드에서는 첨부파일을 포함하지만, 목록 조회 시에는 포함되지 않습니다.

이것이 의도된 동작이라면 (목록 조회 시 성능을 위해 첨부파일 제외) 문제없지만, 다음을 권장합니다:

  1. 메서드에 주석을 추가하여 의도를 명확히 하거나
  2. 별도의 DTO(예: PostSummaryResponse)를 사용하여 목록용과 상세용을 구분

참고: 이전 리뷰에서 언급된 N+1 쿼리 문제는 현재 코드에는 해당되지 않습니다. 이 메서드는 첨부파일을 조회하지 않습니다.


56-72: 파일 업로드 검증이 부족합니다.

파일 크기 제한이나 허용된 파일 타입 검증이 없습니다. 악의적인 사용자가 대용량 파일이나 실행 파일을 업로드할 수 있습니다.

다음 검증을 추가하는 것을 권장합니다:

  1. 파일 크기 제한 (개별 파일 및 전체 업로드)
  2. 파일 확장자/MIME 타입 화이트리스트
  3. 파일명 sanitization (이미 FileUploadService에서 일부 처리됨)

예시:

private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
private static final Set<String> ALLOWED_EXTENSIONS = Set.of("jpg", "jpeg", "png", "pdf", "doc", "docx");

private void validateFile(MultipartFile file) {
  if (file.getSize() > MAX_FILE_SIZE) {
    throw new CustomException(ErrorCode.FILE_SIZE_EXCEEDED);
  }
  
  String filename = file.getOriginalFilename();
  String extension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
  if (!ALLOWED_EXTENSIONS.contains(extension)) {
    throw new CustomException(ErrorCode.INVALID_FILE_TYPE);
  }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38761dd and 33a57f2.

📒 Files selected for processing (1)
  • backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)
backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java (1)
  • Service (16-92)

@discipline24 discipline24 merged commit 6240d4f into main Nov 9, 2025
1 check passed
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.

3 participants