Skip to content

Conversation

@dahyun24
Copy link
Contributor

@dahyun24 dahyun24 commented Nov 15, 2025

🔗 연관된 이슈

🚀 변경 유형

  • ✨ 기능 추가 (feature)
  • 🐛 버그 수정 (fix)
  • 📝 문서 변경 (docs)
  • ♻️ 리팩토링 (refactor)
  • 🧪 테스트 추가 / 수정 (test)
  • ⚙️ 설정 변경 (chore)

📝 작업 내용

  • 마이페이지
    • 사용자 프로필 조회
    • 사용자가 속한 열린 앨범 조회
    • 사용자가 만든 열린 앨범 조회
    • 사용자가 속한 닫힌 앨범 조회

📸 스크린샷

사용자 프로필 조회

스크린샷 2025-11-15 오후 3 34 03

사용자가 속한 열린앨범 조회

스크린샷 2025-11-15 오후 1 12 20

사용자가 만든 열린 앨범 조회

스크린샷 2025-11-15 오후 1 24 33

사용자가 속한 닫힌 앨범 조회

스크린샷 2025-11-15 오후 3 33 40

💬 리뷰 요구사항

📜 리뷰 규칙

Reviewer는 아래 P5 Rule을 참고하여 리뷰를 진행합니다.
P5 Rule을 통해 Reviewer는 Reviewee에게 리뷰의 의도를 보다 정확히 전달할 수 있습니다.

  • P1: 꼭 반영해주세요 (Comment)
  • P2: 적극적으로 고려해주세요 (Comment)
  • P3: 웬만하면 반영해 주세요 (Comment)
  • P4: 반영해도 좋고 넘어가도 좋습니다 (Approve)
  • P5: 그냥 사소한 의견입니다 (Approve)

Summary by CodeRabbit

  • New Features

    • 열린/내가 만든 열린/닫힌 앨범 목록 조회 추가(메이커 정보·최대 3개 썸네일 포함, 페이징, 별도 엔드포인트)
    • 내 정보 조회 API 추가(프로필·이름·앨범/사진/좋아요 수)
    • 사진 다운로드 요청에 입력 검증(NotBlank/NotEmpty) 추가
  • Chores

    • 응답 DTO 및 페이지 응답 스키마 보강(스웨거 메타데이터·required 확대)
    • 앨범 조회 매핑 로직·페이지 변환기 및 조회용 저장소·사진 썸네일 조회 쿼리 추가/보강
    • 성공 응답 코드 추가
  • Removed

    • 치즈네컷 Presigned URL 발급 기능 및 관련 응답/엔드포인트 제거

@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2025

Walkthrough

사용자 기준 열린 앨범, 내가 만든 열린 앨범, 닫힌 앨범 조회 기능(서비스·컨트롤러·Swagger), 관련 DTO/매퍼/리포지토리 쿼리 추가 및 사용자 기본정보 조회 API 추가, presigned URL 관련 기능·DTO·매퍼 제거 및 여러 DTO의 OpenAPI 메타데이터 보강이 적용되었습니다.

Changes

Cohort / File(s) 변경 요약
앨범 쿼리 서비스·컨트롤러·Swagger
src/main/java/com/cheeeese/album/application/AlbumQueryService.java, src/main/java/com/cheeeese/album/presentation/AlbumQueryController.java, src/main/java/com/cheeeese/album/presentation/swagger/AlbumQuerySwagger.java
읽기 전용 AlbumQueryService 추가(열린/내가 만든 열린/닫힌 조회) 및 컨트롤러·Swagger 엔드포인트 추가, 페이징 파라미터 제약 포함.
앨범 DTO (페이지/요약 응답)
src/main/java/com/cheeeese/album/dto/response/OpenAlbumPageResponse.java, src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java, src/main/java/com/cheeeese/album/dto/response/ClosedAlbumPageResponse.java, src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java
Open/Closed 페이지 및 요약 레코드 추가(Builder, @Schema 주석 포함).
매퍼
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java
도메인 → DTO 변환 유틸 추가 (페이지/요약 변환).
저장소 / 사진 관련 쿼리
src/main/java/com/cheeeese/album/infrastructure/persistence/UserAlbumRepository.java, src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java, src/main/java/com/cheeeese/cheese4cut/infrastructure/persistence/Cheese4cutPhotoRepository.java
UserAlbumRepository에 오픈/역할/닫힌 조회 메서드 추가, PhotoRepository에 albumIds 기준 Top3 네이티브 쿼리 추가, Cheese4cutPhotoRepository 신규 JPQL 조회 메서드 추가.
사용자 정보 API 및 매퍼/DTO
src/main/java/com/cheeeese/user/application/UserService.java, src/main/java/com/cheeeese/user/presentation/UserController.java, src/main/java/com/cheeeese/user/presentation/swagger/UserSwagger.java, src/main/java/com/cheeeese/user/dto/response/UserInfoResponse.java, src/main/java/com/cheeeese/user/infrastructure/mapper/UserMapper.java
UserService.getUserInfo·컨트롤러 GET /v1/user/me 추가, UserInfoResponse 레코드 및 매퍼(toUserInfoResponse) 추가.
성공 코드
src/main/java/com/cheeeese/global/common/code/SuccessCode.java
USER_INFO_FETCH_SUCCESS, USER_PROFILE_UPDATE_SUCCESS, ALBUM_OPEN_LIST_FETCH_SUCCESS, ALBUM_MY_OPEN_LIST_FETCH_SUCCESS, ALBUM_CLOSED_LIST_FETCH_SUCCESS 상수 추가.
Presigned URL 관련 제거
src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java, src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java, src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java, src/main/java/com/cheeeese/photo/application/PresignedUrlService.java, src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPresignedUrlResponse.java, src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java
Cheese4cut 관련 presigned URL 엔드포인트·서비스 필드·메서드·DTO·매퍼 삭제.
OpenAPI @Schema 보강 (다수 DTO)
여러 src/main/java/com/cheeeese/.../dto/... 파일 (예: AlbumCreationRequest, AlbumCreationResponse, AlbumInvitationResponse, AlbumMakerInfo, Photo*Response 등)
Swagger/OpenAPI @Schema에 requiredProperties 등 메타데이터 추가/수정(스키마 변경·런타임 로직 없음).
유효성 검사 추가
src/main/java/com/cheeeese/photo/dto/request/PhotoDownloadRequest.java
필드 제약 추가: @NotBlank(code), @NotEmpty(photoIds).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Controller as AlbumQueryController
    participant Service as AlbumQueryService
    participant Repo as UserAlbumRepository
    participant PhotoRepo as PhotoRepository
    participant Cheese4cutRepo as Cheese4cutPhotoRepository
    participant Mapper as AlbumQueryMapper
    participant CDN as CdnUrlResolver

    User->>Controller: GET /v1/album/open?page&size
    Controller->>Service: getOpenAlbums(user, page, size)
    Service->>Repo: findOpenAlbumsByUserId(userId, ACTIVE, now, pageable)
    Repo-->>Service: Slice<Album>

    rect rgb(230,245,255)
      note right of Service: 배치로 메이커·썸네일 수집
      Service->>Service: getMakers(albumIds)
      Service->>PhotoRepo: findTop3RecentPhotosInEachAlbum(albumIds, NORMAL)
      PhotoRepo-->>Service: List<Photo>
      Service->>Cheese4cutRepo: findAllCheese4cutPhotosByAlbumIds(albumIds)
      Cheese4cutRepo-->>Service: List<Cheese4cutPhoto>
      Service->>CDN: resolve thumbnail URLs
      CDN-->>Service: thumbnails per album
    end

    Service->>Mapper: toOpenAlbumPageResponse(responses, slice)
    Mapper-->>Service: OpenAlbumPageResponse
    Service-->>Controller: OpenAlbumPageResponse
    Controller-->>User: CommonResponse.success(...)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 집중 검토 대상:
    • AlbumQueryService: 배치 조회 로직(메이커 조회, 썸네일 집계, expired/now 필터), 예외 처리(UserException USER_NOT_FOUND)
    • UserAlbumRepository / PhotoRepository / Cheese4cutPhotoRepository: JPQL/네이티브 쿼리의 필터·정렬·페이징 일관성
    • Presigned URL 제거 여파: 남은 미사용 참조나 매퍼/DTO 삭제의 컴파일 영향

Possibly related PRs

Suggested reviewers

  • zyovn

Poem

🐰 앨범 숲에서 토끼가 외쳐요
열린 것, 닫힌 것, 내가 만든 것 줄지어
썸네일 모아 빛나게 정리했네
코드로 당근 한 조각, 찰칵 축하해요 📸✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 'feat: 마이페이지 구현'은 변경 사항의 핵심을 명확하게 요약하며, 마이페이지 기능 구현이라는 주요 목표를 직관적으로 전달합니다.
✨ 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 feat/#59-mypage

Tip

