Skip to content

Conversation

@dahyun24
Copy link
Contributor

@dahyun24 dahyun24 commented Nov 10, 2025

🔗 연관된 이슈

🚀 변경 유형

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

📝 작업 내용

  • /v1/cheese4cut/{code}/presigned-url
    • 치즈네컷 업로드를 위한 presigned-url 요청
  • /v1/cheese4cut/{code}/fixed
    • 치즈네컷 수동 확정 (MAKER 만의 권한)
  • /v1/cheese4cut/{code}/preview
    • 생성된 치즈네컷 조회 기능
      • 만료된 경우 ("isFinalized": true) -> 최종 치즈4컷 한장 제공
      • "isFinalized": false -> 원본 이미지 4장 제공
  • 앨범 생성과 동시에 Redis에 TTL 7일 넣어서, 1초마다 spring에서 스케줄러를 돌려서 만료된 앨범을 찾는다.
    • 찾아서 치즈네컷 생성

📸 스크린샷

redis에 저장된 모습 expired:album

스크린샷 2025-11-11 오전 2 33 58

스케줄러가 잘 동작한 log

스크린샷 2025-11-11 오전 2 28 58

치즈네컷 presigned-url 발급

스크린샷 2025-11-07 오후 5 42 01

치즈네컷 확정된 후 조회

스크린샷 2025-11-07 오후 5 51 08

치즈네컷 확정 전 조회

스크린샷 2025-11-11 오전 2 56 59

💬 리뷰 요구사항

  • DB에 저장된 특정 앨범(PK)의 상태를 만료로 변경하는 것이기에 redis에서 key는 albumId로 지정했습니다.
  • 스케줄러 log 찍어보려고 log 추가했습니다.
  • 좋아요 가장 많은 4개의 이미지에 대해서 치즈네컷은 프론트에서 만들어주기로 했는데, 7일 만료가 된 경우 원본 4장은 cheese4cut 테이블로 잘 넘어가지만 최종 치즈네컷이 생성되지 않는 문제가 있습니다.
  • 만료된 경우, 서버->프론트로 요청을 보내야할지,, 그냥 백엔드가 치즈네컷도 만들지 고민이네요 (이 부분 빼고 구현하였습니다.)
  • 베스트 네컷 조회 api 만들면 cdn 참고해서 치즈네컷 조회도 추후 수정하겠습니다.

📜 리뷰 규칙

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

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

Summary by CodeRabbit

  • 새로운 기능

    • 앨범 만료 스케줄러 및 만료 처리 자동화 도입
    • 치즈네컷: 미리보기 조회, presigned URL 발급, 수동 확정 API 및 관련 DTO/응답 모델 추가
    • 치즈네컷 엔티티·저장소·검증 로직 추가
  • 개선 사항

    • 앨범 생성 시 만료 추적 등록으로 자동화 강화 (Redis 기반 추적)
    • 상위 인기 사진 4장 선별 및 치즈네컷 생성 로직 적용
    • 미리보기 엔드포인트 보안 예외 추가 및 presigned URL용 버킷 분리

@dahyun24 dahyun24 self-assigned this Nov 10, 2025
@dahyun24 dahyun24 added the ✨feature New feature or request label Nov 10, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 10, 2025

Walkthrough

스케줄링 활성화(@EnableScheduling) 및 Redis 기반 앨범 만료 추적·처리기 추가, Cheese4cut 도메인·서비스·컨트롤러·DTO·매퍼·저장소·예외·검증기·관련 사진 presigned URL·쿼리들이 추가된 기능 구현입니다.

Changes

