Skip to content

Comments

✨Feat: 교환 관련 API 기능 개발#11

Merged
uni-j-uni merged 5 commits intomainfrom
feature/exchange
Dec 13, 2025
Merged

✨Feat: 교환 관련 API 기능 개발#11
uni-j-uni merged 5 commits intomainfrom
feature/exchange

Conversation

@uni-j-uni
Copy link
Contributor

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

✨ 새로운 기능

  • 교환 게시글 CRUD 및 위치 기반 목록 조회 기능 추가
  • 사용자는 현재 위치 기준으로 교환 전 게시글을 빠르게 조회, 교환 게시글 생성·수정·삭제 가능

🛠 개발 상세

  • 교환 게시글 도메인 중심으로 Controller / Service / Repository 계층을 분리하여 구현
  • 이미지 URL과 선호 카테고리는 @ElementCollection으로 분리 저장
  • 페이지네이션은 공통 PageResponse 변환 로직 사용

🧪 테스트 방법

  • 게시글 생성 / 수정 / 삭제 API 호출 테스트 완료
  • 위치 기반 페이지 조회 및 교환 전(BEFORE) 상태 필터링 확인

🧩 추가 고려사항

  • ExchangePost 이미지 조회 성능 개선을 위한 fetch 전략 검토 필요
  • 위치 기반 조회 쿼리 인덱스 및 거리 계산 로직 고도화 고려

🔗 관련 문서 / 이슈

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 교환 게시물 작성·조회·수정·삭제 API 추가(이미지 업로드, 카드/상세 응답, 위치 기반 엔드포인트 포함)
    • 위치 기반 교환 게시물 검색(거리순 페이징) 추가
  • 개선사항

    • 옷 사이즈·상태·카테고리(교환) 분류 체계 추가 — 응답에 상세/요약 형태 반영
    • 게시글 카테고리 단일화(단일 카테고리 사용) 및 상세 응답에 작성자 여부(isAuthor) 표시

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

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

coderabbitai bot commented Dec 13, 2025

Walkthrough

교환 게시물 CRUD와 위치 기반 검색 기능이 추가되었고, 관련 DTO·엔티티·레포지토리·서비스·컨트롤러가 도입되었으며 Post의 카테고리가 List에서 Enum으로 변경되고 Comment 클래스의 Lombok 로그 어노테이션과 일부 클래스 레벨 트랜잭션 어노테이션이 제거되었습니다.

Changes