📝 Customizable high-level summaries are now available!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide custom instructions to shape the summary (bullet lists, tables, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example:

"Create a concise high-level summary as a bullet-point list. Then include a Markdown table showing lines added and removed by each contributing author."


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

🧹 Nitpick comments (4)
src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java (1)

11-41: 열린 앨범 요약 DTO 정의 적절합니다

마이페이지에서 필요한 정보(이벤트 날짜, 메이커 이름, 참여자 수, 만료 예정 시각, 최근 썸네일 등)를 한 번에 담도록 잘 설계되어 있고, 스키마 설명/예시도 충분히 이해하기 좋습니다.

themeEmoji 예시가 "U+1F36F" 포맷인데, 실제 응답이 이모지 코드포인트 문자열인지 실제 이모지 문자인지 한 번만 명세와 맞춰두시면 문서-실제 값 간 혼선을 줄일 수 있을 것 같습니다.

src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1)

41-71: 썸네일 리스트의 null/빈 리스트 표현 방식 한 번 정리 제안

여기서 thumbnails.isEmpty() ? null : thumbnails로 DTO에 세팅하고, DTO 쪽 리스트 필드에는 @JsonInclude(ALWAYS)가 걸려있어서:

  • 썸네일이 없을 때 → JSON에 "recentPhotoThumbnails": null / "thumbnails": null 로 내려가고
  • 일부 서비스 주석에는 “3개 미만이면 아예 제공 안 함”이라고 되어 있어, “필드가 아예 없는 것”과는 의미가 조금 달라집니다.

클라이언트에서 다루기 쉽게 하려면:

  • 항상 리스트(없으면 [])로 내려주거나,
  • 진짜 “아예 제공 안 함”을 의미하고 싶다면 null 대신 빈 리스트를 유지하고 DTO의 JsonInclude 전략을 NON_EMPTY 등으로 조정하는 방법도 있습니다.

현 구조가 명확히 합의된 스펙이라면 그대로 가셔도 되지만, FE/기획과 한 번만 정리해두시면 좋겠습니다.

src/main/java/com/cheeeese/album/application/AlbumQueryService.java (2)

46-118: 쿼리 수(N+1)와 메이커 조회 방식은 추후 확장 시 최적화 여지가 있습니다

현재 구현은:

  • 열린/내 열린 앨범: findOpenAlbums... 로 앨범 슬라이스 조회 후, 각 앨범마다 getMaker(UserRepository 조회) + getRecentThumbnails(PhotoRepository 조회)를 수행하고,
  • 닫힌 앨범: Cheese4cutPhoto는 albumIds 기반 한 번에 모아서 잘 처리하고 있지만, 메이커는 마찬가지로 앨범별로 getMaker를 호출하는 구조입니다.

기본 page size가 작아서 지금은 큰 문제는 없겠지만, 향후:

  • 페이지 크기 증가나 반복 호출이 잦아질 경우 User 조회/Photo 조회에서 N+1 패턴으로 인한 레이턴시가 눈에 띌 수 있고,
  • 메이커가 여러 앨범에서 동일 사용자일 때도 매번 같은 유저를 재조회하게 됩니다.

향후 성능 튜닝 여지가 생기면:

  • 앨범들의 makerId를 모아서 한 번에 IN 조회 후 Map으로 캐싱하거나,
  • User를 앨범 쿼리 단계에서 fetch join 해두는 전략도 고려해볼 만합니다. 지금 단계에서는 구조는 유지하되 개선 포인트로만 인지하고 계셔도 될 것 같습니다.

120-153: 최근 썸네일 노출 정책(3개 미만 시 비노출)에 대한 요구사항 재확인 제안

getRecentThumbnails에서:

  • RECENT_THUMBNAIL_COUNT = 3
  • photos.size() < 3 인 경우 빈 리스트를 반환하고,
  • Mapper에서 빈 리스트면 recentPhotoThumbnailsnull로 세팅하는 구조라,
    → 사진이 1~2장만 있는 앨범은 마이페이지에서 썸네일이 전혀 보이지 않게 됩니다.

이전 PR에서 공유해주신 PhotoService.getRecentThumbnailUrls

0장 → 빈 리스트, 1~4장 → 썸네일 1장, 5장 → 5장 노출
이라는 다른 정책을 의도적으로 사용하고 있었던 것으로 기억합니다. (Based on learnings)

마이페이지는 UI가 달라서 “3장 이상일 때만 모자이크 썸네일을 보여준다”는 새로운 요구사항일 수도 있지만:

  • 1~2장만 있는 열린 앨범도 썸네일 카드가 전혀 안 보이는 게 의도인지,
  • 클라이언트에서 null vs []를 어떻게 취급하는지

한 번 PM/FE와 정의를 맞춰보시면 좋겠습니다. 만약 “가능한 한 썸네일을 보여준다”가 공통 원칙이라면, photos.size() < 3 조건이나 반환 방식(예: 일부라도 노출, 빈 리스트 유지 등)을 조정해야 할 수 있습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1140e8 and a0d0407.

📒 Files selected for processing (16)
  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/ClosedAlbumPageResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumPageResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/UserAlbumRepository.java (2 hunks)
  • src/main/java/com/cheeeese/album/presentation/AlbumQueryController.java (1 hunks)
  • src/main/java/com/cheeeese/album/presentation/swagger/AlbumQuerySwagger.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/infrastructure/persistence/Cheese4cutPhotoRepository.java (1 hunks)
  • src/main/java/com/cheeeese/global/common/code/SuccessCode.java (1 hunks)
  • src/main/java/com/cheeeese/user/application/UserService.java (2 hunks)
  • src/main/java/com/cheeeese/user/dto/response/UserInfoResponse.java (1 hunks)
  • src/main/java/com/cheeeese/user/infrastructure/mapper/UserMapper.java (2 hunks)
  • src/main/java/com/cheeeese/user/presentation/UserController.java (2 hunks)
  • src/main/java/com/cheeeese/user/presentation/swagger/UserSwagger.java (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-13T12:56:22.154Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 58
File: src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java:149-156
Timestamp: 2025-11-13T12:56:22.154Z
Learning: In src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java, the finalizeCheese4cut method intentionally re-sorts photos using findAllByIdInOrderByLikesDescCreatedDesc(request.photoIds()) instead of preserving the client's requested order. This is a defensive measure to ensure photos are always ordered by likes (DESC) and creation time (DESC), regardless of what order the client sends, preventing incorrect ordering from client errors.

Applied to files:

  • src/main/java/com/cheeeese/cheese4cut/infrastructure/persistence/Cheese4cutPhotoRepository.java
  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java
📚 Learning: 2025-10-31T13:17:52.523Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 35
File: src/main/java/com/cheeeese/photo/application/PhotoService.java:46-52
Timestamp: 2025-10-31T13:17:52.523Z
Learning: In src/main/java/com/cheeeese/photo/application/PhotoService.java, the getRecentThumbnailUrls method intentionally returns only the first thumbnail URL when photos.size() < 5, rather than returning all available thumbnails. This is according to product requirements: 0 photos → empty list, 1-4 photos → single thumbnail (most recent), 5 photos → all 5 thumbnails.

Applied to files:

  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java
🧬 Code graph analysis (2)
src/main/java/com/cheeeese/album/application/AlbumQueryService.java (1)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1)
  • AlbumQueryMapper (13-73)
src/main/java/com/cheeeese/user/application/UserService.java (2)
src/main/java/com/cheeeese/user/infrastructure/mapper/UserMapper.java (1)
  • UserMapper (7-27)
src/main/java/com/cheeeese/photo/application/PhotoService.java (1)
  • Service (38-257)
🔇 Additional comments (17)
src/main/java/com/cheeeese/user/infrastructure/mapper/UserMapper.java (1)

18-26: LGTM!

User 엔티티를 UserInfoResponse DTO로 변환하는 매퍼 메서드가 명확하고 올바르게 구현되었습니다.

src/main/java/com/cheeeese/user/presentation/swagger/UserSwagger.java (1)

18-28: LGTM!

API 문서화가 명확하고 완전합니다. 사용자 정보 조회 엔드포인트에 대한 설명이 적절합니다.

src/main/java/com/cheeeese/album/infrastructure/persistence/UserAlbumRepository.java (2)

26-41: LGTM!

열린 앨범 조회 쿼리가 올바르게 구현되었습니다. 필터링 조건과 정렬 기준이 적절합니다.


61-74: LGTM!

닫힌 앨범 조회 쿼리가 올바르게 구현되었습니다. 필터링 조건과 정렬 기준(최신순)이 적절합니다.

src/main/java/com/cheeeese/user/application/UserService.java (1)

52-54: LGTM!

사용자 정보 조회 메서드가 올바르게 구현되었습니다. 매퍼로의 단순한 위임 패턴이 적절합니다.

src/main/java/com/cheeeese/user/presentation/UserController.java (1)

24-28: LGTM!

사용자 정보 조회 엔드포인트가 올바르게 구현되었습니다. REST 패턴과 기존 코드 스타일을 잘 따르고 있습니다.

src/main/java/com/cheeeese/album/dto/response/OpenAlbumPageResponse.java (1)

1-26: LGTM!