Cohort / File(s) Summary
애플리케이션 설정
src/main/java/com/cheeeese/CheeeeseApplication.java
@EnableScheduling 추가로 스케줄링 활성화
앨범 만료 트래킹(레디스)
src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumExpirationRedisRepository.java
Redis에 앨범 트래킹 키/세트 관리 추가: registerAlbum, getTrackedAlbumIds, isExpired, unregister
앨범 만료 스케줄러
src/main/java/com/cheeeese/album/application/AlbumExpirationScheduler.java
고정 지연 스케줄로 등록된 앨범을 조회해 만료 판정 시 expireAlbum 호출 및 unregister, 개별 예외는 로깅 후 계속 처리
앨범 만료 서비스
src/main/java/com/cheeeese/album/application/AlbumExpirationService.java
expireAlbum 구현: 앨범 상태 갱신, 상위 4개 완료 사진 조회로 Cheese4cut 생성·저장 로직
앨범 도메인·서비스 변경
src/main/java/com/cheeeese/album/domain/Album.java, src/main/java/com/cheeeese/album/application/AlbumService.java, src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java
Album.expire() 추가; 앨범 생성 시 Redis 등록 호출 추가; USER_NOT_MAKER 에러코드 추가
Cheese4cut 도메인
src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java
JPA 엔티티 Cheese4cut 추가(앨범 연관, photoIds element-collection, 사이즈 제약 등)
Cheese4cut 서비스·검증기
src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java, src/main/java/com/cheeeese/cheese4cut/application/validator/Cheese4cutValidator.java
preview 조회, presigned URL 발급, 최종 확정(검증 포함) 구현 및 예외 흐름
Cheese4cut DTOs
src/main/java/com/cheeeese/cheese4cut/dto/request/Cheese4cutFixedRequest.java, src/main/java/com/cheeeese/cheese4cut/dto/response/*
고정 확정 요청 DTO(사진 ID 검증), preview/final/presigned-response DTO들과 sealed 응답 계층 추가
Cheese4cut 인프라: 매퍼·저장소·예외·코드
src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java, .../persistence/Cheese4cutRepository.java, .../exception/*, .../exception/code/Cheese4cutErrorCode.java
엔티티↔DTO 매핑 메서드, JpaRepository 인터페이스, 전용 예외·에러코드 추가
Cheese4cut 컨트롤러·Swagger
src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java, .../presentation/swagger/Cheese4cutSwagger.java
GET preview, POST presigned-url, POST fixed 엔드포인트 및 OpenAPI 문서 추가
전역 설정·사진 관련 변경
src/main/java/com/cheeeese/global/config/SecurityConfig.java, src/main/java/com/cheeeese/global/common/code/SuccessCode.java, src/main/java/com/cheeeese/photo/application/PresignedUrlService.java, src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java, .../PhotoLikesRepository.java
preview 경로 보안 예외 추가; 성공 코드 2개 추가; cheese4cut용 presigned PUT URL 생성 메서드(버킷 설정 포함) 및 top4 사진 ID 조회·좋아요 집계 쿼리 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Controller
    participant Service
    participant Validator
    participant Repo
    participant Redis
    participant Scheduler

    rect rgb(230,245,255)
    note right of Controller: Cheese4cut 확정(수동) 흐름
    User->>Controller: POST /v1/cheese4cut/{code}/fixed
    Controller->>Service: finalizeCheese4cut(user, code, request)
    Service->>Repo: find Album by code
    Service->>Validator: validateUserIsMaker(album, user)
    Service->>Validator: validateFinalizePhotos(album, photoIds)
    alt validation ok
        Service->>Repo: save(Cheese4cut)
        Service-->>Controller: 성공 응답
    else validation 실패
        Validator-->>Service: 예외 발생
        Service-->>Controller: 오류 응답
    end
    end

    rect rgb(240,255,240)
    note right of Scheduler: 앨범 만료 자동 처리 흐름
    Scheduler->>Redis: getTrackedAlbumIds()
    Redis-->>Scheduler: Set<albumId>
    loop albumId in set
        Scheduler->>Redis: isExpired(albumId)
        alt expired
            Scheduler->>Service: expireAlbum(albumId)
            Service->>Repo: findTop4CompletedPhotoIdsByLikes / save Cheese4cut
            Scheduler->>Redis: unregister(albumId)
        else not expired
            Scheduler-->>Scheduler: 다음 ID
        end
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • 추가 검토 권장 항목:
    • Cheese4cutService: 트랜잭션 경계, 예외 흐름, 권한 검사 및 상태 검사
    • AlbumExpirationRedisRepository: TTL 판독(isExpired) 논리와 Redis 키/세트 무결성
    • AlbumExpirationScheduler: 스케줄 주기·오류 격리·동시성 영향
    • PhotoRepository / PhotoLikesRepository: JPQL 쿼리(정렬·페이징) 정확성 및 성능

Possibly related issues

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 8.89% 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 제목은 주요 변경사항인 7일 TTL 만료 감지 스케줄러와 치즈네컷 기능 구현을 정확하게 요약하고 있으며, 변경 사항의 핵심을 명확하게 전달합니다.
✨ 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/#39-cheese4cut

📜 Recent 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 ffee386 and 5089da5.

📒 Files selected for processing (2)
  • src/main/java/com/cheeeese/cheese4cut/dto/request/Cheese4cutFixedRequest.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java
🧰 Additional context used
🧠 Learnings (1)
📚 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/cheese4cut/dto/request/Cheese4cutFixedRequest.java
🔇 Additional comments (1)
src/main/java/com/cheeeese/cheese4cut/dto/request/Cheese4cutFixedRequest.java (1)

11-19: 이전 리뷰 이슈가 완벽하게 해결되었습니다!

List<@NotNull Long> photoIds로 수정되어 각 요소의 null 검증이 추가되었습니다. 이제 [null, null, null, null] 같은 입력은 Bean Validation 단계에서 차단되어 500 오류가 발생하지 않습니다.

검증 계층이 명확하게 구성되었습니다:

  • @NotEmpty: 리스트가 null이거나 비어있지 않음을 보장
  • @Size(min = 4, max = 4): 정확히 4개 요소 보장
  • List<@NotNull Long>: 각 요소가 null이 아님을 보장

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.

@dahyun24 dahyun24 changed the title feat: 7일 TTL 만료 후 치즈네컷 생성 로직 구현 feat: 7일 TTL 만료 감지 스케줄러 및 치즈네컷 기능 구현 Nov 10, 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: 8

🧹 Nitpick comments (6)
src/main/java/com/cheeeese/photo/application/PresignedUrlService.java (1)

46-49: Content-Type 지정 누락을 확인하세요.

기존 generatePresignedPutUrl 메서드(라인 26-31)와 달리 contentType을 지정하지 않았습니다. PNG 이미지 업로드 시 image/png를 명시하는 것이 좋습니다.

다음과 같이 수정을 고려하세요:

 PutObjectRequest putObjectRequest = PutObjectRequest.builder()
         .bucket(cheese4cutBucket)
         .key(objectKey)
+        .contentType("image/png")
         .build();
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoLikesRepository.java (1)

25-30: LGTM!

고유한 좋아요 사용자 수를 계산하는 쿼리 메서드가 올바르게 구현되었습니다. JPQL 문법과 메서드 시그니처가 Spring Data JPA 규칙을 잘 따르고 있습니다.

선택사항: photoIds 리스트가 매우 클 경우 IN 절의 성능을 고려해야 할 수 있습니다. 예상되는 사진 개수가 적다면 문제없습니다.

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

8-23: Preview response 구조가 잘 설계되었습니다.

미리보기 상태를 나타내는 DTO로서 필요한 정보들이 적절하게 포함되어 있습니다. 다만 isFinalized가 schema에서 "항상 false"로 명시되어 있지만, 이는 런타임에 강제되지 않습니다. 필요하다면 compact constructor에서 검증을 추가할 수 있습니다.

선택적 개선 (필요시):

@Builder
@Schema(description = "치즈네컷 확정 전 미리보기 응답 DTO (좋아요 TOP 4)")
public record Cheese4cutPreviewResponse(
        @Schema(description = "확정 여부 (항상 false)", example = "false")
        boolean isFinalized,
        // ... 나머지 필드들
) implements Cheese4cutResponse {
    public Cheese4cutPreviewResponse {
        if (isFinalized) {
            throw new IllegalArgumentException("PreviewResponse must have isFinalized=false");
        }
    }
    // ... 나머지 코드
}
src/main/java/com/cheeeese/album/application/AlbumExpirationScheduler.java (1)

19-19: 스케줄링 간격이 너무 짧습니다.

fixedDelay = 1000L (1초)은 7일 TTL 기반의 만료 처리에는 과도하게 빠른 간격입니다. 프로덕션 환경에서 불필요한 Redis 조회가 반복되어 부하가 될 수 있습니다. 1분(60000L) 또는 그 이상의 간격으로 조정하는 것을 권장합니다.

다음과 같이 수정하는 것을 고려하세요:

-   @Scheduled(fixedDelay = 1000L)
+   @Scheduled(fixedDelay = 60000L) // 1분마다 실행
    public void handleAlbumExpirations() {
src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (1)

38-68: 매직 넘버를 상수로 치환하는 것이 좋습니다.
Line 62와 Line 66에 하드코딩된 4는 의미 파악과 추후 수정 시 실수를 유발할 수 있습니다. 클래스 상단에 상수를 두고 재사용하면 다른 위치(예: 자동 만료 로직)와도 일관성을 유지할 수 있습니다.

 @Service
 @RequiredArgsConstructor
 @Transactional
 public class Cheese4cutService {

+    private static final int CHEESE4CUT_PHOTO_COUNT = 4;
+
     private final Cheese4cutRepository cheese4cutRepository;
     private final AlbumRepository albumRepository;
@@
         List<Long> topPhotoIds = photoRepository.findTop4CompletedPhotoIdsByLikes(
                 albumId,
-                PageRequest.of(0, 4)
+                PageRequest.of(0, CHEESE4CUT_PHOTO_COUNT)
         );
 
-        if (topPhotoIds.size() < 4) {
+        if (topPhotoIds.size() < CHEESE4CUT_PHOTO_COUNT) {
             throw new Cheese4cutException(Cheese4cutErrorCode.INSUFFICIENT_COUNT_FOR_CHEESE4CUT);
         }
src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java (1)

36-41: 도메인 비즈니스 로직 메서드 추가를 고려하세요.

현재 엔티티가 빈약한 도메인 모델(anemic domain model) 패턴을 따르고 있습니다. API 응답에서 사용되는 isFinalized 판단 로직이나, frameImageUrl 업데이트를 위한 메서드를 엔티티에 추가하면 도메인 로직을 캡슐화할 수 있습니다.

예시:

// frameImageUrl이 설정되면 확정된 것으로 간주
public boolean isFinalized() {
    return frameImageUrl != null && !frameImageUrl.isEmpty();
}

// 최종 프레임 이미지 업데이트 (7일 만료 시 사용)
public void updateFrameImage(String frameImageUrl) {
    if (this.isFinalized()) {
        throw new Cheese4cutException(Cheese4cutErrorCode.CHEESE4CUT_ALREADY_FINALIZED);
    }
    this.frameImageUrl = frameImageUrl;
}

이렇게 하면 PR 목표에서 언급된 "7일 만료 후 치즈네컷 생성 문제"를 해결하는 데 도움이 될 수 있습니다.

📜 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 63bea57 and 00362d7.

📒 Files selected for processing (26)
  • src/main/java/com/cheeeese/CheeeeseApplication.java (1 hunks)
  • src/main/java/com/cheeeese/album/application/AlbumExpirationScheduler.java (1 hunks)
  • src/main/java/com/cheeeese/album/application/AlbumExpirationService.java (1 hunks)
  • src/main/java/com/cheeeese/album/application/AlbumService.java (3 hunks)
  • src/main/java/com/cheeeese/album/domain/Album.java (1 hunks)
  • src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumExpirationRedisRepository.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/application/validator/Cheese4cutValidator.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/request/Cheese4cutFixedRequest.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 (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPreviewResponse.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutResponse.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/exception/Cheese4cutException.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/exception/code/Cheese4cutErrorCode.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/infrastructure/persistence/Cheese4cutRepository.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java (1 hunks)
  • src/main/java/com/cheeeese/global/common/code/SuccessCode.java (1 hunks)
  • src/main/java/com/cheeeese/global/config/SecurityConfig.java (1 hunks)
  • src/main/java/com/cheeeese/photo/application/PresignedUrlService.java (2 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)
🧰 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/infrastructure/persistence/PhotoRepository.java
📚 Learning: 2025-11-05T03:06:41.847Z
Learnt from: zyovn
Repo: Say-Cheeeese/BE PR: 44
File: src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoHistoryRepository.java:36-36
Timestamp: 2025-11-05T03:06:41.847Z
Learning: PhotoHistoryRepository의 existsByUserIdAndPhotoIdAndCreatedAt 메서드는 정확한 타임스탬프 일치를 확인하는 시그니처로, "1시간 이내 다운로드" 체크에는 부적합합니다. Spring Data JPA에서는 existsByUserIdAndPhotoIdAndCreatedAtAfter를 사용하여 >= 비교를 수행해야 합니다.

Applied to files:

  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoLikesRepository.java
🧬 Code graph analysis (3)
src/main/java/com/cheeeese/cheese4cut/exception/code/Cheese4cutErrorCode.java (1)
src/main/java/com/cheeeese/cheese4cut/exception/Cheese4cutException.java (1)
  • Getter (7-12)
src/main/java/com/cheeeese/album/application/AlbumExpirationService.java (1)
src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1)
  • Cheese4cutMapper (14-77)
src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (1)
src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1)
  • Cheese4cutMapper (14-77)
🔇 Additional comments (16)
src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java (1)

24-24: LGTM!

새로운 에러 코드가 적절한 HTTP 상태 코드와 함께 일관된 패턴으로 추가되었습니다.

src/main/java/com/cheeeese/global/config/SecurityConfig.java (1)

40-40: LGTM!

치즈네컷 미리보기 엔드포인트를 화이트리스트에 추가하여 인증 없이 접근 가능하도록 설정한 것이 적절합니다.

src/main/java/com/cheeeese/album/domain/Album.java (1)

69-71: LGTM!

앨범 상태를 만료로 변경하는 메서드가 간결하고 명확하게 구현되었습니다. 도메인 엔티티의 상태 변경 패턴을 잘 따르고 있습니다.

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

80-81: Redis 등록 실패 시 처리 방식을 검토하세요.

앨범 생성 후 Redis에 만료 추적을 등록하고 있지만, Redis 등록이 실패해도 앨범은 이미 DB에 저장된 상태입니다. 이로 인해 DB와 Redis 간 불일치가 발생할 수 있습니다.

다음을 확인해주세요:

  1. Redis 등록 실패 시 앨범 생성을 롤백해야 하는지
  2. Redis를 필수 구성 요소로 간주하는지 아니면 장애 허용(fail-safe)으로 처리할지
  3. Redis 장애 시 대체 만료 처리 방안이 있는지

Redis 등록이 필수라면, 다음과 같이 예외 처리를 추가하는 것을 고려하세요:

 albumRepository.save(album);

 userAlbumRepository.save(UserAlbumMapper.toEntity(
         user,
         album,
         Role.MAKER
 ));

-albumExpirationRedisRepository.registerAlbum(album.getId());
+try {
+    albumExpirationRedisRepository.registerAlbum(album.getId());
+} catch (Exception e) {
+    log.error("Failed to register album {} for expiration tracking", album.getId(), e);
+    throw new RuntimeException("앨범 만료 추적 등록에 실패했습니다.", e);
+}

 return AlbumMapper.toCreationResponse(album);
src/main/java/com/cheeeese/cheese4cut/infrastructure/persistence/Cheese4cutRepository.java (1)

8-10: 유니크 제약조건이 이미 설정되어 있습니다.

검증 결과, Cheese4cut 엔티티(src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java)의 album 필드에 @JoinColumn(name = "album_id", nullable = false, unique = true)로 이미 유니크 제약조건이 적용되어 있습니다. 따라서 findByAlbumIdOptional<Cheese4cut>을 반환하는 것은 올바른 설계이며, 앨범당 정확히 하나의 Cheese4cut만 존재함이 보장됩니다.

src/main/java/com/cheeeese/CheeeeseApplication.java (1)

12-12: 스케줄러 설정 확인 완료 - 프로덕션 배포 전 검토 필요

@EnableScheduling 설정은 올바르게 적용되었습니다. 다만, AlbumExpirationScheduler에서 @Scheduled(fixedDelay = 1000L)로 설정되어 1초마다 실행되도록 구성되어 있습니다. 현재 프로젝트에는 스케줄러 스레드 풀 설정이 없으므로 다음을 확인해주세요:

  • 1초 주기가 프로덕션 환경에 적합한지 검토 (Redis 접근 빈도, CPU 부하 고려)
  • 필요시 다음과 같이 스케줄러 풀 크기를 명시적으로 설정
spring:
  task:
    scheduling:
      pool:
        size: 2

스케줄러 실행 간격과 풀 크기 설정이 시스템 리소스에 미치는 영향을 검토한 후 배포하시기 바랍니다.

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

5-9: LGTM! Sealed interface 패턴이 적절하게 사용되었습니다.

치즈네컷 응답의 다형 구조를 sealed interface로 명확하게 제한하여 타입 안전성을 확보했습니다.

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

40-42: LGTM! 성공 코드가 일관된 패턴으로 추가되었습니다.

기존 enum 패턴을 따르며 명확한 메시지를 제공합니다.

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

6-11: LGTM! 간결한 응답 DTO입니다.

Presigned URL 발급 응답으로 적절한 구조입니다.

src/main/java/com/cheeeese/cheese4cut/exception/Cheese4cutException.java (1)

7-12: LGTM! 도메인 예외 클래스가 적절하게 구현되었습니다.

BusinessException을 확장하여 일관된 예외 처리 패턴을 따릅니다.

src/main/java/com/cheeeese/cheese4cut/application/validator/Cheese4cutValidator.java (1)

36-54: LGTM! 사진 검증 로직이 철저합니다.

4개의 유니크한 photo ID 검증, 존재 여부 확인, 그리고 앨범 소속 및 COMPLETED 상태 검증이 적절하게 구현되었습니다.

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

24-32: LGTM! 중첩 record가 깔끔하게 구현되었습니다.

PreviewPhotoInfo가 미리보기 사진 정보를 명확하게 표현합니다.

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

20-39: LGTM! 만료 처리 로직이 안정적으로 구현되었습니다.

각 앨범별로 예외를 독립적으로 처리하여 하나의 실패가 전체 프로세스를 중단시키지 않도록 했으며, 적절한 로깅도 포함되어 있습니다.

src/main/java/com/cheeeese/cheese4cut/exception/code/Cheese4cutErrorCode.java (1)

8-21: 구현이 올바릅니다.

에러 코드 정의가 명확하고, HTTP 상태 코드도 각 에러 유형에 적절하게 매핑되어 있습니다.

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

6-15: Record 구조가 적절합니다.

불변 DTO로서 올바르게 구현되었으며, Swagger 문서화도 명확합니다.

src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java (1)

20-142: API 문서화가 상세하고 명확합니다.

Swagger 문서가 다형적(polymorphic) 응답 구조를 포함한 API 동작을 잘 설명하고 있으며, 에러 케이스에 대한 예시도 제공되어 있습니다.

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.

🎸🧀 수고하셧ㅆ습니다 🐇🦖

치즈네컷 신경쓸게 매우매우매우 많네요;;
만료됐을 때, 서버 -> 프론트로 트리거를 보내서 치즈네컷을 생성하는 방법은.. 지금 생각나는 건 sse나 폴링인데 둘 다 치이이이이이잉이즈에 적합하지 않은 것 같기도 합니다... 차라리 서버에서 만들어야 할 것 같기도 하네요 ㅠ.ㅠ 넘 수고하셧습니다

글고 별 건 아닌데 presigned url 관련 api에서 어떤 건 body에서 앨범 코드 받고 어떤 건 pathVariable에서 코드 받아서 나중에 통일하면 좋을 것 가타요

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

🧹 Nitpick comments (3)
src/main/java/com/cheeeese/album/application/AlbumExpirationService.java (1)

37-44: 이미 EXPIRED 상태인 경우 로그를 추가하세요.

검증 결과, Cheese4cut 엔티티의 album_id 컬럼에 unique = true 제약이 이미 적용되어 있어 동시성 문제로 인한 데이터 중복 생성은 데이터베이스 레벨에서 방지됩니다. Line 42-44의 조기 반환 체크와 함께 race condition의 발생 확률은 매우 낮습니다.

다만 다음 개선이 권장됩니다:

Line 37-40에서 앨범이 이미 EXPIRED 상태인 경우(로직이 실행되지 않는 경우)에도 로그를 추가하여 재시도 호출 여부를 추적할 수 있도록 개선하면 디버깅 및 모니터링이 용이해집니다:

if (album.getStatus() != Album.AlbumStatus.EXPIRED) {
    album.expire();
    log.info("[AlbumExpiration] Album id={} status updated to EXPIRED", albumId);
} else {
    log.debug("[AlbumExpiration] Album id={} already EXPIRED, skipping status update", albumId);
}
src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java (1)

29-33: photoIds@NotNull 추가 권장

@Size(min=4, max=4) 검증은 이전 피드백에 따라 잘 추가되었습니다. 다만 @NotNull을 명시하면 null 안전성을 더 명확히 보장할 수 있습니다.

+import jakarta.validation.constraints.NotNull;
+
 @ElementCollection
 @CollectionTable(name = "cheese4cut_photos", joinColumns = @JoinColumn(name = "cheese4cut_id"))
 @Column(name = "photo_id", nullable = false)
+@NotNull
 @Size(min = 4, max = 4)
 private List<Long> photoIds;
src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (1)

54-56: TODO 작업 추적 필요

PR 목표에서 언급된 "7일 만료 시 최종 치즈네컷이 생성되지 않는 문제"와 연관된 부분입니다. 현재는 확정 여부만 반환하고 실제 4장의 사진을 제공하지 않습니다.

이 작업을 추적할 이슈를 생성해드릴까요?

📜 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 00362d7 and ffee386.

📒 Files selected for processing (11)
  • src/main/java/com/cheeeese/album/application/AlbumExpirationService.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/application/validator/Cheese4cutValidator.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/request/Cheese4cutFixedRequest.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutFinalResponse.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java (1 hunks)
  • src/main/java/com/cheeeese/photo/application/PresignedUrlService.java (3 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java
🧰 Additional context used
🧠 Learnings (1)
📚 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
  • src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java
  • src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java
🧬 Code graph analysis (2)
src/main/java/com/cheeeese/album/application/AlbumExpirationService.java (2)
src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1)
  • Cheese4cutMapper (14-75)
src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (1)
  • Service (34-122)
src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (1)
src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1)
  • Cheese4cutMapper (14-75)
🔇 Additional comments (12)
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (2)

59-60: 포맷 변경이 일관성을 개선했습니다.

쿼리의 SELECT 절 포맷 변경으로 가독성이 향상되었습니다.


93-105: ORDER BY 절의 createdAt ASC 의도를 확인하세요.

좋아요 수가 동일할 때 createdAt ASC(오래된 순)로 정렬하는 것은 일반적이지 않은 패턴입니다. 보통은 최신 사진을 우선하기 위해 DESC를 사용합니다. 이것이 비즈니스 요구사항에 따른 의도된 동작인지 확인해주세요.

참고: 이전 리뷰에서 지적된 enum 하드코딩 문제가 파라미터화로 잘 해결되었습니다.

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

46-58: 사진 조회 및 검증 로직이 적절합니다.

상위 4개의 완료된 사진 ID를 조회하고, 부족할 경우 경고 로그를 남기며 early return하는 로직이 명확하고 안전합니다. 다만 PhotoRepository.findTop4CompletedPhotoIdsByLikes의 ORDER BY 로직이 의도대로 동작하는지 확인이 필요합니다 (PhotoRepository 리뷰 참조).


61-64: Cheese4cut 생성 로직을 확인했습니다.

매퍼를 통한 엔티티 생성 및 저장 로직이 간결합니다. 다만 위에서 언급한 동시성 이슈(Line 37-44)가 해결되어야 중복 생성을 방지할 수 있습니다.

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

43-59: UUID 기반 파일명 적용으로 충돌 위험이 사라졌습니다.

치즈네컷 버킷 분리와 10분 TTL 유지도 요구사항에 잘 부합합니다.

src/main/java/com/cheeeese/cheese4cut/application/validator/Cheese4cutValidator.java (1)

29-53: 예외 코드 및 검증 로직이 요구사항에 맞게 정리되었습니다.

MAKER 권한 검증과 사진 상태/앨범 일치 검증이 모두 적절하게 구성되었습니다.

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

6-11: 확정 응답 DTO가 명확하게 표현되었습니다.

isFinalized의 의미를 Swagger 메타로 명시해 클라이언트 측 이해가 쉬워졌습니다.

src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java (1)

24-141: Swagger 문서가 다형 응답과 권한 플로우를 잘 전달하고 있습니다.

엔드포인트별 예시와 단계 설명이 구체적이라 클라이언트 연동 시 오해 여지가 없습니다.

src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java (1)

27-55: 엔드포인트 구현 확인 완료

세 개의 엔드포인트 모두 적절한 검증 애너테이션과 비즈니스 로직 위임이 잘 구현되어 있습니다. 이전 리뷰의 @NotBlank 추가 요청이 모두 반영되었습니다.

src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (3)

78-86: Null 체크 로직 구현 확인

이전 리뷰에서 논의된 NullPointerException 방어 로직이 적절히 구현되었습니다. 두 쿼리 사이의 타이밍 이슈를 고려한 안전한 구현입니다.


93-101: Presigned URL 발급 로직 확인

앨범 검증, 권한 확인, URL 생성이 적절히 구현되어 있습니다. @Transactional(readOnly=true) 사용도 적절합니다.


103-121: 치즈네컷 확정 로직 검증 완료

비즈니스 규칙(만료 확인, 제작자 권한, 중복 방지, 사진 검증)이 체계적으로 구현되어 있습니다. 트랜잭션 범위도 적절합니다.

@dahyun24
Copy link
Contributor Author

🎸🧀 수고하셧ㅆ습니다 🐇🦖

치즈네컷 신경쓸게 매우매우매우 많네요;; 만료됐을 때, 서버 -> 프론트로 트리거를 보내서 치즈네컷을 생성하는 방법은.. 지금 생각나는 건 sse나 폴링인데 둘 다 치이이이이이잉이즈에 적합하지 않은 것 같기도 합니다... 차라리 서버에서 만들어야 할 것 같기도 하네요 ㅠ.ㅠ 넘 수고하셧습니다

글고 별 건 아닌데 presigned url 관련 api에서 어떤 건 body에서 앨범 코드 받고 어떤 건 pathVariable에서 코드 받아서 나중에 통일하면 좋을 것 가타요

치즈네컷 쪽에서는 presigned url이 필요없을 것 같기도 해서,, 일단 두고 필요없으면 메서드 삭제하겠습니다~

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: 앨범 7일 만료 및 치즈네컷 기능 구현

3 participants