Skip to content

[Fix] 사용자 이미지 Signed URL 조회 방식 전환#204

Merged
yooooonshine merged 6 commits intodevelopfrom
feature/203-fix-userimage
Oct 12, 2025
Merged

[Fix] 사용자 이미지 Signed URL 조회 방식 전환#204
yooooonshine merged 6 commits intodevelopfrom
feature/203-fix-userimage

Conversation

@yooooonshine
Copy link
Contributor

@yooooonshine yooooonshine commented Oct 12, 2025

🧩 작업 내용 요약

  • FollowService · ProfileService 등 사용자 목록 응답에서 정적 URL 대신 Signed URL 생성 로직으로 교체
  • UserEntity 필드 로직 및 UserImageService를 Signed URL 발급에 맞춰 정비
  • Post/Review/Profile/User 관련 서비스·컨트롤러 테스트를 Signed URL 기반 응답으로 업데이트

———

🔍 관련 이슈

———

🧠 변경 이유 및 주요 포인트

  • 정적 경로 노출 시 권한 문제가 발생하던 사용자 이미지 조회 흐름을 Signed URL 기반으로 일원화

———

🧪 테스트 및 검증

  • 단위 테스트 통과
  • 통합 테스트 통과
  • 로컬 서버 정상 구동 확인
  • Swagger 또는 Postman 테스트 완료

Summary by CodeRabbit

  • 신규 기능
    • 사용자 프로필 이미지 유무와(존재 시) URL이 댓글·팔로우·팔로잉·프로필·게시글 응답에 일관되게 반영됩니다.
    • 댓글 응답용 간결한 코멘트 DTO가 추가되어 댓글 목록이 더 표준화되었습니다.
  • 버그 수정
    • 이미지 존재 판별과 null 처리 개선으로 표시 오류 및 간헐적 문제 해결, 공개/비로그인 뷰에서도 상태가 정확히 반영됩니다.
  • 리팩터링
    • 이미지 조회 방식 통합으로 응답 생성 흐름이 일관화되었습니다.
  • 테스트
    • 단위·통합 테스트가 보강되어 이미지 관련 시나리오 검증이 강화되었습니다.

- Post, Profile, UserService 등에서 고정 url로 조회하던 방식을 SignedUrl방식으로 변경하였다.
- 그에 따른 테스트 코드도 수정하였다.
@yooooonshine yooooonshine linked an issue Oct 12, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Oct 12, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

여러 서비스에서 사용자 프로필 이미지 조회를 엔티티 직접 접근에서 UserImageService의 Optional 기반 Signed URL 조회로 전환하고, UserEntity.userImageUrl 필드 제거, 팔로우·코멘트 프로젝션을 레코드/DTO 생성자로 변경하며 테스트들을 모킹·데이터 보강했습니다.

Changes

