Skip to content

Conversation

@zyovn
Copy link
Member

@zyovn zyovn commented Nov 19, 2025

🔗 연관된 이슈

🚀 변경 유형

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

📝 작업 내용

  • 사진 삭제 API 구현

MAKER일 경우, 앨범의 모든 사진 삭제 가능
GUEST일 경우, 본인이 올린 사진만 삭제 가능

📸 스크린샷

image

상세 조회, 띱한 사용자, 띱한 사진 목록 불가능
image

사진 좋아요 생성 및 삭제 불가능
image
image

💬 리뷰 요구사항

📜 리뷰 규칙

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

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

Summary by CodeRabbit

  • 새로운 기능

    • 앨범 내 사진 삭제 REST API 추가
    • 사진 조회용 PhotoReader 및 앨범 참가자 조회용 AlbumReader 컴포넌트 추가
  • 개선 사항

    • 사진 소프트 삭제 지원 및 삭제 권한·참여자 검증 강화
    • 삭제 시 앨범·작성자 사진·좋아요 카운트 조정과 캐시 무효화
    • 좋아요 조회에 상태 필터 적용
  • 기타

    • 사진 좋아요 일괄 삭제 및 안전한 좋아요 차감 메서드 추가
    • 성공·오류 코드와 일부 DB 수정 애너테이션 간소화 추가

@zyovn zyovn self-assigned this Nov 19, 2025
@zyovn zyovn linked an issue Nov 19, 2025 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Nov 19, 2025

Walkthrough

사진 삭제 기능이 추가되고 관련 조회/검증 유틸 및 리포지토리/코드값이 확장·조정되었습니다. PhotoReader/AlbumReader가 도입되어 사진 조회와 참여자 조회를 위임하고, Photo.softDelete로 소프트 삭제하며 좋아요 일괄 삭제·카운트 조정·캐시 무효화가 수행됩니다. DELETE /v1/album/{code}/photo/{photoId} 엔드포인트와 Swagger 인터페이스도 추가되었습니다.

Changes

Cohort / File(s) 요약
프레젠테이션 — 사진 삭제 엔드포인트
src/main/java/com/cheeeese/photo/presentation/PhotoCommandController.java, src/main/java/com/cheeeese/photo/presentation/swagger/PhotoCommandSwagger.java
DELETE API 추가: 요청 위임 및 성공 응답(PHOTO_DELETE_SUCCESS) 정의
애플리케이션 서비스 — 사진 삭제 로직
src/main/java/com/cheeeese/photo/application/PhotoService.java
deletePhoto(User,String,Long) 추가: 앨범/참여자 검증, 권한 확인, 좋아요 삭제, 카운트 감산, 소프트 삭제, 캐시 무효화; 일부 기존 조회를 Reader로 대체
애플리케이션 지원 — 읽기 유틸
src/main/java/com/cheeeese/photo/application/support/PhotoReader.java, src/main/java/com/cheeeese/album/application/support/AlbumReader.java
PhotoReader·AlbumReader 추가: 사진/참여자 조회 로직 분리(존재·삭제 상태 검사, 예외 매핑)
쿼리/정보 서비스 변경
src/main/java/com/cheeeese/photo/application/PhotoQueryService.java, src/main/java/com/cheeeese/photo/application/PhotoInfoService.java
직접 저장소 호출을 PhotoReader로 대체; liked 조회 시 PhotoStatus 필터 적용
검증 로직 추가/수정
src/main/java/com/cheeeese/photo/application/validator/PhotoValidator.java, src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java
validateDeletePermission(...) 추가 및 앨범 참여자 반환 흐름으로 변경(참여·만료·블랙리스트 검증 위임)
도메인 변경
src/main/java/com/cheeeese/photo/domain/Photo.java
softDelete() 메서드 추가(isDeleted=true)
저장소 변경 — PhotoLikes / PhotoRepository
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoLikesRepository.java, src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
PhotoLikes: deleteAllByPhotoId(Long) 추가; PhotoRepository: findLikedPhotosByAlbumAndUserPhotoStatus status 파라미터와 조건 추가
저장소 변경 — Album/User 리포지토리 어노테이션 및 메서드
src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java, src/main/java/com/cheeeese/user/infrastructure/persistence/UserRepository.java
@Modifying에서 clearAutomatically 제거(keep flushAutomatically); UserRepository에 decrementLikeCntBy(Long,int) 안전한 감소 쿼리 추가
에러/성공 코드 추가
src/main/java/com/cheeeese/photo/exception/code/PhotoErrorCode.java, src/main/java/com/cheeeese/global/common/code/SuccessCode.java
PHOTO_ALREADY_DELETED 에러 및 PHOTO_DELETE_SUCCESS 성공 코드 추가

