[BE} SISC-151 [Feat] 게시판 생성 및 대댓글#91
Hidden character warning
Conversation
Walkthrough게시판 도메인을 Board 중심으로 리팩토링하고 PostController를 제거한 뒤, BoardController와 PostInteractionService를 추가해 게시판/게시물/댓글/좋아요/북마크 관련 엔드포인트와 비즈니스 로직을 재배치했습니다. 엔티티·DTO·레포지토리·서비스·테스트가 Board 기반으로 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant BoardController
participant PostService
participant PostInteractionService
participant BoardRepository
participant PostRepository
participant CommentRepository
participant UserRepository
rect rgb(220,235,255)
Note over Client,BoardController: 게시글 생성 (Board 선택)
Client->>BoardController: POST /api/board/posts (multipart/form-data)
BoardController->>PostService: createPost(PostRequest, userId)
PostService->>BoardRepository: findById(boardId)
BoardRepository-->>PostService: Board
PostService->>PostRepository: save(Post)
PostRepository-->>PostService: saved Post
PostService-->>BoardController: 201 Created
BoardController-->>Client: 201 Created
end
rect rgb(220,255,230)
Note over Client,BoardController: 댓글 생성 (부모 선택 가능)
Client->>BoardController: POST /api/board/comments (postId, parentCommentId?, content)
BoardController->>PostInteractionService: createComment(CommentRequest, userId)
PostInteractionService->>PostRepository: findById(postId)
PostRepository-->>PostInteractionService: Post
alt parentCommentId 존재
PostInteractionService->>CommentRepository: findById(parentCommentId)
CommentRepository-->>PostInteractionService: parentComment
PostInteractionService->>CommentRepository: findByParentComment(parentComment)
CommentRepository-->>PostInteractionService: List(children)
alt 부모가 이미 자식이면(손자 시도)
PostInteractionService-->>BoardController: throw ALREADY_CHILD_COMMENT
BoardController-->>Client: 4xx 에러
else 유효
PostInteractionService->>CommentRepository: save(new Comment)
CommentRepository-->>PostInteractionService: saved Comment
PostInteractionService->>PostRepository: save(post with commentCount++)
PostInteractionService-->>BoardController: 201 Created
BoardController-->>Client: 201 Created
end
else 최상위 댓글
PostInteractionService->>CommentRepository: save(new Comment)
CommentRepository-->>PostInteractionService: saved Comment
PostInteractionService->>PostRepository: save(post with commentCount++)
BoardController-->>Client: 201 Created
end
end
rect rgb(255,245,220)
Note over Client,BoardController: 좋아요 토글
Client->>BoardController: POST /api/board/posts/{postId}/likes
BoardController->>PostInteractionService: toggleLike(postId, userId)
PostInteractionService->>PostRepository: findById(postId)
alt 좋아요 없음
PostInteractionService->>PostRepository: save(post likeCount++)
else 이미 좋아요 있음
PostInteractionService->>PostRepository: delete(PostLike) & save(post likeCount--)
end
PostInteractionService-->>BoardController: 200 OK
BoardController-->>Client: 200 OK
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45분 특히 검토가 권장되는 항목:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (1)
24-24: BasePostgresEntity 상속이 누락되었습니다.다른 엔티티들(Post, Comment, PostBookmark)과 달리
PostAttachment가BasePostgresEntity를 상속하지 않아createdAt,updatedAt같은 공통 감사 필드가 없습니다. 이는 일관성 문제를 야기합니다.다음과 같이 수정하세요:
@Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder -public class PostAttachment { +public class PostAttachment extends BasePostgresEntity {backend/src/main/java/org/sejongisc/backend/board/repository/CommentRepository.java (1)
14-16: 중복된 메서드를 제거하세요.
findByPostPostId와findAllByPostPostId는 동일한 기능을 수행하는 중복 메서드입니다. Spring Data JPA에서find...By와findAll...By는 의미상 차이가 없으며, 둘 다 List를 반환합니다.하나의 메서드만 유지하세요:
- List<Comment> findByPostPostId(UUID postId); - List<Comment> findAllByPostPostId(UUID postId);
🧹 Nitpick comments (2)
backend/src/main/java/org/sejongisc/backend/board/repository/BoardRepository.java (1)
1-9: Board 리포지토리가 적절하게 추가되었습니다.기본적인 CRUD 기능을 제공하는 표준 JPA 리포지토리가 잘 구현되었습니다.
향후 필요에 따라 다음과 같은 커스텀 메서드 추가를 고려하세요:
// 하위 게시판 조회 List<Board> findByParentBoard(Board parentBoard); // 최상위 게시판 조회 (부모가 없는 게시판) List<Board> findByParentBoardIsNull(); // 게시글 작성 가능한 게시판인지 확인 (자식 게시판이 없는지) @Query("SELECT CASE WHEN COUNT(b) > 0 THEN false ELSE true END FROM Board b WHERE b.parentBoard.boardId = :boardId") boolean canAcceptPosts(@Param("boardId") UUID boardId);backend/src/main/java/org/sejongisc/backend/board/dto/BoardRequest.java (1)
20-24:boardName입력값 유효성 보강이 필요합니다.
boardName필드에 어떤 제약도 없어서 공백/빈 문자열이 그대로 저장될 수 있습니다. 실제 서비스에서 게시판 이름이 비어 있으면 UX가 깨지고, DB 측에서NOT NULL제약만으로는 걸러지지 않습니다. 최소한@NotBlank를 추가해 컨트롤러 단에서 검증해 주세요.게시판 이름이 비어 있는 요청을 막기 위해 다음과 같이 수정해 주세요:
@Schema(description = "게시판 이름") - private String boardName; + @NotBlank(message = "게시판 이름은 필수입니다.") + private String boardName;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (24)
backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java(1 hunks)backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java(0 hunks)backend/src/main/java/org/sejongisc/backend/board/dto/BoardRequest.java(1 hunks)backend/src/main/java/org/sejongisc/backend/board/dto/CommentRequest.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/dto/PostRequest.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/entity/Board.java(1 hunks)backend/src/main/java/org/sejongisc/backend/board/entity/BoardType.java(0 hunks)backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/entity/Post.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/entity/PostType.java(0 hunks)backend/src/main/java/org/sejongisc/backend/board/repository/BoardRepository.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/PostRepository.java(1 hunks)backend/src/main/java/org/sejongisc/backend/board/service/PostInteractionService.java(1 hunks)backend/src/main/java/org/sejongisc/backend/board/service/PostService.java(2 hunks)backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java(6 hunks)backend/src/main/java/org/sejongisc/backend/common/exception/ErrorCode.java(1 hunks)backend/src/test/java/org/sejongisc/backend/board/service/PostInteractionServiceTest.java(1 hunks)backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java(1 hunks)
💤 Files with no reviewable changes (3)
- backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java
- backend/src/main/java/org/sejongisc/backend/board/entity/BoardType.java
- backend/src/main/java/org/sejongisc/backend/board/entity/PostType.java
🧰 Additional context used
🧬 Code graph analysis (13)
backend/src/main/java/org/sejongisc/backend/board/entity/Board.java (1)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
Entity(22-68)
backend/src/main/java/org/sejongisc/backend/board/service/PostInteractionService.java (2)
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)
Service(35-311)
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (2)
backend/src/main/java/org/sejongisc/backend/board/entity/Board.java (1)
Entity(21-43)backend/src/main/java/org/sejongisc/backend/user/entity/User.java (1)
Entity(13-67)
backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.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/dto/CommentResponse.java (3)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)
Getter(10-26)backend/src/main/java/org/sejongisc/backend/board/dto/CommentRequest.java (1)
ToString(14-30)backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
ToString(16-36)
backend/src/main/java/org/sejongisc/backend/board/dto/BoardRequest.java (2)
backend/src/main/java/org/sejongisc/backend/board/dto/PostRequest.java (1)
ToString(15-33)backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
ToString(16-36)
backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java (2)
backend/src/test/java/org/sejongisc/backend/board/service/PostInteractionServiceTest.java (1)
ExtendWith(38-341)backend/src/test/java/org/sejongisc/backend/board/service/FileUploadServiceTest.java (1)
ExtendWith(20-98)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (2)
backend/src/main/java/org/sejongisc/backend/board/service/PostInteractionService.java (1)
Service(28-214)backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java (1)
Service(16-92)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (4)
backend/src/main/java/org/sejongisc/backend/board/entity/Board.java (1)
Entity(21-43)backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
Entity(22-68)backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (1)
Entity(18-42)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 (3)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)
Entity(21-48)backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
Entity(22-68)backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)
Entity(19-38)
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(21-48)backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
Entity(22-68)backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)
Entity(19-38)backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)
Entity(19-38)backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)
Getter(10-26)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (4)
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)
Entity(21-48)backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (1)
Entity(22-68)backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java (1)
Entity(19-38)backend/src/main/java/org/sejongisc/backend/user/entity/User.java (1)
Entity(13-67)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (2)
backend/src/main/java/org/sejongisc/backend/board/dto/PostSummaryResponse.java (1)
Getter(9-17)backend/src/main/java/org/sejongisc/backend/board/dto/PostRequest.java (1)
ToString(15-33)
🔇 Additional comments (5)
backend/src/main/java/org/sejongisc/backend/common/exception/ErrorCode.java (1)
85-93: 새로운 에러 코드가 적절하게 추가되었습니다.추가된 에러 코드들이 대댓글 및 게시판 기능에 필요한 검증 케이스를 잘 커버하고 있습니다. HTTP 상태 코드와 메시지도 적절합니다.
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java (2)
38-38: user 관계에 명시적 @joincolumn 추가가 적절합니다.명시적인 컬럼 이름 지정으로 JPA 매핑이 명확해졌습니다.
66-67: 낙관적 락 버전 필드가 적절하게 추가되었습니다.
bookmarkCount,likeCount,commentCount같은 동시성 업데이트가 발생할 수 있는 필드들에 대한 낙관적 락 처리가 잘 구현되었습니다.backend/src/main/java/org/sejongisc/backend/board/repository/CommentRepository.java (1)
23-25: 대댓글 조회를 위한 메서드가 적절하게 추가되었습니다.
findByParentComment: 특정 댓글의 대댓글들을 조회findAllByPostPostIdAndParentCommentIsNull: 최상위 댓글만 페이징 처리하여 조회두 메서드 모두 대댓글 기능 구현에 필요한 쿼리를 잘 정의하고 있습니다.
backend/src/main/java/org/sejongisc/backend/board/entity/Comment.java (1)
45-47: 대댓글 삭제 로직이 올바르게 구현되어 있습니다.검증 결과,
PostInteractionService.deleteComment()메서드에서 부모 댓글 삭제 시 자식 댓글들을 먼저 일괄 삭제(deleteAll())한 후 부모 댓글을 삭제하는 로직이 명확하게 구현되어 있습니다. 또한findByParentComment()메서드를 통해 자식 댓글을 조회하고 있으며, 이는 테스트에서도 검증되어 있습니다.
backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java
Outdated
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/board/repository/PostRepository.java
Outdated
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java
Show resolved
Hide resolved
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)
273-294: 게시판 생성 로직의 중복 코드를 제거하세요두 분기 모두
boardName과createdBy를 동일하게 설정하고 있어 코드가 중복됩니다. Builder 패턴의 체이닝을 활용하여 간결하게 리팩토링할 수 있습니다.다음과 같이 리팩토링하세요:
- Board board; - // 하위 게시판인 경우 - if (request.getParentBoardId() != null) { - Board parentBoard = boardRepository.findById(request.getParentBoardId()) - .orElseThrow(() -> new CustomException(ErrorCode.BOARD_NOT_FOUND)); - - board = Board.builder() - .boardName(request.getBoardName()) - .createdBy(user) - .parentBoard(parentBoard) - .build(); - } else { - // 상위 게시판인 경우 - board = Board.builder() - .boardName(request.getBoardName()) - .createdBy(user) - .parentBoard(null) - .build(); - } - - boardRepository.save(board); + // 하위 게시판인 경우 부모 게시판 조회 + Board parentBoard = request.getParentBoardId() != null + ? boardRepository.findById(request.getParentBoardId()) + .orElseThrow(() -> new CustomException(ErrorCode.BOARD_NOT_FOUND)) + : null; + + Board board = Board.builder() + .boardName(request.getBoardName()) + .createdBy(user) + .parentBoard(parentBoard) + .build(); + + boardRepository.save(board);backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java (1)
81-86: 응답 타입 일관성을 개선하세요일부 메서드는
ResponseEntity<Void>를 반환하지만 (예:createPost,createComment), 다른 메서드들은void를 직접 반환합니다 (deletePost,updateComment,deleteComment). HTTP 상태 코드를 명시적으로 제어할 수 있도록 모든 엔드포인트에서ResponseEntity를 사용하는 것을 권장합니다.다음과 같이 수정하세요:
@DeleteMapping("/post/{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(); }
updateComment와deleteComment메서드에도 동일하게 적용하세요.Also applies to: 204-210, 220-225
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.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/PostServiceImpl.java(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (2)
backend/src/main/java/org/sejongisc/backend/board/service/PostInteractionService.java (1)
Service(28-214)backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java (1)
Service(16-92)
backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java (1)
backend/src/main/java/org/sejongisc/backend/common/auth/springsecurity/CustomUserDetailsService.java (1)
RequiredArgsConstructor(16-38)
🔇 Additional comments (6)
backend/src/main/java/org/sejongisc/backend/board/repository/PostRepository.java (1)
19-25: 이전에 지적된 게시판 필터 누락 문제가 수정되었습니다명시적인 JPQL
@Query를 사용하여 OR 조건을 괄호로 감싸고 board 필터를 전체 검색 조건에 적용했습니다. 이제 제목 또는 내용에 키워드가 포함된 게시물을 해당 게시판 내에서만 올바르게 검색합니다.backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (3)
205-206: 이전에 지적된 검색 필터 문제가 해결되었습니다
searchByBoardAndKeyword메서드를 사용하여 board 필터가 제목과 내용 검색 조건 모두에 적용됩니다. 다른 게시판의 게시물이 검색 결과에 섞이는 문제가 수정되었습니다.
276-277: 이전에 지적된 부모 게시판 검증 누락 문제가 해결되었습니다부모 게시판을
boardRepository.findById()로 조회하여 존재하지 않는 경우BOARD_NOT_FOUND예외를 반환합니다. 이제 잘못된parentBoardId로 인한 FK 제약 위반 대신 명확한 에러 응답을 제공합니다.
226-242: 대댓글 구조 조회 로직이 올바르게 구현되었습니다부모 댓글만 페이징하고, 각 부모 댓글의 자식 댓글을 별도로 조회하여 중첩 구조로 반환합니다. 대댓글 기능을 지원하는 적절한 구현입니다.
backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java (2)
189-196: 댓글 작성 엔드포인트 구조가 변경되었습니다이전 리뷰에서 지적된 경로 변수 동기화 문제와 달리, 현재는
/comment엔드포인트를 사용하며postId는 요청 본문(CommentRequest)에서 전달됩니다. 서비스 레이어에서postId검증이 이루어지므로 기능적으로는 문제없습니다.
37-148: Board 중심 리팩토링이 잘 구현되었습니다게시판 생성, 게시물 작성/조회/검색 엔드포인트가
boardId기반으로 일관되게 구현되었습니다. API 설계가 명확하고 Swagger 문서화도 적절합니다.
Summary by CodeRabbit
새로운 기능
개선사항