Skip to content

Conversation

@uni-j-uni
Copy link
Contributor

@uni-j-uni uni-j-uni commented Dec 3, 2025

✨ 새로운 기능

  • 커뮤니티 게시글 CRUD 기능 구현
  • 댓글 CRUD 기능 구현
  • 게시글 이미지 업로드(S3), 카테고리 저장 구조 정리
  • 게시글 카테고리별 조회 + 무한스크롤 지원

🛠 개발 상세

  • Post, Comment 엔티티 및 연관관계 설정
  • 게시글 작성/수정 시 이미지 업로드 및 교체 처리
  • 댓글 삭제 시 Post.commentList 연관관계 정리
  • Post/Comment DTO 및 Mapper 구성
  • 기본 예외 코드(Post/Comment) 추가

🧪 테스트 방법

  • 게시글 작성/조회/수정/삭제 정상 동작 확인
  • 댓글 작성/조회/수정/삭제 동작 확인
  • 이미지 업로드 및 재업로드 정상 여부
  • 카테고리별 무한스크롤 정상 동작 확인

🧩 추가 고려사항

  • 버전 관리 고려 필요 여부
  • 배포 시 별도 주의사항

🔗 관련 문서 / 이슈

Summary by CodeRabbit

  • 새로운 기능

    • 댓글 기능 추가: 게시글에 댓글 작성·조회·수정·삭제 지원
    • 게시글 기능 강화: 이미지 첨부·교체·삭제, 카테고리별 조회, 상세 조회 및 관리자 목록 제공
    • 페이징 개선: 무한 스크롤(InfiniteResponse) 및 표준 페이지 응답 포맷 추가
  • 개선

    • 테스트 로그인 흐름 개선 및 비밀번호 인코딩 처리 보완

✏️ Tip: You can customize this high-level summary in your review settings.

@uni-j-uni uni-j-uni self-assigned this Dec 3, 2025
@uni-j-uni uni-j-uni added the ✨ feature 새로운 기능 요청 label Dec 3, 2025
@uni-j-uni uni-j-uni linked an issue Dec 3, 2025 that may be closed by this pull request
2 tasks
@uni-j-uni uni-j-uni added the 🟠 priority: medium 중간 우선순위 label Dec 3, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2025

Walkthrough

Comment 및 Post 도메인(컨트롤러, 서비스, 리포지토리, 엔티티, DTO, 매퍼)과 전역 페이징/무한스크롤 응답·매퍼가 추가되었고, S3 경로 분리(PathName·S3Config) 및 S3ServiceImpl 매핑이 변경되었으며 AuthServiceImpl에 PasswordEncoder 주입 및 테스트 사용자 자동 생성 로직이 추가되었습니다.

Changes

Cohort / File(s) 변경 사항
인증
src/main/java/com/sku/refit/domain/auth/service/AuthServiceImpl.java
PasswordEncoder 의존성 추가; testLogin()에서 테스트 사용자 미존재 시 인코딩된 비밀번호로 사용자 생성 및 ROLE_USER 할당
Comment 도메인 — 컨트롤러
src/main/java/com/sku/refit/domain/comment/controller/CommentController.java, src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java
댓글 CRUD REST 인터페이스 및 구현체 추가 (POST /api/comments/new, GET /api/comments, PUT /api/comments/{id}, DELETE /api/comments/{id})
Comment 도메인 — 서비스 / 구현
src/main/java/com/sku/refit/domain/comment/service/CommentService.java, src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java
CommentService 인터페이스 및 CommentServiceImpl 추가: create/getAll/update/delete 구현, Post 존재 검증, 작성자 권한 검사, 트랜잭션 경계 설정
Comment 도메인 — 엔티티 / 리포지토리
src/main/java/com/sku/refit/domain/comment/entity/Comment.java, src/main/java/com/sku/refit/domain/comment/repository/CommentRepository.java
Comment JPA 엔티티 추가 (id, content, user, post, update()), CommentRepository에 findAllByPostIdOrderByCreatedAtAsc 메서드 추가
Comment 도메인 — DTO / 매퍼 / 예외
src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java, src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java, src/main/java/com/sku/refit/domain/comment/mapper/CommentMapper.java, src/main/java/com/sku/refit/domain/comment/exception/CommentErrorCode.java
CommentRequest/CommentDetailResponse DTO 추가, CommentMapper 추가(엔티티↔DTO), CommentErrorCode 열거형에 COMMENT_NOT_FOUND 추가
Post 도메인 — 컨트롤러
src/main/java/com/sku/refit/domain/post/controller/PostController.java, src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java
게시물 CRUD 및 목록/카테고리(무한스크롤)용 REST 인터페이스 및 구현체 추가 (multipart 지원 포함)
Post 도메인 — 서비스 / 구현
src/main/java/com/sku/refit/domain/post/service/PostService.java, src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java
PostService 인터페이스 및 PostServiceImpl 추가: 생성/조회/카테고리 페이징(무한 스크롤)/수정/삭제 구현, S3 이미지 업로드·삭제 통합, 예외 처리
Post 도메인 — 엔티티 / 리포지토리
src/main/java/com/sku/refit/domain/post/entity/Post.java, src/main/java/com/sku/refit/domain/post/repository/PostRepository.java
Post 엔티티 추가(카테고리·이미지 ElementCollection, user, comments 관계, update()), PostRepository에 카테고리 기반 페이징 쿼리 추가
Post 도메인 — DTO / 매퍼 / 예외
src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java, src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java, src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java, src/main/java/com/sku/refit/domain/post/exception/PostErrorCode.java
PostRequest/PostDetailResponse DTO 추가, PostMapper 추가, PostErrorCode 열거형에 여러 오류 코드 추가
전역 페이징/무한스크롤 지원
src/main/java/com/sku/refit/global/page/response/PageResponse.java, src/main/java/com/sku/refit/global/page/response/InfiniteResponse.java, src/main/java/com/sku/refit/global/page/mapper/PageMapper.java, src/main/java/com/sku/refit/global/page/mapper/InfiniteMapper.java, src/main/java/com/sku/refit/global/page/exception/PageErrorStatus.java
PageResponse/InfiniteResponse DTO 및 PageMapper/InfiniteMapper 컴포넌트 추가, PageErrorStatus 예외 코드 추가
S3 설정 및 경로 관리
src/main/java/com/sku/refit/global/config/S3Config.java, src/main/java/com/sku/refit/global/s3/entity/PathName.java, src/main/java/com/sku/refit/global/s3/service/S3ServiceImpl.java
S3Config에서 postPath, clothPath 추가(기존 folder2Path 제거), PathName에서 FOLDER 제거 및 POST, CLOTH 추가, S3ServiceImpl#getPrefixPOST/CLOTH 매핑 반영
리소스 / 서브모듈 참조
src/main/resources
서브모듈 커밋 참조 업데이트

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PostController
    participant PostService
    participant S3Service
    participant PostMapper
    participant PostRepository
    participant Database

    rect rgb(200,220,255)
    Note over Client,Database: 게시물 생성 흐름 (이미지 포함)
    Client->>PostController: POST /api/posts/new + PostRequest + images
    PostController->>PostService: createPost(request, images)
    PostService->>S3Service: uploadImages(images, PathName.POST)
    S3Service-->>PostService: imageUrlList
    PostService->>PostMapper: toPost(request, imageUrlList, user)
    PostMapper-->>PostService: Post entity
    PostService->>PostRepository: save(post)
    PostRepository->>Database: INSERT post
    Database-->>PostRepository: saved post
    PostRepository-->>PostService: saved post
    PostService->>PostMapper: toDetailResponse(post, user)
    PostMapper-->>PostService: PostDetailResponse
    PostService-->>PostController: PostDetailResponse
    PostController-->>Client: 200 OK (BaseResponse<PostDetailResponse>)
    end