Cohort / File(s) 변경 요약
Comment 변경
src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java, src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java
@Slf4j 제거 및 클래스 레벨 @Transactional(readOnly = true) 제거 (로깅 어노테이션 및 기본 트랜잭션 정책 삭제).
Exchange 컨트롤러
src/main/java/com/sku/refit/domain/exchange/controller/ExchangeController.java, src/main/java/com/sku/refit/domain/exchange/controller/ExchangeControllerImpl.java
/api/exchanges REST 인터페이스 및 구현 추가(생성/목록(위치 기반)/상세/수정/삭제, multipart 및 검증, BaseResponse 래핑).
Exchange DTOs
src/main/java/com/sku/refit/domain/exchange/dto/request/ExchangePostRequest.java, src/main/java/com/sku/refit/domain/exchange/dto/response/...
교환 게시물 요청/응답 DTO 추가(검증·Swagger 주석 포함): ExchangePostRequest, ExchangePostCardResponse, ExchangePostDetailResponse 등.
Exchange 엔티티 & Enum
src/main/java/com/sku/refit/domain/exchange/entity/ExchangePost.java, .../ExchangeCategory.java, .../ClothSize.java, .../ClothStatus.java, .../ExchangeStatus.java
ExchangePost JPA 엔티티 및 관련 Enum(카테고리·사이즈·상태) 추가, 이미지·선호카테고리 ElementCollection 매핑, User 연관관계 및 update 메서드, 기본 exchangeStatus 설정.
Exchange 리포지토리
src/main/java/com/sku/refit/domain/exchange/repository/ExchangeRepository.java
JpaRepository 추가 및 커스텀 쿼리 findByDistanceAndStatus(Double,Double,ExchangeStatus,Pageable) (ST_Distance_Sphere 사용) 및 findByIdAndExchangeStatus 추가.
Exchange 매퍼
src/main/java/com/sku/refit/domain/exchange/mapper/ExchangeMapper.java
엔티티↔DTO 변환 컴포넌트 추가(toExchangePost, toDetailResponse, toCardResponse).
Exchange 서비스 계층
src/main/java/com/sku/refit/domain/exchange/service/ExchangeService.java, .../ExchangeServiceImpl.java
서비스 인터페이스 및 구현 추가: 이미지 S3 업로드/삭제, CRUD, 권한검증, 위치 기반 페이징 조회, 매퍼·PageMapper 사용, CustomException 처리.
Exchange 예외 코드
src/main/java/com/sku/refit/domain/exchange/exception/ExchangeErrorCode.java
교환 관련 에러 코드 enum 추가(EXCHANGE_NOT_FOUND, EXCHANGE_ACCESS_DENIED, EXCHANGE_CATEGORY_INVALID, EXCHANGE_STATUS_INVALID 등).
Post 카테고리 리팩토링
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/entity/Post.java, src/main/java/com/sku/refit/domain/post/entity/PostCategory.java
Post의 categoryList (List<String>)postCategory (PostCategory enum)로 변경; PostDetailResponse에 isAuthor 필드 추가.
Post 매퍼/서비스/레포지토리 업데이트
src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java, src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java, src/main/java/com/sku/refit/domain/post/repository/PostRepository.java
PostMapper.toPost 시 PostCategory 매개변수 추가(시그니처 변경), 서비스에서 문자열→PostCategory 파싱 및 검증 추가, 레포지토리 메서드명 findByCategoryListContaining*findByPostCategoryContaining*로 변경(호출점 업데이트 필요).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as ExchangeController
    participant Service as ExchangeService
    participant S3
    participant DB as Database

    Client->>Controller: POST /api/exchanges (multipart: imageList + request)
    Controller->>Service: createExchangePost(imageList, request)
    alt images present
        Service->>S3: upload images
        S3-->>Service: imageUrlList
    else no images
        Service-->>Service: imageUrlList = []
    end
    Service->>Service: map request -> ExchangePost (mapper, enums)
    Service->>DB: save(ExchangePost)
    DB-->>Service: saved ExchangePost
    Service-->>Controller: ExchangePostDetailResponse
    Controller-->>Client: 201 Created (BaseResponse)
Loading
sequenceDiagram
    participant Client
    participant Controller as ExchangeController
    participant Service as ExchangeService
    participant DB as Database

    Client->>Controller: GET /api/exchanges?pageNum=&pageSize=&latitude=&longitude=
    Controller->>Controller: validate pageNum/pageSize
    alt invalid pagination
        Controller-->>Client: 400 Bad Request (PageErrorStatus)
    else valid
        Controller->>Service: getExchangePostsByLocation(pageable, lat, lon)
        Service->>DB: findByDistanceAndStatus(lat, lon, BEFORE, pageable)
        DB-->>Service: Page<ExchangePost> (ordered by ST_Distance_Sphere)
        Service-->>Controller: PageResponse<ExchangePostCardResponse>
        Controller-->>Client: 200 OK (BaseResponse)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • 주의가 필요한 파일/영역:
    • ExchangeRepository.findByDistanceAndStatus()의 원시 쿼리와 ST_Distance_Sphere 파라미터/좌표 순서 및 성능
    • ExchangeServiceImpl의 S3 업로드·삭제와 트랜잭션 경계, 예외 발생 시 자원 정리
    • 엔티티 매핑(ExchangePost의 ElementCollection 설정, 컬렉션 테이블 명칭) 및 Lombok 빌더 기본값 확인
    • Post 리팩토링으로 인한 mapper/service/repository 시그니처 변경에 따른 전체 호출점(컴파일) 점검
    • 클래스 레벨 @Transactional 제거 영향 확인 (메서드별 어노테이션 적용 여부)

Possibly related PRs

  • Feature/community #2 — 동일 파일군의 로깅·트랜잭션 어노테이션 변경과 겹침(CommentControllerImpl / CommentServiceImpl 관련).

Poem