Sequence Diagram(s)

sequenceDiagram
    actor User as 사용자
    participant Controller as PhotoCommandController
    participant Service as PhotoService
    participant AlbumReader as AlbumReader
    participant PhotoReader as PhotoReader
    participant PhotoValidator as PhotoValidator
    participant LikesRepo as PhotoLikesRepository
    participant UserRepo as UserRepository
    participant AlbumRepo as AlbumRepository
    participant Photo as Photo
    participant Cache as Cache

    User->>Controller: DELETE /v1/album/{code}/photo/{photoId}
    Controller->>Service: deletePhoto(user, code, photoId)
    Service->>AlbumReader: getAlbumParticipant(album, user)
    AlbumReader-->>Service: UserAlbum (참여자)
    Service->>PhotoReader: getPhotoInAlbum(photoId, code)
    PhotoReader-->>Service: Photo (존재·isDeleted 검사)
    Service->>PhotoValidator: validateDeletePermission(user, userAlbum, album, photo)
    PhotoValidator-->>Service: 권한 확인 통과
    Service->>LikesRepo: deleteAllByPhotoId(photoId)
    LikesRepo-->>Service: 삭제 완료
    Service->>UserRepo: decrementLikeCntBy(authorId, likesCount)
    Service->>AlbumRepo: decrementPhotoCount(albumId, 1)
    Service->>Photo: softDelete()
    Photo-->>Service: isDeleted = true
    Service->>Cache: invalidate(photo)
    Service-->>Controller: PHOTO_DELETE_SUCCESS
    Controller-->>User: 200 OK
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 주의할 파일/영역:
    • PhotoService.deletePhoto() (트랜잭션 경계, 동시성/카운트 일관성, 예외/롤백)
    • PhotoValidator.validateDeletePermission() (권한·소유자 검증 경계)
    • PhotoReader / AlbumReader (예외 매핑과 삭제 상태 검사)
    • 리포지토리 변경 쿼리들 (decrementLikeCntBy, findLikedPhotosByAlbumAndUser 파라미터 변경, @Modifying 옵션)

Possibly related issues

Possibly related PRs

Suggested reviewers

  • dahyun24

Poem

🐰 키보드로 깡충, 코드로 냠냠,
사진 한 장 조용히 안녕이라 말해,
Reader는 길잡이, Validator는 문지기,
좋아요는 정리하고 카운트는 쿡,
깡총—배포 준비 완료! 📸✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.45% 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 제목은 변경사항의 핵심인 사진 삭제 기능 구현을 명확하게 요약하고 있으며, 간결하고 구체적입니다.
✨ 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/#71-photo-delete

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 (4)
src/main/java/com/cheeeese/user/infrastructure/persistence/UserRepository.java (2)

47-54: @Modifying 설정이 다른 메서드들과 불일치합니다.

새로 추가된 decrementLikeCntBy 메서드는 @Modifying 어노테이션에 flushAutomatically 옵션이 없지만, 같은 파일의 다른 업데이트 메서드들(Line 30, 38 등)은 모두 @Modifying(flushAutomatically = true)를 사용하고 있습니다.