Loading
sequenceDiagram
    participant Client
    participant PostController
    participant PostService
    participant PostRepository
    participant Database
    participant InfiniteMapper
    participant PostMapper

    rect rgb(255,240,200)
    Note over Client,Database: 카테고리 기반 무한 스크롤 조회
    Client->>PostController: GET /api/posts?category=...&lastPostId=...&size=...
    PostController->>PostService: getPostsByCategory(category, lastPostId, size)
    PostService->>PostRepository: findByCategoryListContainingAndIdLessThan(category, lastPostId, pageable)
    PostRepository->>Database: SELECT ... (페이징 쿼리)
    Database-->>PostRepository: Page<Post>
    PostRepository-->>PostService: Page<Post>
    loop 각 게시물
      PostService->>PostMapper: toDetailResponse(post, user)
      PostMapper-->>PostService: PostDetailResponse
    end
    PostService->>InfiniteMapper: toInfiniteResponse(contents, lastCursor, hasNext, size)
    InfiniteMapper-->>PostService: InfiniteResponse
    PostService-->>PostController: InfiniteResponse
    PostController-->>Client: 200 OK (BaseResponse<InfiniteResponse>)
    end
Loading
sequenceDiagram
    participant Client
    participant CommentController
    participant CommentService
    participant PostRepository
    participant CommentMapper
    participant CommentRepository
    participant Database

    rect rgb(220,255,220)
    Note over Client,Database: 댓글 생성 흐름
    Client->>CommentController: POST /api/comments/new?postId=... + CommentRequest
    CommentController->>CommentService: createComment(request, postId)
    CommentService->>PostRepository: findById(postId)
    PostRepository->>Database: SELECT post
    Database-->>PostRepository: Post (or null)
    PostRepository-->>CommentService: Post
    CommentService->>CommentMapper: toComment(request, user, post)
    CommentMapper-->>CommentService: Comment entity
    CommentService->>CommentRepository: save(comment)
    CommentRepository->>Database: INSERT comment
    Database-->>CommentRepository: saved comment
    CommentRepository-->>CommentService: saved comment
    CommentService->>CommentMapper: toDetailResponse(comment, user)
    CommentMapper-->>CommentService: CommentDetailResponse
    CommentService-->>CommentController: CommentDetailResponse
    CommentController-->>Client: 200 OK (BaseResponse<CommentDetailResponse>)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • 주의할 파일/영역:
    • PostServiceImpl: S3 업로드/삭제, 에러 복구 및 트랜잭션 경계
    • Comment/Post 엔티티의 JPA 매핑(OneToMany, ElementCollection, fetch/cascade, orphanRemoval)
    • 무한 스크롤(cursor) 로직 및 페이지 경계 처리(페이징/hasNext 결정)
    • S3Config, PathName, S3ServiceImpl 간 매핑 일관성
    • AuthServiceImpl의 테스트 사용자 생성 로직에서 PasswordEncoder 사용 및 역할(Role) 설정

Poem

🐰 새 댓글, 새 글, 깡충 뛰어오르네,
스크롤 끝엔 또 다른 꿈이 흐르네.
이미지 길은 나누어 정리했지요,
테스트 유저도 살짝 채워놓았어요,
당근 한 조각으로 코드 축하 — 훌쩍! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive PR 제목이 '새로운 기능'을 나타내지만, 구체적인 변경 사항을 명확하게 설명하지 못함. 제목을 더 구체적으로 수정하여 커뮤니티 기능의 핵심을 명확히 표현하세요. 예: 'Feature: Community CRUD (Posts and Comments)' 또는 '커뮤니티 게시글/댓글 CRUD 기능 추가'
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 템플릿 구조를 모두 따르고 있으며, 새로운 기능, 개발 상세, 테스트 방법, 추가 고려사항, 관련 문서/이슈 등 필수 섹션이 모두 포함되어 있음.
✨ 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 feature/community

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f443c6e and 16720e3.

📒 Files selected for processing (2)
  • src/main/java/com/sku/refit/domain/post/controller/PostController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (2)
src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1)
  • RestController (23-60)
src/main/java/com/sku/refit/domain/auth/service/AuthServiceImpl.java (1)
  • Slf4j (28-134)
🔇 Additional comments (4)
src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (1)

23-75: 구현이 깔끔하고 일관성이 있습니다.

컨트롤러가 얇은 계층으로 잘 설계되어 있으며, 모든 메서드가 서비스 계층에 위임하고 BaseResponse.success()로 일관되게 응답을 래핑하고 있습니다. CommentController와도 동일한 패턴을 따라 코드베이스 전체의 일관성이 유지되고 있습니다.

src/main/java/com/sku/refit/domain/post/controller/PostController.java (3)