열린 앨범 페이지 응답 DTO가 명확하게 정의되었습니다. 페이지네이션 메타데이터 필드가 적절하게 포함되어 있습니다.

src/main/java/com/cheeeese/cheese4cut/infrastructure/persistence/Cheese4cutPhotoRepository.java (1)

11-18: LGTM!

치즈네컷 사진 조회 쿼리가 올바르게 구현되었습니다. JOIN FETCH를 사용하여 N+1 문제를 방지하고 있으며, 정렬 기준도 적절합니다.

src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1)

1-28: LGTM!

닫힌 앨범 요약 응답 DTO가 명확하게 정의되었습니다. @JsonInclude(ALWAYS) 사용으로 thumbnails 필드가 항상 포함되도록 보장하고 있습니다.

src/main/java/com/cheeeese/album/dto/response/ClosedAlbumPageResponse.java (1)

8-25: 페이지 응답 DTO 구조 일관성 좋습니다

responses + listSize + isFirst/isLast/hasNext 구성으로 다른 페이지 DTO들과 잘 맞고, 스키마 설명도 명확해서 그대로 사용해도 무방해 보입니다.

src/main/java/com/cheeeese/user/dto/response/UserInfoResponse.java (1)

6-23: 마이페이지 사용자 정보 DTO 정의 깔끔합니다

필드 구성이 마이페이지 요약 카드에 필요한 정보(앨범/사진/좋아요 수)를 잘 담고 있고, 스키마 예시도 명확해서 바로 사용 가능해 보입니다.

src/main/java/com/cheeeese/global/common/code/SuccessCode.java (1)

20-27: 성공 코드 추가 컨벤션에 잘 맞습니다

새로 추가된 네 가지 SuccessCode 모두 네이밍/메시지/HttpStatus 사용이 기존 코드와 일관적이라 그대로 두셔도 좋겠습니다.

src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1)

15-39: 페이지 메타데이터 매핑 로직 적절합니다

Slice에서 isFirst/isLast/hasNext를 그대로 끌어와서 DTO에 싱크 맞추는 구조가 직관적이고, listSize를 실제 응답 리스트 사이즈 기준으로 두신 것도 사용성 측면에서 좋습니다.

src/main/java/com/cheeeese/album/presentation/swagger/AlbumQuerySwagger.java (1)

18-68: 마이페이지 앨범 조회 스웨거 정의 명확합니다

엔드포인트 분리(/open, /open/me, /closed)와 요약/설명, 페이지 파라미터 기본값이 잘 문서화되어 있어 FE 입장에서 사용하기 편해 보입니다. 정렬 기준(만료 임박 순, 생성일 최신 순)도 설명으로 명시해 두신 점 좋습니다.

src/main/java/com/cheeeese/album/presentation/AlbumQueryController.java (3)

20-26: 컨트롤러 구조가 올바르게 구성되었습니다.

Spring REST 컨트롤러의 표준 패턴을 잘 따르고 있으며, API 버저닝과 의존성 주입이 적절히 설정되어 있습니다.


40-51: 엔드포인트 구현이 적절합니다.

사용자가 생성한 열린 앨범 조회 로직이 올바르게 구현되어 있으며, 적절한 서비스 메서드를 호출하고 있습니다. 페이지네이션 검증은 위에서 언급한 사항을 참고하세요.


58-58: 닫힌 앨범 페이지 크기는 의도적인 설계입니다.

검증 결과, 닫힌 앨범의 size=6 기본값은 다음과 같이 일관되게 적용되고 있습니다:

  • 컨트롤러와 Swagger 문서에서 모두 size=6으로 설정
  • 열린 앨범의 size=2와는 의도적으로 다른 값
  • 다른 엔드포인트들(사진 조회 등)과도 서로 다른 기본값을 사용

UI 요구사항에 따른 의도적인 설계로 보이므로 추가 조치가 필요하지 않습니다.

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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0d0407 and 1ccab8b.

