-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/community #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughComment 및 Post 도메인(컨트롤러, 서비스, 리포지토리, 엔티티, DTO, 매퍼)과 전역 페이징/무한스크롤 응답·매퍼가 추가되었고, S3 경로 분리(PathName·S3Config) 및 S3ServiceImpl 매핑이 변경되었으며 AuthServiceImpl에 PasswordEncoder 주입 및 테스트 사용자 자동 생성 로직이 추가되었습니다. Changes
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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🧰 Additional context used🧬 Code graph analysis (1)src/main/java/com/sku/refit/domain/post/controller/PostControllerImpl.java (2)
🔇 Additional comments (4)
Comment |
There was a problem hiding this 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 체크 대신Optional의ifPresentOrElse또는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 페치 타입으로 인한 성능 문제 가능성
user와post관계 모두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.java의commentList와user관계도 동일하게LAZY로 변경을 고려해 주세요.src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1)
18-19: Swagger example 형식을 개선할 수 있습니다.
categoryList는List<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: 에러 코드 정의 적절함, 권한 관련 에러 코드 추가 권장일관된 패턴으로 잘 구현되어 있습니다. 다만,
updatePost와deletePost에서 권한 검증 추가 시 사용할 권한 관련 에러 코드(예: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
📒 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 경로 상수가 올바르게 구현되었습니다
POST와CLOTH상수가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 경로 구성이 올바르게 추가되었습니다.새로운
postPath및clothPath필드가 적절하게 추가되었으며, 기존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메서드는lastPostId와size를 사용하여 커서 기반 페이징을 구현하며, 무한 스크롤에 적합합니다.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 のパラメータ
lastBookIdがlastPostIdに変更する必要があることを確認してください。この修正は、PostController、PostControllerImpl、PostService、および PostServiceImpl に一貫して適用する必要があります。
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java
Outdated
Show resolved
Hide resolved
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java
Outdated
Show resolved
Hide resolved
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java
Outdated
Show resolved
Hide resolved
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java
Show resolved
Hide resolved
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java
Show resolved
Hide resolved
| /** | ||
| * 모든 게시글 목록을 조회합니다. | ||
| * | ||
| * @return 게시글 상세 응답 리스트 | ||
| */ | ||
| List<PostDetailResponse> getAllPosts(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
페이징 없이 모든 게시글을 조회하면 성능 문제가 발생합니다.
getAllPosts() 메서드는 페이징 없이 모든 게시글을 반환합니다. 게시글 수가 증가하면 다음 문제가 발생합니다:
- 메모리 부족: 수천 개의 게시글과 연관된 엔티티(User, Comment 등)를 한 번에 로드
- 응답 시간 증가: 대량의 데이터 직렬화 및 전송
- 데이터베이스 부하: 불필요한 대량 쿼리 실행
다음 중 하나를 적용하세요:
옵션 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.
There was a problem hiding this 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엔티티의commentList에orphanRemoval = 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
📒 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를 통해 일관성 있게 처리되고 있습니다.
There was a problem hiding this 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
📒 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 삭제 패턴을 잘 따르고 있습니다.
| @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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| @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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
파라미터 이름의 일관성을 개선해야 합니다.
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.
| @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.
✨ 새로운 기능
🛠 개발 상세
🧪 테스트 방법
🧩 추가 고려사항
🔗 관련 문서 / 이슈
Summary by CodeRabbit
새로운 기능
개선
✏️ Tip: You can customize this high-level summary in your review settings.