62-62: 기본 페이지 크기가 무한 스크롤에 적합한지 확인이 필요합니다.

Line 62의 기본 size 값이 3으로 설정되어 있는데, 일반적으로 무한 스크롤 페이지네이션에서는 10-20개가 표준입니다. 이것이 의도적인 설정인지(예: 테스트 목적 또는 특정 UX 요구사항) 확인해 주세요.


36-89: API 설계가 잘 되어 있습니다.

모든 엔드포인트가 명확한 문서화와 함께 RESTful 원칙을 잘 따르고 있으며, 파라미터 명명이 일관성 있게 유지되고 있습니다. Multipart 파일 업로드 처리도 적절하게 구성되어 있습니다.


51-53: The /admin endpoint is already protected by global security configuration.

The getAllPosts() endpoint security is enforced at the SecurityFilterChain level in SecurityConfig.java (lines 128-129). The regex pattern ".*/admin/.*" matches the full path /api/posts/admin and requires the ADMIN role via hasRole("ADMIN"). Explicit @PreAuthorize annotation is unnecessary when security is configured globally through HttpSecurity.authorizeHttpRequests().


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: 15

🧹 Nitpick comments (12)
src/main/java/com/sku/refit/domain/auth/service/AuthServiceImpl.java (1)

103-118: 테스트 사용자 자동 생성 로직 승인

테스트 로그인 시 사용자가 없으면 자동으로 생성하는 로직이 적절하게 구현되었습니다. PasswordEncoder를 사용하여 비밀번호를 안전하게 인코딩하고 있습니다.

한 가지 개선 제안: orElse(null) 후 null 체크 대신 OptionalifPresentOrElse 또는 orElseGet을 활용하면 더 간결해질 수 있습니다.

- User user = userRepository.findByUsername(testUsername).orElse(null);
-
- if (user == null) {
-   user =
-       User.builder()
+ User user = userRepository.findByUsername(testUsername).orElseGet(() -> {
+   User newUser = User.builder()
            .profileImageUrl(testUsername + ".png")
            .nickname("김다입")
            .username(testUsername)
            .password(passwordEncoder.encode(testPassword))
            .locationConsent(true)
            .role(Role.ROLE_USER)
            .build();
-
-   userRepository.save(user);
-   log.info("테스트 사용자 생성됨: {}", user.getUsername());
- }
+   userRepository.save(newUser);
+   log.info("테스트 사용자 생성됨: {}", newUser.getUsername());
+   return newUser;
+ });
src/main/java/com/sku/refit/domain/comment/entity/Comment.java (1)

41-47: EAGER 페치 타입으로 인한 성능 문제 가능성

userpost 관계 모두 FetchType.EAGER를 사용하고 있습니다. 관련 코드 스니펫에서 Post 엔티티도 commentList에 EAGER 페치를 사용하는 것을 확인했습니다.

이 구조는 다음과 같은 문제를 야기할 수 있습니다:

  • Comment 조회 시 항상 User와 Post를 함께 로드
  • 여러 Comment 조회 시 N+1 쿼리 문제 발생
  • 양방향 EAGER 관계로 인한 연쇄 로딩 (Comment → Post → commentList → 각 Comment의 User)

LAZY 페치로 변경하고 필요 시 @EntityGraph 또는 fetch join을 사용하는 것을 권장합니다:

- @ManyToOne(fetch = FetchType.EAGER)
+ @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "user_id", nullable = false)
  private User user;

- @ManyToOne(fetch = FetchType.EAGER)
+ @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "post_id", nullable = false)
  private Post post;

Post.javacommentListuser 관계도 동일하게 LAZY로 변경을 고려해 주세요.

src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1)

18-19: Swagger example 형식을 개선할 수 있습니다.

categoryListList<String> 타입인데 example이 "자유, 수선" 문자열로 표현되어 있습니다. API 문서의 명확성을 위해 JSON 배열 형식으로 표현하는 것이 좋습니다.

다음과 같이 수정할 수 있습니다:

-  @Schema(description = "카테고리 리스트", example = "자유, 수선")
+  @Schema(description = "카테고리 리스트", example = "[\"자유\", \"수선\"]")
   private List<String> categoryList;
src/main/java/com/sku/refit/domain/post/repository/PostRepository.java (1)

16-19: 대량 데이터에서 쿼리 성능을 모니터링하세요.

findByCategoryListContaining 메서드는 post_category 조인 테이블과 조인하여 검색합니다. 게시글이 많아지면 인덱싱 전략이 필요할 수 있습니다.

향후 성능 개선을 위한 고려사항:

  • post_category 테이블의 category 컬럼에 인덱스 추가
  • 쿼리 실행 계획 모니터링 및 slow query 로그 확인
  • 필요시 @Query로 최적화된 JPQL 작성 고려
src/main/java/com/sku/refit/domain/post/entity/Post.java (2)

61-63: User 관계에 EAGER fetch 전략을 재검토하세요.

User에 대해 FetchType.EAGER를 사용하면 Post를 조회할 때마다 User 정보가 자동으로 로드됩니다. 대부분의 경우 필요하지만, 모든 게시글 목록 조회 시 추가 쿼리가 발생할 수 있습니다.

필요에 따라 LAZY로 변경하고, 필요한 곳에서만 명시적으로 로드하는 것을 고려하세요:

-  @ManyToOne(fetch = FetchType.EAGER)
+  @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "user_id", nullable = false)
   private User user;

서비스 레이어에서 필요시 fetch join 사용:

@Query("SELECT p FROM Post p JOIN FETCH p.user WHERE p.id = :id")
Optional<Post> findByIdWithUser(@Param("id") Long id);

51-59: 빈 컬렉션에 대한 검증을 추가하세요.

categoryList@NotEmpty 검증이 PostRequest에 있지만, 엔티티 레벨에서는 빈 리스트가 저장될 수 있습니다. nullable=false는 컬럼이 null이 아님을 보장하지만, 빈 컬렉션은 허용합니다.

엔티티 레벨에서 검증을 추가하거나, 최소 하나의 카테고리가 있는지 확인하는 로직을 추가하세요:

   @ElementCollection
   @CollectionTable(name = "post_category", joinColumns = @JoinColumn(name = "post_id"))
-  @Column(nullable = false)
+  @Column(nullable = false)
+  @Size(min = 1, message = "최소 하나의 카테고리가 필요합니다")
   private List<String> categoryList;

