Skip to content

Conversation

@dahyun24
Copy link
Contributor

@dahyun24 dahyun24 commented Oct 30, 2025

🔗 연관된 이슈

🚀 변경 유형

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

📝 작업 내용

  • NCP에서 Object Storage에 PUT 이벤트를 트리거로 두고, action으로 썸네일 생성을 만들었습니다.
  • 썸네일 생성이 완료되면 썸네일용 버킷(say-cheeeese-thumbnail)에 오리지널과 동일한 규칙으로 album/{albumCode}/thumbnail/{photoId}_{imageName} 으로 저장
  • 저장 후 /internal/thumbnail/complete api로 photoId를 담아서 서버로 보내줍니다.
  • 이후 서버에서 Photo 테이블의 상태를 complete로 바꾸게 됩니다.

📸 스크린샷

스크린샷 2025-10-28 오후 11 20 21

💬 리뷰 요구사항

Summary by CodeRabbit

  • 새로운 기능
    • 사진 업로드 완료를 수신하는 내부 콜백 엔드포인트가 추가되었습니다.
    • 썸네일 생성 완료 시 사진의 상태와 썸네일 URL이 자동으로 업데이트됩니다.
  • 버그 수정
    • 썸네일 업데이트 실패 시 명확한 오류 코드가 추가되어 장애 원인 파악이 개선됩니다.

@dahyun24 dahyun24 self-assigned this Oct 30, 2025
@dahyun24 dahyun24 added the ✨feature New feature or request label Oct 30, 2025
@dahyun24 dahyun24 linked an issue Oct 30, 2025 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Oct 30, 2025

Walkthrough

외부 서비스의 썸네일 생성 완료 콜백을 처리하는 내부 엔드포인트(/internal/thumbnail/complete)와 관련 DTO, 컨트롤러, 서비스, 레포지토리 업데이트, 도메인 필드 및 성공/오류 코드가 추가되었습니다.

Changes

코호트 / 파일(s) 변경 요약
응답 및 상태 코드
src/main/java/com/cheeeese/global/common/code/SuccessCode.java
THUMBNAIL_PRODUCE_COMPLETE(HttpStatus.OK, "썸네일 생성이 성공적으로 완료되었습니다.") 상수 추가
보안 설정
src/main/java/com/cheeeese/global/config/SecurityConfig.java
보안 화이트리스트에 "/internal/thumbnail/complete" 경로 추가
컨트롤러
src/main/java/com/cheeeese/photo/presentation/PhotoCallbackController.java
POST /internal/thumbnail/complete 엔드포인트 추가; @Valid @RequestBody PhotoCompleteRequest 수신 후 PhotoCallbackService.markUploadCompleted(request) 호출하고 CommonResponse<Void>THUMBNAIL_PRODUCE_COMPLETE 반환
서비스 계층
src/main/java/com/cheeeese/photo/application/PhotoCallbackService.java
트랜잭션 서비스 PhotoCallbackService 추가; markUploadCompleted(PhotoCompleteRequest request)로 상태를 UPLOADINGCOMPLETED로 업데이트하고 thumbnailUrl 저장, 업데이트 실패 시 PhotoException(THUMBNAIL_UPDATE_FAILED) 던짐
요청 DTO
src/main/java/com/cheeeese/photo/dto/request/PhotoCompleteRequest.java
불변 레코드 PhotoCompleteRequest(Long photoId, String thumbnailUrl) 추가 (@NotNull 검증 포함)
데이터 접근 계층
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
int updateStatusAndUrl(Long photoId, PhotoStatus expectedStatus, PhotoStatus newStatus, String thumbnailUrl) 메서드 추가 (@Modifying @Query로 상태와 thumbnailUrl 동시 업데이트)
도메인 모델
src/main/java/com/cheeeese/photo/domain/Photo.java
thumbnailUrl 필드(@Column(name = "thumbnail_url", columnDefinition = "TEXT")) 및 빌더/생성자 반영 추가
예외 코드
src/main/java/com/cheeeese/photo/exception/code/PhotoErrorCode.java
THUMBNAIL_UPDATE_FAILED(HttpStatus.CONFLICT, "썸네일 상태 업데이트에 실패했습니다.") 상수 추가
매퍼
src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoMapper.java
toEntity(...) 빌더에 thumbnailUrl(null) 초기화 추가

Sequence Diagram(s)

sequenceDiagram
    participant External as 외부 서비스
    participant Controller as PhotoCallbackController
    participant Service as PhotoCallbackService
    participant Repo as PhotoRepository
    participant DB as Database

    External->>Controller: POST /internal/thumbnail/complete\n{ photoId, thumbnailUrl }
    Controller->>Service: markUploadCompleted(request)
    Service->>Repo: updateStatusAndUrl(photoId, UPLOADING, COMPLETED, thumbnailUrl)
    Repo->>DB: UPDATE photo SET status=COMPLETED, thumbnail_url=:thumbnailUrl\nWHERE id=:photoId AND status=UPLOADING
    DB-->>Repo: 업데이트된 행 수 (n)

    alt 성공 (n == 1)
        Repo-->>Service: 1
        Service-->>Controller: 정상 완료
        Controller-->>External: 200 OK (THUMBNAIL_PRODUCE_COMPLETE)
    else 실패 (n == 0)
        Repo-->>Service: 0
        Service-->>Service: PhotoException(THUMBNAIL_UPDATE_FAILED) 발생
        Service-->>Controller: 예외 전파
        Controller-->>External: 에러 응답
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • 추가 검토 포인트:
    • PhotoCallbackService.markUploadCompleted(...)의 트랜잭션 경계 및 예외 매핑
    • PhotoRepository.updateStatusAndUrl(...)의 JPQL(조건부 업데이트) 및 동시성 영향
    • Photo 엔티티의 스키마 변경(마이그레이션, nullable) 및 PhotoMapper 초기값 처리
    • 보안 화이트리스트에 추가된 경로의 접근 범위 검증

Possibly related PRs

Suggested reviewers

  • zyovn

"🐰
썸네일 왔네, 작은 빛 하나—
콜백 오면 나는 깡충 달려가,
상태 바꾸고 URL 품에 안아,
데이터 속에 조용히 새로 핀 꽃.
찰칵, 코드에 한 줄 축하를 전하네!"

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: 썸네일 생성 및 callback API 구현"은 변경 사항의 핵심 내용을 명확하게 반영하고 있습니다. 추가된 파일들(PhotoCallbackController, PhotoCallbackService, PhotoCompleteRequest, 새로운 repository 메서드)과 보안 화이트리스트 업데이트는 모두 이 제목이 설명하는 두 가지 주요 기능—썸네일 생성 및 callback API 구현—에 정렬되어 있습니다. 제목은 간결하고 구체적이므로 팀원들이 변경 사항의 목적을 신속하게 파악할 수 있습니다.
✨ 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/#26-photo-thumbnail

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

🧹 Nitpick comments (3)
src/main/java/com/cheeeese/photo/application/PhotoCallbackService.java (1)

7-7: Spring의 @transactional 사용 권장

jakarta.transaction.Transactional 대신 Spring의 @Transactional을 사용하는 것이 좋습니다. Spring의 버전은 더 많은 기능과 프레임워크 통합을 제공하며, 프로젝트의 다른 서비스 클래스들과 일관성을 유지할 수 있습니다.

다음 diff를 적용하세요:

 package com.cheeeese.photo.application;
 
 import com.cheeeese.photo.domain.PhotoStatus;
 import com.cheeeese.photo.exception.PhotoException;
 import com.cheeeese.photo.exception.code.PhotoErrorCode;
 import com.cheeeese.photo.infrastructure.persistence.PhotoRepository;
-import jakarta.transaction.Transactional;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 @Service
 @Transactional

Also applies to: 12-12

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

35-35: @param 어노테이션 추가 권장

명시성과 일관성을 위해 파라미터에 @Param 어노테이션을 추가하는 것이 좋습니다. 기존 메서드(Lines 27-30)도 동일한 패턴을 따르고 있습니다.

다음 diff를 적용하세요:

     @Modifying(clearAutomatically = true, flushAutomatically = true)
     @Query("UPDATE Photo p SET p.status = :status WHERE p.id = :photoId")
-    int updateStatus(Long photoId, PhotoStatus status);
+    int updateStatus(@Param("photoId") Long photoId, @Param("status") PhotoStatus status);
src/main/java/com/cheeeese/photo/presentation/PhotoCallbackController.java (1)

19-23: 로깅 추가 권장

외부 시스템(NCP Object Storage)에서 호출되는 중요한 콜백 엔드포인트이므로, 감사(audit) 및 디버깅을 위한 로깅을 추가하는 것이 좋습니다. 최소한 다음 정보를 로깅해야 합니다:

  • 요청 수신 (photoId)
  • 처리 성공/실패

다음과 같이 로깅을 추가하세요:

 package com.cheeeese.photo.presentation;
 
 import com.cheeeese.global.common.CommonResponse;
 import com.cheeeese.photo.dto.request.PhotoCompleteRequest;
 import com.cheeeese.photo.application.PhotoCallbackService;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import static com.cheeeese.global.common.code.SuccessCode.THUMBNAIL_PRODUCE_COMPLETE;
 