Cohort / File(s) Change summary
User 엔티티·이미지 서비스·DTO
src/main/java/.../user/entity/UserEntity.java, src/main/java/.../user/service/UserImageService.java, src/main/java/.../web/user/dto/response/UserInfoResponse.java
UserEntity에서 userImageUrl 필드 및 이미지 관련 메서드 제거. UserImageServicecreateImageGetUrlOptional(long) 추가(읽기 전용 트랜잭션) 및 기존 조회/예외 로직 정리. UserInfoResponse.from(...)UserInfoResponse.of(UserEntity, String)로 변경.
서비스들: Optional 기반 이미지 조회 일원화
src/main/java/.../follow/service/FollowService.java, src/main/java/.../post/service/PostService.java, src/main/java/.../postReview/service/PostReviewService.java, src/main/java/.../profile/service/ProfileService.java, src/main/java/.../user/service/UserService.java, src/main/java/.../postReview/service/PostReviewCommentService.java
각 서비스가 엔티티의 직접 이미지 필드 대신 userImageService.createImageGetUrlOptional(userId) 사용. Optional.isPresent()hasUserImage 판단, optional.orElse(null)로 응답에 전달. UserImageService, UserImageEntityRepository 등 의존성 주입 추가; 일부 조회 메서드에 @Transactional(readOnly = true) 추가.
팔로우 DTO 및 리포지토리 프로젝션
src/main/java/.../follow/dto/FollowerWithStatus.java, src/main/java/.../follow/dto/FollowingWithStatus.java, src/main/java/.../follow/repository/FollowEntityRepository.java
인터페이스 기반 프로젝션을 record로 전환. JPQL 쿼리에서 new ...(...) 생성자 프로젝션으로 변경해 DTO 인스턴스 반환하도록 수정.
PostReview 코멘트: DTO·리포지토리·서비스 변경
src/main/java/.../postReview/dto/PostReviewCommentDto.java, src/main/java/.../postReview/repository/PostReviewCommentRepository.java, src/main/java/.../postReview/service/PostReviewCommentService.java
새 레코드 PostReviewCommentDto 추가. 리포지토리 쿼리 반환 타입을 DTO로 변경. 서비스는 댓글 매핑 시 UserImageService로 Optional 이미지 URL 조회 및 hasUserImage/URL 채움. 리포지토리 필드명·의존성 반영.
UserImage 리포지토리 확장
src/main/java/.../user/repository/UserImageEntityRepository.java
배치 조회 메서드 List<UserImageEntity> findAllByUserIdIn(List<Long> userIds) 추가.
테스트: 모킹 및 통합 데이터 보강
src/test/java/.../follow/service/FollowMockingServiceTest.java, .../post/service/PostServiceTest.java, .../postReview/service/PostReviewServiceTest.java, .../profile/ProfileServiceTest.java, .../user/service/UserServiceTest.java, .../web/profile/controller/ProfileControllerIntegrationTest.java, .../web/user/controller/UserControllerTest.java, .../web/post/controller/PublicPostControllerTest.java
다수 테스트에서 UserImageServiceUserImageEntityRepository@Mock/주입하고 스텁 추가. 통합 테스트는 이미지 엔티티 저장으로 프로필 이미지 존재 검증 보강. 테스트 코드에서 응답 팩토리 변경(fromof) 반영.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Service as 서비스 (Post/Profile/Follow/Comment)
  participant UIS as UserImageService
  participant UIR as UserImageEntityRepository
  participant URLP as URL Provider

  Service->>UIS: createImageGetUrlOptional(userId)
  UIS->>UIR: findByUserId(userId) / findAllByUserIdIn(ids)
  alt 이미지 존재
    UIR-->>UIS: UserImageEntity(path,name,ext)
    UIS->>URLP: convert to signed URL
    URLP-->>UIS: signedUrl
    UIS-->>Service: Optional.of(signedUrl)
  else 이미지 없음
    UIR-->>UIS: empty / []
    UIS-->>Service: Optional.empty()
  end
  note right of Service: hasUserImage = optional.isPresent()\nimageUrl = optional.orElse(null)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

토끼가 말하네, 깡충깡충 코드밭을 건너 🥕
Optional 주머니에 URL을 소중히 담아,
있으면 서명된 길을 건네주고, 없으면 살포시 비워두네.
테스트도 모킹도 함께 뛰놀며, 변경을 축하하네.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning 제공된 PR 설명에는 작업 내용, 관련 이슈, 변경 이유 및 테스트 검증 항목이 포함되어 있으나 저장소에서 요구하는 "## 개요" 및 "## 작업사항" 섹션이 빠져 있어 지정된 템플릿 구조를 따르고 있지 않습니다. PR 설명을 저장소의 설명 템플릿에 맞춰 "## 개요" 섹션에 변경 개요를, "## 작업사항" 섹션에 수행된 구체적 작업 목록을 추가하도록 수정해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 "[Fix] 사용자 이미지 Signed URL 조회 방식 전환"은 이 PR의 핵심 변경사항인 사용자 이미지 조회 방식을 Static URL에서 Signed URL 방식으로 전환하는 내용을 명확하고 간결하게 요약하고 있어 적절합니다.
Linked Issues Check ✅ Passed 이 PR은 이슈 #203에서 요구한 사용자 이미지 조회 방식을 Static URL에서 Signed URL 방식으로 변경한다는 목표를 FollowService, ProfileService, PostService 등 주요 서비스와 관련 테스트 전반에 걸쳐 일관되게 구현하고 있어 요구사항을 충족합니다.
Out of Scope Changes Check ✅ Passed 이번 PR의 모든 변경사항은 사용자 이미지 조회 방식 전환과 이를 위한 서비스 및 테스트 구조 개편과 직접적으로 연관되어 있으며, 이슈 범위를 벗어난 수정은 발견되지 않습니다.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd101dd and 0c3cfb4.

📒 Files selected for processing (1)
  • src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (6 hunks)

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

🧹 Nitpick comments (2)
src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (1)

62-73: 사용자 이미지 URL 조회 로직의 중복 제거를 고려하세요.