또는 빌더에서 기본값을 설정하세요:

   @ElementCollection
   @CollectionTable(name = "post_category", joinColumns = @JoinColumn(name = "post_id"))
   @Column(nullable = false)
+  @Builder.Default
   private List<String> categoryList = new ArrayList<>();
src/main/java/com/sku/refit/domain/comment/service/CommentService.java (1)

22-28: 대량 댓글 처리를 위한 페이징을 고려하세요.

getAllCommentsByPostId 메서드는 페이징 없이 특정 게시글의 모든 댓글을 반환합니다. 일반적으로 게시글당 댓글 수는 제한적이지만, 인기 게시글의 경우 수백~수천 개의 댓글이 달릴 수 있습니다.

향후 확장성을 고려하여 페이징 지원 메서드를 추가하세요:

/**
 * 특정 게시글의 댓글을 페이징하여 조회
 *
 * @param postId 게시글 ID
 * @param pageable 페이징 정보
 * @return 페이징된 댓글 상세 정보
 */
PageResponse<CommentDetailResponse> getCommentsByPostId(Long postId, Pageable pageable);

현재 메서드는 유지하되, 댓글 수가 많을 때를 대비한 대안을 마련하세요.

src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (2)

45-63: createPost에서 이미지 업로드 실패 시 예외 처리 누락

createPost 메서드에서 이미지 업로드 중 예외가 발생하면, 이미 업로드된 이미지들이 S3에 남아 있게 됩니다. updatePost와 달리 try-catch 블록이 없어 일관성이 부족합니다.

  public PostDetailResponse createPost(PostRequest request, List<MultipartFile> images) {

    User user = userService.getCurrentUser();
    List<String> imageUrlList = new ArrayList<>();

    if (images != null && !images.isEmpty()) {
+     try {
        for (MultipartFile image : images) {
          String imageUrl = s3Service.uploadImage(PathName.POST, image).getImageUrl();
          imageUrlList.add(imageUrl);
        }
+     } catch (Exception e) {
+       // 업로드 실패 시 이미 업로드된 이미지 정리
+       for (String imageUrl : imageUrlList) {
+         s3Service.deleteFile(s3Service.extractKeyNameFromUrl(imageUrl));
+       }
+       throw new CustomException(PostErrorCode.IMAGE_UPLOAD_FAILED);
+     }
    }

    Post post = postMapper.toPost(request, imageUrlList, user);
    postRepository.save(post);

    return postMapper.toDetailResponse(post, user);
  }

65-73: 전체 게시글 조회 시 페이지네이션 고려

findAll()은 데이터가 많아지면 성능 문제가 발생할 수 있습니다. 카테고리별 조회처럼 페이지네이션 적용을 고려해 보세요.

src/main/java/com/sku/refit/domain/post/exception/PostErrorCode.java (1)

15-27: 에러 코드 정의 적절함, 권한 관련 에러 코드 추가 권장

