Skip to content

Conversation

@daye200
Copy link
Collaborator

@daye200 daye200 commented May 26, 2025

#96

Summary by CodeRabbit

  • 신규 기능

    • 북마크 생성, 목록 조회 및 장소 추가 API가 도입되어, 사용자가 이미지와 메타데이터를 포함한 북마크를 관리할 수 있습니다.
    • 이미지 업로드 및 파일 저장 기능이 추가되어, 업로드된 이미지를 서버에 저장하고 /uploads/** 경로를 통해 접근할 수 있습니다.
    • 장소 목록을 페이지네이션하여 조회할 수 있는 엔드포인트가 추가되었습니다.
  • 설정 및 보안

    • 파일 업로드 디렉터리 경로를 환경설정에서 지정할 수 있도록 설정이 추가되었습니다.
    • 업로드된 이미지에 대한 GET 요청이 인증 없이 허용되어 접근성이 향상되었습니다.
  • 문서화

    • Swagger/OpenAPI 기반의 API 문서가 북마크 및 장소 관련 엔드포인트에 적용되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented May 26, 2025

"""

Walkthrough

이번 변경 사항은 파일 업로드 경로 설정, 정적 리소스 제공, 북마크(Bookmark) 관리 REST API 및 서비스, 장소(Place) 페이징 조회 API 추가, 보안 설정 개선, 리포지토리 어노테이션 및 엔티티 필드 변경 등을 포함합니다.

Changes

파일/경로 변경 요약
.../config/FileStorageConfig.java
.../config/FileStorageProperties.java
.../config/StaticResourceConfig.java
파일 업로드 경로 설정 및 정적 리소스 제공을 위한 스프링 설정 클래스 추가
.../controller/BookmarkController.java
.../service/BookmarkService.java
.../model/dto/BookmarkRequest.java
.../model/dto/BookmarkResponse.java
.../repository/BookmarkRepository.java
.../service/FileStorageService.java
북마크 생성, 조회, 장소 추가 API와 서비스, DTO, 파일 저장 서비스 및 리포지토리 신규 도입
.../controller/PlaceController.java
.../service/PlaceService.java
.../repository/PlaceRepository.java
장소 페이징 조회 API 추가, 서비스 메서드 및 리포지토리 ID 타입 변경과 조회 메서드 추가
.../config/WebSecurityConfig.java 업로드 파일(GET /uploads/**) 경로에 대한 공개 접근 허용 및 기본 CORS 설정 적용
.../repository/MemberRepository.java
.../repository/RefreshTokenRepository.java
리포지토리 인터페이스에 @repository 어노테이션 추가
src/main/resources/application.yml 파일 업로드 경로(file.upload-dir) 환경설정 추가
.../model/postgres/Bookmark.java Bookmark 엔티티 PK 필드명 변경, 지연로딩 적용, Place와 다대다 관계 추가
.../model/postgres/Place.java Place 엔티티 placeId 필드에 컬럼명 및 제약조건 추가
.../util/exception/ErrorCode.java 북마크, 장소, 파일 관련 신규 에러 코드 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as BookmarkController
    participant Service as BookmarkService
    participant Storage as FileStorageService
    participant Repo as BookmarkRepository

    Client->>Controller: POST /api/bookmark/create (multipart/form-data)
    Controller->>Service: create(CustomUserDetails, BookmarkRequest)
    Service->>Storage: storeImage(MultipartFile)
    Storage-->>Service: "/uploads/{filename}"
    Service->>Repo: save(Bookmark)
    Repo-->>Service: Bookmark
    Service-->>Controller: BookmarkResponse
    Controller-->>Client: 201 Created + BookmarkResponse
Loading
sequenceDiagram
    participant Client
    participant Controller as BookmarkController
    participant Service as BookmarkService
    participant Repo as BookmarkRepository

    Client->>Controller: GET /api/bookmark/mylist
    Controller->>Service: listByMember(CustomUserDetails)
    Service->>Repo: findAllByMember(Member)
    Repo-->>Service: List<Bookmark>
    Service-->>Controller: List<BookmarkResponse>
    Controller-->>Client: 200 OK + List<BookmarkResponse>
Loading

Possibly related PRs

  • 즐겨찾기_entity_추가 #102: Bookmark 엔티티 클래스의 필드명 변경, fetch 타입 수정, Place와의 다대다 관계 추가와 관련된 PR로 본 PR에서 Bookmark 엔티티 변경과 직접 연관됨.

Poem

🐰
파일 업로드 바람 타고
북마크가 폴짝, 새로 태어나요!
이미지도 척, 태그도 쏙—
장소 목록 한눈에 쏙쏙!
새 경로, 새 보안,
토끼는 기뻐 깡총깡총!

"""

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link

PR 빌드 검증 결과: ✅ 빌드 성공

빌드 검증이 완료되었습니다.

1 similar comment
@github-actions
Copy link

PR 빌드 검증 결과: ✅ 빌드 성공

빌드 검증이 완료되었습니다.

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

🔭 Outside diff range comments (1)
src/main/java/jombi/freemates/service/PlaceService.java (1)

78-89: 🛠️ Refactor suggestion

루프 내 개별 save 호출로 인한 퍼포먼스 저하 가능성

placeRepository.save(place)를 장소 개수만큼 반복 호출하면 DB round-trip 이 반복되어 성능이 떨어집니다. 모든 Place 객체의 필드만 수정한 뒤 한꺼번에 saveAll 또는 flush를 사용하는 방식이 효율적입니다. 예시 diff:

-    for (Place place : places) {
+    List<Place> updated = new ArrayList<>();
+    for (Place place : places) {
       ...
-      placeRepository.save(place);
+      updated.add(place);
       ...
     }
+    placeRepository.saveAll(updated);

또한, 대용량일 경우 배치 크기를 지정하거나 chunking 을 고려해 주세요.

♻️ Duplicate comments (1)
src/main/java/jombi/freemates/model/constant/PinColor.java (1)

6-17: Enum 직렬화 방식 통일 필요

Visibility enum 과 마찬가지로, 클라이언트가 “빨강/노랑 …” 한글 값을 직접 받을 필요가 있을 경우 @JsonValue 메서드를 추가해 직렬화 방식을 맞춰주세요.

+import com.fasterxml.jackson.annotation.JsonValue;

 @AllArgsConstructor
 @Getter
 public enum PinColor {
   RED("빨강"),
   YELLOW("노랑"),
   GREEN("초록"),
   BLUE("파랑"),
   PURPLE("보라"),
   PINK("핑크");

   private final String description;

+  @JsonValue
+  public String jsonValue() {
+    return description;
+  }
 }
🧹 Nitpick comments (22)
src/main/java/jombi/freemates/model/constant/Visibility.java (1)

6-14: 🎨 Enum 직렬화용 @jsonvalue 메서드 추가 권장

현재 Enum 이름(PUBLIC, PRIVATE)이 그대로 JSON 으로 노출될 가능성이 높습니다. 클라이언트가 “공개/비공개” 한글 값을 직접 받기를 원한다면 @JsonValue 또는 커스텀 @JsonSerialize 를 지정해주는 편이 안전합니다.

+import com.fasterxml.jackson.annotation.JsonValue;

 @AllArgsConstructor
 @Getter
 public enum Visibility {
     PUBLIC("공개"),
     PRIVATE("비공개");

     private final String description;

+    @JsonValue
+    public String jsonValue() {
+        return description;
+    }
 }
src/main/java/jombi/freemates/service/PlaceFetchService.java (1)

120-121: 빈 리스트 생성 시 Collections.emptyList() 사용 고려

new ArrayList<>() 는 불필요한 가변 리스트 객체를 매번 생성합니다.
해당 필드는 초기 상태에서 수정되지 않는다면 Collections.emptyList() 로 불변 객체를 제공해 GC 부하를 줄일 수 있습니다.

-            .tags(new ArrayList<>())
+            .tags(Collections.emptyList())

단, 이후 JPA 영속 상태에서 tags 를 수정할 필요가 있다면 그대로 두셔도 무방합니다.

src/main/java/jombi/freemates/repository/BookmarkRepository.java (1)

11-13: 페이징 메서드 추가 고려

findAllByMember(Member member) 는 사용자가 북마크를 많이 보유할 경우 한 번에 모든 데이터를 불러오게 됩니다.
장기적으로는 Page<Bookmark> 를 반환하도록 오버로드하거나 Slice 기반 쿼리를 추가하여 페이징을 지원하는 것이 성능‧트래픽 측면에서 안전합니다.

+ Page<Bookmark> findAllByMember(Member member, Pageable pageable);
src/main/java/jombi/freemates/model/postgres/Place.java (1)

3-15: 사용되지 않는 import 정리 필요

org.checkerframework.common.aliasing.qual.Unique 가 현재 클래스에서 사용되지 않습니다.
IDE-level 검사에서는 통과할 수 있으나, CI 단계에서 unused-import 규칙이 걸려 있다면 빌드가 실패할 수 있습니다.

src/main/java/jombi/freemates/config/StaticResourceConfig.java (1)

8-19: 필드 주입 대신 생성자 주입으로 전환 권장

@Value 필드 주입은 테스트 작성과 불변성 확보에 불리합니다. Lombok @RequiredArgsConstructor(또는 명시적 생성자)와 final 키워드를 사용해 생성자 주입으로 전환하면 가독성과 테스트 용이성이 향상됩니다.

- @Configuration
-public class StaticResourceConfig implements WebMvcConfigurer {
-  @Value("${file.upload-dir}")        // application.yml 에 정의된 경로
-  private String uploadDir;
+@Configuration
+@RequiredArgsConstructor
+public class StaticResourceConfig implements WebMvcConfigurer {
+  @Value("${file.upload-dir}")        // application.yml 에 정의된 경로
+  private final String uploadDir;
src/main/java/jombi/freemates/model/dto/BookmarkRequest.java (2)

9-10: 불필요한 import 제거 권장

@RequiredArgsConstructor는 현재 클래스에서 사용되지 않고 있습니다. IDE 경고를 줄이고 가독성을 높이기 위해 사용하지 않는 import 문을 삭제해 주세요.


14-19: @ToStringMultipartFile 노출은 로그 과다·보안 위험

@ToString이 모든 필드를 문자열로 출력하므로, 업로드된 파일 메타데이터가 로그에 그대로 남을 수 있습니다. 파일 크기에 따라 성능 저하도 발생할 수 있습니다.

-@ToString
+@ToString(exclude = "file")

또는 필드 수준에 @ToString.Exclude를 추가해 주세요.

src/main/java/jombi/freemates/model/dto/BookmarkResponse.java (1)

13-18: 응답 DTO 불변(immutable) 설계 고려

API 응답 객체는 주로 읽기 전용으로 사용되므로, @Setter를 제거하고 @Builder·@AllArgsConstructor만으로 값을 주입하면 불변성이 확보되어 추후 사이드이펙트 위험을 줄일 수 있습니다.

src/main/java/jombi/freemates/model/dto/KakaoPlaceCrawlDetail.java (1)

21-22: 가변 리스트 노출로 인한 캡슐화 깨짐

tags 필드가 ArrayList로 그대로 노출돼 외부 코드에서 자유롭게 수정할 수 있습니다. DTO의 무결성을 보장하려면 불변 리스트로 감싸거나 defensive copy를 반환하도록 변경해 주세요.

-  @Builder.Default
-  private List<String> tags = new ArrayList<>();
+  @Builder.Default
+  private List<String> tags = new ArrayList<>();
+
+  public List<String> getTags() {
+    return List.copyOf(tags); // Java 10+
+  }
+
+  public void setTags(List<String> tags) {
+    this.tags = new ArrayList<>(tags);
+  }

또는 @Singular를 사용해 빌더 단계에서만 추가하도록 하는 방법도 있습니다.

src/main/java/jombi/freemates/service/PlaceService.java (2)

89-89: 로그 메시지 형식 개선 제안

현재 "이미지 저장된 거 {} 카카오에서 크롤링한거{}" 는 중간 공백이 없어 가독성이 떨어집니다.

-log.debug("이미지 저장된 거 {} 카카오에서 크롤링한거{}", ...);
+log.debug("저장된 이미지: {}, 크롤링 이미지: {}", ...);

소소하지만 디버깅 시 가독성이 크게 향상됩니다.


96-102: getAllPlaces() 는 페이징/캐싱 고려 필요

전체 레코드를 그대로 반환하면 데이터가 많을 때 메모리 사용량과 응답 시간이 급격히 증가합니다.

  • Spring Data Pageable 파라미터를 받아 페이징 처리
  • 혹은 Redis/캐시 계층 도입
    을 검토해 주세요.
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (2)

149-157: tags 추출 로직 간결화 가능

Stream API 를 활용하면 가독성과 성능(내부 루프 최적화) 모두 개선됩니다.

-List<String> tags = new ArrayList<>();
-...
-  JsonArray tagsArray = addInfo.getAsJsonArray("tags");
-  for (JsonElement el : tagsArray) {
-    tags.add(el.getAsString());
-  }
+List<String> tags = StreamSupport.stream(addInfo.getAsJsonArray("tags").spliterator(), false)
+                                 .map(JsonElement::getAsString)
+                                 .collect(Collectors.toList());

또한, tags 리스트를 그대로 DTO 에 넘기면 외부 수정 가능성이 있으므로 Collections.unmodifiableList(tags) 를 전달해 불변성을 확보하는 것도 고려해 주세요.


110-117: 파라미터명 변경과 메서드명 불일치

메서드명은 crawlByPlaceId 인데 파라미터는 kakaoPlaceId 로 변경되었습니다.
혼동을 피하려면 메서드명도 crawlByKakaoPlaceId 로 맞추거나 Javadoc 에 명확히 기재하는 것이 좋습니다.

src/main/java/jombi/freemates/config/FileStorageProperties.java (1)

8-16: 프로퍼티 값 검증 추가 제안

uploadDir 가 설정되지 않았을 때 애플리케이션이 부팅 직후 NPE 로 종료될 수 있습니다.
@Validated@NotBlank 를 사용해 필수 프로퍼티임을 명시하면 조기 검증이 가능합니다.

 @Configuration
 @ConfigurationProperties(prefix = "file")
+@Validated
 ...
-public class FileStorageProperties {
-  private String uploadDir;
+public class FileStorageProperties {
+  @NotBlank
+  private String uploadDir;

이를 위해 org.springframework.validation.annotation.Validatedjakarta.validation.constraints.NotBlank 를 추가해 주세요.

src/main/java/jombi/freemates/config/FileStorageConfig.java (1)

13-23: 프로퍼티 검증 및 예외 유형 명확화 제안

props.getUploadDir()null 이거나 공백일 경우 프로젝트 루트가 생성될 수 있습니다.
또한 현재 RuntimeException 은 모호하므로 서비스 로직에서 구분하기 어렵습니다.

+    String uploadDir = props.getUploadDir();
+    Assert.hasText(uploadDir, "'file.upload-dir' 프로퍼티가 비어있습니다");
+
-    Path path = Paths
-        .get(props.getUploadDir())
+    Path path = Paths
+        .get(uploadDir)
         .toAbsolutePath()
         .normalize();
@@
-    } catch (IOException e) {
-      throw new RuntimeException("업로드 디렉터리 생성 실패", e);
+    } catch (IOException e) {
+      throw new FileSystemException(
+          "업로드 디렉터리 생성 실패: " + path, null, e.getMessage());
     }

org.springframework.util.Assertjava.nio.file.FileSystemException 사용을 고려해 보세요.

src/main/java/jombi/freemates/service/FileStorageService.java (1)

16-17: uploadDir 디렉터리 존재 보장 확인

FileStorageConfig에서 디렉터리를 미리 생성하겠지만, 안전을 위해 storeImage 내부에서 Files.createDirectories(uploadDir) 로 한 번 더 보장하는 편이 좋습니다. 컨테이너 재배포로 디렉터리가 유실될 경우를 대비할 수 있습니다.

src/main/java/jombi/freemates/model/postgres/Bookmark.java (1)

25-28: 엔티티 PK 명칭 혼동 우려

필드명이 favoriteId인데 엔티티명은 Bookmark입니다. 팀 내 다른 도메인(예: Favorite)과 혼동될 여지가 있으니 bookmarkId 등으로 통일하는 것을 권장합니다.

src/main/java/jombi/freemates/service/BookmarkService.java (2)

52-66: 중복 매핑 로직 추출 제안

create, listByMember 두 곳에서 Bookmark → BookmarkResponse 변환 코드가 중복됩니다. 전용 메서드 또는 ModelMapper/MapStruct 를 활용해 중복을 제거하면 유지보수성이 높아집니다.


52-54: @Transactional 읽기 전용 옵션 고려

listByMember 는 조회만 수행하므로

@Transactional(readOnly = true)

를 사용하면 플러시 검사 비용을 줄일 수 있습니다.

src/main/java/jombi/freemates/controller/BookmarkController.java (3)

15-20: 불필요한 import 정리 필요

ResponseEntity@Controller는 본 파일에서 사용되지 않습니다. 사용하지 않는 import를 그대로 두면 IDE / 빌드 도구에서 경고가 발생하고 가독성이 떨어집니다.

-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Controller;

45-66: API 문서 주석과 실제 코드 사이의 괴리

@Operation 설명 블록의 에러코드 섹션이 비어 있습니다. 스웨거 문서에 빈 섹션이 노출되면 사용자 혼란을 야기할 수 있으므로 실제 예외 케이스(예: 400 Bad Request, 401 Unauthorized 등)를 기입하거나 섹션을 제거해 주세요.
또한 summary 가 “즐겨찾기 생성”이지만, REST 에서는 자원 생성 자체가 이미 POST /api/bookmark 로 암시됩니다. path 에 /create 를 붙이는 것이 과연 필요한지 함께 고려해 주세요.


100-104: RESTful URL·HTTP 메서드 설계 개선 제안

POST /api/bookmark/create, GET /api/bookmark/list 처럼 동사형 하위 경로를 두는 것은 REST 원칙(자원=명사)을 벗어납니다. 일반적으로는

  • 생성: POST /api/bookmarks
  • 사용자별 조회: GET /api/bookmarks (AuthenticationPrincipal 로 필터링)

과 같이 명사형 경로로 충분합니다. URL 을 변경하면 클라이언트·서버 모두 직관성이 높아집니다. 배포 전 API 호환성 영향 여부를 검토해 주세요.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3a6fad and 8cfd12f.

📒 Files selected for processing (19)
  • src/main/java/jombi/freemates/config/FileStorageConfig.java (1 hunks)
  • src/main/java/jombi/freemates/config/FileStorageProperties.java (1 hunks)
  • src/main/java/jombi/freemates/config/StaticResourceConfig.java (1 hunks)
  • src/main/java/jombi/freemates/config/WebSecurityConfig.java (2 hunks)
  • src/main/java/jombi/freemates/controller/BookmarkController.java (1 hunks)
  • src/main/java/jombi/freemates/controller/PlaceController.java (2 hunks)
  • src/main/java/jombi/freemates/model/constant/PinColor.java (1 hunks)
  • src/main/java/jombi/freemates/model/constant/Visibility.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/BookmarkRequest.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/BookmarkResponse.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/KakaoPlaceCrawlDetail.java (2 hunks)
  • src/main/java/jombi/freemates/model/postgres/Bookmark.java (1 hunks)
  • src/main/java/jombi/freemates/model/postgres/Place.java (2 hunks)
  • src/main/java/jombi/freemates/repository/BookmarkRepository.java (1 hunks)
  • src/main/java/jombi/freemates/service/BookmarkService.java (1 hunks)
  • src/main/java/jombi/freemates/service/FileStorageService.java (1 hunks)
  • src/main/java/jombi/freemates/service/PlaceFetchService.java (2 hunks)
  • src/main/java/jombi/freemates/service/PlaceService.java (1 hunks)
  • src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (3 hunks)
🔇 Additional comments (3)
src/main/java/jombi/freemates/config/StaticResourceConfig.java (1)

15-19: 리소스 핸들러 경로 주의

addResourceLocations("file:" + uploadDir + "/") 에서

  1. uploadDir 값이 슬래시로 끝나는 경우 // 가 중복될 수 있습니다.
  2. OS 마다 경로 구분자가 달라 발생할 수 있는 문제를 Paths.get(uploadDir).toUri().toString() 으로 예방할 수 있습니다.

추가 검증을 권합니다.

src/main/java/jombi/freemates/service/PlaceService.java (1)

86-86: setTags 호출 시 Null 보호 필요 여부 확인

크롤링 결과가 null 이거나 빈 리스트일 수도 있으므로, place.setTags() 전에 tagsnull 인지 확인하거나 Collections.emptyList() 로 치환하는 방어 로직을 추가하면 NPE 를 예방할 수 있습니다.

src/main/java/jombi/freemates/config/WebSecurityConfig.java (1)

34-35:

Details

❌ Incorrect review comment

CORS 기본값만으로는 프론트엔드 Preflight 실패 가능

cors(withDefaults())allowedOrigins("*") 가 아니며, 표준 GET/HEAD 만 허용합니다.
프론트엔드에서 Authorization 헤더를 보내거나 PUT/DELETE 요청을 사용할 경우 403 이 발생할 수 있으니, 별도 CorsConfigurationSource 빈을 정의해 정확한 도메인·헤더·메서드를 등록하는 편이 안전합니다.


CORS 기본 설정 확인을 위해 다음 정보를 조회합니다:


🌐 Web query:

What are the default values for CorsConfiguration (allowedOrigins, allowedMethods, allowedHeaders) when using http.cors(withDefaults()) in Spring Security?

💡 Result:

In Spring Security, when you use http.cors(withDefaults()), the default CorsConfiguration applied has the following default values:

  • allowedOrigins: * (allows all origins)
  • allowedMethods: GET, HEAD, POST (only these HTTP methods are allowed by default)
  • allowedHeaders: * (allows all headers)

To summarize:

  • allowedOrigins: all origins allowed by default (*)
  • allowedMethods: GET, HEAD, POST
  • allowedHeaders: all headers allowed by default (*)

This means that by default, the CORS support in Spring Security permits requests from any origin, allows GET, HEAD, and POST methods, and permits any headers. If you want to customize these values, you'll need to provide your own CorsConfigurationSource.


CORS 기본 설정 재확인 필요

http.cors(withDefaults())의 기본 CorsConfiguration은 다음과 같습니다:

  • allowedOrigins: “*” (모든 출처 허용)
  • allowedMethods: GET, HEAD, POST
  • allowedHeaders: “*” (모든 헤더 허용)

따라서 PUT/DELETE 등 기타 HTTP 메서드를 사용하거나, 사전 요청(Preflight) 시점에 CORS 설정이 필요한 요청을 보낼 경우 403 에러가 발생할 수 있습니다.
별도 CorsConfigurationSource 빈을 정의하여 허용할 도메인, 메서드, 헤더(그리고 자격증명 사용 시 allowCredentials(true))를 명시적으로 등록하세요.

  • 파일: src/main/java/jombi/freemates/config/WebSecurityConfig.java
  • 라인: 34–35

Likely an incorrect or invalid review comment.

Comment on lines 21 to 25
private String title;
private String description;
private PinColor pinColor;
private Visibility visibility;
private MultipartFile file;
Copy link

@coderabbitai coderabbitai bot May 26, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

요청 DTO에 Bean Validation 추가 필요

title, description, pinColor, visibility 값이 null/빈 문자열로 전달되는 경우 서비스 계층에서 NPE 또는 비즈니스 오류가 발생할 가능성이 있습니다. DTO 단에서 검증을 거치는 편이 안전합니다.

+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;

   private String title;
   private String description;
-  private PinColor pinColor;
-  private Visibility visibility;
+  @NotNull
+  private PinColor pinColor;
+  @NotNull
+  private Visibility visibility;
+
+  @NotBlank
+  private String title;
+  private String description;

컨트롤러 메서드에 @Valid를 붙이면 자동으로 검증됩니다.

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

🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/model/dto/BookmarkRequest.java around lines 21
to 25, the DTO fields lack Bean Validation annotations, which can lead to null
or empty values causing NPE or business errors downstream. Add appropriate
validation annotations such as @NotBlank or @NotNull to each field to enforce
non-null and non-empty constraints. Ensure that the controller method handling
this DTO has the @Valid annotation to trigger validation.

Copy link
Member

Choose a reason for hiding this comment

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

중요한건 아님

Copy link

Choose a reason for hiding this comment

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

@Cassiiopeia 네, 알겠습니다. 해당 제안은 넘어가겠습니다.

    /|   /|  
   (  . .)
    )___(
   /     \
  (  o   o )  
   \   -   /
    \_____/

Comment on lines 32 to 36
@GetMapping("/all")
public ResponseEntity<List<Place>> getAllPlaces() {
List<Place> places = placeService.getAllPlaces();
return ResponseEntity.ok(places);
}
Copy link

@coderabbitai coderabbitai bot May 26, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

엔티티 직접 반환·페이징 미구현으로 인한 성능·보안 위험

List<Place> 엔티티를 그대로 반환하면

  1. LAZY 필드의 순환 직렬화로 인한 StackOverflow/쿼리 난수화를 유발할 수 있고,
  2. DB 컬럼 전부(내부 식별자·FK 등) 가 노출됩니다.
    또한 전체 데이터를 한 번에 내려주면 레코드 수가 늘어날수록 응답 지연·메모리 폭증이 발생합니다.
-  @GetMapping("/all")
-  public ResponseEntity<List<Place>> getAllPlaces() {
-    List<Place> places = placeService.getAllPlaces();
-    return ResponseEntity.ok(places);
-  }
+  @GetMapping
+  public ResponseEntity<Page<PlaceDto>> getPlaces(
+      @PageableDefault(size = 20) Pageable pageable) {
+    Page<PlaceDto> page = placeService.getPlaces(pageable)
+                                      .map(PlaceDto::from);
+    return ResponseEntity.ok(page);
+  }
  • DTO 로 치환하여 노출 필드를 제한하고,
  • Pageable 로 페이징 처리해 주세요.
    추가로, @PreAuthorize("hasRole('ROLE_USER')")권한을 부여하지 않으면 인증 없이 모든 사용자가 장소 전체 목록을 열람할 수 있습니다.

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

🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/controller/PlaceController.java around lines 32
to 36, returning the entity list directly without DTO transformation and
pagination poses performance and security risks. Refactor the method to convert
Place entities into a DTO that exposes only necessary fields, and implement
pagination using Pageable to limit data volume. Also, add appropriate
authorization annotations like @PreAuthorize to restrict access to authenticated
users with specific roles.

Copy link
Member

Choose a reason for hiding this comment

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

@daye200 저도 이건 동의

페이지네이션 쓰세요 파라미터는 저렇게 받지말고

pageSize
pageCount

이거두개였나?

페이지 번호, 페이지크기

이거 파라미터 두개받고
Page<> 객체 반환받아주세요

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines 18 to 29
public String storeImage(MultipartFile file) {
String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
String filename = UUID.randomUUID() + "." + ext;
Path target = uploadDir.resolve(filename);
try {
file.transferTo(target);
} catch (Exception e) {
throw new RuntimeException("파일 저장 실패", e);
}
// 예: "/uploads/xxxxx.jpg" 같은 URL 경로 리턴
return "/uploads/" + filename;
}
Copy link

@coderabbitai coderabbitai bot May 26, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

파일 검증·예외 처리 보강 필요

  1. file.getOriginalFilename()null일 경우 NPE가 발생하고, 확장자(ext)도 null이 되어 filename"uuid.null" 형식으로 저장됩니다.
  2. 업로드 용량·MIME 타입·빈 파일 여부를 체크하지 않아 악성/빈 파일이 저장될 수 있습니다.
  3. catch (Exception e)로 모든 예외를 삼켜 RuntimeException으로 래핑하면, 상위 계층에서 원인 분석이 어려워집니다.
@@
-  public String storeImage(MultipartFile file) {
-    String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
-    String filename = UUID.randomUUID() + "." + ext;
+  public String storeImage(MultipartFile file) {
+    if (file == null || file.isEmpty()) {
+      throw new IllegalArgumentException("업로드할 파일이 없습니다.");
+    }
+    String originalName = StringUtils.cleanPath(file.getOriginalFilename());
+    String ext = StringUtils.getFilenameExtension(originalName);
+    if (ext == null) {
+      throw new IllegalArgumentException("지원하지 않는 확장자입니다: " + originalName);
+    }
+    String filename = UUID.randomUUID() + "." + ext.toLowerCase();
@@
-    } catch (Exception e) {
-      throw new RuntimeException("파일 저장 실패", e);
+    } catch (IOException | IllegalStateException e) {
+      throw new UncheckedIOException("파일 저장 실패", e);
     }

이렇게 하면 입력 검증·예외 원인 파악이 용이해집니다.

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

🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/FileStorageService.java around lines 18
to 29, the method storeImage does not handle the case where
getOriginalFilename() returns null, leading to potential NullPointerException
and filenames like "uuid.null". Additionally, it lacks validation for file size,
MIME type, and empty files, and catches all exceptions broadly, which hampers
debugging. To fix this, add null checks for getOriginalFilename() and validate
the filename and extension, implement checks for file size, MIME type, and empty
files before saving, and catch specific exceptions instead of all, providing
clearer error messages for easier troubleshooting.

Copy link
Member

Choose a reason for hiding this comment

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

이런오류는 함 예외처리해야되긴함

Copy link

Choose a reason for hiding this comment

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

@Cassiiopeia 네, 맞습니다! 파일 업로드 관련 코드는 특히 예외 처리가 중요하죠.

현재 코드에서 개선이 필요한 부분들:

  • null 파일명 처리
  • 파일 크기/타입 검증
  • 구체적인 예외 타입별 처리

이 부분들을 개선한 코드를 작성해드릴까요? 아니면 나중에 별도 PR로 처리하실 건가요?


✏️ Learnings added
Learnt from: Cassiiopeia
PR: freeMates/FreeMates_Backend#103
File: src/main/java/jombi/freemates/service/FileStorageService.java:18-29
Timestamp: 2025-05-27T01:22:10.869Z
Learning: 파일 업로드 서비스에서는 null 파일명, 파일 크기/타입 검증, 구체적인 예외 처리가 필요하다고 사용자(Cassiiopeia)가 동의함. 한국어 환경에서 파일 업로드 관련 예외 처리의 중요성을 인정받음.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 24 to 37
public BookmarkResponse create(CustomUserDetails customUserDetails, BookmarkRequest req) {
// 파일 저장 → URL
String imageUrl = storage.storeImage(req.getFile());
Member member = customUserDetails.getMember();

// 엔티티 생성 및 저장
Bookmark b = Bookmark.builder()
.member(member)
.title(req.getTitle())
.description(req.getDescription())
.pinColor(req.getPinColor())
.visibility(req.getVisibility())
.imageUrl(imageUrl)
.build();
Copy link

@coderabbitai coderabbitai bot May 26, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

요청 데이터·파일 검증 누락

req.getFile()null 이거나 빈 파일이면 storeImage 내부에서 예외가 발생합니다. 비즈니스 계층에서 먼저 검증하여 사용자 친화적인 오류 메시지를 반환하세요.

또한, 같은 로직을 테스트 코드 없이 신뢰하기 어렵습니다. 단위 테스트에서

  • 정상 생성
  • 빈 파일 업로드
  • 확장자 누락
    케이스를 검증해 주시기 바랍니다.

storeImage 메서드가 null 또는 빈 파일을 어떻게 처리하는지 확인하기 위해 아래 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
# storeImage 호출 위치 및 구현 정의 검색
rg "storeImage" -n src

Length of output: 255


storeImage 메서드가 null 또는 빈 파일을 어떻게 처리하는지 확인하기 위해 아래 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
rg -n -A50 "public String storeImage" src/main/java/jombi/freemates/service/FileStorageService.java

Length of output: 581


파일 업로드 입력 검증 로직 추가 필요

현재 BookmarkService#create 메서드에서 req.getFile()이 null 또는 빈 파일인지 검증하지 않고 바로
storage.storeImage를 호출하고, 실제 FileStorageService#storeImage에도 null·empty 파일과 확장자 누락 처리가 없어
NPE 또는 “파일 저장 실패” 런타임 예외가 발생할 수 있습니다. 사용자 친화적인 오류 메시지와 안정적인 동작을 위해 아래와 같이 수정을 권장합니다.

  • src/main/java/jombi/freemates/service/BookmarkService.java
    create 메서드 시작부에 file == null || file.isEmpty() 검증 추가 (라인 24–27)

  • src/main/java/jombi/freemates/service/FileStorageService.java
    storeImage 진입부에 null·empty 파일 검증 및 ext == null(확장자 누락) 처리 로직 추가 (라인 18–21)

예시 변경(diff):

 public BookmarkResponse create(CustomUserDetails customUserDetails, BookmarkRequest req) {
-   String imageUrl = storage.storeImage(req.getFile());
+   MultipartFile file = req.getFile();
+   if (file == null || file.isEmpty()) {
+     throw new InvalidRequestException("업로드할 이미지를 선택해주세요.");
+   }
+   String imageUrl = storage.storeImage(file);
 public String storeImage(MultipartFile file) {
-   String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
+   if (file == null || file.isEmpty()) {
+     throw new InvalidRequestException("유효한 이미지를 업로드해주세요.");
+   }
+   String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
+   if (ext == null || ext.isBlank()) {
+     throw new InvalidRequestException("파일 확장자를 확인해주세요.");
+   }

단위 테스트에도 아래 케이스를 반드시 추가해주세요:

  • 정상 업로드 (유효 이미지)
  • file == null
  • 빈 파일 업로드
  • 확장자 누락
📝 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
public BookmarkResponse create(CustomUserDetails customUserDetails, BookmarkRequest req) {
// 파일 저장 → URL
String imageUrl = storage.storeImage(req.getFile());
Member member = customUserDetails.getMember();
// 엔티티 생성 및 저장
Bookmark b = Bookmark.builder()
.member(member)
.title(req.getTitle())
.description(req.getDescription())
.pinColor(req.getPinColor())
.visibility(req.getVisibility())
.imageUrl(imageUrl)
.build();
public BookmarkResponse create(CustomUserDetails customUserDetails, BookmarkRequest req) {
// 파일 저장 → URL
MultipartFile file = req.getFile();
if (file == null || file.isEmpty()) {
throw new InvalidRequestException("업로드할 이미지를 선택해주세요.");
}
String imageUrl = storage.storeImage(file);
Member member = customUserDetails.getMember();
// 엔티티 생성 및 저장
Bookmark b = Bookmark.builder()
.member(member)
.title(req.getTitle())
.description(req.getDescription())
.pinColor(req.getPinColor())
.visibility(req.getVisibility())
.imageUrl(imageUrl)
.build();
Suggested change
public BookmarkResponse create(CustomUserDetails customUserDetails, BookmarkRequest req) {
// 파일 저장 → URL
String imageUrl = storage.storeImage(req.getFile());
Member member = customUserDetails.getMember();
// 엔티티 생성 및 저장
Bookmark b = Bookmark.builder()
.member(member)
.title(req.getTitle())
.description(req.getDescription())
.pinColor(req.getPinColor())
.visibility(req.getVisibility())
.imageUrl(imageUrl)
.build();
public String storeImage(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new InvalidRequestException("유효한 이미지를 업로드해주세요.");
}
String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
if (ext == null || ext.isBlank()) {
throw new InvalidRequestException("파일 확장자를 확인해주세요.");
}
// ... the rest of the existing implementation ...
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/BookmarkService.java lines 24 to 37,
the create method calls storage.storeImage(req.getFile()) without validating if
the file is null or empty, which can cause runtime exceptions. Add a check at
the start of the method to verify if req.getFile() is null or empty and throw a
user-friendly exception if so. Also, review the storeImage method in
FileStorageService.java around lines 18 to 21 to add null, empty file, and
extension validation, throwing clear exceptions for invalid input. Additionally,
implement unit tests for normal upload, null file, empty file, and missing
extension cases to ensure robustness.

Copy link
Member

Choose a reason for hiding this comment

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

ㅋㅋ 근데 이런건 기본적으로 해줘야해

파일유효성 체크는 좀 필수긴함.. 로깅도 제대로

Copy link

Choose a reason for hiding this comment

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

@Cassiiopeia 맞습니다! 파일 검증과 로깅은 정말 기본이어야 하는 부분이네요.

현재 코드를 보면 @Slf4j는 있지만 실제로 로깅이 거의 없고, 파일 검증도 전혀 없는 상태입니다. 아래와 같이 종합적으로 개선하는 것을 제안합니다:

BookmarkService.java 개선:

@Transactional
public BookmarkResponse create(CustomUserDetails customUserDetails, BookmarkRequest req) {
    log.info("북마크 생성 요청 - 사용자: {}, 제목: {}", 
        customUserDetails.getUsername(), req.getTitle());
    
    // 파일 검증
    MultipartFile file = req.getFile();
    if (file == null || file.isEmpty()) {
        log.warn("파일 업로드 실패 - 빈 파일: 사용자 {}", customUserDetails.getUsername());
        throw new InvalidRequestException("업로드할 이미지를 선택해주세요.");
    }
    
    try {
        String imageUrl = storage.storeImage(file);
        log.info("이미지 저장 완료 - URL: {}", imageUrl);
        
        Member member = customUserDetails.getMember();
        Bookmark b = Bookmark.builder()
            .member(member)
            .title(req.getTitle())
            .description(req.getDescription())
            .pinColor(req.getPinColor())
            .visibility(req.getVisibility())
            .imageUrl(imageUrl)
            .build();
        
        bookmarkRepository.save(b);
        log.info("북마크 생성 완료 - ID: {}, 사용자: {}", b.getFavoriteId(), member.getNickname());
        
        return BookmarkResponse.builder()
            .memberId(member.getMemberId())
            .nickname(member.getNickname())
            .imageUrl(imageUrl)
            .title(b.getTitle())
            .description(b.getDescription())
            .pinColor(b.getPinColor())
            .visibility(b.getVisibility())
            .build();
            
    } catch (Exception e) {
        log.error("북마크 생성 실패 - 사용자: {}, 오류: {}", 
            customUserDetails.getUsername(), e.getMessage(), e);
        throw e;
    }
}

FileStorageService.java 개선:

public String storeImage(MultipartFile file) {
    log.debug("이미지 저장 시작 - 파일명: {}, 크기: {}", 
        file.getOriginalFilename(), file.getSize());
        
    if (file == null || file.isEmpty()) {
        log.warn("이미지 저장 실패 - 유효하지 않은 파일");
        throw new InvalidRequestException("유효한 이미지를 업로드해주세요.");
    }
    
    String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
    if (ext == null || ext.isBlank()) {
        log.warn("이미지 저장 실패 - 확장자 없음: {}", file.getOriginalFilename());
        throw new InvalidRequestException("파일 확장자를 확인해주세요.");
    }
    
    // 이미지 파일 타입 검증 추가
    if (!isValidImageExtension(ext)) {
        log.warn("이미지 저장 실패 - 지원하지 않는 확장자: {}", ext);
        throw new InvalidRequestException("지원하지 않는 이미지 형식입니다.");
    }
    
    try {
        String filename = UUID.randomUUID() + "." + ext;
        Path targetLocation = uploadDir.resolve(filename);
        file.transferTo(targetLocation);
        
        String imageUrl = "/uploads/" + filename;
        log.info("이미지 저장 완료 - URL: {}", imageUrl);
        return imageUrl;
        
    } catch (Exception e) {
        log.error("이미지 저장 실패 - 파일: {}, 오류: {}", 
            file.getOriginalFilename(), e.getMessage(), e);
        throw new RuntimeException("파일 저장에 실패했습니다.", e);
    }
}

이렇게 하면 파일 검증 + 상세한 로깅이 모두 포함되어 운영 시 문제 추적이 훨씬 쉬워질 것 같습니다!


✏️ Learnings added
Learnt from: Cassiiopeia
PR: freeMates/FreeMates_Backend#103
File: src/main/java/jombi/freemates/service/BookmarkService.java:24-37
Timestamp: 2025-05-27T01:22:25.997Z
Learning: This codebase requires comprehensive file validation and proper logging as essential development standards. When implementing file upload functionality, both input validation (null, empty, file extension checks) and detailed logging (info, warn, error levels with contextual information) must be included.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 70 to 71
@ModelAttribute @Valid BookmarkRequest request
) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

@Valid 사용 효과가 없음

BookmarkRequest 필드에는 @NotBlank, @NotNull, @Size 등의 제약 어노테이션이 없으므로 @Valid 가 실제로 유효성 검사를 수행하지 않습니다. 필드 제약을 명시해 두면 컨트롤러 진입 전에 400 에러로 방어할 수 있어 안정성이 높아집니다.

🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/controller/BookmarkController.java around lines
70 to 71, the @Valid annotation on the BookmarkRequest parameter is ineffective
because the BookmarkRequest class lacks validation annotations like @NotBlank,
@NotNull, or @Size on its fields. To fix this, add appropriate validation
annotations to the fields within the BookmarkRequest class to enable meaningful
validation before controller logic executes.

@github-actions
Copy link

PR 빌드 검증 결과: ✅ 빌드 성공

빌드 검증이 완료되었습니다.

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/jombi/freemates/controller/BookmarkController.java (1)

70-71: @Valid 사용 효과가 없음

BookmarkRequest 필드에는 @NotBlank, @NotNull, @Size 등의 제약 어노테이션이 없으므로 @Valid 가 실제로 유효성 검사를 수행하지 않습니다. 필드 제약을 명시해 두면 컨트롤러 진입 전에 400 에러로 방어할 수 있어 안정성이 높아집니다.

🧹 Nitpick comments (2)
src/main/resources/application.yml (1)

31-33: [제안] 파일 저장 디렉토리 설정 네이밍 및 환경변수 활용 개선
현재 file.upload-dir 키는 다소 일반적이며, 운영환경별로 변경 시 설정 파일을 직접 수정해야 합니다.

  • 환경변수 오버라이드를 위해 기본값과 함께 ${...:...} 문법 사용을 권장합니다.
  • 설정 그룹명(file)이 다른 설정과 충돌할 수 있으므로, file-storage 등 보다 구체적인 prefix를 고려해보세요.

예시:

 file:
-  upload-dir: /tmp/uploads
+  upload-dir: ${UPLOAD_DIR:/tmp/uploads}

또는

-file:
-  upload-dir: ${UPLOAD_DIR:/tmp/uploads}
+# file-storage:
+file-storage:
+  upload-dir: ${UPLOAD_DIR:/tmp/uploads}
src/main/java/jombi/freemates/controller/BookmarkController.java (1)

84-84: 요약 문구를 더 명확하게 개선할 수 있습니다.

현재 "즐겨찾기 가져오기"보다는 "즐겨찾기 목록 조회"가 GET /list 엔드포인트의 기능을 더 정확하게 표현합니다.

-      summary = "즐겨찾기 가져오기",
+      summary = "즐겨찾기 목록 조회",
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cfd12f and 632f657.

📒 Files selected for processing (2)
  • src/main/java/jombi/freemates/controller/BookmarkController.java (1 hunks)
  • src/main/resources/application.yml (1 hunks)
🔇 Additional comments (3)
src/main/java/jombi/freemates/controller/BookmarkController.java (3)

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

필요한 라이브러리들이 정확히 임포트되어 있고, 패키지 구조도 적절합니다.


27-35: 컨트롤러 클래스 구조가 잘 설계되었습니다.

Swagger 문서화, REST 컨트롤러 어노테이션, 의존성 주입이 올바르게 구성되어 있습니다.


101-105: 인증된 사용자의 즐겨찾기만 조회하는 적절한 구현입니다.

JWT 인증을 통해 현재 사용자의 즐겨찾기만 조회하도록 구현되어 있어 보안이 적절히 고려되었습니다.

Comment on lines 66 to 67
@PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseStatus(HttpStatus.CREATED)
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

파일 업로드 엔드포인트의 적절한 구현을 확인합니다.

multipart/form-data를 사용한 파일 업로드와 HTTP 201 Created 상태 코드 사용이 적절합니다. 파일 크기 제한이나 허용 파일 형식에 대한 검증이 서비스 레이어에서 처리되고 있는지 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# Description: BookmarkService에서 파일 업로드 관련 검증 로직 확인
# Expected: 파일 크기, 형식 등의 검증 로직 존재

# BookmarkService의 create 메서드에서 파일 검증 로직 확인
ast-grep --pattern 'class BookmarkService {
  $$$
  create($_, $_) {
    $$$
  }
  $$$
}'

# 파일 업로드 관련 검증 로직 검색
rg -A 10 "file.*valid|upload.*valid|size.*check|type.*check" --type java

Length of output: 166


추가 검증: BookmarkService 파일 경로 확인 및 create 메서드 내 검증 로직 유무 확인


🏁 Script executed:

#!/bin/bash
# 1. BookmarkService 클래스 파일 위치 찾기
service_file=$(rg -l "class BookmarkService" --type java)

echo "🔍 BookmarkService 파일 경로: $service_file"

# 2. 클래스 상단부(1~200라인) 출력
echo "===== 클래스 상단부 (1~200라인) ====="
sed -n '1,200p' "$service_file"

# 3. create 메서드 정의 및 주변 코드 확인
echo "===== create 메서드 및 주변(20라인) ====="
rg -n "create\s*\(" -A 20 "$service_file"

# 4. MultipartFile 관련 검증 로직 검색
echo "===== MultipartFile 검증 로직 검색 ====="
rg -n "MultipartFile" -A 10 "$service_file"

Length of output: 3902


파일 업로드 검증 로직 누락 – 서비스 레이어에 파일 크기 및 형식 체크 추가 필요

현재 BookmarkService#create 메서드에는 파일 크기 제한이나 허용 형식에 대한 검증 로직이 없습니다. FileStorageService.storeImage 내부에 검증이 구현되어 있지 않다면, 다음 위치에 검증 로직을 추가해 주세요.

  • 대상 파일: src/main/java/jombi/freemates/service/BookmarkService.java
  • 메서드: public BookmarkResponse create(CustomUserDetails customUserDetails, BookmarkRequest req)

검증 예시

// 파일 크기 제한 (예: 최대 5MB)
if (req.getFile().getSize() > 5 * 1024 * 1024) {
  throw new FileValidationException("파일 크기가 5MB를 초과합니다.");
}
// 허용 형식 검사 (예: PNG, JPG)
String contentType = req.getFile().getContentType();
if (!List.of("image/png", "image/jpeg").contains(contentType)) {
  throw new FileValidationException("허용되지 않는 파일 형식입니다.");
}

위와 같은 검증을 서비스 레이어에 구현하거나, 컨트롤러 단에서 @Validated 및 커스텀 어노테이션을 활용해 처리해 주세요.

🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/controller/BookmarkController.java at lines
66-67, the file upload endpoint correctly uses multipart/form-data and returns
HTTP 201 Created, but there is no validation for uploaded file size or type. Add
validation logic in the BookmarkService#create method to check that the uploaded
file does not exceed a size limit (e.g., 5MB) and is of an allowed content type
(e.g., image/png or image/jpeg). Implement these checks before processing the
file to ensure invalid files are rejected early.

Copy link
Member

@Cassiiopeia Cassiiopeia left a comment

Choose a reason for hiding this comment

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

자잘구리한거 수정하고 머지 ㄱㄱ

Comment on lines 32 to 36
@GetMapping("/all")
public ResponseEntity<List<Place>> getAllPlaces() {
List<Place> places = placeService.getAllPlaces();
return ResponseEntity.ok(places);
}
Copy link
Member

Choose a reason for hiding this comment

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

@daye200 저도 이건 동의

페이지네이션 쓰세요 파라미터는 저렇게 받지말고

pageSize
pageCount

이거두개였나?

페이지 번호, 페이지크기

이거 파라미터 두개받고
Page<> 객체 반환받아주세요

Comment on lines 21 to 25
private String title;
private String description;
private PinColor pinColor;
private Visibility visibility;
private MultipartFile file;
Copy link
Member

Choose a reason for hiding this comment

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

중요한건 아님

private String description;
private PinColor pinColor;
private Visibility visibility;
// --- 아래 필드 추가 ---
Copy link
Member

Choose a reason for hiding this comment

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

검수검수 삭제요망

Comment on lines 24 to 37
public BookmarkResponse create(CustomUserDetails customUserDetails, BookmarkRequest req) {
// 파일 저장 → URL
String imageUrl = storage.storeImage(req.getFile());
Member member = customUserDetails.getMember();

// 엔티티 생성 및 저장
Bookmark b = Bookmark.builder()
.member(member)
.title(req.getTitle())
.description(req.getDescription())
.pinColor(req.getPinColor())
.visibility(req.getVisibility())
.imageUrl(imageUrl)
.build();
Copy link
Member

Choose a reason for hiding this comment

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

ㅋㅋ 근데 이런건 기본적으로 해줘야해

파일유효성 체크는 좀 필수긴함.. 로깅도 제대로

.build();
}

@Transactional(Transactional.TxType.SUPPORTS)
Copy link
Member

Choose a reason for hiding this comment

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

이건뭐요??

Comment on lines 18 to 29
public String storeImage(MultipartFile file) {
String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
String filename = UUID.randomUUID() + "." + ext;
Path target = uploadDir.resolve(filename);
try {
file.transferTo(target);
} catch (Exception e) {
throw new RuntimeException("파일 저장 실패", e);
}
// 예: "/uploads/xxxxx.jpg" 같은 URL 경로 리턴
return "/uploads/" + filename;
}
Copy link
Member

Choose a reason for hiding this comment

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

이런오류는 함 예외처리해야되긴함

@github-actions
Copy link

github-actions bot commented Jun 2, 2025

PR 빌드 검증 결과: ✅ 빌드 성공

빌드 검증이 완료되었습니다.

@github-actions
Copy link

github-actions bot commented Jun 2, 2025

PR 빌드 검증 결과: ❌ 빌드 실패

빌드 검증이 완료되었습니다.

@github-actions
Copy link

github-actions bot commented Jun 2, 2025

PR 빌드 검증 결과: ✅ 빌드 성공

빌드 검증이 완료되었습니다.

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

♻️ Duplicate comments (2)
src/main/java/jombi/freemates/model/postgres/Bookmark.java (1)

37-38: 과거 피드백 반영 확인됨

과거 리뷰에서 요청된 @ManyToOne(fetch = FetchType.LAZY) 가 적절히 적용되었습니다. 이는 N+1 문제를 방지하는데 도움이 됩니다.

src/main/java/jombi/freemates/service/BookmarkService.java (1)

31-71: 파일 검증 및 로깅 개선 필요

기본적인 null/empty 체크는 추가되었지만, 여전히 파일 크기 제한과 허용 형식 검증이 누락되어 있습니다. 또한 실패 시 로깅이 부족합니다.

누락된 검증 항목:

  • 파일 크기 제한 (예: 최대 5MB)
  • 허용 파일 형식 검증 (예: image/png, image/jpeg)
  • 실패 케이스 로깅

다음과 같이 개선을 권장합니다:

@Transactional
public BookmarkResponse create(
    CustomUserDetails customUserDetails,
    BookmarkRequest req,
    MultipartFile image
) {
+   log.info("북마크 생성 요청 - 사용자: {}, 제목: {}", 
+       customUserDetails.getUsername(), req.getTitle());

    String imageUrl = null;
    if (image != null && !image.isEmpty()) {
+       // 파일 크기 검증 (5MB 제한)
+       if (image.getSize() > 5 * 1024 * 1024) {
+           log.warn("파일 크기 초과 - 사용자: {}, 크기: {}", 
+               customUserDetails.getUsername(), image.getSize());
+           throw new InvalidRequestException("파일 크기가 5MB를 초과합니다.");
+       }
+       
+       // 파일 형식 검증
+       String contentType = image.getContentType();
+       if (!List.of("image/png", "image/jpeg", "image/jpg").contains(contentType)) {
+           log.warn("지원하지 않는 파일 형식 - 사용자: {}, 타입: {}", 
+               customUserDetails.getUsername(), contentType);
+           throw new InvalidRequestException("지원하지 않는 이미지 형식입니다.");
+       }
+       
        imageUrl = storage.storeImage(image);
+       log.info("이미지 저장 완료 - URL: {}", imageUrl);
    }
🧹 Nitpick comments (2)
src/main/java/jombi/freemates/model/dto/BookmarkRequest.java (1)

10-10: 사용되지 않는 import 제거 필요

RequiredArgsConstructorMultipartFile import가 사용되지 않고 있습니다. 코드 정리를 위해 제거해주세요.

-import lombok.RequiredArgsConstructor;
-import org.springframework.web.multipart.MultipartFile;

Also applies to: 13-13

src/main/java/jombi/freemates/service/BookmarkService.java (1)

73-87: 성능 모니터링을 위한 로깅 추가 권장

메서드 로직은 올바르나 사용자별 북마크 조회 성능 모니터링을 위한 로깅이 있으면 운영에 도움이 될 것 같습니다.

@Transactional(Transactional.TxType.SUPPORTS)
public List<BookmarkResponse> listByMember(CustomUserDetails customUserDetails) {
    Member member = customUserDetails.getMember();
+   log.debug("북마크 목록 조회 - 사용자: {}", member.getNickname());
    return bookmarkRepository.findAllByMember(member).stream()
        .map(b -> BookmarkResponse.builder()
            .memberId(member.getMemberId())
            .nickname(member.getNickname())
            .imageUrl(b.getImageUrl())
            .title(b.getTitle())
            .description(b.getDescription())
            .pinColor(b.getPinColor())
            .visibility(b.getVisibility())
            .build())
        .collect(Collectors.toList());
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 349af0d and 5ae3be3.

📒 Files selected for processing (11)
  • src/main/java/jombi/freemates/controller/BookmarkController.java (1 hunks)
  • src/main/java/jombi/freemates/controller/PlaceController.java (2 hunks)
  • src/main/java/jombi/freemates/model/dto/BookmarkRequest.java (1 hunks)
  • src/main/java/jombi/freemates/model/dto/BookmarkResponse.java (1 hunks)
  • src/main/java/jombi/freemates/model/postgres/Bookmark.java (3 hunks)
  • src/main/java/jombi/freemates/model/postgres/Place.java (3 hunks)
  • src/main/java/jombi/freemates/repository/BookmarkRepository.java (1 hunks)
  • src/main/java/jombi/freemates/service/BookmarkService.java (1 hunks)
  • src/main/java/jombi/freemates/service/FileStorageService.java (1 hunks)
  • src/main/java/jombi/freemates/service/PlaceService.java (2 hunks)
  • src/main/java/jombi/freemates/util/exception/ErrorCode.java (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/main/java/jombi/freemates/service/PlaceService.java
  • src/main/java/jombi/freemates/model/postgres/Place.java
  • src/main/java/jombi/freemates/util/exception/ErrorCode.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/jombi/freemates/controller/PlaceController.java
  • src/main/java/jombi/freemates/service/FileStorageService.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/jombi/freemates/model/dto/BookmarkRequest.java (1)
src/main/java/jombi/freemates/model/dto/BookmarkResponse.java (1)
  • Builder (13-30)
src/main/java/jombi/freemates/model/dto/BookmarkResponse.java (1)
src/main/java/jombi/freemates/model/dto/BookmarkRequest.java (1)
  • ToString (15-31)
src/main/java/jombi/freemates/model/postgres/Bookmark.java (1)
src/main/java/jombi/freemates/model/postgres/Place.java (1)
  • Entity (29-88)
src/main/java/jombi/freemates/controller/BookmarkController.java (1)
src/main/java/jombi/freemates/service/BookmarkService.java (1)
  • Slf4j (23-119)
🔇 Additional comments (8)
src/main/java/jombi/freemates/model/dto/BookmarkResponse.java (1)

1-31: LGTM! 표준적인 응답 DTO 구조입니다.

북마크 응답 데이터를 담기 위한 DTO 클래스가 잘 구성되어 있습니다. Lombok 어노테이션을 적절히 사용하여 boilerplate 코드를 줄였고, 필드들도 북마크 기능에 필요한 정보들을 포함하고 있습니다.

src/main/java/jombi/freemates/model/dto/BookmarkRequest.java (1)

21-31: LGTM! 북마크 요청 DTO가 잘 구성되어 있습니다.

필요한 필드들이 적절히 정의되어 있고 Swagger 문서화를 위한 Schema 어노테이션도 포함되어 있습니다.

src/main/java/jombi/freemates/repository/BookmarkRepository.java (1)

1-17: LGTM! 잘 설계된 Repository 인터페이스입니다.

Spring Data JPA 규약을 잘 따라 구현되었고, 메서드명이 명확하여 가독성이 좋습니다. findAllByMemberfindByBookmarkIdAndMember 메서드는 북마크 기능에 필요한 쿼리들을 적절히 표현하고 있습니다.

src/main/java/jombi/freemates/model/postgres/Bookmark.java (3)

3-3: 적절한 import 추가

새로운 JPA 어노테이션들과 컬렉션 관련 import들이 필요에 따라 잘 추가되었습니다.

Also applies to: 7-7, 11-16


34-35: ID 필드 개선 사항 승인

favoriteId에서 bookmarkId로 변경된 것이 의미상 더 명확하고, @Column 어노테이션으로 updatable = false, nullable = false 속성을 명시한 것도 좋습니다. 이는 ID 필드의 불변성과 필수성을 명확히 표현합니다.


52-58: Many-to-Many 관계 설정이 적절합니다

북마크와 장소 간의 다대다 관계가 적절히 설정되었습니다. @JoinTable을 사용하여 중간 테이블 bookmark_place의 구조를 명확히 정의했고, ArrayList로 초기화하여 NPE를 방지했습니다.

src/main/java/jombi/freemates/service/BookmarkService.java (1)

89-118: 잘 구현된 메서드입니다

권한 검증, 엔티티 존재 확인, 중복 방지, 적절한 로깅이 모두 포함되어 있어 안전하고 신뢰할 수 있는 구현입니다.

src/main/java/jombi/freemates/controller/BookmarkController.java (1)

123-128: 깔끔한 구현입니다

서비스로의 단순 위임과 인증 처리가 적절하게 구현되어 있습니다.

@github-actions
Copy link

github-actions bot commented Jun 2, 2025

PR 빌드 검증 결과: ✅ 빌드 성공

빌드 검증이 완료되었습니다.

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

🧹 Nitpick comments (1)
src/main/java/jombi/freemates/controller/BookmarkController.java (1)

88-93: 수동 DTO 매핑보다는 검증 어노테이션 활용 고려

현재 개별 @RequestParam으로 받아서 수동으로 BookmarkRequest 객체를 구성하고 있습니다. 요청 데이터 검증을 위해 BookmarkRequest 클래스에 @NotBlank, @NotNull 등의 Bean Validation 어노테이션을 추가하고 @Valid를 사용하는 것을 고려해보세요.

// BookmarkRequest에 검증 어노테이션 추가 후
@PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<BookmarkResponse> create(
    @Valid @ModelAttribute BookmarkRequest req,
    @RequestParam(value = "image", required = false) MultipartFile image,
    @AuthenticationPrincipal CustomUserDetails user
) {
    BookmarkResponse response = bookmarkService.create(user, req, image);
    return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae3be3 and a2876fe.

📒 Files selected for processing (1)
  • src/main/java/jombi/freemates/controller/BookmarkController.java (1 hunks)
🔇 Additional comments (3)
src/main/java/jombi/freemates/controller/BookmarkController.java (3)

1-42: 클래스 구조와 의존성 주입이 올바르게 구현됨

컨트롤러 클래스의 기본 구조가 Spring Boot 베스트 프랙티스를 잘 따르고 있습니다. Swagger 문서화, 로깅, 의존성 주입이 적절히 설정되어 있습니다.


122-127: 단순하고 명확한 목록 조회 구현

인증된 사용자의 즐겨찾기 목록을 조회하는 간단한 엔드포인트로, 구현이 명확하고 적절합니다.


162-171: API 문서와 구현이 일치함을 확인

과거 리뷰에서 지적된 Request Body vs RequestParam 불일치 문제가 해결되었습니다. 현재 문서(149-150라인)와 구현(166라인) 모두 RequestParam을 사용하도록 일치되어 있습니다.

Comment on lines +75 to +95
@PostMapping(value = "/create",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<BookmarkResponse> create(
@RequestParam String title,
@RequestParam String description,
@RequestParam PinColor pinColor,
@RequestParam Visibility visibility,
@RequestParam(value = "image", required = false)
@Parameter(description = "이미지 파일", content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
MultipartFile image,
@AuthenticationPrincipal CustomUserDetails user
) {
BookmarkRequest req = new BookmarkRequest();
req.setTitle(title);
req.setDescription(description);
req.setPinColor(pinColor);
req.setVisibility(visibility);
BookmarkResponse response = bookmarkService.create(user, req, image);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

파일 업로드 검증 로직 확인 필요

멀티파트 파일 업로드 엔드포인트가 올바르게 구현되어 있으나, 파일 크기 제한이나 허용 형식에 대한 검증이 컨트롤러나 서비스 레이어에서 처리되고 있는지 확인이 필요합니다.

다음 스크립트로 BookmarkService에서 파일 검증 로직이 구현되어 있는지 확인하겠습니다:


🏁 Script executed:

#!/bin/bash
# BookmarkService의 create 메서드에서 파일 검증 로직 확인
echo "=== BookmarkService 파일 위치 확인 ==="
fd -t f "BookmarkService.java"

echo "=== create 메서드 내용 확인 ==="
rg -A 20 "public.*create.*CustomUserDetails.*BookmarkRequest.*MultipartFile" --type java

echo "=== 파일 검증 관련 코드 검색 ==="
rg -B 5 -A 10 "getSize\(\)|getContentType\(\)|file.*valid|upload.*valid" --type java

Length of output: 456


파일 업로드 검증 로직 추가 필요

서비스 레이어(create 메서드)에서 MultipartFile에 대한 크기·형식 검증이 전혀 구현되어 있지 않습니다. 악성 파일 업로드나 서버 과부하를 방지하기 위해 아래 위치에 검증 로직을 추가해주세요.

• src/main/java/jombi/freemates/service/BookmarkService.java
public BookmarkResponse create(CustomUserDetails user, BookmarkRequest req, MultipartFile image) 내 파일 유효성 검사
• src/main/java/jombi/freemates/controller/BookmarkController.java
– 컨트롤러 단에서도 요청 파라미터 유효성(필수 여부, 최대 크기 등) 재검증

추가 검증 예시:

  • image.getSize()로 maxFileSize 비교
  • image.getContentType()으로 허용된 MIME 타입(image/png, image/jpeg 등) 확인
  • 위반 시 BadRequestException 또는 MultipartException 발생 처리
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/controller/BookmarkController.java lines 75 to
95 and in src/main/java/jombi/freemates/service/BookmarkService.java within the
create method, add validation logic for the MultipartFile image parameter. In
the controller, re-validate request parameters including checking if the image
size exceeds a defined maxFileSize and if the content type is among allowed MIME
types like image/png or image/jpeg. In the service layer's create method,
implement similar checks on image.getSize() and image.getContentType(), and if
validation fails, throw appropriate exceptions such as BadRequestException or
MultipartException to prevent malicious or oversized file uploads.

@daye200 daye200 merged commit dc125ec into main Jun 2, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants