Skip to content

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

Closed
wiggle01 wants to merge 9 commits intomainfrom
SISC1-151-BE-게시물목록조회

Hidden character warning

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

Sisc1 151 be 게시물목록조회#70
wiggle01 wants to merge 9 commits intomainfrom
SISC1-151-BE-게시물목록조회

Conversation

@wiggle01
Copy link
Contributor

@wiggle01 wiggle01 commented Nov 1, 2025

Sisc1 151 be 게시물목록조회

  1. 게시물 CRUD
  2. 댓글 CRUD
  3. 사이드바 구현
  4. 게시물 목록 분리(공지사항/일반 게시물)

Summary by CodeRabbit

새로운 기능

  • 게시물 관리

    • 게시물 작성, 수정, 삭제 기능 추가
    • 게시판 유형별 게시물 조회 및 페이지네이션 지원
  • 댓글 기능

    • 댓글 작성, 수정, 삭제 기능 추가
    • 게시물별 댓글 페이지네이션 조회
  • 사용자 상호작용

    • 게시물 좋아요 토글 기능
    • 게시물 북마크 토글 기능
  • 게시물 검색 및 첨부

    • 게시물 검색 기능 추가 (제목/내용 기준)
    • 게시물 파일 첨부 지원

테스트

  • 서비스 테스트 추가
    • 게시물 및 파일 업로드 기능 테스트

@coderabbitai
Copy link

coderabbitai bot commented Nov 1, 2025

예제 설명

Walkthrough

게시판 기능의 완전한 구현을 소개합니다. 게시글 및 댓글의 CRUD 작업, 좋아요/북마크 토글, 파일 첨부 관리 기능이 포함된 엔티티, 저장소, 서비스, REST 컨트롤러, 파일 업로드 서비스가 추가되었습니다.

Changes