6개의 메서드에서 동일한 패턴이 반복됩니다:

final Optional<String> userImageUrl = userImageService.createImageGetUrlOptional(u.getId());
final boolean hasUserImage = userImageUrl.isPresent();

헬퍼 메서드를 추출하여 유지보수성을 개선할 수 있습니다.

다음과 같이 리팩터링할 수 있습니다:

+private record UserImageInfo(boolean hasImage, String imageUrl) {
+    static UserImageInfo from(Optional<String> imageUrl) {
+        return new UserImageInfo(imageUrl.isPresent(), imageUrl.orElse(null));
+    }
+}
+
 return followRepository.findFollowersOrderByCreatedAt(userId, PageRequest.of(page, size))
     .map(u -> {
-        final Optional<String> userImageUrl = userImageService.createImageGetUrlOptional(u.getId());
-        final boolean hasUserImage = userImageUrl.isPresent();
+        final UserImageInfo imageInfo = UserImageInfo.from(
+            userImageService.createImageGetUrlOptional(u.getId()));
 
         return new GetFollowersResponse(
             u.getId(),
-            hasUserImage,
-            userImageUrl.orElse(null),
+            imageInfo.hasImage(),
+            imageInfo.imageUrl(),
             u.getName(),
             u.getEmail()
         );
     });

Also applies to: 84-95, 112-123, 134-144, 155-166, 183-194

src/test/java/hanium/modic/backend/web/profile/controller/ProfileControllerIntegrationTest.java (1)

54-67: 테스트 데이터 설정이 올바릅니다.

사용자 이미지 엔티티 생성 및 저장이 적절하게 구현되어 Signed URL 기반 응답 테스트를 지원합니다.

경로 문자열을 상수나 팩토리 메서드로 추출하면 유지보수성이 향상됩니다:

private static final String IMAGE_PATH_FORMAT = "profiles/%d/image.png";

UserImageEntity image = UserImageEntity.builder()
    .user(target)
    .imagePath(String.format(IMAGE_PATH_FORMAT, target.getId()))
    // ... 나머지 필드
    .build();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d71e2d and 649e0c4.