일관된 패턴으로 잘 구현되어 있습니다. 다만, updatePostdeletePost에서 권한 검증 추가 시 사용할 권한 관련 에러 코드(예: POST_UNAUTHORIZED)를 추가하면 더 명확한 에러 처리가 가능합니다.

  POST_NOT_FOUND("POST001", "게시글이 존재하지 않습니다.", HttpStatus.NOT_FOUND),
  INVALID_CATEGORY("POST002", "유효하지 않은 카테고리입니다.", HttpStatus.BAD_REQUEST),
  IMAGE_UPLOAD_FAILED("POST003", "이미지 업로드 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
  IMAGE_DELETE_FAILED("POST004", "이미지 삭제 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
  POST_CREATE_FAILED("POST005", "게시글 생성 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
  POST_UPDATE_FAILED("POST006", "게시글 수정 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
- POST_DELETE_FAILED("POST007", "게시글 삭제 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
+ POST_DELETE_FAILED("POST007", "게시글 삭제 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
+ POST_UNAUTHORIZED("POST008", "해당 게시글에 대한 권한이 없습니다.", HttpStatus.FORBIDDEN);
src/main/java/com/sku/refit/domain/comment/controller/CommentController.java (1)

43-52: 경로 패턴 일관성 확인

@PutMapping("{id}")@DeleteMapping("{id}")는 동작하지만, "/{id}"로 명시적으로 슬래시를 포함하면 다른 엔드포인트와 일관성이 높아집니다. 기능상 문제는 없습니다.

- @PutMapping("{id}")
+ @PutMapping("/{id}")
  @Operation(summary = "특정 댓글 수정", description = "특정 댓글의 내용을 수정합니다.")
  ResponseEntity<BaseResponse<CommentDetailResponse>> updateComment(
      @Parameter(description = "댓글 식별자", example = "1") @PathVariable Long id,
      @RequestBody @Valid CommentRequest request);

- @DeleteMapping("{id}")
+ @DeleteMapping("/{id}")
  @Operation(summary = "특정 댓글 삭제", description = "특정 댓글을 삭제합니다.(Hard Delete)")
  ResponseEntity<BaseResponse<Void>> deleteComment(
      @Parameter(description = "댓글 식별자", example = "1") @PathVariable Long id);
src/main/java/com/sku/refit/domain/post/controller/PostController.java (1)

70-84: 파라미터 이름의 일관성을 개선하세요.

Line 77의 파라미터 이름이 image (단수)로 되어 있는데, Line 48의 createPost 메서드에서는 images (복수)를 사용하고 있습니다. 두 메서드 모두 List<MultipartFile>을 받으므로, 일관성을 위해 복수형(images)을 사용하는 것이 좋습니다.

다음과 같이 수정하는 것을 권장합니다:

       @Parameter(
               description = "수정할 이미지 리스트",
               content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
-          @RequestPart(value = "image", required = false)
-          List<MultipartFile> image,
+          @RequestPart(value = "images", required = false)
+          List<MultipartFile> images,

구현체인 PostControllerImpl.java와 서비스 계층에서도 동일하게 수정이 필요합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfd3629 and c2d3939.

📒 Files selected for processing (30)
  • src/main/java/com/sku/refit/domain/auth/service/AuthServiceImpl.java (3 hunks)
  • src/main/java/com/sku/refit/domain/comment/controller/CommentController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/entity/Comment.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/exception/CommentErrorCode.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/mapper/CommentMapper.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/repository/CommentRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/service/CommentService.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/controller/PostController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/entity/Post.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/exception/PostErrorCode.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/repository/PostRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/service/PostService.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (1 hunks)
  • src/main/java/com/sku/refit/global/config/S3Config.java (1 hunks)
  • src/main/java/com/sku/refit/global/page/exception/PageErrorStatus.java (1 hunks)
  • src/main/java/com/sku/refit/global/page/mapper/InfiniteMapper.java (1 hunks)
  • src/main/java/com/sku/refit/global/page/mapper/PageMapper.java (1 hunks)
  • src/main/java/com/sku/refit/global/page/response/InfiniteResponse.java (1 hunks)
  • src/main/java/com/sku/refit/global/page/response/PageResponse.java (1 hunks)
  • src/main/java/com/sku/refit/global/s3/entity/PathName.java (1 hunks)
  • src/main/java/com/sku/refit/global/s3/service/S3ServiceImpl.java (1 hunks)
  • src/main/resources (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
src/main/java/com/sku/refit/domain/comment/mapper/CommentMapper.java (1)
src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (1)
  • Component (16-42)
src/main/java/com/sku/refit/global/page/mapper/InfiniteMapper.java (1)
src/main/java/com/sku/refit/global/page/mapper/PageMapper.java (1)
  • Component (11-24)
src/main/java/com/sku/refit/global/page/mapper/PageMapper.java (1)
src/main/java/com/sku/refit/global/page/mapper/InfiniteMapper.java (1)
  • Component (12-24)
src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (3)
src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (1)
  • Getter (14-24)
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1)
  • Getter (13-38)
src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (1)
src/main/java/com/sku/refit/domain/comment/mapper/CommentMapper.java (1)
  • Component (14-30)
src/main/java/com/sku/refit/domain/post/entity/Post.java (5)
src/main/java/com/sku/refit/domain/comment/entity/Comment.java (1)
  • Entity (26-52)
src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (1)
  • Getter (14-24)
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (1)
  • Getter (17-35)
src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1)
  • Getter (13-38)
src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (2)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)
  • Service (27-97)
src/main/java/com/sku/refit/domain/auth/service/AuthServiceImpl.java (1)
  • Slf4j (28-134)
src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (2)
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (1)
  • Getter (17-35)
src/main/java/com/sku/refit/global/page/response/InfiniteResponse.java (1)
src/main/java/com/sku/refit/global/page/response/PageResponse.java (1)
  • Getter (12-34)
src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (2)
src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1)
  • RestController (23-60)
src/main/java/com/sku/refit/domain/auth/service/AuthServiceImpl.java (1)
  • Slf4j (28-134)
src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1)
src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (1)
  • RestController (23-75)
src/main/java/com/sku/refit/global/page/response/PageResponse.java (1)
src/main/java/com/sku/refit/global/page/response/InfiniteResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (3)
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (1)
  • Getter (17-35)
src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1)
  • Getter (13-38)
src/main/java/com/sku/refit/domain/comment/entity/Comment.java (5)
src/main/java/com/sku/refit/domain/post/entity/Post.java (1)
  • Entity (33-78)
src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (1)
  • Getter (14-24)
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (1)
  • Getter (17-35)
src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1)
  • Getter (13-38)
🔇 Additional comments (26)
src/main/resources (1)

1-1: 서브모듈 커밋 참조 업데이트 검증 필요

src/main/resources 서브모듈의 커밋이 업데이트되었습니다 (f6f4348 → 547102). 이 변경이 PR의 커뮤니티 기능 구현과 어떻게 연관되는지, 그리고 새 커밋에서 어떤 변경사항이 포함되었는지 확인이 필요합니다.

구체적으로 다음을 검증해 주세요:

  • 새 커밋에서 어떤 리소스/구성이 변경되었는지
  • 이 업데이트가 포스트/댓글 기능(S3 경로 설정, 카테고리 구조 등)과 어떤 관계가 있는지
  • 빌드 또는 배포 시 필요한 추가 단계가 있는지
src/main/java/com/sku/refit/global/page/exception/PageErrorStatus.java (1)

13-23: LGTM!

에러 코드 enum이 프로젝트의 다른 에러 코드들(PostErrorCode, CommentErrorCode)과 일관된 패턴으로 잘 구현되었습니다. 코드, 메시지, HTTP 상태 매핑이 적절합니다.

src/main/java/com/sku/refit/domain/comment/exception/CommentErrorCode.java (1)

15-17: LGTM!

BaseErrorCode 인터페이스를 구현한 에러 코드 enum이 프로젝트 컨벤션에 맞게 잘 정의되었습니다.

향후 댓글 권한 검증(작성자가 아닌 사용자의 수정/삭제 시도 등)을 위한 UNAUTHORIZED_COMMENT_ACCESS 같은 에러 코드 추가를 고려해 볼 수 있습니다.

src/main/java/com/sku/refit/global/s3/entity/PathName.java (1)

11-14: S3 경로 상수가 올바르게 구현되었습니다

POSTCLOTH 상수가 PathName 열거형에 적절히 추가되었으며, 각각 @Schema 어노테이션으로 잘 문서화되어 있습니다. S3ServiceImpl.getPrefix() 메서드에서 두 상수 모두 정확하게 처리되고 있으며(case POST -> s3Config.getPostPath(), case CLOTH -> s3Config.getClothPath()), PostServiceImpl에서도 게시글 이미지 업로드 시 PathName.POST가 올바르게 사용되고 있습니다.

src/main/java/com/sku/refit/global/s3/service/S3ServiceImpl.java (1)

195-196: PathName 매핑이 올바르게 업데이트되었습니다.

새로운 POST 및 CLOTH PathName 케이스가 S3Config의 해당 경로 필드와 올바르게 매핑되었습니다.

src/main/java/com/sku/refit/global/page/mapper/PageMapper.java (1)

14-23: 페이지 매핑 로직이 올바르게 구현되었습니다.

Spring Data의 0-based 페이지 번호를 1-based로 변환하는 로직이 적절하며, InfiniteMapper와 일관된 패턴을 따르고 있습니다.

src/main/java/com/sku/refit/domain/comment/mapper/CommentMapper.java (1)

17-29: 매퍼 구현이 올바르며 PostMapper와 일관된 패턴을 따릅니다.

  • toComment 메서드는 요청 데이터로부터 Comment 엔티티를 올바르게 생성합니다.
  • toDetailResponse의 isWriter 로직이 사용자 ID 비교를 통해 적절하게 구현되었습니다.
src/main/java/com/sku/refit/global/config/S3Config.java (1)

42-46: S3 경로 구성이 올바르게 추가되었습니다.

새로운 postPathclothPath 필드가 적절하게 추가되었으며, 기존 profileImagePath 패턴과 일관성을 유지합니다.

src/main/java/com/sku/refit/global/page/mapper/InfiniteMapper.java (1)

15-23: 무한 스크롤 응답 매퍼가 올바르게 구현되었습니다.

InfiniteResponse 생성 로직이 명확하며, PageMapper와 일관된 패턴을 따릅니다.

src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (1)

19-41: Post 매퍼가 올바르게 구현되었습니다.

  • toPost는 요청 데이터와 이미지 URL 목록으로부터 Post 엔티티를 적절히 생성합니다.
  • toDetailResponse는 댓글 ID 목록을 스트림으로 추출하는 등 응답 데이터를 올바르게 조합합니다.
  • CommentMapper와 일관된 패턴을 따릅니다.
src/main/java/com/sku/refit/domain/comment/repository/CommentRepository.java (1)

14-16: Repository 인터페이스가 올바르게 구현되었습니다.

Spring Data JPA의 메서드 이름 규칙을 따르며, findAllByPostIdOrderByCreatedAtAsc 쿼리 메서드가 적절하게 정의되었습니다.

src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (1)

30-36: 구현이 올바릅니다.

컨트롤러 메서드가 적절하게 @Valid 검증을 사용하고 있으며, 서비스 레이어로 위임한 후 표준화된 BaseResponse로 래핑하여 반환합니다.

src/main/java/com/sku/refit/domain/post/repository/PostRepository.java (1)

14-20: Repository 메서드가 적절하게 구성되었습니다.

findByCategoryListContaining 메서드는 @ElementCollection으로 매핑된 categoryList와 함께 정상적으로 작동합니다. Spring Data JPA가 자동으로 적절한 조인 쿼리를 생성합니다.

src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (1)

17-35: DTO 구조와 검증이 적절합니다.

PostRequest는 적절한 검증 어노테이션을 사용하고 있습니다:

  • @NotEmpty로 빈 카테고리 리스트 방지
  • @NotBlank로 제목과 내용의 빈 값/공백 방지

Swagger 문서화도 잘 되어 있습니다.

src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (1)

14-24: DTO 구조가 적절합니다.

CommentRequest@NotBlank 검증으로 빈 댓글 내용을 방지하며, 간결하고 명확한 구조를 가지고 있습니다.

src/main/java/com/sku/refit/domain/post/service/PostService.java (2)

30-37: 메서드 시그니처가 적절합니다.

이미지 업로드를 포함한 게시글 생성 메서드가 명확하게 정의되어 있습니다.


46-53: 카테고리 기반 무한 스크롤 지원이 적절합니다.

getPostsByCategory 메서드는 lastPostIdsize를 사용하여 커서 기반 페이징을 구현하며, 무한 스크롤에 적합합니다.

src/main/java/com/sku/refit/domain/comment/service/CommentService.java (2)

13-20: 메서드 시그니처가 적절합니다.

댓글 생성 시 postId를 별도 파라미터로 받는 것은 명확하고 적절한 설계입니다.


39-44: Hard Delete 명시가 유용합니다.

JavaDoc에 "Hard Delete"를 명시한 것은 좋은 관행입니다. 향후 소프트 삭제로 변경할 때 명확한 변경 지점이 됩니다.

src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (1)

75-105: 커서 기반 페이지네이션 로직 구현 적절함

size + 1개를 조회하여 다음 페이지 존재 여부를 판단하는 방식이 올바르게 구현되어 있습니다. posts.getLast() 호출 전 isEmpty() 체크로 안전하게 처리됩니다.

src/main/java/com/sku/refit/global/page/response/PageResponse.java (1)

12-34: 페이지네이션 응답 DTO 구현 적절함

InfiniteResponse와 일관된 패턴으로 잘 구현되어 있습니다. Swagger 어노테이션과 Lombok 활용이 적절합니다.

src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)

38-53: 댓글 생성 로직 구현 적절함

게시글 존재 여부 확인, 현재 사용자 조회, 댓글 저장 및 응답 변환이 잘 구현되어 있습니다.

src/main/java/com/sku/refit/domain/comment/controller/CommentController.java (1)

28-42: 컨트롤러 인터페이스 구현 적절함

OpenAPI 어노테이션을 활용한 API 문서화와 Jakarta Validation을 통한 요청 검증이 잘 구현되어 있습니다.

src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1)

23-59: 구현이 깔끔하고 일관성이 있습니다!

컨트롤러 구현이 표준 Spring MVC 패턴을 잘 따르고 있으며, 다음 사항들이 적절하게 처리되었습니다:

  • @PathVariable@RequestParam 적절한 사용
  • @Valid를 통한 입력 검증
  • 서비스 계층으로의 적절한 위임
  • BaseResponse를 사용한 일관된 응답 형식
src/main/java/com/sku/refit/global/page/response/InfiniteResponse.java (1)

12-28: 무한 스크롤 응답 DTO가 잘 설계되었습니다!

커서 기반 페이지네이션을 위한 필드 구성이 적절하며, 모든 필드가 Swagger 어노테이션으로 명확하게 문서화되어 있습니다. PageResponse와 명확히 구분되는 역할을 가지고 있습니다.

src/main/java/com/sku/refit/domain/post/controller/PostController.java (1)

55-63: パラメータ名を確認して修正してください。

Line 59 のパラメータ lastBookIdlastPostId に変更する必要があることを確認してください。この修正は、PostController、PostControllerImpl、PostService、および PostServiceImpl に一貫して適用する必要があります。

Comment on lines +39 to +44
/**
* 모든 게시글 목록을 조회합니다.
*
* @return 게시글 상세 응답 리스트
*/
List<PostDetailResponse> getAllPosts();
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

페이징 없이 모든 게시글을 조회하면 성능 문제가 발생합니다.