코호트 / 파일 변경 요약
엔티티 및 Enum
backend/src/main/java/org/sejongisc/backend/board/entity/Post.java, Comment.java, PostAttachment.java, PostLike.java, PostBookmark.java, BoardType.java, PostType.java
JPA 엔티티 7개 추가: Post (게시글), Comment (댓글), PostAttachment (파일), PostLike (좋아요), PostBookmark (북마크), 그리고 BoardType/PostType 열거형. 각 엔티티는 UUID 기본 키, Lombok 보일러플레이트, JPA 매핑 어노테이션 포함
요청/응답 DTO
backend/src/main/java/org/sejongisc/backend/board/dto/PostRequest.java, CommentRequest.java, PostResponse.java, CommentResponse.java, PostSummaryResponse.java, PostAttachmentResponse.java
6개 DTO 클래스 추가. PostRequest/CommentRequest는 검증 제약 포함, 응답 DTO들은 Lombok 생성 메서드로 엔티티-DTO 매핑 지원
저장소
backend/src/main/java/org/sejongisc/backend/board/repository/PostRepository.java, CommentRepository.java, PostAttachmentRepository.java, PostLikeRepository.java, PostBookmarkRepository.java
5개 JpaRepository 인터페이스 추가. 게시판 타입/제목/내용 검색, 페이징, 관계 엔티티 조회 메서드 포함
서비스
backend/src/main/java/org/sejongisc/backend/board/service/PostService.java, PostServiceImpl.java
PostService 인터페이스와 구현체 추가. 11개 메서드: 게시글 CRUD, 댓글 CRUD, 좋아요/북마크 토글, 검색, 상세 조회 기능 포함. 파일 업로드/삭제, 트랜잭션 경계, 소유권 검증 포함
파일 업로드 서비스
backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java
파일 저장/삭제, 경로 반환 기능 제공. UUID 기반 파일명 생성, 경로 방지 검증, 오류 처리 포함
REST 컨트롤러
backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java
PostController 추가: 11개 REST 엔드포인트(/api/post/*). 게시글 CRUD, 댓글 CRUD, 좋아요/북마크 토글, 검색, 페이징 조회. CustomUserDetails 인증, Swagger 태그 포함
설정 및 예외
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
JPA 엔티티 스캔 패키지에 board 모듈 추가. 4개 새 ErrorCode (POST_NOT_FOUND 등) 추가. User 엔티티에 @JsonIgnoreProperties 추가 (직렬화 문제 방지)
단위 테스트
backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java, FileUploadServiceTest.java
2개 테스트 클래스 추가. PostServiceImplTest: 저장, 업데이트, 삭제, 좋아요/북마크 토글, 댓글 CRUD 커버. FileUploadServiceTest: 저장, 삭제, 빈 파일 처리, 예외 케이스 검증

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant PC as PostController
    participant PS as PostServiceImpl
    participant DB as Repositories
    participant FUS as FileUploadService

    User->>PC: POST /api/post (PostRequest + files)
    PC->>PS: savePost(request, userId)
    PS->>DB: userRepository.findById(userId)
    activate PS
    PS->>PS: 새 Post 생성
    PS->>DB: postRepository.save(post)
    PS->>FUS: store(file) for each file
    FUS->>FUS: UUID + 파일명 생성
    FUS-->>PS: 저장된 파일명
    PS->>DB: postAttachmentRepository.save(attachment)
    deactivate PS
    PS-->>PC: void
    PC-->>User: 201 Created

    User->>PC: GET /api/post/{postId}?commentPage=0&commentSize=10
    PC->>PS: getPostDetail(postId, pageNum, pageSize)
    PS->>DB: postRepository.findById(postId)
    PS->>DB: commentRepository.findAllByPostId(postId, pageable)
    PS->>DB: postAttachmentRepository.findAllByPostId(postId)
    PS-->>PC: PostResponse (comments + attachments)
    PC-->>User: 200 OK + PostResponse

    User->>PC: POST /api/post/{postId}/like
    PC->>PS: toggleLike(postId, userId)
    PS->>DB: postLikeRepository.findByPostIdAndUserId(...)
    alt 좋아요 없음
        PS->>DB: postLikeRepository.save(new PostLike)
        PS->>DB: post.likeCount++
    else 좋아요 있음
        PS->>DB: postLikeRepository.delete(postLike)
        PS->>DB: post.likeCount--
    end
    PS-->>PC: void
    PC-->>User: 200 OK

    User->>PC: DELETE /api/post/{postId}
    PC->>PS: deletePost(postId, userId)
    PS->>DB: postRepository.findById(postId)
    PS->>PS: 소유권 검증
    PS->>DB: commentRepository.findAllByPostId(postId)
    PS->>FUS: delete(filename) for each attachment
    PS->>DB: postAttachmentRepository.deleteAllByPostId(postId)
    PS->>DB: postLikeRepository.deleteAllByPostId(postId)
    PS->>DB: postBookmarkRepository.deleteAllByPostId(postId)
    PS->>DB: commentRepository.deleteAllByPostId(postId)
    PS->>DB: postRepository.delete(post)
    PS-->>PC: void
    PC-->>User: 204 No Content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45분

주의가 필요한 영역:

  • PostServiceImpl.java: 복잡한 비즈니스 로직, 다중 저장소 상호작용, 트랜잭션 경계, 소유권 검증, 파일 관리 포함. 각 메서드의 오류 처리 및 상태 관리 검증 필요
  • FileUploadService.java: 파일 시스템 작업, 하드코딩된 경로 ("C:/uploads/"), 경로 방지 검증, 크로스 플랫폼 호환성 고려 필요
  • PostController.java: 다양한 엔드포인트 매개변수 바인딩, 인증 처리, 페이징 로직, multipart 폼 데이터 처리 확인 필요
  • PostServiceImplTest.java: 광범위한 목(mock) 설정, 엔티티-DTO 매핑 검증, 소유권 및 권한 확인 로직 커버리지 확인 필요

Possibly related PRs

Suggested reviewers

  • discipline24
  • Kosw6
  • ochanhyeok

Poem

🐰 게시판 기능이 쏘옥! 📝
댓글도, 좋아요도, 북마크도 🌟
파일까지 척척 올라가고 ✨
데이터 흐름이 춤을 춘다네 🎭
완벽한 CRUD, 완벽한 팀워크! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.58% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 PR의 주요 변화를 정확하게 반영합니다. 게시물 목록 조회 기능 구현이 PR의 핵심이며, 게시물/댓글 CRUD 및 기타 기능들도 포함되어 있습니다.
✨ 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: 8

🧹 Nitpick comments (9)
backend/src/main/java/org/sejongisc/backend/board/dto/BoardDto.java (2)

8-10: 클래스 구조가 적절합니다.

Lombok 어노테이션을 사용한 불변 DTO 설계가 올바르며, 코드베이스의 다른 DTO들과 일관성 있는 패턴을 따르고 있습니다.

선택사항: 객체 생성 시 더 유연한 방식을 위해 @Builder 어노테이션을 추가하는 것을 고려해보세요.

 @Getter
+@Builder
 @AllArgsConstructor
 public class BoardDto {

11-13: 필드 타입이 적절하나 유효성 검증 추가를 권장합니다.

필드 타입과 명명은 적절하지만, API 안정성 향상을 위해 유효성 검증 어노테이션 추가를 고려해보세요.

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
 @Getter
 @AllArgsConstructor
 public class BoardDto {
+    @NotNull
     private UUID id;
+    @NotBlank
     private String name;
     private boolean isPrivate;
 }
backend/src/main/java/org/sejongisc/backend/board/domain/Board.java (3)

20-20: Team 엔티티와의 관계 매핑을 고려하세요.

현재 teamId가 단순 UUID 필드로 되어있는데, 관련 코드 스니펫에서 Team 엔티티가 존재하는 것을 확인했습니다. JPA의 관계 매핑을 사용하면 객체 지향적인 탐색과 참조 무결성 검증이 가능합니다.

관계 매핑 사용을 권장합니다:

-    private UUID teamId;
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "team_id")
+    private Team team;

28-28: JPA Auditing 활용을 권장합니다.

createdAt 필드를 수동으로 관리하는 대신, Spring Data JPA의 Auditing 기능을 사용하면 생성/수정 시간이 자동으로 관리됩니다. 코드베이스에 BasePostgresEntity가 이미 존재하는 것으로 보이니 일관성을 위해 확장하거나 Auditing 애노테이션을 사용하세요.

다음과 같이 수정하세요:

+    @CreationTimestamp
     private LocalDateTime createdAt;

또는 BasePostgresEntity를 상속:

-public class Board {
+public class Board extends BasePostgresEntity {
 
     @Id
     @GeneratedValue(strategy = GenerationType.UUID)
     @Column(name = "board_id", columnDefinition = "BINARY(16)")
     private UUID id;
 
     // ...
-    
-    private LocalDateTime createdAt;

10-10: 엔티티에 @Setter 사용을 재고해주세요.

엔티티에 @Setter를 사용하면 모든 필드가 외부에서 수정 가능해져 도메인 불변성이 깨질 수 있습니다. 필요한 경우에만 선택적으로 setter를 만들거나, 비즈니스 메서드를 통해 상태를 변경하는 것을 권장합니다.

backend/src/main/java/org/sejongisc/backend/board/domain/Post.java (2)

16-20: Hibernate 특정 애노테이션 사용을 재고하세요.

@GenericGenerator는 Hibernate 특정 기능입니다. Spring Data JPA 3.0+에서는 GenerationType.UUID를 직접 지원하므로, 이식성을 위해 표준 방식 사용을 권장합니다.

다음과 같이 수정하세요:

     @Id
-    @GeneratedValue(generator = "uuid2")
-    @GenericGenerator(name = "uuid2", strategy = "uuid2")
+    @GeneratedValue(strategy = GenerationType.UUID)
     @Column(name = "post_id", columnDefinition = "BINARY(16)")
     private UUID id;

그리고 import 제거:

-import org.hibernate.annotations.GenericGenerator;

36-40: update 메서드에 입력 검증을 추가하세요.

null이나 빈 문자열 검증 없이 필드를 업데이트하면 데이터 무결성 문제가 발생할 수 있습니다.

다음과 같이 개선하세요:

     public void update(String title, String content, PostType type) {
+        if (title == null || title.trim().isEmpty()) {
+            throw new IllegalArgumentException("제목은 필수입니다");
+        }
+        if (content == null || content.trim().isEmpty()) {
+            throw new IllegalArgumentException("내용은 필수입니다");
+        }
+        if (type == null) {
+            throw new IllegalArgumentException("게시물 타입은 필수입니다");
+        }
         this.title = title;
         this.content = content;
         this.postType = type;
     }
backend/src/main/java/org/sejongisc/backend/board/dto/CommentCreateRequest.java (1)

12-12: 유효성 검증 애노테이션 추가를 권장합니다.

댓글 내용에 대한 검증이 없으면 빈 내용이나 과도하게 긴 내용이 저장될 수 있습니다. Jakarta Validation API를 사용하여 입력을 검증하세요.

다음과 같이 수정하세요:

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
 @Getter
 @NoArgsConstructor
 public class CommentCreateRequest {
     private UUID postId;
+    @NotBlank(message = "댓글 내용은 필수입니다")
+    @Size(max = 5000, message = "댓글은 5000자를 초과할 수 없습니다")
     private String content;
     private UUID parentId; // 대댓글이면 존재, 아니면 null
 }

컨트롤러에서 @Valid 애노테이션도 추가해야 합니다.

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

190-199: 댓글 수 계산에서 N+1 쿼리가 발생합니다.
toSummary가 목록의 각 게시글마다 commentRepository.findByPostId를 호출하고 있어 게시글 N개에 대해 추가로 N번의 쿼리가 발생합니다. 조회 빈도가 높은 API인 만큼, findByPostId 대신 댓글 수를 일괄로 구하거나 게시글 목록 조회 시 함께 조인하여 가져오는 방식으로 개선하는 편이 좋겠습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f80909a and e5e4775.

📒 Files selected for processing (27)
  • backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/domain/Board.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/domain/Comment.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/domain/Post.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/domain/PostAttachment.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/domain/PostBookmark.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/domain/PostLike.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/domain/PostType.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/BoardDto.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/CommentCreateRequest.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/CommentUpdateRequest.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentDto.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/dto/PostCreateRequest.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/dto/PostUpdateRequest.java (1 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/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/PostService.java (1 hunks)
  • backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1 hunks)
  • backend/src/test/java/org/sejongisc/backend/board/controller/PostControllerTest.java (1 hunks)
  • backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (18)
backend/src/main/java/org/sejongisc/backend/board/dto/PostCreateRequest.java (4)
backend/src/main/java/org/sejongisc/backend/board/dto/BoardDto.java (1)
  • Getter (8-14)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentDto.java (1)
  • Getter (7-14)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • Getter (11-24)
backend/src/main/java/org/sejongisc/backend/board/dto/PostUpdateRequest.java (1)
  • Getter (10-17)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentCreateRequest.java (2)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1)
  • Getter (9-17)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentUpdateRequest.java (1)
  • Getter (6-10)
backend/src/main/java/org/sejongisc/backend/board/domain/PostLike.java (3)
backend/src/main/java/org/sejongisc/backend/board/domain/Comment.java (1)
  • Entity (9-36)
backend/src/main/java/org/sejongisc/backend/board/domain/PostAttachment.java (1)
  • Entity (9-36)
backend/src/main/java/org/sejongisc/backend/board/domain/PostBookmark.java (1)
  • Entity (9-30)
backend/src/main/java/org/sejongisc/backend/board/domain/PostType.java (1)
backend/src/main/java/org/sejongisc/backend/betting/entity/MarketType.java (1)
  • MarketType (3-6)
backend/src/main/java/org/sejongisc/backend/board/dto/BoardDto.java (4)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1)
  • Getter (9-17)
backend/src/main/java/org/sejongisc/backend/board/dto/PostCreateRequest.java (1)
  • Getter (11-20)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • Getter (11-24)
backend/src/main/java/org/sejongisc/backend/board/dto/PostSummaryResponse.java (1)
  • Getter (9-17)
backend/src/main/java/org/sejongisc/backend/board/domain/Post.java (9)
backend/src/main/java/org/sejongisc/backend/board/domain/Board.java (1)
  • Entity (8-29)
backend/src/main/java/org/sejongisc/backend/board/domain/Comment.java (1)
  • Entity (9-36)
backend/src/main/java/org/sejongisc/backend/board/domain/PostAttachment.java (1)
  • Entity (9-36)
backend/src/main/java/org/sejongisc/backend/board/domain/PostBookmark.java (1)
  • Entity (9-30)
backend/src/main/java/org/sejongisc/backend/board/domain/PostLike.java (1)
  • Entity (9-30)
backend/src/main/java/org/sejongisc/backend/board/dto/PostCreateRequest.java (1)
  • Getter (11-20)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • Getter (11-24)
backend/src/main/java/org/sejongisc/backend/board/dto/PostUpdateRequest.java (1)
  • Getter (10-17)
backend/src/main/java/org/sejongisc/backend/common/entity/postgres/BasePostgresEntity.java (1)
  • ToString (17-35)
backend/src/main/java/org/sejongisc/backend/board/domain/Board.java (3)
backend/src/main/java/org/sejongisc/backend/board/domain/Post.java (1)
  • Entity (9-41)
backend/src/main/java/org/sejongisc/backend/board/dto/BoardDto.java (1)
  • Getter (8-14)
backend/src/main/java/org/sejongisc/backend/user/entity/Team.java (1)
  • Entity (9-27)
backend/src/main/java/org/sejongisc/backend/board/domain/PostBookmark.java (3)
backend/src/main/java/org/sejongisc/backend/board/domain/Comment.java (1)
  • Entity (9-36)
backend/src/main/java/org/sejongisc/backend/board/domain/PostAttachment.java (1)
  • Entity (9-36)
backend/src/main/java/org/sejongisc/backend/board/domain/PostLike.java (1)
  • Entity (9-30)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (2)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentCreateRequest.java (1)
  • Getter (8-14)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • Getter (11-24)
backend/src/main/java/org/sejongisc/backend/board/repository/PostLikeRepository.java (4)
backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceRepository.java (1)
  • Repository (17-55)
backend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java (1)
  • UserBetRepository (11-17)
backend/src/main/java/org/sejongisc/backend/template/repository/TemplateRepository.java (1)
  • Repository (10-13)
backend/src/main/java/org/sejongisc/backend/auth/repository/RefreshTokenRepository.java (1)
  • RefreshTokenRepository (9-14)
backend/src/test/java/org/sejongisc/backend/board/controller/PostControllerTest.java (2)
backend/src/test/java/org/sejongisc/backend/template/controller/TemplateControllerTest.java (2)
  • Test (142-169)
  • WebMvcTest (46-265)
backend/src/test/java/org/sejongisc/backend/backtest/controller/BacktestControllerTest.java (3)
  • WebMvcTest (43-207)
  • Test (120-141)
  • Test (71-87)
backend/src/main/java/org/sejongisc/backend/board/dto/PostSummaryResponse.java (1)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • Getter (11-24)
backend/src/main/java/org/sejongisc/backend/board/domain/Comment.java (7)
backend/src/main/java/org/sejongisc/backend/board/domain/Post.java (1)
  • Entity (9-41)
backend/src/main/java/org/sejongisc/backend/board/domain/PostAttachment.java (1)
  • Entity (9-36)
backend/src/main/java/org/sejongisc/backend/board/domain/PostBookmark.java (1)
  • Entity (9-30)
backend/src/main/java/org/sejongisc/backend/board/domain/PostLike.java (1)
  • Entity (9-30)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentCreateRequest.java (1)
  • Getter (8-14)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1)
  • Getter (9-17)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentUpdateRequest.java (1)
  • Getter (6-10)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (4)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentDto.java (1)
  • Getter (7-14)
backend/src/main/java/org/sejongisc/backend/board/dto/PostCreateRequest.java (1)
  • Getter (11-20)
backend/src/main/java/org/sejongisc/backend/board/dto/PostSummaryResponse.java (1)
  • Getter (9-17)
backend/src/main/java/org/sejongisc/backend/board/dto/PostUpdateRequest.java (1)
  • Getter (10-17)
backend/src/main/java/org/sejongisc/backend/board/dto/PostUpdateRequest.java (3)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentDto.java (1)
  • Getter (7-14)
backend/src/main/java/org/sejongisc/backend/board/dto/PostCreateRequest.java (1)
  • Getter (11-20)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • Getter (11-24)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentUpdateRequest.java (3)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentCreateRequest.java (1)
  • Getter (8-14)
backend/src/main/java/org/sejongisc/backend/board/dto/CommentResponse.java (1)
  • Getter (9-17)
backend/src/main/java/org/sejongisc/backend/board/dto/PostUpdateRequest.java (1)
  • Getter (10-17)
backend/src/main/java/org/sejongisc/backend/board/domain/PostAttachment.java (3)
backend/src/main/java/org/sejongisc/backend/board/domain/Post.java (1)
  • Entity (9-41)
backend/src/main/java/org/sejongisc/backend/board/domain/PostBookmark.java (1)
  • Entity (9-30)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentDto.java (1)
  • Getter (7-14)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentDto.java (3)
backend/src/main/java/org/sejongisc/backend/board/dto/PostCreateRequest.java (1)
  • Getter (11-20)
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java (1)
  • Getter (11-24)
backend/src/main/java/org/sejongisc/backend/board/dto/PostUpdateRequest.java (1)
  • Getter (10-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Backend Gate
🔇 Additional comments (6)
backend/src/main/java/org/sejongisc/backend/board/repository/PostAttachmentRepository.java (1)

9-11: 깔끔한 리포지토리 구현입니다.

Spring Data JPA 메서드 네이밍 규칙을 잘 따르고 있으며, PostAttachment 엔티티와의 관계가 명확합니다.

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

11-12: 리포지토리 메서드가 올바르게 정의되었습니다.

Spring Data JPA의 쿼리 메서드 네이밍을 잘 따르고 있으며, 보드별 게시물 조회와 타입별 필터링이 명확합니다.

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

3-5: 깔끔한 enum 정의입니다.

공지사항과 일반 게시물을 구분하는 명확한 enum으로, PR 목표와 잘 일치합니다.

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

8-9: 표준 리포지토리 구현입니다.

JpaRepository의 기본 CRUD 메서드만 사용하는 깔끔한 구조입니다.

backend/src/main/java/org/sejongisc/backend/board/domain/Post.java (1)

22-24: 지연 로딩 설정이 적절합니다.

Board와의 관계에서 FetchType.LAZY를 사용한 것은 N+1 문제 방지를 위한 좋은 선택입니다.

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

9-12: 계층형 댓글 구조를 잘 지원합니다.

게시물별 댓글 조회와 대댓글 조회를 위한 메서드가 명확하게 정의되어 있습니다.

Comment on lines 54 to 59
@PostMapping("/{postId}/comments")
public UUID createComment(@PathVariable UUID postId,
@RequestBody CommentCreateRequest request,
@RequestParam UUID userId) {
return postService.createComment(request, userId);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

경로 변수 postId를 실제로 활용하도록 수정이 필요합니다.
현재 구현은 경로 변수로 받은 postId를 전혀 사용하지 않고 CommentCreateRequest만 그대로 서비스에 전달합니다. 그러나 PostServiceImpl.createCommentrequest.getPostId()null이면 IllegalArgumentException("postId 가 필요합니다.")를 던집니다. 일반적인 REST 호출(경로에만 postId를 지정하고 본문에는 content만 담는 경우)에서는 무조건 500 계열 예외로 실패하게 됩니다. 반드시 경로 값을 서비스에 전달하도록 변경해야 합니다.

-        return postService.createComment(request, userId);
+        return postService.createComment(postId, request, userId);

서비스/인터페이스 시그니처도 함께 아래처럼 조정해주세요(별도 파일 수정 필요):

-public interface PostService {
-    UUID createComment(CommentCreateRequest request, UUID userId);
+public interface PostService {
+    UUID createComment(UUID postId, CommentCreateRequest request, UUID userId);
-    public UUID createComment(CommentCreateRequest request, UUID userId) {
-        if (request.getPostId() == null) throw new IllegalArgumentException("postId 가 필요합니다.");
-        postRepository.findById(request.getPostId()) …
-        Comment comment = Comment.builder()
-                .postId(request.getPostId())
+    public UUID createComment(UUID postId, CommentCreateRequest request, UUID userId) {
+        if (postId == null) throw new IllegalArgumentException("postId 가 필요합니다.");
+        postRepository.findById(postId) …
+        Comment comment = Comment.builder()
+                .postId(postId)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@PostMapping("/{postId}/comments")
public UUID createComment(@PathVariable UUID postId,
@RequestBody CommentCreateRequest request,
@RequestParam UUID userId) {
return postService.createComment(request, userId);
}
@PostMapping("/{postId}/comments")
public UUID createComment(@PathVariable UUID postId,
@RequestBody CommentCreateRequest request,
@RequestParam UUID userId) {
return postService.createComment(postId, request, userId);
}
🤖 Prompt for AI Agents
In
backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java
around lines 54 to 59, the method ignores the path variable postId and passes
only the request to the service; update the controller to set the postId from
the @PathVariable onto the CommentCreateRequest (or call a service method
overload that accepts postId) before invoking postService.createComment so the
service receives a non-null postId and avoids IllegalArgumentException; also
update the PostService/PostServiceImpl interface and implementation signatures
as suggested so they accept the postId (or a request already populated with it)
and adjust any callers accordingly.

Comment on lines 16 to 18
@Id
@GeneratedValue
private UUID id;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

UUID 생성 전략을 명시해주세요.

@GeneratedValue만 사용하면 데이터베이스에 따라 예상치 못한 동작이 발생할 수 있습니다. UUID 생성 전략을 명시적으로 지정하는 것이 좋습니다.

다음과 같이 수정하세요:

     @Id
-    @GeneratedValue
+    @GeneratedValue(strategy = GenerationType.UUID)
+    @Column(name = "board_id", columnDefinition = "BINARY(16)")
     private UUID id;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Id
@GeneratedValue
private UUID id;
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "board_id", columnDefinition = "BINARY(16)")
private UUID id;
🤖 Prompt for AI Agents
In backend/src/main/java/org/sejongisc/backend/board/domain/Board.java around
lines 16-18, the UUID field uses @GeneratedValue without a concrete generator
which can behave inconsistently across databases; change the annotations to
specify a UUID generator (e.g., add @GeneratedValue(generator = "UUID") and
declare a @GenericGenerator(name = "UUID", strategy =
"org.hibernate.id.UUIDGenerator") on the entity or field), keep the UUID type,
and ensure necessary imports are added so Hibernate will reliably generate
UUIDs.

Comment on lines 9 to 41
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class Post {

@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(name = "post_id", columnDefinition = "BINARY(16)")
private UUID id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private Board board;

@Column(nullable = false)
private String title;

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

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PostType postType;

public void update(String title, String content, PostType type) {
this.title = title;
this.content = content;
this.postType = type;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

감사(Audit) 필드가 누락되었습니다.

게시물 엔티티에 생성 시간과 수정 시간이 없으면 게시물 관리와 정렬에 문제가 생길 수 있습니다. 코드베이스에 BasePostgresEntity가 있는 것을 확인했는데, 이를 상속하거나 직접 audit 애노테이션을 추가하세요.

방법 1 (권장): BasePostgresEntity 상속

+import org.sejongisc.backend.common.entity.postgres.BasePostgresEntity;
+
 @Entity
 @Getter
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 @AllArgsConstructor
 @Builder
-public class Post {
+public class Post extends BasePostgresEntity {

방법 2: 직접 필드 추가

+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
 @Entity
+@EntityListeners(AuditingEntityListener.class)
 @Getter
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 @AllArgsConstructor
 @Builder
 public class Post {
     // ... existing fields ...
     
+    @CreatedDate
+    @Column(nullable = false, updatable = false)
+    private LocalDateTime createdAt;
+    
+    @LastModifiedDate
+    @Column(nullable = false)
+    private LocalDateTime updatedAt;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class Post {
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(name = "post_id", columnDefinition = "BINARY(16)")
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private Board board;
@Column(nullable = false)
private String title;
@Column(nullable = false, columnDefinition = "TEXT")
private String content;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PostType postType;
public void update(String title, String content, PostType type) {
this.title = title;
this.content = content;
this.postType = type;
}
}
import org.sejongisc.backend.common.entity.postgres.BasePostgresEntity;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class Post extends BasePostgresEntity {
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(name = "post_id", columnDefinition = "BINARY(16)")
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private Board board;
@Column(nullable = false)
private String title;
@Column(nullable = false, columnDefinition = "TEXT")
private String content;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PostType postType;
public void update(String title, String content, PostType type) {
this.title = title;
this.content = content;
this.postType = type;
}
}
🤖 Prompt for AI Agents
In backend/src/main/java/org/sejongisc/backend/board/domain/Post.java around
lines 9 to 41, the Post entity is missing audit fields (created/modified
timestamps); either make Post extend the project's BasePostgresEntity so it
inherits audit columns and remove duplicate id/annotations if BasePostgresEntity
already defines them, or add createdAt and updatedAt fields with appropriate JPA
audit annotations (@CreatedDate, @LastModifiedDate) and ensure the class is
annotated with @EntityListeners(AuditingEntityListener.class) and Spring Data
JPA auditing is enabled; update imports and constructors/builders accordingly
and run tests to confirm there are no duplicate column definitions.

Comment on lines 22 to 29
@Column(nullable = false)
private UUID postId;

@Column(nullable = false)
private UUID userId;

@CreationTimestamp
private LocalDateTime createdAt;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

중복 북마크를 막는 유니크 제약이 필요합니다.

서비스 레이어에서 existsByUserIdAndPostId로 중복을 체크하더라도 동시에 두 요청이 들어오면 동일 userId/postId 조합이 여러 건 저장될 수 있어 북마크 카운트나 해제 로직이 꼬일 위험이 있습니다. 테이블 차원의 유니크 제약으로 데이터 정합성을 보장해 주세요.

다음과 같은 형태로 유니크 제약과 명시적 컬럼명을 추가하는 방안을 제안드립니다.

-@Table(name = "post_bookmark")
+@Table(
+        name = "post_bookmark",
+        uniqueConstraints = @UniqueConstraint(columnNames = {"post_id", "user_id"})
+)
 ...
-    @Column(nullable = false)
+    @Column(name = "post_id", nullable = false)
     private UUID postId;
 
-    @Column(nullable = false)
+    @Column(name = "user_id", nullable = false)
     private UUID userId;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/src/main/java/org/sejongisc/backend/board/domain/PostBookmark.java
around lines 22–29, the entity lacks a table-level unique constraint on the
(userId, postId) pair and explicit column names, which allows race conditions to
create duplicate bookmarks; add a @Table annotation with a composite
uniqueConstraints definition for (user_id, post_id) and change the field @Column
annotations to explicit column names (e.g., @Column(name = "post_id", nullable =
false) and @Column(name = "user_id", nullable = false)); also create the
corresponding DB migration (Flyway/Liquibase) to add the unique index/constraint
on the existing table so the database enforces uniqueness.

@Getter
@NoArgsConstructor
public class CommentCreateRequest {
private UUID postId;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

postId 필드의 중복 가능성을 확인하세요.

댓글 생성 시 postId가 URL 경로 파라미터로 이미 전달될 가능성이 높습니다. 컨트롤러 구현을 확인하여 필드가 중복되지 않는지 검증하세요.

다음 스크립트로 컨트롤러에서 postId 사용을 확인하세요:


🏁 Script executed:

#!/bin/bash
# PostController에서 댓글 생성 엔드포인트 확인
ast-grep --pattern $'createComment($$$@PathVariable$$$ postId$$$, $$$CommentCreateRequest$$$) {
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# PostController 찾기
fd -t f "PostController" -e java

Length of output: 165


🏁 Script executed:

#!/bin/bash
# 댓글 생성 관련 컨트롤러 메서드 검색 (유연한 패턴)
rg -n "createComment|create.*comment" --type java -i -A 5 -B 2

Length of output: 9042


🏁 Script executed:

#!/bin/bash
# CommentCreateRequest 필드 확인
fd -t f "CommentCreateRequest" -e java | head -1 | xargs cat

Length of output: 288


@PathVariable postId가 추출되지만 서비스 레이어에 전달되지 않아 무시됩니다.

컨트롤러에서 @PathVariable UUID postId로 URL 경로에서 추출되지만, 서비스 호출 시 전달되지 않습니다. 대신 서비스는 CommentCreateRequestpostId 필드를 사용하므로, URL의 postId와 요청 본문의 postId가 다를 경우 요청 본문의 값이 우선됩니다.

CommentCreateRequest에서 postId 필드를 제거하고 컨트롤러에서 경로 파라미터를 서비스에 전달하도록 수정해야 합니다:

// PostController.java - line 55-59
return postService.createComment(postId, request, userId);
🤖 Prompt for AI Agents
In
backend/src/main/java/org/sejongisc/backend/board/dto/CommentCreateRequest.java
around line 11, the DTO contains a postId field which allows the request body to
override the @PathVariable postId extracted in the controller; remove the postId
field from CommentCreateRequest and update controller/service call sites to pass
the path variable explicitly (e.g., postService.createComment(postId,
requestBody, userId)) so the URL path parameter is the single source of truth.

public interface PostRepository extends JpaRepository<Post, UUID> {
List<Post> findByBoardId(UUID boardId);
List<Post> findByBoardIdAndPostType(UUID boardId, PostType postType);
List<Post> findByTitleContainingIgnoreCaseOrContentContainingIgnoreCase(String titleKeyword, String contentKeyword);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

검색 메서드의 파라미터 중복을 개선하세요.

동일한 키워드를 두 번 전달해야 하는 메서드 시그니처는 오류를 유발하기 쉽습니다. @Query 애노테이션을 사용하여 단일 파라미터로 제목과 내용을 모두 검색하도록 개선하는 것이 좋습니다.

다음과 같이 수정하세요:

+    @Query("SELECT p FROM Post p WHERE LOWER(p.title) LIKE LOWER(CONCAT('%', :keyword, '%')) OR LOWER(p.content) LIKE LOWER(CONCAT('%', :keyword, '%'))")
+    List<Post> searchByKeyword(@Param("keyword") String keyword);
-    List<Post> findByTitleContainingIgnoreCaseOrContentContainingIgnoreCase(String titleKeyword, String contentKeyword);

필요한 import 추가:

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
🤖 Prompt for AI Agents
In
backend/src/main/java/org/sejongisc/backend/board/repository/PostRepository.java
around line 13, the repository method requires passing the same keyword twice
which is error-prone; replace the derived query method with a custom @Query that
searches title OR content using a single @Param("keyword") parameter (e.g.,
WHERE LOWER(p.title) LIKE LOWER(CONCAT('%', :keyword, '%')) OR LOWER(p.content)
LIKE LOWER(CONCAT('%', :keyword, '%'))), update the method signature to accept
one String keyword annotated with @Param("keyword"), and add the imports for
org.springframework.data.jpa.repository.Query and
org.springframework.data.repository.query.Param.

Comment on lines 131 to 149
public UUID createComment(CommentCreateRequest request, UUID userId) {
if (request == null) throw new IllegalArgumentException("요청이 존재하지 않습니다.");
if (userId == null) throw new IllegalArgumentException("userId 가 필요합니다.");
if (request.getPostId() == null) throw new IllegalArgumentException("postId 가 필요합니다.");
if (isBlank(request.getContent())) throw new IllegalArgumentException("댓글 내용이 비어 있습니다.");

// 게시글 존재 확인
postRepository.findById(request.getPostId())
.orElseThrow(() -> new NoSuchElementException("게시글을 찾을 수 없습니다. id=" + request.getPostId()));

Comment comment = Comment.builder()
.postId(request.getPostId())
.userId(userId)
.content(request.getContent())
.parentId(request.getParentId())
.build();

return commentRepository.save(comment).getId();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

postId를 PathVariable 로 전달받도록 시그니처를 조정해주세요.
컨트롤러에서 postId를 경로로 받고 있음에도 이 구현은 여전히 request.getPostId()를 필수로 요구합니다. 결과적으로 본문에 postId를 넣지 않으면 즉시 IllegalArgumentException이 발생합니다. 서비스 시그니처를 createComment(UUID postId, CommentCreateRequest request, UUID userId) 형태로 변경하고, 여기서 받은 postId를 검증·사용하도록 수정해야 합니다. 반드시 컨트롤러/인터페이스와 함께 맞춰 주세요.

-    public UUID createComment(CommentCreateRequest request, UUID userId) {
-        if (request == null) throw new IllegalArgumentException("요청이 존재하지 않습니다.");
-        if (userId == null) throw new IllegalArgumentException("userId 가 필요합니다.");
-        if (request.getPostId() == null) throw new IllegalArgumentException("postId 가 필요합니다.");
+    public UUID createComment(UUID postId, CommentCreateRequest request, UUID userId) {
+        if (postId == null) throw new IllegalArgumentException("postId 가 필요합니다.");
+        if (request == null) throw new IllegalArgumentException("요청이 존재하지 않습니다.");
+        if (userId == null) throw new IllegalArgumentException("userId 가 필요합니다.");
         if (isBlank(request.getContent())) throw new IllegalArgumentException("댓글 내용이 비어 있습니다.");
 
-        postRepository.findById(request.getPostId())
-                .orElseThrow(() -> new NoSuchElementException("게시글을 찾을 수 없습니다. id=" + request.getPostId()));
+        postRepository.findById(postId)
+                .orElseThrow(() -> new NoSuchElementException("게시글을 찾을 수 없습니다. id=" + postId));
 
         Comment comment = Comment.builder()
-                .postId(request.getPostId())
+                .postId(postId)
                 .userId(userId)
                 .content(request.getContent())
                 .parentId(request.getParentId())
                 .build();

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java
around lines 131-149, the method currently demands postId from the request body
which conflicts with the controller passing postId as a PathVariable; change the
service method signature to createComment(UUID postId, CommentCreateRequest
request, UUID userId), remove the request.getPostId() null-check, validate that
the incoming postId param is not null (throw IllegalArgumentException if it is),
use that postId when checking post existence and when building the Comment, and
update the corresponding service interface and controller method signatures to
match this new parameter order.

Comment on lines 25 to 61
@WebMvcTest(PostController.class)
class PostControllerTest {

@Autowired private MockMvc mockMvc;
@Autowired private ObjectMapper objectMapper;

@MockBean private PostService postService;

// 슬라이스 테스트 시 JPA 메타모델 경고 방지 (환경에 따라 필요)
@MockBean JpaMetamodelMappingContext jpaMetamodelMappingContext;

@Test
@DisplayName("[POST] /api/posts — 생성 200 & UUID 반환")
void createPost_ok() throws Exception {
UUID userId = UUID.randomUUID();
UUID postId = UUID.randomUUID();

PostCreateRequest req = new PostCreateRequest(
UUID.randomUUID(),
"제목",
"내용",
PostType.NORMAL,
List.of(new PostAttachmentDto("a.hwp","application/haansoft-hwp","/a.hwp"))
);

given(postService.createPost(any(PostCreateRequest.class), eq(userId))).willReturn(postId);

mockMvc.perform(post("/api/posts")
.param("userId", userId.toString())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andExpect(content().string(postId.toString()));

verify(postService).createPost(any(PostCreateRequest.class), eq(userId));
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

보안 설정 누락으로 모든 요청이 401로 실패합니다.

@WebMvcTest는 기본적으로 Spring Security를 활성화합니다. 그런데 이 테스트 클래스에는 우리 프로젝트의 SecurityConfig나 그 안에서 필요한 JwtParser, AuditorAware<String> 등의 빈이 전혀 로드되지 않으므로, 모든 요청이 인증 없이 실행되어 401 응답만 돌려주게 됩니다. 지금 상태로는 테스트 실행 시 전부 실패합니다. @Import(SecurityConfig.class)로 실제 보안 설정을 포함시키고 필요한 빈을 @MockBean으로 주입하거나, 테스트 목적이라면 @AutoConfigureMockMvc(addFilters = false)/@WithMockUser 등으로 보안 필터를 비활성화해 주세요.

🤖 Prompt for AI Agents
backend/src/test/java/org/sejongisc/backend/board/controller/PostControllerTest.java
lines 25-61: the WebMvcTest is failing with 401 because Spring Security is
active but SecurityConfig and required security beans (e.g., JwtParser,
AuditorAware<String>) are not loaded; fix by either importing your real security
config and mocking required beans (add @Import(SecurityConfig.class) on the test
and add @MockBean for JwtParser, AuditorAware<String>, etc.), or disable
security for this slice test by using @AutoConfigureMockMvc(addFilters = false)
or annotating the test method/class with @WithMockUser so requests are
authenticated during the test.

@discipline24 discipline24 reopened this Nov 2, 2025
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: 5

♻️ Duplicate comments (2)
backend/src/main/java/org/sejongisc/backend/board/controller/PostController.java (1)

110-116: postId PathVariable 미사용으로 댓글 생성 실패
경로에 /api/post/{postId}/comment를 두었지만 메소드 파라미터에 @PathVariable UUID postId가 없고, 서비스에도 경로 값을 전달하지 않습니다. PostServiceImpl.createCommentrequest.getPostId()null이면 CustomException(ErrorCode.POST_NOT_FOUND)를 던지므로, 일반적인 REST 호출(본문에 postId 미포함)에서는 항상 예외가 발생합니다. 컨트롤러 시그니처를 createComment(@PathVariable UUID postId, …)로 바꾸고, 서비스도 postService.createComment(postId, request, userId); 형태로 수정해야 합니다. 이와 함께 PostService/PostServiceImpl 시그니처, Comment 빌드 로직도 경로로 받은 postId를 사용하도록 맞춰주세요.

수정 안내 예시:

-  public ResponseEntity<Void> createComment(
-      @RequestBody CommentRequest request,
-      @AuthenticationPrincipal CustomUserDetails customUserDetails) {
+  public ResponseEntity<Void> createComment(
+      @PathVariable UUID postId,
+      @RequestBody CommentRequest request,
+      @AuthenticationPrincipal CustomUserDetails customUserDetails) {
     UUID userId = customUserDetails.getUserId();
-    postService.createComment(request, userId);
+    postService.createComment(postId, request, userId);
     return ResponseEntity.ok().build();
   }
backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java (1)

227-249: 댓글 생성 시 본문 postId 의존으로 500 발생
createCommentrequest.getPostId()를 필수로 요구해 본문에 postId를 넣지 않으면 POST_NOT_FOUND 예외가 발생합니다. 컨트롤러가 경로 변수 /api/post/{postId}/comment를 제공하므로 서비스 시그니처를 createComment(UUID postId, CommentRequest request, UUID userId)로 바꾸고, 아래처럼 경로로 받은 값을 검증·사용하도록 수정해주세요. Comment.builder()에도 동일한 값을 전달하고, CommentRequest에서 postId 필드는 optional 로 취급하거나 제거해야 합니다.

수정 예시:

-  public void createComment(CommentRequest request, UUID userId) {
-    Post post = postRepository.findById(request.getPostId())
+  public void createComment(UUID postId, CommentRequest request, UUID userId) {
+    if (postId == null) throw new CustomException(ErrorCode.POST_NOT_FOUND);
+    Post post = postRepository.findById(postId)
         .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));

     User user = userRepository.findById(userId)
         .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

     Comment comment = Comment.builder()
-        .postId(request.getPostId())
+        .postId(postId)
         .user(user)
         .content(request.getContent())
         .build();
🧹 Nitpick comments (6)
backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java (3)

40-70: 커스텀 예외 사용 권장

파일 저장 실패 시 범용 RuntimeException 대신 커스텀 예외를 사용하는 것이 좋습니다. 이렇게 하면 상위 계층에서 예외를 더 명확하게 처리할 수 있습니다.

ErrorCode에 파일 관련 에러 코드를 추가하고 CustomException을 사용하세요:

// ErrorCode.java에 추가
FILE_EMPTY(HttpStatus.BAD_REQUEST, "빈 파일은 저장할 수 없습니다."),
FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "파일 저장에 실패했습니다."),
FILE_PATH_INVALID(HttpStatus.BAD_REQUEST, "유효하지 않은 파일 경로입니다.");

그리고 store() 메서드에서:

   public String store(MultipartFile file) {
     if (file.isEmpty()) {
-      throw new RuntimeException("빈 파일은 저장할 수 없습니다.");
+      throw new CustomException(ErrorCode.FILE_EMPTY);
     }
     
     ...
     
       if (!destinationFile.getParent().equals(this.rootLocation)) {
-        throw new RuntimeException("현재 디렉토리 밖에 저장할 수 없습니다.");
+        throw new CustomException(ErrorCode.FILE_PATH_INVALID);
       }
       
     ...
     
     } catch (IOException e) {
-      throw new RuntimeException("파일 저장 실패: " + originalFilename, e);
+      throw new CustomException(ErrorCode.FILE_UPLOAD_FAILED);
     }
   }

52-58: 경로 보안 검증 개선 제안

현재 getParent().equals() 검증은 작동하지만, startsWith()를 사용하면 더 명확하고 안전합니다.

다음과 같이 개선할 수 있습니다:

-      // 상위 디렉토리로 벗어나려는지 보안 체크
-      if (!destinationFile.getParent().equals(this.rootLocation)) {
-        throw new RuntimeException("현재 디렉토리 밖에 저장할 수 없습니다.");
+      // 경로 순회 공격 방지
+      if (!destinationFile.startsWith(this.rootLocation)) {
+        throw new CustomException(ErrorCode.FILE_PATH_INVALID);
       }

76-83: 파일 삭제 예외 처리 개선

삭제 메서드에서도 커스텀 예외를 사용하는 것이 좋습니다.

   public void delete(String filename) {
     try {
       Path file = this.rootLocation.resolve(filename).normalize();
       Files.deleteIfExists(file);
     } catch (IOException e) {
-      throw new RuntimeException("파일 삭제 실패: " + filename, e);
+      throw new CustomException(ErrorCode.FILE_DELETE_FAILED);
     }
   }

ErrorCode에 추가:

FILE_DELETE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "파일 삭제에 실패했습니다.")
backend/src/main/java/org/sejongisc/backend/board/repository/PostAttachmentRepository.java (1)

13-13: 삭제 메서드 성능 개선 권장

deleteAllByPostId는 현재 Spring Data JPA의 네이밍 컨벌션을 따르고 있어 작동하지만, 내부적으로 SELECT 후 DELETE를 수행하여 비효율적입니다.

다음 diff를 적용하여 성능을 개선하세요:

+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.transaction.annotation.Transactional;
+
 public interface PostAttachmentRepository extends JpaRepository<PostAttachment, UUID> {
 
   List<PostAttachment> findAllByPostId(UUID postId);
 
+  @Modifying
+  @Transactional
+  @Query("DELETE FROM PostAttachment pa WHERE pa.postId = :postId")
   void deleteAllByPostId(UUID postId);
 }

이렇게 하면 한 번의 DELETE 쿼리로 처리되어 성능이 향상됩니다.

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

22-32: 첨부파일과 게시글 간 외래키 연관을 명시해주세요
postId만 보관하면 잘못된 게시글 ID로도 레코드가 생성돼 데이터 무결성이 깨질 수 있습니다. @ManyToOne + @JoinColumn(FK) 또는 유니크 제약으로 참조 무결성을 확보해 주세요.

-  @Column(nullable = false)
-  private UUID postId;
+  @ManyToOne(fetch = FetchType.LAZY, optional = false)
+  @JoinColumn(name = "post_id", nullable = false,
+      foreignKey = @ForeignKey(name = "fk_post_attachment_post"))
+  private Post post;
backend/src/main/java/org/sejongisc/backend/board/dto/CommentRequest.java (1)

21-22: 검증 메시지 일관성을 위해 메시지 추가 권장

postId 필드의 @NotNull 어노테이션에 메시지가 누락되어 있습니다. content 필드(Line 24)와 마찬가지로 사용자 친화적인 검증 메시지를 추가하는 것이 좋습니다.

다음 diff를 적용하여 일관성을 개선하세요:

-  @NotNull
+  @NotNull(message = "게시글 ID는 필수 항목입니다.")
   private UUID postId;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

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

📒 Files selected for processing (26)
  • 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/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/board/service/FileUploadServiceTest.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 (2)
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostRepository.java
  • backend/src/main/java/org/sejongisc/backend/board/repository/PostBookmarkRepository.java
🧰 Additional context used
🧬 Code graph analysis (13)
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/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/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/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/Comment.java (5)
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/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/main/java/org/sejongisc/backend/board/dto/PostResponse.java (4)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)
  • Getter (10-26)
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 (16-37)
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 (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 (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/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/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-57)
backend/src/main/java/org/sejongisc/backend/board/entity/PostAttachment.java (1)
  • Entity (10-33)
backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java (1)
  • Entity (10-27)
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/dto/CommentRequest.java (4)
backend/src/main/java/org/sejongisc/backend/board/dto/PostAttachmentResponse.java (1)
  • Getter (10-26)
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/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)
🔇 Additional comments (17)
backend/src/main/java/org/sejongisc/backend/common/config/PrimaryDataSourceConfig.java (1)

66-66: 변경사항 승인

게시판 엔티티 패키지가 JPA 엔티티 스캔 대상에 올바르게 추가되었습니다. 다른 모듈과 일관된 패턴을 따르고 있습니다.

backend/src/test/java/org/sejongisc/backend/board/service/FileUploadServiceTest.java (2)

35-54: 테스트 커버리지 우수

파일 저장 성공 케이스에 대한 테스트가 잘 작성되었습니다. 파일명 형식, 파일 존재 여부, 내용 검증을 모두 포함하고 있습니다.


71-97: 삭제 기능 테스트 잘 작성됨

파일 삭제 성공 케이스와 존재하지 않는 파일 삭제 시나리오를 모두 커버하고 있어 좋습니다.

backend/src/main/java/org/sejongisc/backend/common/exception/ErrorCode.java (1)

75-85: 에러 코드 정의 적절

게시판 기능을 위한 새로운 에러 코드들이 적절하게 정의되었습니다. HTTP 상태 코드와 메시지가 각 상황에 맞게 설정되어 있습니다.

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

3-3: Hibernate 직렬화 이슈 해결

@JsonIgnoreProperties 애노테이션 추가가 적절합니다. Hibernate의 lazy loading 프록시로 인한 JSON 직렬화 문제를 방지하는 표준 패턴입니다.

Also applies to: 20-20

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

3-7: 게시판 타입 enum 정의 적절

게시판 타입이 명확하게 정의되었습니다. 주석도 이해하기 쉽게 작성되었습니다.

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

19-25: LGTM!

정적 팩토리 메서드가 엔티티를 DTO로 적절하게 매핑하고 있으며, 빌더 패턴을 통해 깔끔하게 구현되어 있습니다.

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

27-35: LGTM!

Comment 엔티티에서 CommentResponse로의 매핑이 올바르게 구현되어 있으며, 모든 필수 필드가 포함되어 있습니다.

backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java (4)

66-96: LGTM!

파일 첨부를 포함한 게시글 저장 테스트가 잘 구현되어 있습니다. Mock 설정과 검증이 적절합니다.


98-122: LGTM!

게시글 수정 시 기존 첨부 파일 삭제 및 신규 파일 저장 로직이 올바르게 테스트되고 있습니다.


137-159: LGTM!

게시글 삭제 시 연관된 모든 데이터(첨부파일, 댓글, 좋아요, 북마크)가 함께 삭제되는지 검증하고 있어 데이터 무결성을 보장합니다.


231-261: LGTM!

댓글 삭제 권한 검증이 잘 구현되어 있습니다. 작성자와 관리자 모두 테스트하고 있으며, 댓글 수 감소도 함께 검증합니다.

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

24-36: LGTM!

필수 필드에 대한 검증 어노테이션과 메시지가 일관되게 잘 적용되어 있습니다. files 필드가 선택적으로 처리되는 것도 적절합니다.

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

26-27: LGTM!

User 관계에 FetchType.LAZY를 사용하여 N+1 문제를 방지하고 있습니다. 좋은 성능 최적화입니다.


23-24: 설계 확인: postId를 UUID로 저장

postId@ManyToOne 관계가 아닌 일반 UUID 컬럼으로 저장하고 있습니다. 이는 순환 참조를 방지하기 위한 의도적인 설계로 보이며, 대부분의 경우 적절한 선택입니다. 다만 Post 엔티티의 필드에 직접 접근이 필요한 경우에는 별도로 조회해야 한다는 점을 인지하세요.

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

13-20: LGTM!

Spring Data JPA 명명 규칙을 따르는 적절한 쿼리 메서드들입니다. 특히 deleteAllByPostId@Transactional 어노테이션을 추가한 것이 대량 삭제 작업에 적합합니다.

backend/src/main/java/org/sejongisc/backend/board/service/PostService.java (1)

13-44: LGTM!

서비스 인터페이스가 명확하고 잘 정의되어 있습니다. 메서드 시그니처가 단일 책임 원칙을 따르고 있으며, 반환 타입도 적절합니다.


private UUID postId;
private BoardType boardType;
private User user;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

사용자 엔티티 직접 노출로 인한 보안/직렬화 위험 차단 필요
PostResponseUser 엔티티를 그대로 반환하면 민감 정보가 외부로 노출되고, LAZY 로딩 환경에서 직렬화 시 LazyInitializationException이 빈번히 발생합니다. API 전용 사용자 DTO 또는 최소한의 식별 정보만 노출하도록 구조를 바꿔주세요.

-  private User user;
+  private UUID authorId;
+  private String authorNickname;
+  // 필요 시 별도 UserResponse DTO 도입 고려
🤖 Prompt for AI Agents
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java around
line 29: 현재 PostResponse가 User 엔티티를 직접 보유하고 있어 민감정보가 노출되고 LAZY 로딩 시 직렬화 예외가 발생할
수 있으므로 User 엔티티를 제거하고 API 전용 최소 정보 DTO(예: UserSummaryDto 또는 UserIdNameDto)를 필드로
사용하도록 바꿉니다. 구체적으로 PostResponse에서 User 타입 필드를 삭제하고 id, username 등 필요한 식별/표시 필드만
담는 새로운 DTO 클래스를 만들거나 기존 DTO를 사용하며, 엔티티 -> DTO 매핑은 서비스 계층에서 수행해서 필요한 필드만 복사(또는
JPA 프로젝션/쿼리로 선택 조회)하도록 변경하고, 컨트롤러/매퍼 호출부도 이 DTO로 반환되게 수정하세요.

Comment on lines +10 to +26
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PostBookmark extends BasePostgresEntity {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID postBookmarkId;

@Column(nullable = false)
private UUID postId;

@Column(nullable = false)
private UUID userId;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

중복 북마크를 막기 위한 유니크 제약이 필요합니다
postIduserId에 대한 유니크 제약이 없어 동시 요청 시 중복 북마크가 생성되고 집계 수치가 틀어질 수 있습니다. DB 레벨 유니크 인덱스로 무결성을 보장해주세요.

-@Entity
+@Entity
+@Table(
+    name = "post_bookmark",
+    uniqueConstraints = @UniqueConstraint(
+        name = "uk_post_bookmark_post_user",
+        columnNames = {"post_id", "user_id"}
+    )
+)
 public class PostBookmark extends BasePostgresEntity {
🤖 Prompt for AI Agents
In backend/src/main/java/org/sejongisc/backend/board/entity/PostBookmark.java
around lines 10 to 26, there is no DB-level uniqueness constraint on the
combination of postId and userId which allows duplicate bookmarks under
concurrent requests; add a unique constraint at the entity/table level (e.g.,
annotate the entity with a @Table(uniqueConstraints =
@UniqueConstraint(columnNames = {"post_id","user_id"})) or equivalent using the
actual column names) and ensure the column names match the DB columns, and also
add a corresponding schema migration (SQL/ Flyway/Liquibase) to create a unique
index on (post_id, user_id) so the database enforces uniqueness for existing and
new rows.

Comment on lines +5 to +6
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

사용하지 않는 import 제거 필요

CreationTimestampLocalDateTime import가 코드에서 사용되지 않습니다. BasePostgresEntity가 이미 타임스탬프 필드를 제공하는 것으로 보입니다.

다음 diff를 적용하여 사용하지 않는 import를 제거하세요:

-import org.hibernate.annotations.CreationTimestamp;
-import java.time.LocalDateTime;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
🤖 Prompt for AI Agents
In backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java around
lines 5-6, remove the unused imports
"org.hibernate.annotations.CreationTimestamp" and "java.time.LocalDateTime"
since they are not referenced (BasePostgresEntity already provides timestamps);
delete those import lines and run a build/static analysis to ensure no remaining
unused imports or compilation issues.

Comment on lines +10 to +27
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PostLike extends BasePostgresEntity {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID postLikeId;

@Column(nullable = false)
private UUID postId;

@Column(nullable = false)
private UUID userId;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

데이터 무결성: 유니크 제약조건 추가 필요

동일한 사용자가 같은 게시물에 여러 번 좋아요를 등록할 수 있는 문제가 있습니다. (postId, userId) 조합에 대한 유니크 제약조건이 필요합니다.

다음 diff를 적용하여 유니크 제약조건을 추가하세요:

 @Entity
+@Table(uniqueConstraints = {
+    @UniqueConstraint(columnNames = {"postId", "userId"})
+})
 @Getter
 @Setter
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
 public class PostLike extends BasePostgresEntity {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PostLike extends BasePostgresEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID postLikeId;
@Column(nullable = false)
private UUID postId;
@Column(nullable = false)
private UUID userId;
}
@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(columnNames = {"postId", "userId"})
})
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PostLike extends BasePostgresEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID postLikeId;
@Column(nullable = false)
private UUID postId;
@Column(nullable = false)
private UUID userId;
}
🤖 Prompt for AI Agents
In backend/src/main/java/org/sejongisc/backend/board/entity/PostLike.java around
lines 10 to 27, the entity lacks a uniqueness constraint on the (postId, userId)
pair which allows duplicate likes; add a class-level @Table annotation with a
UniqueConstraint on columnNames = {"postId","userId"} (e.g.
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"postId",
"userId"}))) so JPA/Ddl generation enforces uniqueness, and also add a
corresponding DB migration (or alter table) to create the unique
index/constraint in the database if schema changes are managed outside JPA.

Comment on lines +19 to +20
// @Value("${file.upload-dir}")
private final static String UPLOAD_DIRS = "C:/uploads/";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

하드코딩된 경로 제거 필요

업로드 디렉토리가 Windows 전용 경로로 하드코딩되어 있어 다른 운영 체제에서 작동하지 않습니다. 프로덕션 환경(Linux)에서 배포 시 문제가 발생할 수 있습니다.

다음 diff를 적용하여 설정 파일에서 경로를 주입받도록 수정하세요:

-//  @Value("${file.upload-dir}")
-  private final static String UPLOAD_DIRS = "C:/uploads/";
+  @Value("${file.upload-dir:./uploads}")
+  private String uploadDir;
 
   private Path rootLocation;

그리고 init() 메서드를 수정하세요:

   @PostConstruct
   public void init() {
     try {
-      this.rootLocation = Paths.get(UPLOAD_DIRS).toAbsolutePath().normalize();
+      this.rootLocation = Paths.get(uploadDir).toAbsolutePath().normalize();
       Files.createDirectories(this.rootLocation);
     } catch (IOException e) {

application.properties 또는 application.yml에 다음을 추가하세요:

file.upload-dir=/var/uploads
🤖 Prompt for AI Agents
In
backend/src/main/java/org/sejongisc/backend/board/service/FileUploadService.java
around lines 19-20, the upload directory is hardcoded to a Windows path which
must be removed; replace the static constant with a configurable field injected
from application properties (e.g. use @Value("${file.upload-dir}") private
String uploadDir), update any constructor/field declarations accordingly, and
modify the init() method to use the injected uploadDir (create the directory if
it doesn't exist using OS-independent APIs like java.nio.file.Paths/Files and
handle exceptions); also add file.upload-dir=/var/uploads to
application.properties/application.yml as instructed.

@nayoung04 nayoung04 closed this Nov 7, 2025
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