📒 Files selected for processing (15)
  • src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (8 hunks)
  • src/main/java/hanium/modic/backend/domain/post/service/PostService.java (8 hunks)
  • src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewService.java (6 hunks)
  • src/main/java/hanium/modic/backend/domain/profile/service/ProfileService.java (4 hunks)
  • src/main/java/hanium/modic/backend/domain/user/entity/UserEntity.java (0 hunks)
  • src/main/java/hanium/modic/backend/domain/user/service/UserImageService.java (2 hunks)
  • src/main/java/hanium/modic/backend/domain/user/service/UserService.java (3 hunks)
  • src/main/java/hanium/modic/backend/web/user/dto/response/UserInfoResponse.java (1 hunks)
  • src/test/java/hanium/modic/backend/domain/follow/service/FollowMockingServiceTest.java (2 hunks)
  • src/test/java/hanium/modic/backend/domain/post/service/PostServiceTest.java (4 hunks)
  • src/test/java/hanium/modic/backend/domain/postReview/service/PostReviewServiceTest.java (6 hunks)
  • src/test/java/hanium/modic/backend/domain/profile/ProfileServiceTest.java (2 hunks)
  • src/test/java/hanium/modic/backend/domain/user/service/UserServiceTest.java (1 hunks)
  • src/test/java/hanium/modic/backend/web/profile/controller/ProfileControllerIntegrationTest.java (2 hunks)
  • src/test/java/hanium/modic/backend/web/user/controller/UserControllerTest.java (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/hanium/modic/backend/domain/user/entity/UserEntity.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/test/java/hanium/modic/backend/web/profile/controller/ProfileControllerIntegrationTest.java (1)
src/test/java/hanium/modic/backend/domain/user/factory/UserFactory.java (1)
  • UserFactory (9-29)
src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (1)
src/test/java/hanium/modic/backend/domain/follow/service/FollowServiceTest.java (1)
  • Transactional (26-136)
🔇 Additional comments (20)
src/main/java/hanium/modic/backend/domain/user/service/UserService.java (1)

62-65: LGTM! Signed URL 통합이 올바르게 구현되었습니다.

UserImageService를 통한 Optional 기반 이미지 URL 조회와 null 처리가 적절합니다.

src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (1)

57-57: 읽기 전용 트랜잭션 애너테이션 추가 승인

조회 메서드에 @Transactional(readOnly = true) 추가가 적절합니다. 읽기 전용 최적화를 활성화하여 성능을 향상시킵니다.

Also applies to: 78-78, 100-100, 128-128, 149-149, 171-171

src/main/java/hanium/modic/backend/domain/user/service/UserImageService.java (1)

35-50: LGTM! Optional 기반 API 설계가 우수합니다.

두 가지 조회 메서드 제공:

  • createImageGetUrlOptional: 이미지가 없을 수 있는 경우를 위한 Optional 반환
  • createImageGetUrl: 이미지가 반드시 존재해야 하는 경우를 위한 예외 발생

읽기 전용 트랜잭션 애너테이션과 Optional 체인 사용이 적절합니다.

src/test/java/hanium/modic/backend/domain/user/service/UserServiceTest.java (1)

43-44: Mock 추가가 적절하지만 테스트 검증을 확인하세요.

UserImageService mock이 추가되었습니다. getUserInfoTest 테스트(라인 110-123)가 이미지 URL 조회를 검증하도록 스텁 동작을 추가하는 것이 좋습니다.

다음과 같이 테스트를 개선할 수 있습니다:

@Test
@DisplayName("유저 정보 조회 테스트")
void getUserInfoTest() {
    // given
    final Long userId = 1L;
    UserEntity user = UserFactory.createMockUser(userId);
    when(userImageService.createImageGetUrlOptional(userId))
        .thenReturn(Optional.of("https://signed-url.com/image.jpg"));

    // when
    UserInfoResponse response = userService.getUserInfo(user);

    // then
    assertThat(response.id()).isEqualTo(userId);
    assertThat(response.userEmail()).isEqualTo(user.getEmail());
    assertThat(response.userName()).isEqualTo(user.getName());
    assertThat(response.userImageUrl()).isEqualTo("https://signed-url.com/image.jpg");
    verify(userImageService).createImageGetUrlOptional(userId);
}
src/test/java/hanium/modic/backend/domain/profile/ProfileServiceTest.java (1)

43-44: Mock 추가 확인

UserImageService mock이 추가되었습니다. 프로필 조회 테스트가 이미지 URL 처리를 검증하도록 스텁 동작 추가를 고려하세요.

src/test/java/hanium/modic/backend/domain/post/service/PostServiceTest.java (2)

68-69: LGTM! Mock 추가가 적절합니다.

UserImageService mock이 올바르게 추가되었습니다.


184-184: 이미지 부재 시나리오 테스트가 우수합니다.

Optional.empty() 반환을 명시적으로 스텁하여 사용자 이미지가 없는 경우를 테스트합니다. 엣지 케이스 처리가 적절합니다.

Also applies to: 697-697

src/test/java/hanium/modic/backend/web/user/controller/UserControllerTest.java (1)

140-140: LGTM! UserInfoResponse API 변경 반영

UserInfoResponse.from(mockUser)에서 UserInfoResponse.of(mockUser, "URL")로 변경하여 명시적 이미지 URL 매개변수를 받는 새로운 팩토리 메서드를 올바르게 사용합니다.

src/test/java/hanium/modic/backend/domain/postReview/service/PostReviewServiceTest.java (2)

27-27: LGTM!

UserImageService 의존성이 올바르게 추가되었으며, 모킹 설정이 적절합니다.

Also applies to: 41-42


78-78: LGTM!

각 테스트 시나리오에서 UserImageService의 stubbing이 정확합니다:

  • Line 78: 사용자 이미지가 있는 경우 Optional.of(url) 반환
  • Line 153: 리뷰 이미지는 없지만 사용자 이미지는 있는 경우 Optional.of(url) 반환
  • Line 189: 사용자 이미지가 없는 경우 Optional.empty() 반환

테스트 단언문들도 Optional 결과에 따라 hasUserImageuserImageUrl을 올바르게 검증하고 있습니다.

Also applies to: 153-153, 189-189

src/main/java/hanium/modic/backend/web/user/dto/response/UserInfoResponse.java (1)

17-25: LGTM!

팩토리 메서드 시그니처 변경이 적절합니다:

  • userImageUrl을 외부에서 명시적으로 전달받도록 변경
  • hasUserImage는 전달받은 userImageUrl의 null 여부로 올바르게 결정
  • 서비스 레이어의 Optional 기반 조회 패턴과 일관성 있게 통합됨
src/main/java/hanium/modic/backend/domain/post/service/PostService.java (3)

10-10: LGTM!

UserImageService 의존성이 올바르게 추가되었습니다.

Also applies to: 32-32, 57-57


110-111: LGTM!

getPost 메서드에서 Optional 기반 이미지 URL 조회가 올바르게 구현되었습니다:

  • Line 110: userImageService.createImageGetUrlOptional()로 Optional 조회
  • Line 111: isPresent()로 hasUserImage 결정
  • Lines 143: orElse(null)로 null-safe하게 URL 전달

Also applies to: 140-150


161-162: LGTM!

getPostForPublic 메서드에서도 동일한 Optional 기반 패턴이 일관성 있게 적용되었습니다.

Also applies to: 191-201

src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewService.java (3)

8-8: LGTM!

UserImageService 의존성이 올바르게 추가되었습니다.

Also applies to: 26-26, 40-40


126-127: LGTM!

getPostReviewDetail 메서드에서 Optional 기반 이미지 URL 조회가 올바르게 구현되었습니다:

  • Line 126: createImageGetUrlOptional()로 Optional 조회
  • Line 127: isPresent()로 hasUserImage 결정
  • Line 132: orElse(null)로 null-safe하게 URL 전달

Also applies to: 132-132


166-167: LGTM!

getPostReviews 메서드의 페이지 매핑 로직에서도 동일한 Optional 기반 패턴이 일관성 있게 적용되었습니다.

Also applies to: 176-176

src/main/java/hanium/modic/backend/domain/profile/service/ProfileService.java (3)

3-3: LGTM!

UserImageService 의존성이 올바르게 추가되었습니다.

Also applies to: 13-13, 24-24


31-32: LGTM!

getMyProfile 메서드에서 Optional 기반 이미지 URL 조회가 올바르게 구현되었습니다:

  • Line 31: createImageGetUrlOptional()로 Optional 조회
  • Line 32: isPresent()로 hasUserImage 결정
  • Line 39: orElse(null)로 null-safe하게 URL 전달

Also applies to: 38-39


55-56: LGTM!

getProfile 메서드에서도 동일한 Optional 기반 패턴이 일관성 있게 적용되었습니다.

Also applies to: 62-63

- FollowService에서 고정 url로 조회하던 방식을 SignedUrl방식으로 변경하였다.
- 그에 따른 테스트 코드도 수정하였다.
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: 3

🧹 Nitpick comments (7)
src/main/java/hanium/modic/backend/domain/follow/repository/FollowEntityRepository.java (1)

53-58: 타입 일관성 개선 권장

JPQL의 CASE WHEN 표현식은 항상 true 또는 false를 반환하므로 null이 될 수 없습니다. 하지만 FollowerWithStatus 레코드는 Boolean (nullable wrapper)을 사용하고 있습니다. 타입 일관성과 null-safety를 위해 레코드 정의에서 boolean primitive 타입 사용을 고려해보세요.

동일한 제안이 FollowingWithStatus (lines 73-78)에도 적용됩니다.

src/main/java/hanium/modic/backend/domain/follow/dto/FollowingWithStatus.java (1)

3-8: primitive boolean 타입 사용 권장

isFollowingBoolean wrapper 타입으로 선언되어 있지만, 리포지토리의 JPQL 쿼리는 항상 non-null 값(true 또는 false)을 생성합니다. null 처리 부담을 제거하고 타입 안정성을 높이기 위해 boolean primitive 타입 사용을 권장합니다.

다음과 같이 수정:

 public record FollowingWithStatus(
 	Long id,
 	String name,
 	String email,
-	Boolean isFollowing
+	boolean isFollowing
 ) {
 }
src/main/java/hanium/modic/backend/domain/follow/dto/FollowerWithStatus.java (1)

3-8: primitive boolean 타입 사용 권장

isFollowingBoolean wrapper 타입으로 선언되어 있지만, 리포지토리의 JPQL 쿼리는 항상 non-null 값을 생성합니다. null 처리 오버헤드를 제거하고 의도를 명확히 하기 위해 boolean primitive 타입 사용을 권장합니다.

다음과 같이 수정:

 public record FollowerWithStatus(
 	Long id,
 	String name,
 	String email,
-	Boolean isFollowing
+	boolean isFollowing
 ) {
 }
src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (2)

109-110: 코드 포맷팅 개선 권장

메서드 호출이 두 줄에 걸쳐 분리되어 있어 가독성이 떨어집니다. 한 줄로 작성하거나 메서드 체이닝 스타일로 정리하는 것을 권장합니다.

예시:

-		return followRepository.findFollowersWithStatusOrderByCreatedAt(targetUserId, currentUserId,
-				PageRequest.of(page, size))
+		return followRepository.findFollowersWithStatusOrderByCreatedAt(
+			targetUserId, currentUserId, PageRequest.of(page, size))
 			.map(u -> {

Also applies to: 180-181


63-73: createImageGetUrlOptional은 외부 네트워크 호출이 아닌 로컬 서명 생성만 수행합니다
userImageRepository.findByUserId()가 호출되는 만큼 DB 조회가 사용자 수만큼 발생하므로, JPA fetch join 또는 IN절 기반 배치 조회 전략 도입해 N+1 문제 완화 고려

src/test/java/hanium/modic/backend/web/post/controller/PublicPostControllerTest.java (1)

58-59: 테스트 커버리지 개선이 필요합니다.

테스트에서 mockUser를 생성하지만, 사용자 이미지 관련 필드는 하드코딩된 falsenull로 설정되어 실제 동작을 검증하지 못하고 있습니다. PR의 목적이 Signed URL 방식으로 전환하는 것인 만큼, 다양한 시나리오(이미지 있음/없음)를 테스트하는 것이 좋습니다.

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

-		GetPostResponse mockResponse = new GetPostResponse(
-			mockUser.getName(),
-			false,
-			null,
+		// 이미지가 없는 사용자 케이스
+		GetPostResponse mockResponseNoImage = new GetPostResponse(
+			mockUser.getName(),
+			false,
+			null,

또한 이미지가 있는 케이스를 위한 별도 테스트 추가를 권장합니다:

@Test
@DisplayName("공개 게시글 조회 성공 - 사용자 이미지 포함")
void getPost_Success_WithUserImage() throws Exception {
    // 이미지 있는 케이스 테스트
}
src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewCommentService.java (1)

42-55: N+1 쿼리 잠재성 검토

  • getComments API는 기본 size=10, 최대 30까지 페이징 처리합니다.
  • UserImageService.createImageGetUrlOptional()를 댓글마다 개별 호출해 최대 30회 호출됩니다.
  • 빈번한 호출이 성능 이슈로 이어질 수 있으니, 필요 시 Map<Long, Optional<String>> createImageGetUrlsForUsers(Set<Long> userIds) 같은 배치 조회 메서드 도입을 검토하세요.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 649e0c4 and c3d05a1.

📒 Files selected for processing (8)
  • src/main/java/hanium/modic/backend/domain/follow/dto/FollowerWithStatus.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/follow/dto/FollowingWithStatus.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/follow/repository/FollowEntityRepository.java (2 hunks)
  • src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (6 hunks)
  • src/main/java/hanium/modic/backend/domain/postReview/dto/PostReviewCommentDto.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/postReview/repository/PostReviewCommentRepository.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewCommentService.java (2 hunks)
  • src/test/java/hanium/modic/backend/web/post/controller/PublicPostControllerTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewCommentService.java (1)
src/main/java/hanium/modic/backend/domain/user/service/UserImageService.java (1)
  • Service (22-106)
⏰ 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 (9)
src/main/java/hanium/modic/backend/domain/postReview/dto/PostReviewCommentDto.java (2)

1-12: LGTM!

도메인 레이어 DTO로서 적절하게 구현되었습니다. Record 타입을 사용하여 간결하고 명확한 데이터 구조를 제공합니다.


1-12: LGTM!

레포지토리 레이어의 프로젝션 DTO로 적절하게 구현되었습니다. Record 사용으로 간결하고 불변성이 보장됩니다.

src/main/java/hanium/modic/backend/domain/postReview/repository/PostReviewCommentRepository.java (2)

8-8: 좋은 아키텍처 개선입니다!

리포지토리가 웹 레이어의 PostReviewCommentResponse 대신 도메인 레이어의 PostReviewCommentDto를 반환하도록 변경되었습니다. 이는 계층 간 책임 분리를 명확히 하는 좋은 개선입니다.

  • 리포지토리는 도메인 데이터를 반환
  • 서비스 레이어에서 웹 응답으로 변환
  • 이미지 URL 등 추가 데이터 조합은 서비스에서 처리

Also applies to: 14-26


13-26: LGTM!

레포지토리가 응답 DTO 대신 도메인 DTO를 반환하도록 변경되어 계층 분리가 개선되었습니다. 서비스 레이어에서 UserImageService를 통해 추가 데이터를 보강하는 구조가 적절합니다.

src/test/java/hanium/modic/backend/web/post/controller/PublicPostControllerTest.java (1)

58-59: 사용자 이미지 하드코딩 검증 필요

hasUserImagefalse, userImageUrlnull로 고정한 것이 “이미지 없는 사용자” 시나리오 의도인지 확인하세요. 다른 테스트에서 사용자 이미지 처리 방식을 찾을 수 없어 누락 가능성이 있으니, 프로필 이미지가 있는 경우와 없는 경우를 모두 다루는 테스트 추가를 검토하세요.

src/main/java/hanium/modic/backend/domain/follow/repository/FollowEntityRepository.java (1)

53-78: LGTM!

JPQL 생성자 프로젝션 패턴이 올바르게 적용되었습니다. 레코드 정의와 생성자 인자가 정확히 일치하며, LEFT JOIN을 통한 팔로우 상태 확인 로직도 정확합니다.

src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (3)

30-30: LGTM!

UserImageService 의존성 주입이 올바르게 추가되었습니다. Signed URL 기반 이미지 조회를 위한 필수 변경사항입니다.


57-57: LGTM!

읽기 전용 메서드에 @Transactional(readOnly = true) 어노테이션이 적절하게 추가되었습니다. 이는 데이터베이스 최적화와 의도 명확화에 도움이 됩니다.

Also applies to: 78-78, 100-100, 128-128, 149-149, 171-171


63-73: LGTM!

Optional 처리 패턴이 일관되게 적용되었습니다:

  • Optional<String>로 이미지 URL 조회
  • .isPresent()로 이미지 존재 여부 확인
  • .orElse(null)로 안전한 null 처리

Signed URL 마이그레이션 목표에 부합하는 올바른 구현입니다.

Also applies to: 84-95

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 (1)
src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (1)

191-218: 불필요한 중복 쿼리를 제거하세요.

getMyFollowings 메서드에서 동일한 쿼리가 두 번 실행됩니다:

  • Line 196: followRepository.findFollowingOrderByCreatedAt(...)
  • Line 204: 동일한 쿼리 재실행

Line 196에서 조회한 followings 변수를 재사용해야 합니다.

다음 diff를 적용하여 중복 쿼리를 제거하세요:

 	// 3. 응답 생성
-	return followRepository.findFollowingOrderByCreatedAt(userId, PageRequest.of(page, size))
+	return followings
 		.map(u -> {
 			final Optional<String> userImageUrl = userImageService.createImageGetUrlOptional(u.getId());
 			final boolean hasUserImage = userImageUrl.isPresent();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c27cdf0 and 69f1f0a.

📒 Files selected for processing (3)
  • src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (6 hunks)
  • src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewCommentService.java (4 hunks)
  • src/main/java/hanium/modic/backend/domain/user/repository/UserImageEntityRepository.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewCommentService.java
⏰ 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 (3)
src/main/java/hanium/modic/backend/domain/user/repository/UserImageEntityRepository.java (1)

3-3: LGTM! N+1 문제 해결을 위한 배치 조회 메서드가 잘 추가되었습니다.

findAllByUserIdIn 메서드는 여러 사용자의 이미지를 한 번에 조회하여 N+1 문제를 효과적으로 해결합니다. Spring Data JPA 네이밍 컨벤션을 올바르게 따르고 있습니다.

Also applies to: 14-15

src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (2)

62-89: 배치 조회 및 Optional 기반 URL 처리가 잘 구현되었습니다.

각 메서드에서 다음 패턴을 올바르게 적용했습니다:

  1. 사용자 목록 조회
  2. N+1 문제 방지를 위한 배치 이미지 조회
  3. Optional 기반 Signed URL 생성

이 접근 방식은 성능을 개선하고 권한 문제를 해결합니다.

Also applies to: 93-120, 124-156, 160-187, 222-258


62-89: @transactional(readOnly = true) 어노테이션 추가가 적절합니다.

읽기 전용 트랜잭션 설정은 데이터베이스 성능 최적화에 도움이 됩니다. 모든 조회 메서드에 일관되게 적용되었습니다.

Also applies to: 93-120, 124-156, 160-187, 191-218, 222-258

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 (1)
src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (1)

196-217: 중복 쿼리를 제거하세요.

Line 196에서 이미 조회한 followings 결과를 사용하지 않고, Line 204에서 동일한 쿼리를 다시 실행하고 있습니다.

다음 diff를 적용하여 중복 쿼리를 제거하세요:

 	// 3. 응답 생성
-	return followRepository.findFollowingOrderByCreatedAt(userId, PageRequest.of(page, size))
+	return followings
 		.map(u -> {
♻️ Duplicate comments (1)
src/test/java/hanium/modic/backend/domain/follow/service/FollowMockingServiceTest.java (1)

43-47: UserImageService 스터빙 및 검증 로직이 여전히 누락되어 있습니다.

이전 리뷰에서 지적된 문제가 해결되지 않았습니다. userImageService.createImageGetUrlOptional() 호출에 대한 스터빙과 응답 DTO의 hasUserImage, userImageUrl 필드 검증이 필요합니다.

다음을 추가하세요:

  1. 각 테스트에서 userImageService.createImageGetUrlOptional() 스터빙:
when(userImageService.createImageGetUrlOptional(2L)).thenReturn(Optional.of("http://image-url-2"));
when(userImageService.createImageGetUrlOptional(3L)).thenReturn(Optional.empty());
  1. 응답 DTO 필드 검증 추가:
assertThat(result.getContent().get(0).hasUserImage()).isTrue();
assertThat(result.getContent().get(0).userImageUrl()).isEqualTo("http://image-url-2");
assertThat(result.getContent().get(1).hasUserImage()).isFalse();
assertThat(result.getContent().get(1).userImageUrl()).isNull();
🧹 Nitpick comments (1)
src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewCommentService.java (1)

42-61: 배치 조회 패턴이 추가되었으나 개선 여지가 있습니다.

N+1 문제를 해결하기 위해 배치 조회를 추가한 것은 좋습니다 (lines 43-45). JPA 영속성 컨텍스트에 미리 로드하여 후속 서비스 호출(line 49)이 캐시를 사용하도록 하는 패턴입니다.

향후 개선 시 고려사항: UserImageService에 배치 조회 메서드를 추가하면 의도가 더 명확해집니다.

예시:

// UserImageService에 추가
public Map<Long, String> createImageGetUrlsForUsers(List<Long> userIds) {
    List<UserImageEntity> images = userImageEntityRepository.findAllByUserIdIn(userIds);
    return images.stream()
        .collect(Collectors.toMap(
            UserImageEntity::getUserId,
            img -> imageUtil.createImageGetUrl(img.getImagePath())
        ));
}

서비스 코드:

List<Long> userIds = prcs.getContent().stream()
    .map(PostReviewCommentDto::userId)
    .distinct()
    .toList();

Map<Long, String> userImageUrls = userImageService.createImageGetUrlsForUsers(userIds);

return prcs.map(prc -> {
    final String userImageUrl = userImageUrls.get(prc.userId());
    final boolean hasUserImage = userImageUrl != null;
    // ...
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c27cdf0 and cd101dd.

📒 Files selected for processing (4)
  • src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (6 hunks)
  • src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewCommentService.java (4 hunks)
  • src/main/java/hanium/modic/backend/domain/user/repository/UserImageEntityRepository.java (2 hunks)
  • src/test/java/hanium/modic/backend/domain/follow/service/FollowMockingServiceTest.java (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/hanium/modic/backend/domain/postReview/service/PostReviewCommentService.java (2)
src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (1)
  • Service (28-279)
src/main/java/hanium/modic/backend/domain/user/service/UserImageService.java (1)
  • Service (22-106)
⏰ 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 (2)
src/main/java/hanium/modic/backend/domain/user/repository/UserImageEntityRepository.java (1)

15-15: 배치 조회 메서드 추가가 적절합니다.

N+1 문제 해결을 위한 배치 조회 메서드가 올바르게 추가되었습니다. Spring Data JPA의 In 키워드를 사용한 메서드 네이밍 규칙을 정확히 따르고 있습니다.

src/main/java/hanium/modic/backend/domain/follow/service/FollowService.java (1)

66-88: 배치 조회 패턴이 올바르게 적용되었습니다.

N+1 문제 해결을 위한 배치 조회 로직과 Optional 기반 이미지 URL 처리가 적절하게 구현되었습니다. JPA 영속성 컨텍스트를 활용하여 후속 조회 시 캐시된 데이터를 사용하는 패턴입니다.

@yooooonshine yooooonshine merged commit 1379026 into develop Oct 12, 2025
1 of 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.

FIX : UserImage 조회 실패 문제 해결

1 participant