diff --git a/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java b/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java index 35ef319..7fb48fd 100644 --- a/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java +++ b/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java @@ -9,17 +9,21 @@ import com.umc.naoman.global.result.ResultResponse; import com.umc.naoman.global.security.annotation.LoginMember; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -57,8 +61,8 @@ public ResultResponse uploadPhotoList(@Valid @Req @GetMapping("/all") @Operation(summary = "특정 공유그룹의 전체 사진 조회 API", description = "특정 공유 그룹의 전체 사진을 조회하는 API입니다.") - public ResultResponse getAllPhotoListByShareGroup(@RequestParam Long shareGroupId, @LoginMember Member member, - @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { + public ResultResponse getAllPhotoListByShareGroup(@RequestParam Long shareGroupId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, + @LoginMember Member member) { Page allPhotoListByShareGroup = photoService.getAllPhotoList(shareGroupId, member, pageable); return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPhotoListInfo(allPhotoListByShareGroup)); } @@ -70,4 +74,12 @@ public ResultResponse deletePhotoList(@Valid @Req List photoList = photoService.deletePhotoList(request, member); return ResultResponse.of(DELETE_PHOTO, photoConverter.toPhotoDeleteInfo(photoList)); } + + @GetMapping("/download") + @Operation(summary = "사진 다운로드 API", description = "여러장의 사진을 다운로드할 주소를 받는 API입니다. 해당 공유그룹에 속해있는 회원만 다운로드 요청할 수 있습니다.") + public ResultResponse getPhotoDownloadUrlList(@RequestParam List photoIdList, @RequestParam Long shareGroupId, + @LoginMember Member member) { + PhotoResponse.PhotoDownloadUrlListInfo photoDownloadUrlList = photoService.getPhotoDownloadUrlList(photoIdList, shareGroupId, member); + return ResultResponse.of(DOWNLOAD_PHOTO, photoDownloadUrlList); + } } diff --git a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java index 1cdd149..78f7411 100644 --- a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java +++ b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java @@ -94,4 +94,13 @@ public PhotoResponse.PhotoDeleteInfo toPhotoDeleteInfo(List photoList) { .build(); } + public PhotoResponse.PhotoDownloadUrlListInfo toPhotoDownloadUrlListInfo(List photoList) { + List photoDownloadUrlList = photoList.stream() + .map(Photo::getUrl) + .collect(Collectors.toList()); + + return PhotoResponse.PhotoDownloadUrlListInfo.builder() + .photoDownloadUrlList(photoDownloadUrlList) + .build(); + } } diff --git a/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java b/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java index a9b87a5..35dad94 100644 --- a/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java +++ b/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java @@ -4,6 +4,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; import java.time.LocalDateTime; import java.util.List; @@ -68,4 +70,11 @@ public static class PhotoDeleteInfo { private List photoIdList; } + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class PhotoDownloadUrlListInfo { + private List photoDownloadUrlList; + } } diff --git a/src/main/java/com/umc/naoman/domain/photo/repository/PhotoRepository.java b/src/main/java/com/umc/naoman/domain/photo/repository/PhotoRepository.java index 0ab8983..6c8f96c 100644 --- a/src/main/java/com/umc/naoman/domain/photo/repository/PhotoRepository.java +++ b/src/main/java/com/umc/naoman/domain/photo/repository/PhotoRepository.java @@ -12,4 +12,5 @@ public interface PhotoRepository extends JpaRepository { Page findAllByShareGroupId(Long shareGroupId, Pageable pageable); List findByIdInAndShareGroupId(List photoIdList, Long shareGroupId); + List findByIdIn(List photoIdList); } diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java index afd1185..84c426a 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoService.java @@ -4,6 +4,7 @@ import com.umc.naoman.domain.photo.dto.PhotoRequest; import com.umc.naoman.domain.photo.dto.PhotoResponse; import com.umc.naoman.domain.photo.entity.Photo; +import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -18,4 +19,6 @@ public interface PhotoService { Page getAllPhotoList(Long shareGroupId, Member member, Pageable pageable); List deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Member member); + + PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member); } diff --git a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java index 520c06d..a7edde8 100644 --- a/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/photo/service/PhotoServiceImpl.java @@ -14,7 +14,6 @@ import com.umc.naoman.domain.photo.entity.Photo; import com.umc.naoman.domain.photo.repository.PhotoRepository; import com.umc.naoman.domain.shareGroup.entity.ShareGroup; -import com.umc.naoman.domain.shareGroup.repository.ProfileRepository; import com.umc.naoman.domain.shareGroup.service.ShareGroupService; import com.umc.naoman.global.error.BusinessException; import io.awspring.cloud.s3.S3Template; @@ -42,7 +41,6 @@ public class PhotoServiceImpl implements PhotoService { private final PhotoRepository photoRepository; private final ShareGroupService shareGroupService; private final PhotoConverter photoConverter; - private final ProfileRepository profileRepository; @Value("${spring.cloud.aws.s3.bucket}") private String bucketName; @@ -57,7 +55,7 @@ public class PhotoServiceImpl implements PhotoService { @Override @Transactional public List getPreSignedUrlList(PhotoRequest.PreSignedUrlRequest request, Member member) { - shareGroupService.findProfile(request.getShareGroupId(), member.getId()); + validateShareGroupAndProfile(request.getShareGroupId(), member); return request.getPhotoNameList().stream() .map(this::getPreSignedUrl) @@ -112,8 +110,8 @@ private String generateFileAccessUrl(String fileName) { @Override @Transactional public PhotoResponse.PhotoUploadInfo uploadPhotoList(PhotoRequest.PhotoUploadRequest request, Member member) { - shareGroupService.findProfile(member.getId(), request.getShareGroupId()); ShareGroup shareGroup = shareGroupService.findShareGroup(request.getShareGroupId()); + shareGroupService.findProfile(request.getShareGroupId(), member.getId()); int uploadCount = 0; for (String photoUrl : request.getPhotoUrlList()) { @@ -156,15 +154,15 @@ public Page getAllPhotoList(Long shareGroupId, Member member, Pageable pa @Override @Transactional public List deletePhotoList(PhotoRequest.PhotoDeletedRequest request, Member member) { - // 멤버가 해당 공유 그룹에 대한 권한이 있는지 확인 - shareGroupService.findProfile(member.getId(), request.getShareGroupId()); + validateShareGroupAndProfile(request.getShareGroupId(), member); // 요청된 사진 ID 목록과 공유 그룹 ID를 기반으로 사진 목록 조회 List photoList = photoRepository.findByIdInAndShareGroupId(request.getPhotoIdList(), request.getShareGroupId()); // 사진 목록 크기 검증 if (photoList.size() != request.getPhotoIdList().size()) { - throw new BusinessException(PHOTO_NOT_FOUND); // 요청한 사진이 일부 또는 전부 없을 경우 예외 발생 + // 요청한 사진이 일부 또는 전부 없을 경우 예외 발생 + throw new BusinessException(PHOTO_NOT_FOUND); } // 각 사진에 대해 S3에서 객체 삭제 및 데이터베이스에서 삭제 @@ -185,4 +183,24 @@ private void deletePhoto(Photo photo) { photoRepository.delete(photo); } + @Override + @Transactional(readOnly = true) + public PhotoResponse.PhotoDownloadUrlListInfo getPhotoDownloadUrlList(List photoIdList, Long shareGroupId, Member member) { + validateShareGroupAndProfile(shareGroupId, member); + List photoList = photoRepository.findByIdIn(photoIdList); + + if (photoList.size() != photoIdList.size()) { + // 요청한 사진이 일부 또는 전부 없을 경우 예외 발생 + throw new BusinessException(PHOTO_NOT_FOUND); + } + + return photoConverter.toPhotoDownloadUrlListInfo(photoList); + } + + private void validateShareGroupAndProfile(Long shareGroupId, Member member) { + // 해당 공유 그룹이 존재하는지 확인 + shareGroupService.findShareGroup(shareGroupId); + // 멤버가 해당 공유 그룹에 속해있는지 확인 + shareGroupService.findProfile(shareGroupId, member.getId()); + } } diff --git a/src/main/java/com/umc/naoman/global/error/code/S3ErrorCode.java b/src/main/java/com/umc/naoman/global/error/code/S3ErrorCode.java index c49b6bf..49c7157 100644 --- a/src/main/java/com/umc/naoman/global/error/code/S3ErrorCode.java +++ b/src/main/java/com/umc/naoman/global/error/code/S3ErrorCode.java @@ -12,7 +12,8 @@ public enum S3ErrorCode implements ErrorCode { UNAUTHORIZED_GET(403, "ES3000", "사진을 조회할 권한이 없습니다."), UNAUTHORIZED_DELETE(403, "ES3000", "사진을 삭제할 권한이 없습니다."), UNAUTHORIZED_UPLOAD(403, "ES3000", "사진을 업로드할 권한이 없습니다."), - PHOTO_NOT_FOUND(404, "ES3005", "요청한 사진이 존재하지 않습니다."), + PHOTO_NOT_FOUND(404, "ES3000", "요청한 사진이 존재하지 않습니다."), + FAILED_DOWNLOAD_PHOTO(500, "ES3000", "사진을 다운로드하는 도중 문제가 발생하였습니다.") ; diff --git a/src/main/java/com/umc/naoman/global/result/ResultResponse.java b/src/main/java/com/umc/naoman/global/result/ResultResponse.java index 1c91392..6d0ccbc 100644 --- a/src/main/java/com/umc/naoman/global/result/ResultResponse.java +++ b/src/main/java/com/umc/naoman/global/result/ResultResponse.java @@ -28,4 +28,4 @@ public static ResultResponse of(ResultCode resultCode) { .data(null) .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/umc/naoman/global/result/code/PhotoResultCode.java b/src/main/java/com/umc/naoman/global/result/code/PhotoResultCode.java index 027de1f..20a51ce 100644 --- a/src/main/java/com/umc/naoman/global/result/code/PhotoResultCode.java +++ b/src/main/java/com/umc/naoman/global/result/code/PhotoResultCode.java @@ -11,7 +11,8 @@ public enum PhotoResultCode implements ResultCode { CREATE_PRESIGNED_URL(200, "SP000", "성공적으로 Presigned URL을 요청하였습니다."), UPLOAD_PHOTO(200, "SP000", "성공적으로 이미지를 업로드하였습니다."), RETRIEVE_PHOTO(200, "SP000", "성공적으로 이미지를 조회하였습니다."), - DELETE_PHOTO(200, "SP000", "성공적으로 이미지를 삭제하였습니다.") + DELETE_PHOTO(200, "SP000", "성공적으로 이미지를 삭제하였습니다."), + DOWNLOAD_PHOTO(200, "SP000", "성공적으로 이미지를 디운로드하였습니다.") ; private final int status;