🐰
교환 글이 폴짝 피어나네, 위도·경도에 발자국 남기고,
사진은 구름 타고 떠나며 URL로 돌아와,
카테고리는 깔끔히 한 송이로, 바운스 없이,
깡총깡총 코드 풀고 당근 하나 남기네 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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 PR 설명이 필수 템플릿 구조를 대부분 충족하며, 새로운 기능, 개발 상세, 테스트 방법, 추가 고려사항, 관련 이슈를 모두 포함하고 있습니다.
Title check ✅ Passed PR 제목 '✨Feat: 교환 관련 API 기능 개발'은 변경 사항의 주요 내용을 정확하게 반영합니다. 교환(exchange) 관련 API 기능이 추가되었으며, 이는 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/exchange

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (2)

131-132: 중복된 조회수 증가 호출을 제거하세요.

post.increaseViews()가 연속으로 두 번 호출되어 게시물 조회 시마다 조회수가 2씩 증가합니다. 이는 잘못된 조회수 데이터를 초래합니다.

다음 diff를 적용하여 중복 호출을 제거하세요:

     post.increaseViews();
-
-    post.increaseViews();

     log.info(

163-173: 중복된 이미지 삭제 로직을 제거하세요.

기존 이미지들이 두 번 삭제되고 있습니다 (lines 163-165와 lines 171-173). 첫 번째 삭제 후 파일이 이미 제거되었기 때문에 두 번째 삭제 시도는 실패하게 됩니다.

Lines 171-173의 중복 삭제 로직을 제거하세요.

다음 diff를 적용하세요:

       for (MultipartFile image : images) {
         newImageUrls.add(s3Service.uploadImage(PathName.POST, image).getImageUrl());
       }
-
-      for (String imageUrl : oldImageUrls) {
-        s3Service.deleteFile(s3Service.extractKeyNameFromUrl(imageUrl));
-      }

     } catch (Exception e) {
🧹 Nitpick comments (8)
src/main/java/com/sku/refit/domain/post/dto/request/PostRequest.java (1)

22-24: String 기반 카테고리 검증 개선 권장

postCategory 필드가 String 타입이지만 실제로는 PostCategory enum 값으로 파싱됩니다. 현재 @NotEmpty는 공백만 있는 문자열을 허용하며, 유효하지 않은 enum 값이 입력되면 런타임 오류가 발생할 수 있습니다.

다음 두 가지 방식 중 하나를 고려하세요:

방식 1: 타입을 PostCategory enum으로 변경 (권장)

- @NotEmpty(message = "게시글 카테고리는 필수입니다.")
- @Schema(description = "게시글 카테고리", example = "FREE")
- private String postCategory;
+ @NotNull(message = "게시글 카테고리는 필수입니다.")
+ @Schema(description = "게시글 카테고리", example = "FREE")
+ private PostCategory postCategory;

방식 2: 커스텀 validation 어노테이션 추가

- @NotEmpty(message = "게시글 카테고리는 필수입니다.")
+ @NotBlank(message = "게시글 카테고리는 필수입니다.")
+ @Pattern(regexp = "FREE|REPAIR|INFO", message = "유효하지 않은 카테고리입니다.")
src/main/java/com/sku/refit/domain/post/entity/Post.java (1)

61-64: 주석 처리된 코드 제거 권장

주석 처리된 categoryList 관련 코드는 새로운 postCategory enum으로 대체되었으므로 제거하는 것이 좋습니다. 주석으로 남겨둔 코드는 코드베이스를 혼란스럽게 만들 수 있습니다.

- //  @ElementCollection
- //  @CollectionTable(name = "post_category", joinColumns = @JoinColumn(name = "post_id"))
- //  @Column(nullable = false)
- //  private List<String> categoryList;
src/main/java/com/sku/refit/domain/exchange/entity/ClothSize.java (1)

10-11: 불필요한 Lombok 어노테이션

ClothSize Enum은 필드가 없으므로 @RequiredArgsConstructor@Getter가 불필요합니다. 이 어노테이션들은 무해하지만 코드를 간소화할 수 있습니다.

다음 diff를 적용하여 불필요한 어노테이션을 제거하세요:

-@Getter
-@RequiredArgsConstructor
 @Schema(description = "옷 사이즈 Enum")
 public enum ClothSize {
src/main/java/com/sku/refit/domain/exchange/service/ExchangeService.java (1)

39-39: Javadoc 누락

getExchangePost 메서드에 Javadoc 주석이 없어 다른 메서드들과 문서화 일관성이 떨어집니다.

다음 diff를 적용하여 Javadoc을 추가하세요:

+  /**
+   * 교환 게시글 단건 조회
+   *
+   * @param exchangePostId 교환 게시글 ID
+   * @return 교환 게시글 상세 응답
+   */
   ExchangePostDetailResponse getExchangePost(Long exchangePostId);
src/main/java/com/sku/refit/domain/exchange/entity/ExchangePost.java (2)

44-49: @ElementCollection의 fetch 전략을 명시하세요.

PR 목표에서 언급된 대로, 이미지 조회 성능 개선을 위해 @ElementCollection의 fetch 전략을 검토해야 합니다. 기본값은 LAZY이지만, 명시적으로 지정하는 것이 좋습니다. 카드 리스트 조회 시 N+1 쿼리가 발생할 수 있습니다.

다음 옵션을 고려하세요:

옵션 1: EAGER fetch (카드 리스트에서 항상 이미지가 필요한 경우)

 @ElementCollection
+@org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
 @CollectionTable(
     name = "exchange_post_image_url",
     joinColumns = @JoinColumn(name = "exchange_post_id"))

옵션 2: Entity Graph 사용 (선택적 fetch)
Repository에서 필요한 경우에만 fetch하도록 @EntityGraph를 사용하세요.

Also applies to: 69-75


49-49: 필드 초기화가 불필요합니다.

@ElementCollection 필드에 대한 = new ArrayList<>() 초기화는 JPA가 프록시로 관리하므로 불필요합니다. 제거하는 것이 JPA의 관리 의도를 명확히 합니다.

 @ElementCollection
 @CollectionTable(
     name = "exchange_post_image_url",
     joinColumns = @JoinColumn(name = "exchange_post_id"))
 @Column(name = "image_url", nullable = false)
-private List<String> imageUrlList = new ArrayList<>();
+private List<String> imageUrlList;

동일하게 Line 75의 preferCategories에도 적용하세요.

Also applies to: 75-75

src/main/java/com/sku/refit/domain/exchange/controller/ExchangeController.java (1)

57-62: 하드코딩된 기본 좌표값을 재고하세요.

서울역 좌표를 기본값으로 사용하는 것은 사용자가 위치를 제공하지 않았을 때 예상치 못한 결과를 초래할 수 있습니다. 위치 기반 검색에서 좌표는 필수 파라미터로 만드는 것이 더 명확합니다.

 @GetMapping
 @Operation(summary = "교환 게시글 목록(페이지) 조회 (위치 기반)")
 ResponseEntity<BaseResponse<PageResponse<ExchangePostCardResponse>>> getExchangePostsByLocation(
     @Parameter(description = "페이지 번호", example = "1") @RequestParam Integer pageNum,
     @Parameter(description = "페이지 크기", example = "4") @RequestParam Integer pageSize,
     @Parameter(description = "위도", example = "37.544018")
-        @RequestParam(defaultValue = "37.544018")
+        @RequestParam(required = true)
         Double latitude,
     @Parameter(description = "경도", example = "126.951592")
-        @RequestParam(defaultValue = "126.951592")
+        @RequestParam(required = true)
         Double longitude);
src/main/java/com/sku/refit/domain/exchange/service/ExchangeServiceImpl.java (1)

123-127: @ElementCollection은 항상 초기화되므로 null 체크가 불필요합니다.

JPA의 @ElementCollection은 항상 빈 컬렉션으로 초기화되므로 null이 될 수 없습니다. null 체크는 불필요하며 혼란을 줄 수 있습니다.

-if (exchangePost.getImageUrlList() != null) {
-  for (String imageUrl : exchangePost.getImageUrlList()) {
+if (!exchangePost.getImageUrlList().isEmpty()) {
+  for (String imageUrl : exchangePost.getImageUrlList()) {
     s3Service.deleteFile(s3Service.extractKeyNameFromUrl(imageUrl));
   }
 }

Also applies to: 182-186

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6424e1a and 5d71994.

📒 Files selected for processing (24)
  • src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (0 hunks)
  • src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (0 hunks)
  • src/main/java/com/sku/refit/domain/exchange/controller/ExchangeController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/controller/ExchangeControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/dto/request/ExchangePostRequest.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostCardResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/entity/ClothSize.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/entity/ClothStatus.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/entity/ExchangeCategory.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/entity/ExchangePost.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/entity/ExchangeStatus.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/exception/ExchangeErrorCode.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/mapper/ExchangeMapper.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/repository/ExchangeRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/service/ExchangeService.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/service/ExchangeServiceImpl.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 (3 hunks)
  • src/main/java/com/sku/refit/domain/post/entity/Post.java (2 hunks)
  • src/main/java/com/sku/refit/domain/post/entity/PostCategory.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (2 hunks)
  • src/main/java/com/sku/refit/domain/post/repository/PostRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (3 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java
  • src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java
🧰 Additional context used
🧬 Code graph analysis (7)
src/main/java/com/sku/refit/domain/exchange/entity/ExchangePost.java (3)
src/main/java/com/sku/refit/domain/exchange/dto/request/ExchangePostRequest.java (1)
  • Getter (17-63)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostCardResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1)
  • Getter (17-60)
src/main/java/com/sku/refit/domain/exchange/entity/ClothStatus.java (1)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1)
  • Getter (17-60)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostCardResponse.java (1)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1)
  • Getter (17-60)
src/main/java/com/sku/refit/domain/exchange/controller/ExchangeControllerImpl.java (1)
src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1)
  • RestController (22-65)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (3)
src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (1)
  • Schema (12-151)
src/main/java/com/sku/refit/domain/exchange/dto/request/ExchangePostRequest.java (1)
  • Getter (17-63)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostCardResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/exchange/dto/request/ExchangePostRequest.java (2)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostCardResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1)
  • Getter (17-60)