📒 Files selected for processing (10)
  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/UserAlbumRepository.java (2 hunks)
  • src/main/java/com/cheeeese/album/presentation/AlbumQueryController.java (1 hunks)
  • src/main/java/com/cheeeese/album/presentation/swagger/AlbumQuerySwagger.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (0 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java (0 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java (0 hunks)
  • src/main/java/com/cheeeese/photo/application/PresignedUrlService.java (0 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (2 hunks)
  • src/main/java/com/cheeeese/user/application/UserService.java (2 hunks)
💤 Files with no reviewable changes (4)
  • src/main/java/com/cheeeese/photo/application/PresignedUrlService.java
  • src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java
  • src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/UserAlbumRepository.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-13T12:56:22.154Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 58
File: src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java:149-156
Timestamp: 2025-11-13T12:56:22.154Z
Learning: In src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java, the finalizeCheese4cut method intentionally re-sorts photos using findAllByIdInOrderByLikesDescCreatedDesc(request.photoIds()) instead of preserving the client's requested order. This is a defensive measure to ensure photos are always ordered by likes (DESC) and creation time (DESC), regardless of what order the client sends, preventing incorrect ordering from client errors.

Applied to files:

  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
📚 Learning: 2025-10-31T13:17:52.523Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 35
File: src/main/java/com/cheeeese/photo/application/PhotoService.java:46-52
Timestamp: 2025-10-31T13:17:52.523Z
Learning: In src/main/java/com/cheeeese/photo/application/PhotoService.java, the getRecentThumbnailUrls method intentionally returns only the first thumbnail URL when photos.size() < 5, rather than returning all available thumbnails. This is according to product requirements: 0 photos → empty list, 1-4 photos → single thumbnail (most recent), 5 photos → all 5 thumbnails.

Applied to files:

  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
🧬 Code graph analysis (2)
src/main/java/com/cheeeese/album/application/AlbumQueryService.java (1)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1)
  • AlbumQueryMapper (13-73)
src/main/java/com/cheeeese/user/application/UserService.java (1)
src/main/java/com/cheeeese/user/infrastructure/mapper/UserMapper.java (1)
  • UserMapper (7-27)
🔇 Additional comments (16)
src/main/java/com/cheeeese/user/application/UserService.java (2)

7-7: LGTM!

새로운 getUserInfo 메서드를 위해 필요한 import가 올바르게 추가되었습니다.

Also applies to: 10-10


43-45: LGTM!

사용자 정보 조회 메서드가 올바르게 구현되었습니다. 매퍼를 통한 간단한 DTO 변환으로 단일 책임 원칙을 잘 따르고 있으며, 클래스 레벨의 읽기 전용 트랜잭션이 적절하게 적용됩니다.

src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1)

62-71: 코드 포맷팅 정리 확인

공백 제거만 있는 비기능적 변경으로 문제없습니다.

src/main/java/com/cheeeese/album/presentation/swagger/AlbumQuerySwagger.java (3)

24-38: 열린 앨범 조회 엔드포인트 정의 확인

Swagger 정의가 명확하고 유효성 검증도 적절합니다. 기본 size가 2로 작지만, UI 요구사항에 따른 의도적인 설정으로 보입니다.


40-54: 내가 만든 열린 앨범 조회 엔드포인트 정의 확인

Swagger 정의가 일관성 있고 적절합니다.


56-70: 닫힌 앨범 조회 엔드포인트 정의 확인

닫힌 앨범에 대해 더 큰 페이지 크기(기본 6, 최대 20)를 사용하는 것은 히스토리 데이터 조회에 적합합니다.

src/main/java/com/cheeeese/album/application/AlbumQueryService.java (7)

46-59: 열린 앨범 조회 로직 확인

메서드 로직이 명확하고 적절합니다. LocalDateTime.now()를 사용하여 현재 시각 기준으로 필터링하는 것이 의도된 동작으로 보입니다.


61-74: 내가 만든 열린 앨범 조회 로직 확인

메이커 역할 필터링이 올바르게 적용되었습니다.


123-140: 열린 앨범 응답 구성 로직 확인

메서드 구조가 명확합니다. Line 133-134의 메이커 예외 처리는 이전 코멘트에서 언급한 것과 동일한 패턴입니다.


142-153: 메이커 조회 및 검증 로직 확인

Line 149-151의 크기 검증은 모든 메이커가 존재하는지 확실히 확인합니다. 이는 데이터 정합성을 강제하는 엄격한 접근이지만, 이전 코멘트에서 언급한 대로 부분 실패 시나리오를 고려할 필요가 있습니다.


155-186: 최근 썸네일 맵 생성 로직에서 엄격한 필터링

Line 180-185에서 정확히 3개의 썸네일을 가진 앨범만 반환합니다. 이는 사진이 1-2개만 있는 앨범에서는 썸네일이 전혀 표시되지 않음을 의미합니다.

Retrieved learnings에 따르면 PhotoService.getRecentThumbnailUrls에서는 "1-4 photos → single thumbnail"을 반환하는 제품 요구사항이 있었습니다. 현재 구현이 동일한 요구사항을 따르는지 확인이 필요합니다.

만약 1-2개의 사진만 있는 앨범도 썸네일을 표시해야 한다면, 다음과 같이 수정하세요:

     return thumbnailsMap.entrySet().stream()
-            .filter(entry -> entry.getValue().size() == RECENT_THUMBNAIL_COUNT)
+            .filter(entry -> !entry.getValue().isEmpty())
             .collect(Collectors.toMap(
                     Map.Entry::getKey,
                     entry -> List.copyOf(entry.getValue())
             ));

또는 1-2개일 때는 첫 번째만 반환하는 등 특정 비즈니스 로직이 필요한 경우:

     return thumbnailsMap.entrySet().stream()
+            .filter(entry -> !entry.getValue().isEmpty())
             .collect(Collectors.toMap(
                     Map.Entry::getKey,
-                    entry -> List.copyOf(entry.getValue())
+                    entry -> {
+                        List<String> thumbnails = entry.getValue();
+                        if (thumbnails.size() < RECENT_THUMBNAIL_COUNT) {
+                            return List.of(thumbnails.get(0)); // 1-2개일 때는 첫 번째만
+                        }
+                        return List.copyOf(thumbnails);
+                    }
             ));

33-44: 서비스 구성 및 상수 정의 확인

읽기 전용 트랜잭션 설정과 의존성 구성이 적절합니다.


76-121: 닫힌 앨범 조회 로직은 현재 올바르게 구현되어 있습니다.

검증 결과:

  1. 데이터 정합성 보장: Album 엔티티의 makerId 필드는 @Column(nullable=false)로 선언되어 있으므로, 데이터베이스 제약으로 인해 makerId는 절대 null이 될 수 없습니다.

  2. 메이커 누락 시나리오: FK 제약이 명시적으로 적용되어 있다면, 외래키 제약을 위반하는 고아 레코드(orphaned records)는 발생할 수 없습니다. 현재 설계에서 메이커가 누락된 앨범은 데이터베이스 수준에서 차단됩니다.

  3. Fail-fast 원칙 준수: 방어적 프로그래밍의 좋은 예시는 가드 문(guard statements)을 통해 예상치 못한 상황에 빠르게 대응하여 fail-fast 원칙을 따르는 것입니다. getMakers 메서드의 예외 발생은 데이터 무결성 위반에 대한 올바른 대응입니다.

  4. 오류 처리: null이 유효하지 않으면 예외를 던져야 하고, 예외가 발생한 곳을 정확히 추적할 수 있습니다. null을 다음 레이어로 전달하면 null의 출처를 잃어버립니다.

현재 코드의 UserException 발생은 데이터 정합성을 보장하는 적절한 방어 프로그래밍이므로, 메이커 정보를 null로 처리하거나 앨범을 제외하는 제안은 필요하지 않습니다.

Likely an incorrect or invalid review comment.

src/main/java/com/cheeeese/album/presentation/AlbumQueryController.java (3)

29-40: 열린 앨범 조회 엔드포인트 구현 확인

이전 리뷰 코멘트에서 요청된 페이지네이션 파라미터 유효성 검증(@Min, @Max)이 올바르게 적용되었습니다.


42-53: 내가 만든 열린 앨범 조회 엔드포인트 구현 확인

유효성 검증이 적절하게 적용되었고, 구현이 일관성 있습니다.


55-66: 닫힌 앨범 조회 엔드포인트 구현 확인

닫힌 앨범에 더 큰 최대 크기(20)를 허용하는 것이 적절하며, 유효성 검증이 올바르게 구성되었습니다.

@dahyun24 dahyun24 self-assigned this Nov 15, 2025
@dahyun24 dahyun24 added the ✨feature New feature or request label Nov 15, 2025
@dahyun24 dahyun24 linked an issue Nov 15, 2025 that may be closed by this pull request
4 tasks
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

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/com/cheeeese/photo/dto/response/PhotoPageResponse.java (1)

21-34: 예제 JSON의 필드명 likesCnt ↔ 실제 응답 필드 likeCnt 불일치

예제 JSON에서는 "likesCnt"를 사용하지만, 실제 PhotoListResponse 레코드 필드는 likeCnt입니다. API 문서를 보고 구현하는 클라이언트 입장에서 혼동이 생길 수 있어, 예제를 실제 필드명에 맞춰 정리하는 게 좋아 보입니다.

-                            "likesCnt": 1,
+                            "likeCnt": 1,
🧹 Nitpick comments (14)
src/main/java/com/cheeeese/photo/dto/response/PhotoLikedResponse.java (1)

7-15: 설명 문구 개선을 고려해보세요.

@Schemadescription이 "내가 띱한 사진 목록 조회 API"로 되어 있는데, 이는 엔드포인트가 아닌 응답 DTO 클래스에 대한 설명입니다. "내가 띱한 사진 응답" 또는 "내가 띱한 사진 정보"와 같이 DTO 자체를 설명하는 문구가 더 정확할 수 있습니다.

requiredProperties는 모든 필드를 올바르게 포함하고 있으며, 일관된 패턴을 따르고 있습니다.

선택적으로 다음과 같이 수정할 수 있습니다:

 @Schema(
-        description = "내가 띱한 사진 목록 조회 API",
+        description = "내가 띱한 사진 응답",
         requiredProperties = {
                 "photoId",
                 "thumbnailUrl",
                 "isDownloaded",
                 "isRecentlyDownloaded"
         }
 )
src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutFinalResponse.java (1)

9-15: PR 목적과의 관련성을 확인해주세요.

이 파일 변경사항은 OpenAPI 문서화를 개선하는 내용이지만, PR의 주요 목적인 "마이페이지 구현"과 직접적인 연관성이 명확하지 않습니다.

참고: Java record의 모든 컴포넌트는 기본적으로 required이므로 requiredProperties 명시는 선택사항입니다. 명시적 문서화를 위해 추가하는 것은 괜찮지만, 마이페이지 기능과 관련이 없다면 별도 PR로 분리하는 것을 고려해보세요.

src/main/java/com/cheeeese/auth/dto/request/AuthReissueRequest.java (1)

7-12: OpenAPI 스키마 메타데이터 개선이 적절합니다.

requiredProperties 속성 추가로 API 문서에서 필수 필드를 명시적으로 표현하고 있습니다.

선택적으로 런타임 검증 일관성을 위해 필드에 Bean Validation 어노테이션 추가를 고려해보세요:

public record AuthReissueRequest(
        @Schema(
                description = "유효한 refreshToken",
                example = "eyJh.eqi57hK"
        )
        @NotBlank(message = "refreshToken은 필수입니다")
        String refreshToken
) {
}

이렇게 하면 OpenAPI 스키마 생성과 런타임 검증을 모두 처리할 수 있습니다.

src/main/java/com/cheeeese/album/dto/response/AlbumParticipantResponse.java (1)

12-24: OpenAPI required 속성과 Java 레벨 검증의 불일치를 해결하는 것을 권장합니다.

requiredProperties에 명시된 필드들이 OpenAPI 스키마 상으로는 필수로 표시되지만, Java 코드 레벨에서는 참조 타입(String, LocalDate, Integer 등)이면서 검증 어노테이션(@NotNull 등)이 없어 null 값이 가능합니다. 특히 maxParticipantCountcurrentParticipantCount는 boxed Integer 타입이므로 null이 될 수 있습니다.

API 계약과 구현 간의 일관성을 위해 필수 필드에 @NotNull 또는 jakarta.validation.constraints.NotNull 어노테이션 추가를 고려해주세요.

예시:

 public record AlbumParticipantResponse(
 
         @Schema(description = "만료 여부", example = "false")
         boolean isExpired,
 
         @Schema(description = "앨범 제목", example = "졸업식")
+        @NotNull
         String title,
 
         @Schema(description = "앨범 테마 이모지 (또는 이미지 URL)", example = "U+1F9C0")
+        @NotNull
         String themeEmoji,
src/main/java/com/cheeeese/album/dto/response/NewEnterResponse.java (1)

51-71: 내부 레코드의 스키마 문서화가 잘 추가되었습니다.

RecentPhotoResponserequiredProperties가 실제 필드와 일치하며, @NotNull 어노테이션과 함께 사용되어 문서화와 검증이 일관되게 구현되었습니다.

다만, 외부 레코드 NewEnterResponse의 필드들은 @SchemarequiredProperties에 명시되어 있지만 @NotNull 어노테이션이 없습니다. 내부 레코드와의 일관성을 위해 외부 레코드의 필드에도 @NotNull 어노테이션을 추가하는 것을 고려해보세요.

외부 레코드 필드에 @NotNull 추가 예시:

@NotNull
@Schema(description = "참여 상태 (항상 NEW)", example = "NEW")
AlbumJoinStatus joinStatus,

@NotNull
@Schema(description = "앨범 제목", example = "여름 바캉스")
String title,

// ... 나머지 필드들에도 동일하게 적용
src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java (1)

9-17: DTO 필드에 검증 어노테이션 추가 검토 (선택사항)

컨트롤러에서 @Valid 어노테이션을 사용하여 요청을 검증하고 있으므로, 현재 null 값 허용 문제는 이 엔드포인트를 통할 때 완화됩니다. 다만 DTO 자체에 검증 어노테이션을 추가하면 다음과 같은 이점이 있습니다:

  • 필드별 검증 규칙을 명시적으로 문서화
  • 다른 컨트롤러나 서비스에서 재사용할 때 일관된 검증 보장
  • IDE와 유효성 검사 도구의 지원 향상

필요에 따라 다음과 같은 검증 어노테이션 추가를 검토하세요:

@NotBlank(message = "앨범 테마 이모지는 필수입니다")
String themeEmoji,

@NotBlank(message = "앨범 이름은 필수입니다")
String title,

@Positive(message = "참여자 수는 양수여야 합니다")
int participant,

@NotNull(message = "행사 날짜는 필수입니다")
LocalDate eventDate
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1)

58-66: 정렬 기준에 id 추가를 고려하면 결과 안정성이 올라갑니다 (선택 사항)

현재는 ORDER BY p.createdAt DESC 하나만 사용하고 있어서 같은 시각에 생성된 사진이 많을 경우 반환 순서가 DB 실행 계획에 따라 약간씩 달라질 수 있습니다. 필요하다면 아래처럼 p.id를 함께 포함해 두면, 네이티브 쿼리(created_at DESC, photo_id DESC)와도 정렬 기준이 맞고 결과가 더 안정적으로 유지될 것 같습니다.

-        ORDER BY p.createdAt DESC
+        ORDER BY p.createdAt DESC, p.id DESC
src/main/java/com/cheeeese/album/dto/response/AlbumCreationResponse.java (1)

9-18: 코드 포맷 개선을 고려하세요.

@Schema 어노테이션의 닫는 괄호와 public record 선언 사이에 줄바꿈이 없어 가독성이 떨어집니다.

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

 @Schema(
         description = "앨범 생성 응답",
         requiredProperties = {
                 "themeEmoji",
                 "title",
                 "eventDate",
                 "currentPhotoCnt",
                 "code"
         }
-)public record AlbumCreationResponse(
+)
+public record AlbumCreationResponse(
src/main/java/com/cheeeese/album/dto/response/AlbumInvitationResponse.java (1)

9-20: 코드 포맷 개선을 고려하세요.

@Schema 어노테이션의 닫는 괄호와 public record 선언 사이에 줄바꿈이 없어 가독성이 떨어집니다.

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

 @Schema(
         description = "앨범 초대장 확인 응답 DTO",
         requiredProperties = {
                 "title",
                 "themeEmoji",
                 "eventDate",
                 "expiredAt",
                 "makerName",
                 "makerProfileImage",
                 "isExpired"
         }
-)public record AlbumInvitationResponse(
+)
+public record AlbumInvitationResponse(
src/main/java/com/cheeeese/user/dto/response/UserInfoResponse.java (1)

6-16: 코드 포맷 개선을 고려하세요.

@Schema 어노테이션의 닫는 괄호와 public record 선언 사이에 줄바꿈이 없어 가독성이 떨어집니다.

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

 @Builder
 @Schema(
         description = "사용자 기본 정보 응답",
         requiredProperties = {
                 "profileImage",
                 "name",
                 "albumCount",
                 "photoCount",
                 "likesCount"
         }
-)public record UserInfoResponse(
+)
+public record UserInfoResponse(
src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1)

10-19: 코드 포맷 개선을 고려하세요.

@Schema 어노테이션의 닫는 괄호와 public record 선언 사이에 줄바꿈이 없어 가독성이 떨어집니다.

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

 @Builder
 @Schema(
         description = "닫힌 앨범 요약 정보",
         requiredProperties = {
                 "code",
                 "title",
                 "makerName",
                 "eventDate"
         }
 )
+
 public record ClosedAlbumSummaryResponse(
src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java (1)

50-52: JsonInclude 설정과 매퍼 로직의 불일치

@JsonInclude(JsonInclude.Include.ALWAYS)로 인해 recentPhotoThumbnails가 null이어도 항상 직렬화되지만, AlbumQueryMapper.toOpenAlbumSummaryResponse (line 42)에서는 thumbnails.isEmpty() ? null : thumbnails로 빈 리스트를 명시적으로 null로 변환합니다.

빈 썸네일 리스트를 클라이언트에 전달하는 의도라면 ALWAYS 대신 다음을 권장합니다:

-        @JsonInclude(JsonInclude.Include.ALWAYS)
+        @JsonInclude(JsonInclude.Include.NON_NULL)
         @Schema(description = "최근 업로드된 사진 썸네일 3장")
         List<String> recentPhotoThumbnails

또는 매퍼에서 null 대신 빈 리스트를 반환하도록 수정하세요.

src/main/java/com/cheeeese/album/application/AlbumQueryService.java (2)

103-118: 메이커 검증을 더 일찍 수행하여 실패 빠르게 처리

Lines 107-108에서 메이커가 없을 때 예외를 던지지만, 이는 이미 cheese4cutPhotoRepository에서 모든 사진을 조회하고 그룹핑한 이후입니다. buildOpenAlbumResponses (line 128)처럼 응답 생성 전에 메이커 맵을 먼저 검증하면 불필요한 데이터베이스 조회를 방지할 수 있습니다.

다음과 같이 리팩토링을 권장합니다:

         }
 
         Map<Long, User> makerMap = getMakers(expiredAlbums.getContent());
+        // getMakers already validates all makers exist
 
         List<Cheese4cutPhoto> allCheese4cutPhotos = cheese4cutPhotoRepository.findAllCheese4cutPhotosByAlbumIds(albumIds);
 
         Map<Long, List<Cheese4cutPhoto>> cheese4cutPhotoMap = allCheese4cutPhotos.stream()
                 .collect(Collectors.groupingBy(
                         c4p -> c4p.getCheese4cut().getAlbum().getId(),
                         Collectors.toList()
                 ));
 
         List<ClosedAlbumSummaryResponse> responses = expiredAlbums.getContent().stream()
                 .map(album -> {
                     List<Cheese4cutPhoto> c4pList = cheese4cutPhotoMap.getOrDefault(album.getId(), List.of());
 
-                    User maker = Optional.ofNullable(makerMap.get(album.getMakerId()))
-                            .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND));
+                    User maker = makerMap.get(album.getMakerId()); // Already validated by getMakers
 
                     List<String> thumbnails = c4pList.stream()

169-178: 수동 그룹핑을 Collectors.groupingBy로 리팩토링 권장

Lines 171-178의 수동 루프는 Collectors.groupingBy를 사용하면 더 간결하고 가독성이 높아집니다.

-        Map<Long, List<String>> thumbnailsMap = new HashMap<>();
-
-        for (Photo photo : photos) {
-            Long albumId = photo.getAlbum().getId();
-            List<String> thumbnails = thumbnailsMap.computeIfAbsent(albumId, key -> new ArrayList<>());
-
-            if (thumbnails.size() < RECENT_THUMBNAIL_COUNT) {
-                thumbnails.add(cdnUrlResolver.resolveThumbnail(photo.getThumbnailUrl()));
-            }
-        }
+        Map<Long, List<String>> thumbnailsMap = photos.stream()
+                .collect(Collectors.groupingBy(
+                        photo -> photo.getAlbum().getId(),
+                        Collectors.collectingAndThen(
+                                Collectors.mapping(
+                                        photo -> cdnUrlResolver.resolveThumbnail(photo.getThumbnailUrl()),
+                                        Collectors.toList()
+                                ),
+                                list -> list.stream().limit(RECENT_THUMBNAIL_COUNT).collect(Collectors.toList())
+                        )
+                ));

참고: 리포지토리 쿼리 findTop3RecentPhotosInEachAlbum이 이미 앨범당 최대 3개로 제한한다면, limit(RECENT_THUMBNAIL_COUNT) 부분은 생략 가능합니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ccab8b and 4c689fd.

📒 Files selected for processing (31)
  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumCreationResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumInvitationResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumMakerInfo.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumParticipantListResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumParticipantResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/ClosedAlbumPageResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/ExistingEnterResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/NewEnterResponse.java (2 hunks)
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumPageResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/UploadAvailableCountResponse.java (1 hunks)
  • src/main/java/com/cheeeese/auth/dto/request/AuthReissueRequest.java (1 hunks)
  • src/main/java/com/cheeeese/auth/dto/response/AuthExchangeResponse.java (1 hunks)
  • src/main/java/com/cheeeese/auth/dto/response/AuthReissueResponse.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutFinalResponse.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPresignedUrlResponse.java (0 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPreviewResponse.java (2 hunks)
  • src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (0 hunks)
  • src/main/java/com/cheeeese/photo/dto/request/PhotoDownloadRequest.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoDetailResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoDownloadResponse.java (2 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoLikedPageResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoLikedResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoListResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoPageResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoPresignedUrlResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (2 hunks)
  • src/main/java/com/cheeeese/user/dto/response/UserInfoResponse.java (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPresignedUrlResponse.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/album/dto/response/ClosedAlbumPageResponse.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-31T13:17:52.523Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 35
File: src/main/java/com/cheeeese/photo/application/PhotoService.java:46-52
Timestamp: 2025-10-31T13:17:52.523Z
Learning: In src/main/java/com/cheeeese/photo/application/PhotoService.java, the getRecentThumbnailUrls method intentionally returns only the first thumbnail URL when photos.size() < 5, rather than returning all available thumbnails. This is according to product requirements: 0 photos → empty list, 1-4 photos → single thumbnail (most recent), 5 photos → all 5 thumbnails.

Applied to files:

  • src/main/java/com/cheeeese/photo/dto/response/PhotoListResponse.java
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java
  • src/main/java/com/cheeeese/album/dto/response/NewEnterResponse.java
📚 Learning: 2025-11-13T12:56:22.154Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 58
File: src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java:149-156
Timestamp: 2025-11-13T12:56:22.154Z
Learning: In src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java, the finalizeCheese4cut method intentionally re-sorts photos using findAllByIdInOrderByLikesDescCreatedDesc(request.photoIds()) instead of preserving the client's requested order. This is a defensive measure to ensure photos are always ordered by likes (DESC) and creation time (DESC), regardless of what order the client sends, preventing incorrect ordering from client errors.

Applied to files:

  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPreviewResponse.java
  • src/main/java/com/cheeeese/album/application/AlbumQueryService.java
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutFinalResponse.java
🧬 Code graph analysis (1)
src/main/java/com/cheeeese/album/application/AlbumQueryService.java (1)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1)
  • AlbumQueryMapper (13-73)
🔇 Additional comments (25)
src/main/java/com/cheeeese/auth/dto/response/AuthExchangeResponse.java (1)

7-17: LGTM! OpenAPI 문서화가 적절히 추가되었습니다.

클래스 레벨의 @Schema 어노테이션이 올바르게 추가되어 API 명세를 개선합니다. requiredProperties에 명시된 모든 필드가 실제 레코드의 필드와 일치하며, 문서화 목적으로 적절합니다.

src/main/java/com/cheeeese/album/dto/response/ExistingEnterResponse.java (1)

9-19: API 문서 명세가 정확하게 개선되었습니다.

레코드의 모든 필드를 requiredProperties에 명시적으로 선언하여 OpenAPI 스키마가 더 명확해졌습니다. 필드 이름도 모두 정확하게 일치합니다.

src/main/java/com/cheeeese/auth/dto/response/AuthReissueResponse.java (1)

7-13: OpenAPI 스키마 메타데이터 개선이 적절합니다.

requiredProperties 선언이 레코드 구조와 정확히 일치하며, API 문서화를 개선합니다. PR 전반에 걸친 일관된 메타데이터 개선 패턴의 일부로 보입니다.

src/main/java/com/cheeeese/album/dto/response/UploadAvailableCountResponse.java (1)

7-14: LGTM! OpenAPI 스키마 메타데이터가 올바르게 추가되었습니다.

클래스 레벨의 @Schema 어노테이션이 올바르게 적용되었으며, requiredProperties에 나열된 모든 속성명이 레코드 필드명과 정확히 일치합니다. 이는 API 문서의 명확성을 향상시키고 API 소비자에게 명시적인 계약을 제공합니다.

src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutFinalResponse.java (1)

24-31: LGTM!

내부 record에 대한 OpenAPI 스키마 정의가 올바르게 추가되었습니다. 모든 필드가 requiredProperties에 정확히 나열되어 있고, API 문서의 명확성을 향상시킵니다.

src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPreviewResponse.java (2)

10-18: requiredProperties에 myRole 필드 누락 확인 필요

requiredProperties 배열에 myRole 필드가 포함되어 있지 않습니다. 33번 라인에 정의된 myRole 필드가 API 응답에서 항상 존재해야 하는 필수 필드라면 requiredProperties에 추가해야 합니다.

만약 myRole이 선택적(optional) 필드로 의도된 것이라면 현재 구현이 올바르지만, 그렇지 않다면 다음과 같이 수정이 필요합니다:

 @Schema(
         description = "치즈네컷 확정 전 미리보기 응답 DTO (좋아요 TOP 4)",
         requiredProperties = {
                 "isFinalized",
                 "previewPhotos",
                 "uniqueLikesCount",
-                "participant"
+                "participant",
+                "myRole"
         }
 )

myRole 필드가 응답에서 항상 존재하는지 확인해 주세요.


37-44: LGTM!

중첩 레코드 PreviewPhotoInfo의 Swagger 스키마 메타데이터가 올바르게 추가되었습니다. 모든 필드가 requiredProperties에 포함되어 있고, 명확한 설명이 제공되어 있습니다.

src/main/java/com/cheeeese/album/dto/response/NewEnterResponse.java (1)

11-23: OpenAPI 스키마 문서화가 잘 추가되었습니다.

requiredProperties에 명시된 8개의 필드가 실제 레코드 필드와 정확히 일치하며, API 문서 품질을 향상시키는 좋은 개선사항입니다.

src/main/java/com/cheeeese/album/dto/response/AlbumParticipantListResponse.java (2)

10-15: LGTM! 필수 속성 메타데이터가 올바르게 추가되었습니다.

레코드의 실제 컴포넌트와 requiredProperties 목록이 일치합니다. OpenAPI 스키마 문서화가 개선되었습니다.


21-29: 코드 리뷰 주석은 부정확합니다.

User 엔티티의 profileImage 필드는 @Column(name = "profile_image", nullable = false)로 선언되어 있어, 데이터베이스 수준에서 null 값을 허용하지 않습니다. 따라서 사용자가 프로필 이미지를 설정하지 않아도 프로필 이미지 값이 null이 되는 상황은 발생할 수 없습니다. ParticipantInfo에서 profileImage를 필수 필드(requiredProperties)로 지정한 것은 올바른 결정이며, 현재 구현은 정확합니다.

Likely an incorrect or invalid review comment.

src/main/java/com/cheeeese/photo/dto/response/PhotoListResponse.java (1)

7-17: 클래스 레벨 스키마 requiredProperties 지정 좋습니다

레코드 필드들과 requiredProperties 키가 일치하고, 마이페이지/사진 목록 API 스펙을 더 명확하게 해줘서 좋습니다. 런타임 동작 변화도 없어서 부담 없이 적용 가능해 보입니다.

src/main/java/com/cheeeese/photo/dto/response/PhotoLikedPageResponse.java (1)

9-18: 페이지 응답 DTO 스키마 메타데이터 정리 잘 되어 있습니다

페이지네이션 공통 필드들을 requiredProperties로 명시해서 OpenAPI 문서 일관성이 높아졌습니다. 다른 페이지 응답 DTO들과 패턴도 맞아서 유지보수에 유리해 보입니다.

src/main/java/com/cheeeese/album/dto/response/AlbumMakerInfo.java (1)

6-12: 앨범 메이커 DTO 필수 필드 명시가 명확합니다

makerName, makerProfileImage 두 필드를 requiredProperties로 고정해 둔 점이 직관적이고, 실제 레코드 필드와도 정확히 일치해서 문서/응답 간 혼동 여지가 없어 보입니다.

src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1)

73-93: 앨범당 최대 3개 사진을 가져오는 네이티브 쿼리 설계가 목적에 잘 부합합니다

ROW_NUMBER() OVER (PARTITION BY album_id ORDER BY created_at DESC, photo_id DESC)로 앨범별 최신 3장만 뽑고, 밖에서 ORDER BY t.album_id ASC, t.rn ASC로 정렬해 주는 구조가 이전에 논의되었던 “앨범당 TOP N만 가져오기” 성능 이슈를 잘 해소해 주는 구현으로 보입니다. List<Photo>를 반환하도록 설계된 것도 JPA 엔티티 매핑 관점에서 자연스럽습니다.

src/main/java/com/cheeeese/photo/dto/response/PhotoPageResponse.java (1)

9-18: 사진 페이지 응답 DTO 스키마 일관성 좋습니다

페이지 공통 필드(responses, listSize, isFirst, isLast, hasNext)를 requiredProperties로 명시한 덕분에 다른 페이지 DTO들과의 스펙 일관성이 좋아졌고, 문서 생성 시에도 필수 정보가 잘 드러날 것 같습니다.

src/main/java/com/cheeeese/user/dto/response/UserInfoResponse.java (1)

17-31: 잘 구현되었습니다!

사용자 기본 정보를 담는 DTO의 구조와 필드 정의가 적절합니다. primitive 타입(long)을 사용하여 null 불가능한 카운트 값을 표현한 것도 requiredProperties와 일치합니다.

src/main/java/com/cheeeese/photo/dto/response/PhotoDetailResponse.java (1)

7-19: 잘 작성되었습니다!

@Schema 어노테이션과 public record 선언 사이의 포맷이 적절하며, requiredProperties가 모든 필드를 올바르게 포함하고 있습니다.

src/main/java/com/cheeeese/photo/dto/response/PhotoPresignedUrlResponse.java (1)

9-26: 잘 작성되었습니다!

외부 record와 내부 record 모두에 대한 @Schema 어노테이션이 적절하게 구성되어 있으며, requiredProperties가 올바르게 정의되어 있습니다.

src/main/java/com/cheeeese/photo/dto/response/PhotoDownloadResponse.java (1)

10-50: 잘 작성되었습니다!

외부 record와 내부 nested record 모두에 대한 @Schema 어노테이션이 적절하게 구성되어 있으며, requiredProperties가 모든 필수 필드를 올바르게 정의하고 있습니다.

src/main/java/com/cheeeese/album/dto/response/OpenAlbumPageResponse.java (1)

8-34: 잘 작성되었습니다!

페이지네이션을 위한 DTO 구조가 적절하며, listSize, isFirst, isLast, hasNext 등의 메타데이터 필드가 잘 정의되어 있습니다. @Schema 어노테이션과 requiredProperties도 올바르게 구성되었습니다.

src/main/java/com/cheeeese/album/application/AlbumQueryService.java (5)

46-59: LGTM!

페이지네이션과 활성 앨범 필터링 로직이 올바르게 구현되었습니다.


61-74: LGTM!

MAKER 역할로 필터링하여 사용자가 생성한 앨범만 조회하는 로직이 정확합니다.


131-139: LGTM!

메이커 맵을 먼저 검증한 후 응답을 생성하는 흐름이 올바릅니다. 다만 Line 133-134의 Optional.ofNullablegetMakers에서 이미 모든 메이커의 존재를 검증했으므로 기술적으로는 불필요하지만, 방어적 코딩 관점에서 허용 가능합니다.


142-153: LGTM!

메이커를 일괄 조회하고 누락된 메이커가 있을 경우 즉시 실패하는 패턴이 우수합니다. N+1 쿼리를 방지하고 조기 실패를 보장합니다.


180-185: 필터링 로직 확인: 정확히 3개 썸네일만 반환하는 것이 확실함

검증 결과, 라인 181의 필터가 정확히 entry.getValue().size() == RECENT_THUMBNAIL_COUNT (=3)을 강제하고 있음을 확인했습니다.

문제점:

  • 라인 175: 루프는 thumbnails.size() < RECENT_THUMBNAIL_COUNT 조건으로 1~3개까지 수락
  • 라인 181: 필터는 size == 3 조건으로 정확히 3개만 반환
  • 결과: 사진이 1~2개인 앨범은 썸네일이 표시되지 않음

모순 사항:
1-3개까지 수집한 후 정확히 3개만 필터링하는 로직이 의도와 일치하는지 제품 팀에 확인이 필요합니다.

의도가 "가능한 만큼의 썸네일 표시"라면 라인 181의 필터를 제거하세요:

-        return thumbnailsMap.entrySet().stream()
-                .filter(entry -> entry.getValue().size() == RECENT_THUMBNAIL_COUNT)
-                .collect(Collectors.toMap(
+        return thumbnailsMap.entrySet().stream()
+                .collect(Collectors.toMap(
                         Map.Entry::getKey,
                         entry -> List.copyOf(entry.getValue())
                 ));

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c689fd and e85990c.

📒 Files selected for processing (4)
  • src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/request/PhotoDownloadRequest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/photo/dto/request/PhotoDownloadRequest.java
🔇 Additional comments (5)
src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (2)

11-19: 스키마 일관성 개선 확인됨

이전 리뷰에서 논의된 @JsonInclude.ALWAYSrequiredProperties 불일치 문제가 현재 코드에서는 해결된 것으로 보입니다. thumbnails 필드가 nullable=true로 선언되어 있고 requiredProperties에서 제외되어 있어 일관성이 확보되었습니다.


27-28: NPE 우려는 부당함 - 호출 코드에서 null 체크 완료

AlbumQueryService.java 107-108줄에서 이미 Optional.ofNullable(makerMap.get(album.getMakerId())).orElseThrow(...)maker가 null이 아님을 보장합니다. null인 경우 매퍼 호출 전에 예외를 던지므로, 매퍼의 maker.getName() 호출(67줄)은 안전합니다.

makerName을 필수 속성으로 유지하는 것이 올바릅니다.

Likely an incorrect or invalid review comment.

src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (2)

15-26: 페이지 응답 매핑 로직 확인됨

listSizeresponses.size()로 설정하여 실제 반환된 항목 수를 나타내고, 페이지네이션 메타데이터(isFirst, isLast, hasNext)를 Slice에서 올바르게 가져오고 있습니다.


28-39: 페이지 응답 매핑 로직 확인됨

toOpenAlbumPageResponse와 동일한 패턴으로 페이지네이션 메타데이터를 올바르게 처리하고 있습니다.

src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java (1)

11-24: 리뷰 의견 재검토 완료: 이전 지적 사항이 코드에 반영되어 있습니다.

코드 검증 결과, maker 파라미터는 toOpenAlbumSummaryResponse 호출 시점에서 null이 될 수 없습니다:

  1. AlbumQueryService.getMakers(): makerIds 크기와 조회된 makers 맵 크기를 검증하며, 불일치 시 UserException을 던집니다 (lines 149-150)
  2. 호출 지점: 모든 mapper 호출 전에 Optional.ofNullable(makerMap.get(...)).orElseThrow()를 사용하여 null 체크를 수행합니다 (lines 107-108, 133-134)

maker는 이미 null-safe하게 처리되고 있으며, requiredPropertiesmakerName을 포함한 것이 정확합니다.

Likely an incorrect or invalid review comment.

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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e85990c and 0cd1281.

📒 Files selected for processing (3)
  • src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java
🔇 Additional comments (1)
src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1)

19-34: Record 구조가 잘 정의되었습니다.

과거 리뷰 논의에서 제기된 thumbnails 필드의 @JsonInclude 관련 이슈가 적절히 해결된 것으로 보입니다. 현재 구현에서는:

  • thumbnailsrequiredProperties에서 제외되어 선택적 필드로 처리됨
  • nullable = true로 명시되어 null 값 허용이 명확함
  • 별도의 @JsonInclude 애너테이션 없이 기본 직렬화 동작 사용

이는 개발자의 의도와 일치하며, OpenAPI 스키마와 실제 동작 간의 일관성이 유지됩니다.

Based on learnings

@Say-Cheeeese Say-Cheeeese deleted a comment from coderabbitai bot Nov 15, 2025
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

🧹 Nitpick comments (2)
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1)

79-99: 앨범별 TOP3 네이티브 쿼리 설계는 좋아 보이며, 스키마/DB 호환성만 한번 확인해 주세요

전반적으로는 이전에 지적되던 “페이지네이션 없이 모든 COMPLETED 사진을 가져오는” 문제를 잘 해소하는 구현입니다. DB 단에서 앨범별 TOP3만 뽑아오도록 제한해서 성능 측면에서도 합리해 보입니다.

다만, 아래 몇 가지는 한 번만 체크해 주시면 좋겠습니다.

  • ORDER BY p.created_at DESC, p.photo_id DESC에서 실제 컬럼명이 정말 photo_id인지 확인이 필요합니다. 만약 PK 컬럼이 id라면 런타임 SQL 에러가 날 수 있으니, 스키마에 맞춰 컬럼명을 맞춰 주세요.
  • 네이티브 쿼리에서 SELECT *로 서브쿼리(t) 결과를 반환하고, 반환 타입을 List<Photo>로 받고 있습니다. 보통 매핑에 필요한 컬럼들만 있으면 추가 컬럼(rn)은 무시되지만, 사용하는 JPA 구현체/네이밍 전략에 따라 미묘한 이슈가 생길 수도 있어 통합 테스트로 한 번 호출을 검증해 두는 게 안전합니다.
  • ROW_NUMBER() 윈도우 함수는 DB 버전 의존성이 있으니, 운영 DB(MySQL 8+, PostgreSQL 등)가 해당 함수를 지원하는지 이미 확인하셨다면 괜찮고, 아니라면 다른 방식(서브쿼리 + LIMIT 등)도 고려가 필요합니다.
  • albumIds 리스트가 매우 커질 가능성이 있다면, 호출부에서 입력 크기 제한이나 상위 앨범 목록 자체의 페이징도 함께 고려해 두면 장기적으로 안전합니다.

구현 방향 자체는 좋고, 이전 성능 이슈에 대한 해결책으로 적절해 보입니다. 위 항목들만 한번 실제 스키마/환경 기준으로 검증해 주세요.

src/main/java/com/cheeeese/photo/dto/response/PhotoDetailResponse.java (1)

9-21: @Schema 설명 문구 개선을 고려하세요.

현재 description이 "사진 상세 조회 API"로 되어 있는데, 이 클래스는 API 자체가 아닌 응답 DTO입니다. "사진 상세 조회 응답" 또는 "사진 상세 정보"와 같이 더 정확한 표현으로 변경하는 것이 좋습니다.

제안하는 수정:

 @Schema(
-        description = "사진 상세 조회 API",
+        description = "사진 상세 정보",
         requiredProperties = {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd1281 and 6d251a7.

📒 Files selected for processing (12)
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java (0 hunks)
  • src/main/java/com/cheeeese/global/common/code/SuccessCode.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoDetailResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoLikedPageResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoLikedResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoListResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/response/PhotoPageResponse.java (1 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1 hunks)
  • src/main/java/com/cheeeese/user/application/UserService.java (2 hunks)
  • src/main/java/com/cheeeese/user/infrastructure/mapper/UserMapper.java (2 hunks)
  • src/main/java/com/cheeeese/user/presentation/UserController.java (2 hunks)
  • src/main/java/com/cheeeese/user/presentation/swagger/UserSwagger.java (2 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/java/com/cheeeese/user/presentation/UserController.java
  • src/main/java/com/cheeeese/photo/dto/response/PhotoLikedPageResponse.java
  • src/main/java/com/cheeeese/photo/dto/response/PhotoListResponse.java
  • src/main/java/com/cheeeese/global/common/code/SuccessCode.java
  • src/main/java/com/cheeeese/user/infrastructure/mapper/UserMapper.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-13T12:56:22.154Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 58
File: src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java:149-156
Timestamp: 2025-11-13T12:56:22.154Z
Learning: In src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java, the finalizeCheese4cut method intentionally re-sorts photos using findAllByIdInOrderByLikesDescCreatedDesc(request.photoIds()) instead of preserving the client's requested order. This is a defensive measure to ensure photos are always ordered by likes (DESC) and creation time (DESC), regardless of what order the client sends, preventing incorrect ordering from client errors.

Applied to files:

  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
📚 Learning: 2025-10-31T13:17:52.523Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 35
File: src/main/java/com/cheeeese/photo/application/PhotoService.java:46-52
Timestamp: 2025-10-31T13:17:52.523Z
Learning: In src/main/java/com/cheeeese/photo/application/PhotoService.java, the getRecentThumbnailUrls method intentionally returns only the first thumbnail URL when photos.size() < 5, rather than returning all available thumbnails. This is according to product requirements: 0 photos → empty list, 1-4 photos → single thumbnail (most recent), 5 photos → all 5 thumbnails.

Applied to files:

  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
🧬 Code graph analysis (1)
src/main/java/com/cheeeese/user/application/UserService.java (1)
src/main/java/com/cheeeese/user/infrastructure/mapper/UserMapper.java (1)
  • UserMapper (11-44)
🔇 Additional comments (5)
src/main/java/com/cheeeese/photo/dto/response/PhotoLikedResponse.java (1)

7-15: API 스키마 문서화 개선이 좋습니다.

Swagger 스키마에 requiredProperties를 명시적으로 추가하여 API 문서화와 검증이 개선되었습니다. nameimageUrl이 필수 속성 목록에서 제외된 것으로 보아 이들이 선택적 필드임을 나타냅니다.

다음을 확인해 주세요:

  • nameimageUrl 필드가 실제로 null 가능한지, 비즈니스 로직과 일치하는지 검증 부탁드립니다.
src/main/java/com/cheeeese/photo/dto/response/PhotoPageResponse.java (1)

9-18: 페이지네이션 스키마 문서화 개선이 적절합니다.

모든 페이지네이션 메타데이터 필드(responses, listSize, isFirst, isLast, hasNext)를 필수 속성으로 명시하여 API 계약을 명확히 했습니다. 페이지네이션 응답에서 이러한 필드들은 항상 존재해야 하므로 적절한 변경입니다.

src/main/java/com/cheeeese/user/presentation/swagger/UserSwagger.java (1)

20-31: 잘 구현되었습니다!

Swagger 문서가 명확하고, 메서드 시그니처가 기존 패턴과 일관성있게 작성되었습니다. API 설명도 충분히 상세합니다.

src/main/java/com/cheeeese/user/application/UserService.java (1)

68-70: 잘 구현되었습니다!

사용자 정보 조회 로직이 간결하고 명확합니다. Mapper를 통한 변환 방식이 기존 코드 패턴과 일관성있게 작성되었습니다.

src/main/java/com/cheeeese/photo/dto/response/PhotoDetailResponse.java (1)

11-20: photoId는 Long wrapper 타입이 아닌 long primitive 타입으로 변경하세요.

PhotoDetailResponsephotoIdrequiredProperties에 필수 필드로 선언되어 있지만, Long (nullable) 타입으로 정의되어 있습니다. 검증 결과, Photo 엔티티의 ID는 @Column(nullable = false)@GeneratedValue(strategy = GenerationType.IDENTITY)로 설정되어 있고, PhotoQueryServicegetPhotoDetail() 메서드에서 photoRepository.findByIdAndAlbum_Code().orElseThrow()로 조회되므로 실행 시점에 null이 될 수 없습니다. 따라서 null 처리 검증은 불필요하지만, 타입 선언의 일관성을 위해 long primitive 타입으로 변경하는 것이 좋습니다.

// 현재
Long photoId,

// 권장
long photoId,

Copy link
Member

@zyovn zyovn left a comment

Choose a reason for hiding this comment

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

ㅅ고하셧습니다~

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

🧹 Nitpick comments (3)
src/main/java/com/cheeeese/photo/dto/request/PhotoDownloadRequest.java (1)

14-14: 검증 어노테이션이 올바르게 구현되었습니다.

@NotBlank@NotEmpty 어노테이션이 적절하게 추가되어 이전 리뷰의 요구사항을 완벽히 충족합니다. 에러 메시지도 명확하고 프로젝트 컨벤션을 따릅니다.

선택사항: 다운로드 수량 제한 고려

대량 다운로드로 인한 성능 문제나 남용을 방지하기 위해 photoIds에 최대 개수 제한을 추가하는 것을 고려해보세요:

 @Schema(description = "사진 고유 ID", example = "[1, 2, 3]")
 @NotEmpty(message = "사진 ID 목록은 비어있을 수 없습니다")
+@Size(max = 100, message = "한 번에 최대 100개의 사진만 다운로드할 수 있습니다")
 List<Long> photoIds

참고: 이는 선택적 개선사항이며, 비즈니스 요구사항에 따라 적용 여부를 결정하세요.

Also applies to: 18-18

src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (2)

15-39: 두 페이지 응답 매퍼 간 코드 중복

toOpenAlbumPageResponsetoClosedAlbumPageResponse 메서드가 거의 동일한 구조를 가지고 있습니다. 제네릭을 활용한 통합 메서드로 리팩토링을 고려할 수 있지만, 현재 구현도 타입 안전성을 제공하며 충분히 명확합니다.


41-71: 파라미터 null 안전성 강화 고려

albumthumbnails 파라미터에 대한 null 검증이 없어, null이 전달될 경우 NPE가 발생할 수 있습니다:

  • Lines 47-54, 65-69: album.getCode(), album.getTitle() 등 호출 전 null 체크 없음
  • Lines 55, 69: thumbnails.isEmpty() 호출 전 null 체크 없음 (null이면 NPE 발생)

서비스 계층에서 유효성을 보장하고 있다면 현재 구현도 문제없지만, 방어적 코딩 차원에서 다음과 같은 개선을 고려할 수 있습니다:

public static OpenAlbumSummaryResponse toOpenAlbumSummaryResponse(
        Album album,
        User maker,
        List<String> thumbnails
){
    return OpenAlbumSummaryResponse.builder()
            .code(album.getCode())
            .themeEmoji(album.getThemeEmoji())
            .title(album.getTitle())
            .eventDate(album.getEventDate())
            .makerName(maker.getName())
            .currentParticipant(album.getCurrentParticipant())
            .participant(album.getParticipant())
            .expiredAt(album.getExpiredAt())
            .recentPhotoThumbnails(thumbnails == null || thumbnails.isEmpty() ? null : thumbnails)
            .build();
}

참고: maker는 이전 리뷰에서 논의된 바와 같이 서비스 계층에서 검증되므로 null 체크가 불필요합니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d251a7 and ad9bc10.

📒 Files selected for processing (4)
  • src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumQueryMapper.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/request/PhotoDownloadRequest.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/album/dto/response/OpenAlbumSummaryResponse.java
🔇 Additional comments (2)
src/main/java/com/cheeeese/photo/dto/request/PhotoDownloadRequest.java (1)

4-5: 이전 리뷰 피드백이 정확히 반영되었습니다.

Jakarta Validation 제약 조건 import가 올바르게 추가되었습니다. 이제 Swagger 문서의 requiredProperties와 실제 런타임 검증 로직이 일치합니다.

src/main/java/com/cheeeese/album/dto/response/ClosedAlbumSummaryResponse.java (1)

10-35: LGTM! 이전 논의사항이 잘 반영되었습니다.

이전 리뷰에서 논의된 사항들이 적절히 처리되었습니다:

  • thumbnails 필드: nullable = true가 추가되어 null 값 허용이 명시됨
  • makerName 필드: 서비스 계층에서 검증되어 null이 될 수 없으므로 requiredProperties에 포함된 것이 적절함
  • 스키마 정의가 실제 동작과 일치함

@dahyun24 dahyun24 merged commit 78563fe into develop Nov 16, 2025
1 check passed
@zyovn zyovn deleted the feat/#59-mypage branch November 16, 2025 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 마이페이지 구현

3 participants