-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 사진 다운로드 로직 구현 및 CDN 설정 #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough사진 다운로드 워크플로우를 추가: 앨범 참여자 검증(공통), 사진-앨범 일치 검증, 다운로드용 GET 프리사인 URL 생성, 다운로드 이력 조회/갱신, CDN URL 해석 및 S3 유틸리티와 관련 DTO/매퍼/레포지토리 추가가 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant PhotoController
participant PhotoService
participant AlbumValidator
participant PhotoRepository
participant PhotoHistoryRepository
participant PresignedUrlService
participant CdnUrlResolver
Client->>PhotoController: POST /download-url (PhotoDownloadRequest)
PhotoController->>PhotoService: getDownloadPresignedUrls(user, request)
rect rgb(230,240,255)
Note over PhotoService,AlbumValidator: 사진 조회 및 참여자/소속 검증
PhotoService->>PhotoRepository: findAllByIdIn(photoIds)
PhotoService->>AlbumValidator: validateDownloadPermission(album, user, photos)
AlbumValidator-->>PhotoService: 검증 결과
end
rect rgb(230,255,230)
Note over PhotoService,PhotoHistoryRepository: 최근 다운로드(1시간) 확인
PhotoService->>PhotoHistoryRepository: findByUserIdAndPhotoId(...)
PhotoHistoryRepository-->>PhotoService: 이력 목록
end
rect rgb(255,240,220)
Note over PhotoService,PresignedUrlService: 프리사인 GET URL 생성 (필요 시)
PhotoService->>PresignedUrlService: generatePresignedGetUrl(objectKey)
PresignedUrlService-->>PhotoService: presignedUrl
end
rect rgb(245,230,255)
Note over PhotoService,PhotoHistoryRepository: 이력 생성/갱신
PhotoService->>PhotoHistoryRepository: save/update PhotoHistory
PhotoHistoryRepository-->>PhotoService: 저장 완료
end
PhotoService->>CdnUrlResolver: resolveThumbnail/resolveOriginal(...)
CdnUrlResolver-->>PhotoService: resolved URLs
PhotoService-->>PhotoController: PhotoDownloadResponse
PhotoController-->>Client: CommonResponse<PhotoDownloadResponse>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 (2)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)
87-87: 변수명 개선 제안변수명
existsPhotoInAlbum은 "어떤 사진이 존재하는지"를 암시하지만,allMatch는 모든 사진이 조건을 만족하는지 검증합니다.allPhotosInAlbum또는allPhotosMatchAlbum같은 이름이 더 명확할 것 같습니다.- boolean existsPhotoInAlbum = photos.stream().allMatch(photo -> photo.getAlbum().getId().equals(album.getId())); + boolean allPhotosInAlbum = photos.stream().allMatch(photo -> photo.getAlbum().getId().equals(album.getId())); - if (!existsPhotoInAlbum) { + if (!allPhotosInAlbum) { throw new PhotoException(PhotoErrorCode.PHOTO_NOT_FOUND_IN_ALBUM); }src/main/java/com/cheeeese/photo/presentation/swagger/PhotoSwagger.java (1)
158-176: API 문서화 적절함 - 에러 응답 추가 고려다운로드 presigned URL 발급 API에 대한 기본 문서화가 적절하게 작성되었습니다.
다만, 다른 API들과의 일관성을 위해 다음과 같은 에러 응답 케이스 추가를 고려해보세요:
- 400: 존재하지 않는 photoId 포함 시
- 403: 앨범 참가자가 아닌 경우
- 404: 앨범이 존재하지 않는 경우
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java(2 hunks)src/main/java/com/cheeeese/global/domain/BaseEntity.java(1 hunks)src/main/java/com/cheeeese/global/util/S3Util.java(1 hunks)src/main/java/com/cheeeese/global/util/resolver/CdnUrlResolver.java(1 hunks)src/main/java/com/cheeeese/photo/application/PhotoQueryService.java(4 hunks)src/main/java/com/cheeeese/photo/application/PhotoService.java(6 hunks)src/main/java/com/cheeeese/photo/application/PresignedUrlService.java(2 hunks)src/main/java/com/cheeeese/photo/domain/PhotoHistory.java(2 hunks)src/main/java/com/cheeeese/photo/dto/request/PhotoDownloadRequest.java(1 hunks)src/main/java/com/cheeeese/photo/dto/response/PhotoDownloadResponse.java(1 hunks)src/main/java/com/cheeeese/photo/exception/code/PhotoErrorCode.java(1 hunks)src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoHistoryMapper.java(1 hunks)src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoMapper.java(3 hunks)src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoHistoryRepository.java(2 hunks)src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java(1 hunks)src/main/java/com/cheeeese/photo/presentation/PhotoController.java(2 hunks)src/main/java/com/cheeeese/photo/presentation/swagger/PhotoSwagger.java(3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/PhotoHistoryRepository.javasrc/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/presentation/PhotoController.javasrc/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoMapper.javasrc/main/java/com/cheeeese/photo/application/PhotoQueryService.javasrc/main/java/com/cheeeese/photo/application/PhotoService.java
🧬 Code graph analysis (3)
src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoHistoryMapper.java (1)
src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoLikesMapper.java (2)
PhotoLikesMapper(7-15)toEntity(9-14)
src/main/java/com/cheeeese/photo/application/PhotoQueryService.java (1)
src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoMapper.java (1)
PhotoMapper(14-150)
src/main/java/com/cheeeese/photo/application/PhotoService.java (3)
src/main/java/com/cheeeese/global/util/S3Util.java (1)
S3Util(6-46)src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoHistoryMapper.java (1)
PhotoHistoryMapper(7-15)src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoMapper.java (1)
PhotoMapper(14-150)
🔇 Additional comments (12)
src/main/java/com/cheeeese/photo/exception/code/PhotoErrorCode.java (1)
32-32: 새로운 에러 코드 추가 확인 완료앨범 소속 검증을 위한 에러 코드가 적절하게 추가되었습니다. HttpStatus와 메시지가 기존 패턴과 일관성 있게 정의되었습니다.
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)
84-92: <!-- [scratchpad_start]
[task verification_1/2 npe_risk]
Verify NPE risk from photo.getAlbum() null return
[observations]
- Line 30 in Photo.java: @joincolumn(name = "album_id", nullable = false)
- Album field marked with nullable = false constraint
- Database-level constraint ensures album is never null
[analysis]- NPE risk is mitigated by JPA nullable constraint
- photo.getAlbum() will never return null for persisted Photo entities
[done]
NPE concern is RESOLVED - album field is guaranteed non-null
[/scratchpad_start]
src/main/java/com/cheeeese/photo/application/PhotoQueryService.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 (1)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)
82-82: 변수명을 로직에 맞게 수정하세요.
allMatch를 사용하고 있으므로 변수명을existsPhotoInAlbum에서allPhotosInAlbum또는allPhotosMatchAlbum으로 변경하는 것이 더 명확합니다.- boolean existsPhotoInAlbum = photos.stream().allMatch(photo -> photo.getAlbum().getId().equals(album.getId())); + boolean allPhotosInAlbum = photos.stream().allMatch(photo -> photo.getAlbum().getId().equals(album.getId())); - if (!existsPhotoInAlbum) { + if (!allPhotosInAlbum) { throw new PhotoException(PhotoErrorCode.PHOTO_NOT_FOUND_IN_ALBUM); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java(2 hunks)src/main/java/com/cheeeese/photo/application/PhotoQueryService.java(4 hunks)src/main/java/com/cheeeese/photo/domain/PhotoHistory.java(1 hunks)
🧰 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/application/PhotoQueryService.java
🧬 Code graph analysis (2)
src/main/java/com/cheeeese/photo/domain/PhotoHistory.java (1)
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoHistoryRepository.java (2)
Query(25-36)PhotoHistoryRepository(12-37)
src/main/java/com/cheeeese/photo/application/PhotoQueryService.java (1)
src/main/java/com/cheeeese/photo/infrastructure/mapper/PhotoMapper.java (1)
PhotoMapper(14-150)
🔇 Additional comments (5)
src/main/java/com/cheeeese/photo/domain/PhotoHistory.java (1)
41-43: 구현이 정확합니다.
touch()메서드가BaseEntity의markUpdated()를 올바르게 호출하여 다운로드 이력 갱신 시updatedAt타임스탬프를 업데이트합니다.src/main/java/com/cheeeese/photo/application/PhotoQueryService.java (1)
5-5: CDN URL 해석 로직이 일관되게 잘 구현되었습니다.
CdnUrlResolver가 모든 메서드(getPhotoDetail,getPhotoPageFromDB,buildPhotoLikedResponses)에서 일관되게 사용되고 있으며, 이전 리뷰에서 지적된 썸네일 URL 처리 버그도 수정되었습니다.Also applies to: 36-36, 99-100, 108-108, 114-114, 170-170, 173-173
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (3)
79-87: 빈 리스트 엣지 케이스를 처리해주세요.
photos리스트가 비어있을 경우allMatch는true를 반환하여 검증을 통과합니다. 빈 리스트에 대한 명시적인 검증이 필요한지 확인해주세요.만약 빈 리스트를 허용하지 않는다면 다음과 같이 수정하세요:
public void validateDownloadPermission(Album album, User user, List<Photo> photos) { validateAlbumParticipant(album, user); + + if (photos == null || photos.isEmpty()) { + throw new PhotoException(PhotoErrorCode.PHOTO_NOT_FOUND); + } boolean allPhotosInAlbum = photos.stream().allMatch(photo -> photo.getAlbum().getId().equals(album.getId())); if (!allPhotosInAlbum) { throw new PhotoException(PhotoErrorCode.PHOTO_NOT_FOUND_IN_ALBUM); } }
75-77: 공통 검증 로직 추출이 잘 되었습니다.
validateAlbumParticipant메서드로 공통 검증 로직을 추출하여 코드 중복을 제거하고 유지보수성을 향상시켰습니다.Also applies to: 89-96
82-82: 이 리뷰 코멘트는 근거가 없습니다.Photo 엔티티의 album 관계는
@ManyToOne(fetch = FetchType.LAZY)로 설정되어 있으나,validateDownloadPermission()은@Transactional메서드 내에서 호출되므로 트랜잭션 범위 내에서 lazy loading이 정상 작동합니다. 또한@JoinColumn(name = "album_id", nullable = false)로 인해 데이터베이스 제약 수준에서 album_id가 NULL이 될 수 없으므로,photo.getAlbum()은 절대 null을 반환하지 않습니다. NPE 위험은 실제로 존재하지 않습니다.Likely an incorrect or invalid review comment.
dahyun24
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니당
LGTM~~~
🔗 연관된 이슈
🚀 변경 유형
📝 작업 내용
📸 스크린샷
💬 리뷰 요구사항
📜 리뷰 규칙
Reviewer는 아래 P5 Rule을 참고하여 리뷰를 진행합니다.
P5 Rule을 통해 Reviewer는 Reviewee에게 리뷰의 의도를 보다 정확히 전달할 수 있습니다.
Summary by CodeRabbit