getAllPosts() 메서드는 페이징 없이 모든 게시글을 반환합니다. 게시글 수가 증가하면 다음 문제가 발생합니다:

  1. 메모리 부족: 수천 개의 게시글과 연관된 엔티티(User, Comment 등)를 한 번에 로드
  2. 응답 시간 증가: 대량의 데이터 직렬화 및 전송
  3. 데이터베이스 부하: 불필요한 대량 쿼리 실행

다음 중 하나를 적용하세요:

옵션 1: 페이징 추가 (권장)

/**
 * 모든 게시글 목록을 페이징하여 조회합니다.
 *
 * @param pageable 페이징 정보
 * @return 페이징된 게시글 상세 응답
 */
PageResponse<PostDetailResponse> getAllPosts(Pageable pageable);

옵션 2: 메서드 제거
카테고리별 조회(getPostsByCategory)로 충분하다면 이 메서드를 제거하세요.

옵션 3: 최대 제한 추가

/**
 * 최근 게시글 목록을 조회합니다 (최대 100개).
 *
 * @return 최근 게시글 상세 응답 리스트
 */
List<PostDetailResponse> getRecentPosts();
🤖 Prompt for AI Agents
src/main/java/com/sku/refit/domain/post/service/PostService.java lines 39-44:
getAllPosts() currently returns all posts without paging which will cause
memory, latency, and DB-load issues; replace it with a paged API (preferred) by
changing the signature to accept a Pageable and return a paginated DTO (e.g.,
PageResponse<PostDetailResponse> getAllPosts(Pageable pageable)), then update
the implementation to call the repository method that returns a Page, map
entities to PostDetailResponse while preserving paging metadata, and update
controller endpoints and tests to pass/handle Pageable; alternatively, if global
listing is not needed remove this method or add a bounded recent-list method
(e.g., List<PostDetailResponse> getRecentPosts() limited to 100) and implement
repository query with ORDER BY createdAt DESC and LIMIT to enforce the cap.

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 (1)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)

87-105: orphanRemoval 설정 시 중복 삭제 호출 정리 가능

deleteComment에서 작성자 권한 체크까지 잘 들어간 상태입니다. 다만, 이전 리뷰 코멘트에서 언급된 것처럼 Post 엔티티의 commentListorphanRemoval = true가 설정되어 있다면:

Post post = comment.getPost();
post.getCommentList().remove(comment);

만으로도 JPA가 고아 엔티티를 삭제하므로, 아래 commentRepository.delete(comment); 호출은 중복일 수 있습니다. 트랜잭션 안에서 post.getCommentList().remove(comment);에만 의존하도록 정리하면 영속성 컨텍스트에서 한 번만 삭제가 일어나고, 코드 의도도 더 명확해집니다.

-    Post post = comment.getPost();
-    post.getCommentList().remove(comment);
-
-    commentRepository.delete(comment);
+    Post post = comment.getPost();
+    post.getCommentList().remove(comment);

(단, 실제 매핑이 orphanRemoval = true인지 한 번만 다시 확인해 주세요.)

🧹 Nitpick comments (1)
src/main/java/com/sku/refit/domain/post/entity/Post.java (1)

61-63: User 관계의 fetch 전략 검토를 권장합니다.

User에 대한 FetchType.EAGER 전략은 대부분의 게시글 조회 시 작성자 정보가 필요한 경우 적합할 수 있습니다. 그러나 특정 쿼리에서 사용자 정보가 불필요한 경우 성능 오버헤드가 발생할 수 있습니다.

필요시 LAZY로 변경하고, 사용자 정보가 필요한 경우에만 @EntityGraph 또는 JOIN FETCH를 사용하여 명시적으로 로드하는 것을 고려해보세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2d3939 and 29a17d5.

📒 Files selected for processing (7)
  • src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/controller/PostController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/entity/Post.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java
  • src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java
  • src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/com/sku/refit/domain/post/entity/Post.java (5)
src/main/java/com/sku/refit/domain/comment/entity/Comment.java (1)
  • Entity (26-52)
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1)
  • Getter (13-41)
src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (1)
  • Getter (14-24)
src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (1)
  • Getter (17-35)
src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (1)
src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1)
  • RestController (23-60)
🔇 Additional comments (5)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (3)

38-53: 댓글 생성 플로우와 트랜잭션 구성이 깔끔합니다

현재 사용자 조회 → 게시글 존재 검증 → Comment 매핑/저장 → DTO 변환까지 흐름이 명확하고, 쓰기 메서드에만 별도 @Transactional을 두는 구조도 적절합니다. 이 구간은 그대로 두셔도 될 것 같습니다.


55-66: 게시글 존재 검증 후 댓글 리스트 조회 구현 무난

existsById로 게시글 존재 여부를 먼저 확인하고, 없으면 POST_NOT_FOUND를 던지는 방식이 명확해서 API 관점에서 예측 가능한 동작을 제공합니다. 현재 요구사항 기준으로는 이대로도 충분해 보입니다.


68-85: 작성자 기준 댓글 수정 권한 체크 추가된 점 좋습니다

userService.getCurrentUser()로 현재 사용자 조회 후 comment.getUser().getId()와 비교해 작성자만 수정 가능하도록 막은 부분이 이전 리뷰 피드백도 잘 반영하고 있고, 권한 실패 시 COMMENT_NOT_FOUND로 존재를 감추는 전략도 일관된 설계라 보입니다.

src/main/java/com/sku/refit/domain/post/entity/Post.java (1)

65-71: 이전 리뷰의 EAGER fetch 문제가 해결되었습니다.

commentList의 fetch 전략이 FetchType.LAZY로 올바르게 변경되었습니다. 이를 통해 N+1 문제와 불필요한 데이터 로딩을 방지할 수 있습니다.

src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (1)

23-75: 구현이 깔끔하고 이전 리뷰의 문제점들이 모두 해결되었습니다.

  • lastPostId 파라미터명이 올바르게 수정되었습니다 (line 47).
  • deletePost 메서드명이 올바르게 수정되었습니다 (line 70).
  • 서비스 레이어로의 위임 패턴이 일관되게 적용되어 있습니다.
  • 응답 래핑이 BaseResponse를 통해 일관성 있게 처리되고 있습니다.

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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29a17d5 and f443c6e.