src/main/java/com/sku/refit/domain/exchange/entity/ExchangeCategory.java (2)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostCardResponse.java (1)
  • Getter (12-28)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1)
  • Getter (17-60)
🔇 Additional comments (15)
src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (2)

20-21: 카테고리 타입 변경 승인

PostCategory enum 타입으로의 변경이 적절합니다. API 응답에서 타입 안전성이 확보되고 Swagger 문서에도 명확하게 표시됩니다.


41-42: isAuthor 필드 추가 승인

작성자 본인 여부를 나타내는 isAuthor 필드 추가는 클라이언트에서 수정/삭제 권한 UI를 표시하는 데 유용합니다.

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

83-87: update() 메서드에 postCategory 누락 확인 필요

update() 메서드가 postCategory를 업데이트하지 않습니다. 의도적으로 카테고리 변경을 제한하는 것이라면 괜찮지만, 게시글 수정 시 카테고리도 변경할 수 있어야 한다면 파라미터 추가가 필요합니다.

카테고리 변경이 필요한 경우:

- public void update(String title, String content, List<String> imageUrlList) {
+ public void update(String title, String content, List<String> imageUrlList, PostCategory postCategory) {
    this.title = title;
    this.content = content;
    this.imageUrlList = imageUrlList;
+   this.postCategory = postCategory;
  }

57-59: Enum 영속화 방식 승인

@Enumerated(EnumType.STRING) 사용은 좋은 선택입니다. EnumType.ORDINAL보다 enum 값 순서 변경에 안전하며, DB에서 직접 조회 시 가독성도 높습니다.

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

10-22: 잘 구성된 PostCategory enum

enum 설계가 깔끔합니다. Lombok을 활용한 보일러플레이트 제거, 한글 라벨 제공, Swagger 문서화가 적절하게 적용되어 있습니다.

src/main/java/com/sku/refit/domain/exchange/exception/ExchangeErrorCode.java (1)

15-24: LGTM!

에러 코드 Enum이 프로젝트의 표준 패턴을 잘 따르고 있으며, 명확한 에러 메시지와 적절한 HTTP 상태 코드를 제공합니다.

src/main/java/com/sku/refit/domain/exchange/entity/ClothStatus.java (1)

13-22: LGTM!

Enum 구조가 명확하며, 한국어 레이블을 위한 ko 필드와 생성자 설정이 적절합니다.

src/main/java/com/sku/refit/domain/exchange/entity/ExchangeCategory.java (1)

13-26: LGTM!

교환 카테고리 Enum이 명확하게 정의되어 있으며, 한국어 레이블 지원이 적절합니다.

src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostCardResponse.java (1)

12-28: 잘 구현되었습니다!

간결하고 명확한 DTO 구조입니다. Swagger 문서화도 적절합니다.

src/main/java/com/sku/refit/domain/exchange/mapper/ExchangeMapper.java (1)

22-45: 매핑 로직이 잘 구현되었습니다.

요청 데이터를 엔티티로 변환하는 로직이 명확하고 완전합니다.

src/main/java/com/sku/refit/domain/exchange/entity/ExchangeStatus.java (1)

10-22: 깔끔한 Enum 정의입니다.

교환 상태를 명확하게 정의했으며, 한글 설명이 포함되어 있어 좋습니다.

src/main/java/com/sku/refit/domain/exchange/controller/ExchangeControllerImpl.java (2)

47-52: 페이지네이션 검증이 잘 구현되었습니다.

입력값 검증을 통해 잘못된 페이지 요청을 명확하게 처리하고 있습니다.


88-88: 응답 방식이 일관적입니다.

BaseResponse.success(null)은 CommentControllerImpl의 delete 메서드와 일관된 패턴입니다.

src/main/java/com/sku/refit/domain/exchange/service/ExchangeServiceImpl.java (2)

48-80: 게시글 생성 로직이 잘 구조화되었습니다.

이미지 업로드, Enum 변환, 엔티티 매핑, 저장, 로깅까지 명확하게 구현되어 있습니다.


82-92: 위치 기반 페이지네이션 조회가 적절합니다.

ExchangeStatus.BEFORE 필터링과 거리 기반 정렬을 통해 의도한 비즈니스 로직을 정확히 구현했습니다.

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

♻️ Duplicate comments (2)
src/main/java/com/sku/refit/domain/exchange/repository/ExchangeRepository.java (1)

23-38: 이전 리뷰 코멘트 참조

좌표 필드 인덱스 부재 및 거리 필터링 누락 문제는 이미 이전 리뷰에서 상세히 지적되었습니다. 해당 코멘트를 참고하여 개선을 진행해 주세요.

src/main/java/com/sku/refit/domain/exchange/controller/ExchangeController.java (1)

37-50: 이전 리뷰 코멘트 참조

이미지 리스트 검증 누락 문제는 이전 리뷰에서 상세히 지적되었습니다. imageList가 빈 리스트일 경우 getFirst() 호출 시 예외가 발생하는 문제를 해결해 주세요.

🧹 Nitpick comments (2)
src/main/java/com/sku/refit/domain/exchange/controller/ExchangeController.java (2)

52-62: 페이징 및 좌표 파라미터 검증 추가 권장

현재 pageNum, pageSize, latitude, longitude 파라미터에 대한 범위 검증이 없습니다. 다음 검증을 추가하면 더 견고한 API가 됩니다:

  • pageNum: 0 이상의 값
  • pageSize: 1 이상, 적절한 최대값 이하 (예: 100)
  • latitude: -90.0 ~ 90.0 범위
  • longitude: -180.0 ~ 180.0 범위

구현 예시:

@GetMapping
@Operation(summary = "교환 게시글 목록(페이지) 조회 (위치 기반)")
ResponseEntity<BaseResponse<PageResponse<ExchangePostCardResponse>>> getExchangePostsByLocation(
    @Parameter(description = "페이지 번호", example = "1") 
    @RequestParam @Min(0) Integer pageNum,
    @Parameter(description = "페이지 크기", example = "4") 
    @RequestParam @Min(1) @Max(100) Integer pageSize,
    @Parameter(description = "위도", example = "37.544018")
    @RequestParam(defaultValue = "37.544018") 
    @Min(-90) @Max(90) Double latitude,
    @Parameter(description = "경도", example = "126.951592")
    @RequestParam(defaultValue = "126.951592") 
    @Min(-180) @Max(180) Double longitude);

69-83: 이미지 리스트 선택적 제공 시 검증 추가 권장

수정 시 imageList가 선택적(required=false)이지만, 제공되는 경우 빈 리스트는 허용하지 않아야 합니다. 서비스 레이어에서 다음 로직을 추가하는 것을 권장합니다:

  • imageList가 null이면: 기존 이미지 유지
  • imageList가 제공되었으나 비어있으면: 400 에러 반환
  • imageList가 제공되고 비어있지 않으면: 이미지 업데이트
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d71994 and 9f6108c.

📒 Files selected for processing (4)
  • src/main/java/com/sku/refit/domain/exchange/controller/ExchangeController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/controller/ExchangeControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/repository/ExchangeRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/service/ExchangeServiceImpl.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/sku/refit/domain/exchange/service/ExchangeServiceImpl.java
  • src/main/java/com/sku/refit/domain/exchange/controller/ExchangeControllerImpl.java
🔇 Additional comments (4)
src/main/java/com/sku/refit/domain/exchange/repository/ExchangeRepository.java (1)

21-21: LGTM!

상태 기반 조회 메서드가 올바르게 구현되었습니다. Spring Data JPA의 네이밍 규칙을 잘 따르고 있으며, Optional을 반환하여 안전한 처리를 지원합니다.

src/main/java/com/sku/refit/domain/exchange/controller/ExchangeController.java (3)

33-35: LGTM!

인터페이스 기반 컨트롤러 설계와 Swagger 문서화가 잘 적용되어 있습니다. RESTful 경로 규칙도 적절합니다.


64-67: LGTM!

단일 게시글 조회 엔드포인트가 명확하고 간결하게 정의되어 있습니다.


85-88: LGTM!

게시글 삭제 엔드포인트가 명확하게 정의되어 있습니다.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (1)

34-44: imageUrlList 매핑이 여전히 누락되었습니다.

이전 리뷰에서 지적된 PostDetailResponseimageUrlList 필드 매핑이 아직 처리되지 않았습니다. Line 43에 commentIdList는 매핑되었지만 imageUrlList는 누락되어 있습니다.

다음 diff를 적용하여 imageUrlList를 매핑하세요:

         .category(post.getPostCategory())
+        .imageUrlList(post.getImageUrlList())
         .commentIdList(post.getCommentList().stream().map(Comment::getId).toList())
src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (2)

135-137: 조회수가 중복으로 증가하는 버그

Line 135와 137에서 post.increaseViews()가 두 번 호출되어 조회수가 의도한 것보다 2배로 증가합니다. 하나를 제거해야 합니다.

다음 diff를 적용하여 중복 호출을 제거하세요:

     post.increaseViews();
-
-    post.increaseViews();

168-178: 이미지 삭제 로직 중복

Lines 168-170에서 기존 이미지를 삭제한 후, 172-174에서 새 이미지를 업로드하고, 176-178에서 동일한 기존 이미지를 다시 삭제하려고 시도합니다. 이미 삭제된 파일을 재삭제하려는 것은 불필요하며 오류를 발생시킬 수 있습니다.

다음 diff를 적용하여 중복 삭제 로직을 제거하세요:

       try {
         for (String imageUrl : oldImageUrls) {
           s3Service.deleteFile(s3Service.extractKeyNameFromUrl(imageUrl));
         }

         for (MultipartFile image : images) {
           newImageUrls.add(s3Service.uploadImage(PathName.POST, image).getImageUrl());
         }
-
-        for (String imageUrl : oldImageUrls) {
-          s3Service.deleteFile(s3Service.extractKeyNameFromUrl(imageUrl));
-        }
🧹 Nitpick comments (1)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1)

22-59: 필드 검증 및 null 안전성 고려 권장

DTO 필드들에 다음 사항들을 고려해보세요:

  1. Boolean 래퍼 타입 (line 56): isAuthor 필드가 Boolean 래퍼 타입을 사용하고 있어 null 값이 반환될 수 있습니다. 소비 코드에서 NPE 위험이 있으므로, 기본값이 보장되어야 한다면 primitive boolean 타입 사용을 고려하세요.

  2. 필수 필드 검증: exchangePostId, nickname, title 등 필수 필드에 @NotNull 또는 @NotBlank 같은 검증 어노테이션 추가를 고려하세요.

  3. 좌표 범위 검증 (lines 50, 52): spotLatitudespotLongitude 필드에 @Min/@Max 어노테이션으로 유효 범위(위도: -9090, 경도: -180180) 검증을 추가하면 데이터 무결성을 보장할 수 있습니다.

예시 diff:

+  @NotNull
   @Schema(description = "교환 게시글 식별자", example = "1")
   private Long exchangePostId;

+  @NotBlank
   @Schema(description = "게시글 작성자", example = "김다입")
   private String nickname;

+  @Min(-90) @Max(90)
   @Schema(description = "교환 희망 스팟 위도", example = "37.544018")
   private Double spotLatitude;

+  @Min(-180) @Max(180)
   @Schema(description = "교환 희망 스팟 경도", example = "126.951592")
   private Double spotLongitude;

-  @Schema(description = "작성자 본인 여부", example = "true")
-  private Boolean isAuthor;
+  @Schema(description = "작성자 본인 여부", example = "true")
+  private boolean isAuthor;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f6108c and a808430.

📒 Files selected for processing (5)
  • src/main/java/com/sku/refit/domain/exchange/dto/request/ExchangePostRequest.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/exchange/service/ExchangeServiceImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (2 hunks)
  • src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/sku/refit/domain/exchange/dto/request/ExchangePostRequest.java
  • src/main/java/com/sku/refit/domain/exchange/service/ExchangeServiceImpl.java
🔇 Additional comments (5)
src/main/java/com/sku/refit/domain/exchange/dto/response/ExchangePostDetailResponse.java (1)

17-20: Schema 제목 수정 확인 완료

이전 리뷰에서 지적된 Schema 제목 불일치 문제가 해결되었습니다. @Schema의 title이 클래스명과 일치하게 "ExchangePostDetailResponse DTO"로 올바르게 설정되어 있습니다.

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

20-29: LGTM! 카테고리 리팩토링이 올바르게 적용되었습니다.

메서드 시그니처와 빌더 로직이 새로운 PostCategory enum 구조에 맞게 정확하게 업데이트되었습니다.


41-41: 개선 확인: null 안전성이 추가되었습니다.

이전 리뷰 코멘트에서 지적된 user 파라미터의 null 체크가 올바르게 적용되었습니다.

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

59-66: 개선 확인: 카테고리 검증 로직이 추가되었습니다.

이전 리뷰에서 요청된 PostCategory.valueOf() 예외 처리가 올바르게 구현되었습니다. 잘못된 카테고리 입력 시 적절한 에러 응답을 반환합니다.


101-106: LGTM! 레포지토리 메서드 호출이 올바르게 업데이트되었습니다.

카테고리 리팩토링에 맞춰 findByPostCategoryContaining 계열 메서드로 정확하게 변경되었습니다.

.createdAt(post.getCreatedAt())
.nickname(user.getNickname())
.categoryList(post.getCategoryList())
.nickname(post.getUser().getNickname())
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

post.getUser()가 null일 경우 NPE 가능성

post.getUser().getNickname() 호출 시 post.getUser()가 null이면 NullPointerException이 발생합니다. Post 엔티티의 user 관계가 항상 존재한다고 보장되지 않는다면 방어적 처리가 필요합니다.

다음 diff를 적용하여 안전하게 처리하세요:

-        .nickname(post.getUser().getNickname())
+        .nickname(post.getUser() != null ? post.getUser().getNickname() : "알 수 없음")
🤖 Prompt for AI Agents
In src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java around line
40, calling post.getUser().getNickname() can throw NPE if post.getUser() is
null; update the mapping to handle a null user (e.g., use a null-check or
Optional.ofNullable(post.getUser()).map(User::getNickname).orElse(null)) and
pass null or a safe default into .nickname(...) so the mapper never dereferences
a null user.

@uni-j-uni uni-j-uni changed the title Feature/exchange ✨Feat: 교환 관련 API 기능 개발 Dec 13, 2025
@uni-j-uni uni-j-uni merged commit c072e63 into main Dec 13, 2025
3 checks passed
@uni-j-uni uni-j-uni deleted the feature/exchange branch December 13, 2025 14:17
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: 교환 관련 API 기능 개발

1 participant