일관성을 위해 다음과 같이 수정하는 것을 권장합니다:

-    @Modifying
+    @Modifying(flushAutomatically = true)
     @Query("""
         UPDATE User u
         SET u.likesCnt = u.likesCnt - :count
         WHERE u.id = :userId
         AND u.likesCnt >= :count
     """)
     void decrementLikeCntBy(@Param("userId") Long userId, @Param("count") int count);

14-20: 일관되지 않은 @Modifying 설정 - clearAutomatically 정책 통일 필요

확인 결과, 다음과 같은 불일치가 실제로 존재합니다:

  • incrementPhotoCountdecrementPhotoCount: @Modifying(flushAutomatically = true) - clearAutomatically 없음
  • incrementAlbumCnt: @Modifying(clearAutomatically = true, flushAutomatically = true) - clearAutomatically 있음

코드 분석 결과, PhotoService.java 라인 83(업로드) 및 라인 172(삭제) 이후에도 User 및 Album 객체가 트랜잭션 내에서 참조되고 있습니다. clearAutomatically = false인 상태에서 벌크 업데이트 이후 동일 트랜잭션 내에 로드된 엔티티에 접근하면 영속성 컨텍스트가 클리어되지 않아 stale data 반환 위험이 있습니다.

권장사항: 모든 카운트 업데이트 메서드(incrementPhotoCount, decrementPhotoCount, incrementAlbumCnt)에서 clearAutomatically = true를 일관되게 적용하세요. 현재는 incrementAlbumCnt와 나머지 메서드 간 설정이 다르므로 통일이 필수입니다.

src/main/java/com/cheeeese/photo/domain/Photo.java (1)

75-77: 소프트 삭제 구현이 깔끔합니다.

소프트 삭제 메서드가 명확하게 구현되었습니다. 다만, 멱등성(idempotency)을 고려하여 이미 삭제된 사진에 대해 다시 삭제를 시도할 때의 처리를 방어적으로 추가하는 것도 고려해볼 수 있습니다.

예시:

 public void softDelete() {
+    if (this.isDeleted) {
+        return; // 또는 예외 발생
+    }
     this.isDeleted = true;
 }
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)

101-107: validateAlbumParticipant와의 중복 로직

getAlbumParticipant 메서드가 validateAlbumParticipant와 거의 동일한 로직을 갖고 있습니다. 이중 조회를 방지하기 위한 의도는 이해되지만, validateAlbumParticipant가 내부적으로 getAlbumParticipant를 호출하도록 리팩토링하면 코드 중복을 줄일 수 있습니다.

예시:

 public void validateAlbumParticipant(Album album, User user) {
-    validateAlbumExpiration(album);
-    validateUserBlacklisted(album, user);
-
-    userAlbumRepository.findByUserIdAndAlbumId(user.getId(), album.getId())
-            .orElseThrow(() -> new AlbumException(AlbumErrorCode.USER_NOT_PARTICIPANT));
+    getAlbumParticipant(album, user);
 }
📜 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 15fb22c and 1ca9c0e.