📒 Files selected for processing (1)
  • src/main/java/com/sku/refit/domain/post/controller/PostController.java (1 hunks)
⏰ 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: Build
🔇 Additional comments (4)
src/main/java/com/sku/refit/domain/post/controller/PostController.java (4)

1-30: 패키지 선언 및 임포트가 적절합니다.

필요한 Spring, Jakarta Validation, Swagger, 그리고 도메인 관련 클래스들이 모두 적절하게 임포트되어 있습니다.


32-53: 인터페이스 선언 및 초기 엔드포인트들이 적절합니다.

Swagger 태그와 경로 매핑이 올바르게 설정되어 있고, 게시글 작성 및 전체 조회 엔드포인트의 구조가 적절합니다. multipart 처리와 유효성 검사 어노테이션이 올바르게 적용되었습니다.


65-68: 게시글 단일 조회 엔드포인트가 적절합니다.

표준 REST 패턴을 따르는 단일 리소스 조회 엔드포인트입니다.


86-89: 게시글 삭제 엔드포인트가 올바르게 수정되었습니다.

이전 리뷰에서 지적된 deleteExhibitionLike 메서드 이름이 deletePost로 올바르게 수정되었습니다. 표준 REST 삭제 패턴을 잘 따르고 있습니다.

Comment on lines 55 to 63
@GetMapping
@Operation(summary = "카테코리별 게시글 전체 조회", description = "특정 카테고리의 게시글 리스트를 조회합니다.")
ResponseEntity<BaseResponse<InfiniteResponse<PostDetailResponse>>> getPostByCategory(
@RequestParam String category,
@Parameter(description = "마지막으로 조회한 게시글 식별자(첫 조회 시 생략)", example = "3")
@RequestParam(required = false)
Long lastPostId,
@Parameter(description = "한 번에 조회할 게시글 개수", example = "3") @RequestParam(defaultValue = "3")
Integer size);
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

Fix Korean typo in endpoint summary.

Line 56: "카테코리별" should be "카테고리별".

  @GetMapping
- @Operation(summary = "카테코리별 게시글 전체 조회", description = "특정 카테고리의 게시글 리스트를 조회합니다.")
+ @Operation(summary = "카테고리별 게시글 전체 조회", description = "특정 카테고리의 게시글 리스트를 조회합니다.")
  ResponseEntity<BaseResponse<InfiniteResponse<PostDetailResponse>>> getPostByCategory(

Consider increasing the default page size. Line 62: The default size of 3 is unusually small for infinite scroll pagination. Verify whether this is intentional or should be adjusted to a more typical value like 10–20.

🤖 Prompt for AI Agents
In src/main/java/com/sku/refit/domain/post/controller/PostController.java around
lines 55 to 63, fix the Korean typo in the @Operation summary by changing
"카테코리별" to "카테고리별", and adjust the @RequestParam(defaultValue = "3") for size to
a more typical pagination default (e.g., "10" or another agreed value); update
the annotation defaultValue and any related Javadoc/comments if present to
reflect the new default.

Comment on lines 70 to 84
@PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "게시글 수정", description = "특정 게시글의 상세 내용을 수정합니다.")
ResponseEntity<BaseResponse<PostDetailResponse>> updatePostById(
@Parameter(description = "게시글 식별자", example = "1") @PathVariable Long id,
@Parameter(
description = "수정할 이미지 리스트",
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
@RequestPart(value = "image", required = false)
List<MultipartFile> image,
@Parameter(
description = "게시글 수정 내용",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
@Valid
@RequestPart(value = "request")
PostRequest request);
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

파라미터 이름의 일관성을 개선해야 합니다.

Line 77의 image 파라미터는 List<MultipartFile> 타입임에도 불구하고 단수형으로 명명되어 있습니다. Line 48의 createPost 메서드에서는 동일한 타입을 images (복수형)로 명명했습니다. API 일관성을 위해 복수형으로 통일하는 것이 좋습니다.

  @PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  @Operation(summary = "게시글 수정", description = "특정 게시글의 상세 내용을 수정합니다.")
  ResponseEntity<BaseResponse<PostDetailResponse>> updatePostById(
      @Parameter(description = "게시글 식별자", example = "1") @PathVariable Long id,
      @Parameter(
              description = "수정할 이미지 리스트",
              content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
-         @RequestPart(value = "image", required = false)
-         List<MultipartFile> image,
+         @RequestPart(value = "images", required = false)
+         List<MultipartFile> images,
      @Parameter(
              description = "게시글 수정 내용",
              content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
          @Valid
          @RequestPart(value = "request")
          PostRequest request);

참고: 구현체(PostControllerImpl)에서도 동일하게 파라미터 이름을 수정해야 합니다.

📝 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
@PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "게시글 수정", description = "특정 게시글의 상세 내용을 수정합니다.")
ResponseEntity<BaseResponse<PostDetailResponse>> updatePostById(
@Parameter(description = "게시글 식별자", example = "1") @PathVariable Long id,
@Parameter(
description = "수정할 이미지 리스트",
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
@RequestPart(value = "image", required = false)
List<MultipartFile> image,
@Parameter(
description = "게시글 수정 내용",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
@Valid
@RequestPart(value = "request")
PostRequest request);
@PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "게시글 수정", description = "특정 게시글의 상세 내용을 수정합니다.")
ResponseEntity<BaseResponse<PostDetailResponse>> updatePostById(
@Parameter(description = "게시글 식별자", example = "1") @PathVariable Long id,
@Parameter(
description = "수정할 이미지 리스트",
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
@RequestPart(value = "images", required = false)
List<MultipartFile> images,
@Parameter(
description = "게시글 수정 내용",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
@Valid
@RequestPart(value = "request")
PostRequest request);
🤖 Prompt for AI Agents
In src/main/java/com/sku/refit/domain/post/controller/PostController.java around
lines 70 to 84, the RequestPart parameter named "image" is singular despite
being a List<MultipartFile>, causing inconsistency with createPost which uses
"images"; rename the parameter to "images" (and update its @RequestPart value to
"images") to match naming and API contract, and apply the same change in the
implementation class PostControllerImpl to keep signatures consistent.

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

Labels

✨ feature 새로운 기능 요청 🟠 priority: medium 중간 우선순위

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨Feat: 커뮤니티 페이지 기능 개발

2 participants