+@Slf4j
 @RestController
 @RequestMapping("/internal/thumbnail")
 @RequiredArgsConstructor
 public class PhotoCallbackController {
 
     private final PhotoCallbackService photoCallbackService;
 
     @PostMapping("/complete")
     public CommonResponse<Void> completeUpload(@Valid @RequestBody PhotoCompleteRequest request) {
+        log.info("Received thumbnail completion callback for photoId: {}", request.photoId());
         photoCallbackService.markUploadCompleted(request.photoId());
+        log.info("Successfully marked photo as completed: {}", request.photoId());
         return CommonResponse.success(THUMBNAIL_PRODUCE_COMPLETE);
     }
 }
📜 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 a0e3031 and 67cfb44.

📒 Files selected for processing (6)
  • 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/PhotoCallbackService.java (1 hunks)
  • src/main/java/com/cheeeese/photo/dto/request/PhotoCompleteRequest.java (1 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1 hunks)
  • src/main/java/com/cheeeese/photo/presentation/PhotoCallbackController.java (1 hunks)
🔇 Additional comments (2)
src/main/java/com/cheeeese/global/common/code/SuccessCode.java (1)

32-32: LGTM!

새로운 성공 코드가 기존 패턴을 잘 따르고 있으며, 메시지도 명확합니다.

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

18-23: LGTM - 에러 처리가 적절합니다

updateStatus 호출 후 업데이트된 행 수를 확인하여 존재하지 않는 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.

수고하셨습니다~~ 🎸

나도 이제 뺑이쳐야지...

coderabbitai[bot]

This comment was marked as resolved.

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/application/PhotoCallbackService.java (3)

8-8: Spring의 @Transactional 사용을 권장합니다.

현재 jakarta.transaction.Transactional을 사용하고 있는데, Spring Boot 애플리케이션에서는 일반적으로 org.springframework.transaction.annotation.Transactional을 사용합니다. Spring의 트랜잭션 관리가 더 많은 기능(전파 옵션, 격리 수준, 읽기 전용 등)을 제공하고 Spring 생태계와 더 잘 통합됩니다.

다음 diff를 적용하여 Spring의 트랜잭션 애노테이션으로 변경하세요:

-import jakarta.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;

Also applies to: 13-13


19-30: 콜백 재시도를 위한 멱등성(idempotency) 처리를 고려하세요.

외부 시스템이 콜백을 재시도할 경우, 이미 COMPLETED 상태인 사진에 대해 예외가 발생합니다. 또한 updated == 0일 때 사진이 존재하지 않는 경우와 이미 완료된 경우를 구분하지 못합니다.

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

 public void markUploadCompleted(PhotoCompleteRequest request) {
+    // 먼저 현재 상태 확인
+    Photo photo = photoRepository.findById(request.photoId())
+            .orElseThrow(() -> new PhotoException(PhotoErrorCode.PHOTO_NOT_FOUND));
+    
+    // 이미 완료된 경우 멱등성 처리
+    if (photo.getStatus() == PhotoStatus.COMPLETED) {
+        return;
+    }
+    
     int updated = photoRepository.updateStatusAndUrl(
             request.photoId(),
             PhotoStatus.PROCESSING,
             PhotoStatus.COMPLETED,
             request.thumbnailUrl()
     );
     
     if (updated == 0) {
         throw new PhotoException(PhotoErrorCode.THUMBNAIL_UPDATE_FAILED);
     }
 }

이렇게 하면:

  • 이미 완료된 경우 재시도가 성공적으로 처리됨 (멱등성)
  • 사진이 없는 경우와 상태가 맞지 않는 경우를 명확히 구분

19-30: 모니터링을 위한 로깅 추가를 고려하세요.

외부 시스템으로부터의 콜백이므로, 디버깅과 모니터링을 위해 로그를 추가하는 것이 좋습니다.

+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 @Service
 @Transactional
 @RequiredArgsConstructor
 public class PhotoCallbackService {
 
     private final PhotoRepository photoRepository;
 
     public void markUploadCompleted(PhotoCompleteRequest request) {
+        log.info("Thumbnail completion callback received for photoId: {}", request.photoId());
+        
         int updated = photoRepository.updateStatusAndUrl(
                 request.photoId(),
                 PhotoStatus.PROCESSING,
                 PhotoStatus.COMPLETED,
                 request.thumbnailUrl()
         );
 
         if (updated == 0) {
+            log.warn("Failed to update photo status for photoId: {}", request.photoId());
             throw new PhotoException(PhotoErrorCode.THUMBNAIL_UPDATE_FAILED);
         }
+        
+        log.info("Photo status updated to COMPLETED for photoId: {}", request.photoId());
     }
 }
📜 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 2acd4aa and bc30cf3.

📒 Files selected for processing (1)
  • src/main/java/com/cheeeese/photo/application/PhotoCallbackService.java (1 hunks)

@dahyun24 dahyun24 merged commit 878e395 into develop Oct 31, 2025
1 check passed
@zyovn zyovn deleted the feat/#26-photo-thumbnail branch October 31, 2025 06:15
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