📒 Files selected for processing (16)
  • src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (2 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (1 hunks)
  • src/main/java/com/cheeeese/global/common/code/SuccessCode.java (1 hunks)
  • src/main/java/com/cheeeese/photo/application/PhotoInfoService.java (3 hunks)
  • src/main/java/com/cheeeese/photo/application/PhotoQueryService.java (4 hunks)
  • src/main/java/com/cheeeese/photo/application/PhotoService.java (5 hunks)
  • src/main/java/com/cheeeese/photo/application/support/PhotoReader.java (1 hunks)
  • src/main/java/com/cheeeese/photo/application/validator/PhotoValidator.java (2 hunks)
  • src/main/java/com/cheeeese/photo/domain/Photo.java (1 hunks)
  • src/main/java/com/cheeeese/photo/exception/code/PhotoErrorCode.java (1 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoLikesRepository.java (1 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1 hunks)
  • src/main/java/com/cheeeese/photo/presentation/PhotoCommandController.java (1 hunks)
  • src/main/java/com/cheeeese/photo/presentation/swagger/PhotoCommandSwagger.java (1 hunks)
  • src/main/java/com/cheeeese/photo/presentation/swagger/PhotoSwagger.java (2 hunks)
  • src/main/java/com/cheeeese/user/infrastructure/persistence/UserRepository.java (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-13T12:56:22.161Z
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.161Z
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/application/validator/PhotoValidator.java
  • src/main/java/com/cheeeese/photo/presentation/PhotoCommandController.java
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoLikesRepository.java
  • src/main/java/com/cheeeese/photo/application/PhotoInfoService.java
  • src/main/java/com/cheeeese/photo/domain/Photo.java
  • src/main/java/com/cheeeese/photo/presentation/swagger/PhotoSwagger.java
  • src/main/java/com/cheeeese/photo/application/PhotoService.java
  • src/main/java/com/cheeeese/photo/application/PhotoQueryService.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/photo/application/PhotoInfoService.java
  • src/main/java/com/cheeeese/photo/application/PhotoService.java
  • src/main/java/com/cheeeese/photo/application/PhotoQueryService.java
🧬 Code graph analysis (1)
src/main/java/com/cheeeese/photo/application/validator/PhotoValidator.java (1)
src/main/java/com/cheeeese/cheese4cut/application/validator/Cheese4cutValidator.java (3)
  • Component (22-55)
  • validateFinalizePhotos (36-54)
  • validateUserIsMaker (29-34)
🔇 Additional comments (15)
src/main/java/com/cheeeese/photo/application/PhotoQueryService.java (3)

37-37: PhotoReader 도입으로 관심사 분리가 개선되었습니다.

사진 조회 로직을 PhotoReader로 중앙화한 것은 좋은 리팩토링입니다. Line 72의 TODO 코멘트에서 언급한 바와 같이, 데이터 조회 로직을 Reader 클래스로 분리하는 방향과 일치합니다.


95-95: 사진 조회 로직이 일관성 있게 개선되었습니다.

직접 Repository를 호출하던 방식에서 photoReader.getPhotoInAlbum을 사용하도록 변경하여, 삭제된 사진 체크 및 앨범 소속 검증 로직이 중앙화되었습니다. 이는 코드 중복을 줄이고 일관된 에러 처리를 보장합니다.


75-75: PhotoStatus 필터 추가가 적절합니다.

좋아요한 사진 목록 조회 시 PhotoStatus.COMPLETED 필터를 추가하여, 업로드 중이거나 실패한 사진을 제외하는 것은 올바른 비즈니스 로직입니다.

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

39-48: 쿼리에 삭제 필터 및 상태 필터가 적절히 추가되었습니다.

findLikedPhotosByAlbumAndUser 메서드에 isDeleted = FALSE 조건과 status 파라미터를 추가하여, 삭제된 사진과 완료되지 않은 사진을 필터링할 수 있게 되었습니다. 이는 다른 조회 메서드들(Line 24-25의 findAllByAlbumCodeAndStatus)과 일관된 패턴을 따릅니다.

src/main/java/com/cheeeese/photo/presentation/swagger/PhotoSwagger.java (1)

179-179: API 문서에 향후 리팩토링 계획이 명시되었습니다.

좋아요 생성/삭제 API가 향후 Command로 이동할 예정임을 문서에 명시한 것은 좋은 관행입니다. 이는 CQRS 패턴으로의 점진적 전환 의도를 명확히 전달합니다.

Also applies to: 198-198

src/main/java/com/cheeeese/photo/exception/code/PhotoErrorCode.java (1)

33-33: 이미 삭제된 사진에 대한 에러 코드가 적절히 추가되었습니다.

PHOTO_ALREADY_DELETED 에러 코드를 HttpStatus.CONFLICT와 함께 추가한 것은 적절합니다. 이미 삭제된 리소스에 대한 재삭제 시도는 409 Conflict로 응답하는 것이 RESTful API 관례에 부합하며, PhotoReader와 삭제 워크플로우에서 활용될 수 있습니다.

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

49-49: 사진 삭제 성공 코드가 일관되게 추가되었습니다.

PHOTO_DELETE_SUCCESS 성공 코드가 다른 사진 관련 성공 코드들과 일관된 형식으로 추가되었습니다. PhotoCommandController의 DELETE 엔드포인트에서 사용될 것으로 예상됩니다.

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

40-40: 코드 변경이 적절히 구현되었습니다. 트랜잭션 컨텍스트 검증 완료.

deleteAllByPhotoId 메서드는 PhotoService.javadeletePhoto() 메서드 내 178번 줄에서 호출되며, 해당 메서드는 161번 줄의 @Transactional 어노테이션 아래 위치합니다. 따라서 bulk delete 작업이 트랜잭션 경계 내에서 정상적으로 실행되어 데이터 일관성이 보장됩니다.

src/main/java/com/cheeeese/photo/application/PhotoInfoService.java (1)

7-7: LGTM! PhotoReader를 통한 중앙화된 조회 로직

사진 조회 로직을 PhotoReader로 위임하여 일관성을 높였습니다. 삭제된 사진에 대한 검증 로직도 중앙화되어 관리가 용이합니다.

Also applies to: 30-30, 39-39

src/main/java/com/cheeeese/photo/application/validator/PhotoValidator.java (1)

138-151: LGTM! 삭제 권한 검증 로직이 명확합니다

역할 기반 권한 검증이 올바르게 구현되었습니다:

  • MAKER는 모든 사진 삭제 가능
  • GUEST는 본인 사진만 삭제 가능
  • 앨범 소속 여부도 추가 검증
src/main/java/com/cheeeese/photo/presentation/PhotoCommandController.java (1)

16-33: LGTM! REST 엔드포인트 구현이 적절합니다

표준적인 DELETE 엔드포인트 구현으로 서비스 레이어에 적절히 위임하고 있습니다.

src/main/java/com/cheeeese/photo/presentation/swagger/PhotoCommandSwagger.java (1)

12-34: LGTM! Swagger 문서화가 명확합니다

API 스펙이 잘 문서화되어 있고 파라미터 설명도 충분합니다.

src/main/java/com/cheeeese/photo/application/support/PhotoReader.java (1)

10-37: LGTM! 사진 조회 로직의 중앙화

사진 조회와 삭제 상태 검증을 한 곳에서 처리하여 일관성을 높였습니다. Line 16의 TODO 주석은 점진적 리팩토링을 위한 것으로 적절합니다.

src/main/java/com/cheeeese/photo/application/PhotoService.java (1)

134-134: LGTM! PhotoReader로의 전환

직접 repository 조회에서 PhotoReader를 사용하도록 변경하여 삭제 상태 검증이 자동으로 수행됩니다.

Also applies to: 148-148

src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (1)

34-34: clearAutomatically 제거와 관련하여 문제 없음

PhotoService의 전체 트랜잭션 흐름을 검토한 결과, Album 엔티티의 count 업데이트 후 동일 트랜잭션 내에서 Album을 재조회하는 경우가 없습니다:

  • createPresignedUrls: Album 검증 후 incrementPhotoCount 호출 이전에 getCurrentPhotoCount() 사용. 업데이트 후 album 엔티티 접근 없음
  • deletePhoto: decrementPhotoCountalbum.getCode() 만 접근 (count 필드 미포함)
  • handleFailedUploads: Album 엔티티가 아닌 albumId만 사용

따라서 clearAutomatically 제거로 인한 스테일 데이터 조회 문제는 현재 코드에서는 발생하지 않습니다.

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/com/cheeeese/photo/application/PhotoService.java (1)

170-170: 앨범 사진 카운트 감소 실패 검증 필요

albumRepository.decrementPhotoCount의 반환값을 검증하지 않아 카운트 감소 실패 시 데이터 불일치가 발생할 수 있습니다. handleFailedUploads 메서드(277-279줄)와 동일하게 반환값을 검증해야 합니다.

다음과 같이 수정하세요:

-        albumRepository.decrementPhotoCount(album.getId(), 1);
+        int decremented = albumRepository.decrementPhotoCount(album.getId(), 1);
+        if (decremented == 0) {
+            throw new PhotoException(PhotoErrorCode.PHOTO_COUNT_DECREMENT_FAILED);
+        }
📜 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 1ca9c0e and f6a770d.

📒 Files selected for processing (1)
  • src/main/java/com/cheeeese/photo/application/PhotoService.java (5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-13T12:56:22.161Z
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.161Z
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/application/PhotoService.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/application/PhotoService.java
🔇 Additional comments (1)
src/main/java/com/cheeeese/photo/application/PhotoService.java (1)

134-134: PhotoReader를 사용한 일관된 사진 조회

PhotoReader를 통해 사진을 조회하도록 변경하여 삭제 상태 확인 로직이 중앙화되고 일관성이 향상되었습니다.

Also applies to: 148-148

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/com/cheeeese/photo/application/PhotoService.java (1)

146-159: 좋아요 삭제에서도 PhotoReader 재사용으로 조회 일관성 유지

deletePhotoLikesPhotoReader.getPhoto(photoId) 를 사용해 좋아요 생성/삭제가 동일한 조회 규칙과 예외 처리를 공유하도록 한 점이 좋습니다. 위 createPhotoLikes 에서 언급한 것처럼, PhotoReader 쪽 상태 필터링만 정책에 맞게 되어 있다면 별도 문제는 없어 보입니다.

📜 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 f6a770d and 82e325c.

📒 Files selected for processing (1)
  • src/main/java/com/cheeeese/photo/application/PhotoService.java (5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-13T12:56:22.161Z
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.161Z
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/application/PhotoService.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/application/PhotoService.java
🔇 Additional comments (2)
src/main/java/com/cheeeese/photo/application/PhotoService.java (2)

5-8: PhotoReader · UserAlbum 의존성 추가 방향이 자연스럽습니다

조회/권한 검증 책임을 PhotoReader·AlbumValidator 등 전용 컴포넌트로 분리해서 PhotoService 가 얇아지는 구조가 좋아 보입니다. 현재 추가된 의존성 수나 사용 범위도 과하지 않고, 다른 메서드에 부작용도 없어 그대로 유지해도 될 것 같습니다.

Also applies to: 51-51


132-139: 좋아요 생성 시 PhotoReader 사용으로 조회 규칙 일원화

기존 photoRepository.findById(...).orElseThrow(...) 대신 photoReader.getPhoto(photoId) 를 사용하면서, 사진 조회 조건·예외 코드를 한 곳에서 관리할 수 있게 되어 유지보수성이 좋아졌습니다. 다만 PhotoReader.getPhoto 가 소프트 삭제/비공개 상태의 사진에 대해서는 좋아요가 불가능하도록 적절히 예외를 던지도록 구현되어 있는지만 한 번만 확인해 주세요 (정책 상 필요하다면).

@zyovn zyovn added the ✨feature New feature or request label Nov 19, 2025
Copy link
Contributor

@dahyun24 dahyun24 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!!!!!!

}

public UserAlbum getAlbumParticipant(Album album, User user) {
validateAlbumExpiration(album);
Copy link
Contributor

Choose a reason for hiding this comment

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

오,, get을 validator에다가?ㅎㅎㅎ

Copy link
Member Author

Choose a reason for hiding this comment

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

엥 왜 메서드명 이따구로 썼지?

Copy link
Member Author

Choose a reason for hiding this comment

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

아니다 albumReader로 옮길게

@Operation(
summary = "사진 좋아요 생성 API",
summary = "사진 좋아요 생성 API - 추후 수정 필요 (Command로 이동 예정)",
description = """
Copy link
Contributor

Choose a reason for hiding this comment

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

여기는 어떤 수정이 필요한거죠?

Copy link
Member Author

Choose a reason for hiding this comment

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

앨범 코드를 pathVariable에 추가하고 그것도 검증해야 맞는 로직 같은데 전시회 2일 남은 시점에서 그걸 수정할 순 없을 것 같아서.. 끝나고 수정하려고 남겨놨어

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 (5)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)

87-92: validateAlbumParticipant에서 입장 검증 공통화한 부분 좋습니다 (P4)

validateAlbumParticipant가 바로 만료/블랙리스트를 보지 않고 validateAlbumEntry를 거치도록 바뀌어서, 앨범 입장 관련 검증이 한 군데로 모인 점이 깔끔해졌습니다. 현재 구조에서도 중복이나 부작용은 없어 보이고, 나중에 입장 규칙이 늘어나도 validateAlbumEntry만 보면 되겠네요.

src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (1)

36-42: clearAutomatically 제거에 따른 1차 캐시 상태 한 번만 점검 부탁드립니다 (P3)

이전에는 bulk update 후 clearAutomatically = true로 영속성 컨텍스트를 비워서 Album 엔티티의 currentPhotoCount가 stale 상태로 남지 않도록 하고 있었는데, 지금은 flush만 되고 캐시는 유지됩니다.
이 메서드 호출 시점에 같은 트랜잭션 안에서 이미 Album 엔티티를 로딩해두고 이후 그 엔티티의 currentPhotoCount를 다시 참조하는 코드가 있다면 값 불일치가 생길 수 있어서,

  • 그런 사용처가 없다면 그대로 두셔도 괜찮고,
  • 혹시 있을 수 있다면 clearAutomatically = true를 유지하거나, “이 메서드는 managed Album을 재사용하지 않는 컨텍스트에서만 호출” 정도의 제약을 주석으로 남기는 것도 고려해 보시면 좋겠습니다.

(P3: 웬만하면 한 번 확인해보면 좋을 것 같다는 정도입니다.)

src/main/java/com/cheeeese/album/application/support/AlbumReader.java (1)

1-22: 앨범 참여자 조회를 AlbumReader로 분리한 구조 좋습니다 (P5)

UserAlbumRepository 접근과 USER_NOT_PARTICIPANT 예외 처리를 한 곳으로 모아서, 서비스/밸리데이터 쪽 코드가 많이 정리된 것 같습니다. 지금 정도 규모에선 메서드명(getAlbumParticipant)도 명확하고, 나중에 권한·상태 필터링이 추가돼도 이쪽에서 흡수하기 좋아 보여요. (사소 의견 수준, P5)

src/main/java/com/cheeeese/photo/application/PhotoService.java (2)

3-10: Reader/Service 의존성 추가 구성 방향 괜찮습니다 (P5)

AlbumReader, PhotoReader, UserService를 주입해서 읽기/권한/카운트 갱신 책임을 분리한 구조가 전체적으로 일관돼 보입니다. 특히 삭제 플로우에서 Album/Photo 조회·검증이 Service 안에 흩어져 있지 않고 Reader/Validator로 흘러가는 게 유지보수에 유리해 보여요. (사소 의견, P5)

Also applies to: 28-29, 48-58


174-199: 사진 삭제 플로우 전반적으로 요구사항을 잘 반영한 구현으로 보입니다 (P4)

삭제 흐름을 단계별로 보면:

  1. albumValidator.validateAlbumCodevalidateAlbumEntry로 앨범 존재/만료/블랙리스트를 먼저 막고,
  2. albumReader.getAlbumParticipant로 참가자인지 검증 (USER_NOT_PARTICIPANT),
  3. photoReader.getPhotoInAlbum(photoId, code)로 앨범-사진 매핑을 보장하고,
  4. photoValidator.validateDeletePermission(user, userAlbum, album, photo)에서
    • MAKER: 앨범 내 모든 사진 삭제 가능
    • GUEST: 본인이 올린 사진만 삭제
      같은 권한 규칙을 캡슐화한 것으로 보입니다.
  5. albumRepository.decrementPhotoCount 결과를 체크해 0이면 PHOTO_COUNT_DECREMENT_FAILED를 던지는 것도, 이전 리뷰에서 지적됐던 카운트 불일치 리스크를 잘 방어하고 있습니다.
  6. 이후 photo.getUser() 기준으로 업로더의 사진 수/받은 좋아요 수를 줄이고, PhotoLikes 전체 삭제 → photo.softDelete() → 캐시 무효화까지 한 트랜잭션 안에서 마무리하는 순서도 자연스럽습니다.

지금 구현만으로도 요구사항은 충족하는 것 같고, 추가로 고려해볼 수 있는(반영은 선택인) 부분은 아래 정도입니다. (P4 권장 사항)

  • 삭제 권한 검증(validateDeletePermission)에 대한 단위/통합 테스트에서
    • MAKER가 타인의 사진 삭제 성공,
    • GUEST가 타인의 사진 삭제 시 실패,
    • 이미 soft delete 된 사진 재삭제 시 실패
      케이스가 모두 커버되는지만 한 번 점검해 두시면 안전할 것 같습니다.
📜 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 82e325c and 24a4104.

📒 Files selected for processing (6)
  • src/main/java/com/cheeeese/album/application/support/AlbumReader.java (1 hunks)
  • src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (1 hunks)
  • src/main/java/com/cheeeese/photo/application/PhotoService.java (6 hunks)
  • src/main/java/com/cheeeese/photo/application/validator/PhotoValidator.java (2 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/photo/application/validator/PhotoValidator.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-13T12:56:22.161Z
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.161Z
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/application/PhotoService.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/photo/application/PhotoService.java
🧬 Code graph analysis (1)
src/main/java/com/cheeeese/album/application/support/AlbumReader.java (1)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)
  • Component (21-109)
🔇 Additional comments (2)
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1)

34-49: 메서드 시그니처 변경 확인 완료

findLikedPhotosByAlbumAndUser 메서드에 추가된 PhotoStatus status 파라미터는 올바르게 구현되었습니다. 유일한 호출 위치인 PhotoQueryService.java 75번째 줄에서 PhotoStatus.COMPLETED를 전달하고 있으며, 쿼리에서 삭제되지 않은 특정 상태의 사진만 조회하도록 필터링합니다.

src/main/java/com/cheeeese/photo/application/PhotoService.java (1)

135-152: 좋아요 생성/삭제에서 PhotoReader 사용으로 조회 경로 일원화된 점 좋습니다 (P4)

createPhotoLikes/deletePhotoLikes가 직접 photoRepository.findById를 호출하지 않고 photoReader.getPhoto를 쓰도록 바뀌어서, 사진 조회 로직·예외 코드가 한 곳에서 관리되는 구조가 된 점이 깔끔합니다.

다만 PhotoReader.getPhoto가 “삭제된/비활성화된 사진에 대한 좋아요 생성/삭제를 허용하지 않는다”는 규칙까지 함께 보장하고 있는지(예: PHOTO_ALREADY_DELETED 등으로 막는지)만 한 번 확인해 두시면, 향후 상태 추가 시에도 일관성 유지가 더 쉬울 것 같습니다. (P4~P5 사이 의견)

@zyovn zyovn merged commit a47e595 into develop Nov 19, 2025
1 check passed
@zyovn zyovn deleted the feat/#71-photo-delete branch November 19, 2025 16:19
@coderabbitai coderabbitai bot mentioned this pull request Jan 20, 2026
6 tasks
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