diff --git a/src/main/java/leets/bookmark/domain/bookmark/application/dto/request/BookmarkSaveRequest.java b/src/main/java/leets/bookmark/domain/bookmark/application/dto/request/BookmarkSaveRequest.java index 8c0d1119..8c183903 100644 --- a/src/main/java/leets/bookmark/domain/bookmark/application/dto/request/BookmarkSaveRequest.java +++ b/src/main/java/leets/bookmark/domain/bookmark/application/dto/request/BookmarkSaveRequest.java @@ -1,12 +1,9 @@ package leets.bookmark.domain.bookmark.application.dto.request; -import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import leets.bookmark.domain.bookmark.domain.entity.enums.Platform; import leets.bookmark.domain.notification.application.dto.request.NotificationSaveRequest; -import leets.bookmark.domain.file.application.dto.request.FileSaveRequest; -import leets.bookmark.domain.user.domain.entity.User; import lombok.Builder; @@ -17,7 +14,7 @@ public record BookmarkSaveRequest( @NotBlank String title, @NotBlank String url, String memo, - @NotNull @Valid FileSaveRequest file, + @NotBlank String thumbnailUrl, NotificationSaveRequest notification, @NotNull Platform platform, @NotNull Long categoryId, diff --git a/src/main/java/leets/bookmark/domain/bookmark/application/dto/request/BookmarkUpdateRequest.java b/src/main/java/leets/bookmark/domain/bookmark/application/dto/request/BookmarkUpdateRequest.java index 0234f800..cdbf8f44 100644 --- a/src/main/java/leets/bookmark/domain/bookmark/application/dto/request/BookmarkUpdateRequest.java +++ b/src/main/java/leets/bookmark/domain/bookmark/application/dto/request/BookmarkUpdateRequest.java @@ -12,7 +12,7 @@ public record BookmarkUpdateRequest( String title, String url, String memo, - FileUpdateRequest file, + String thumbnailUrl, NotificationUpdateRequest notification, Platform platform, Long categoryId, diff --git a/src/main/java/leets/bookmark/domain/bookmark/application/usecase/BookmarkUseCaseImpl.java b/src/main/java/leets/bookmark/domain/bookmark/application/usecase/BookmarkUseCaseImpl.java index f77c7a16..fcdfbe83 100644 --- a/src/main/java/leets/bookmark/domain/bookmark/application/usecase/BookmarkUseCaseImpl.java +++ b/src/main/java/leets/bookmark/domain/bookmark/application/usecase/BookmarkUseCaseImpl.java @@ -147,8 +147,8 @@ public void save(Long userId, BookmarkSaveRequest request) { Category category = categoryGetService.findById(request.categoryId()); Bookmark bookmark = bookmarkSaveService.save(request, user, category); - if (request.file() != null) { - fileUseCase.saveFile(user, bookmark, request.file()); + if (request.thumbnailUrl() != null) { + fileUseCase.saveThumbnailFile(user, bookmark, request.thumbnailUrl()); } } @@ -163,10 +163,12 @@ public void update(Long userId, Long bookmarkId, BookmarkUpdateRequest request, if (request.title() != null && !request.title().trim().isEmpty()) { bookmark.updateTitle(request.title()); + updated = true; } - if (request.file() != null) { - fileUseCase.updateFile(user, bookmark, request.file()); + if (request.thumbnailUrl() != null) { + fileUseCase.updateThumbnailImage(user, bookmark, request.thumbnailUrl()); + updated = true; } if (request.platform() != null) { diff --git a/src/main/java/leets/bookmark/domain/bookmark/domain/service/BookmarkUpdateService.java b/src/main/java/leets/bookmark/domain/bookmark/domain/service/BookmarkUpdateService.java index 3e827250..a3e3eeed 100644 --- a/src/main/java/leets/bookmark/domain/bookmark/domain/service/BookmarkUpdateService.java +++ b/src/main/java/leets/bookmark/domain/bookmark/domain/service/BookmarkUpdateService.java @@ -27,7 +27,6 @@ public class BookmarkUpdateService { @Transactional public void update(Bookmark bookmark, BookmarkUpdateRequest request) { - FileUpdateRequest fileRequest = request.file(); bookmark.updateBookmark( request.title(), request.memo() diff --git a/src/main/java/leets/bookmark/domain/file/application/exception/FileErrorCode.java b/src/main/java/leets/bookmark/domain/file/application/exception/FileErrorCode.java index 1b49f437..89c3afac 100644 --- a/src/main/java/leets/bookmark/domain/file/application/exception/FileErrorCode.java +++ b/src/main/java/leets/bookmark/domain/file/application/exception/FileErrorCode.java @@ -10,7 +10,8 @@ public enum FileErrorCode implements ErrorCode { FILE_NOT_FOUND_EXCEPTION(404,"해당하는 파일을 찾을 수 없습니다."), INVALID_FILE_EXTENSION(400, "올바르지 않은 파일 확장자입니다."), - FILE_OWNER_MISMATCH_EXCEPTION(403, "파일 소유자만 접근 가능합니다"); + FILE_OWNER_MISMATCH_EXCEPTION(403, "파일 소유자만 접근 가능합니다."), + S3_UPLOAD_EXCEPTION(500, "파일 업로드에 실패하였습니다."); private final int errorCode; private final String message; diff --git a/src/main/java/leets/bookmark/domain/file/application/exception/S3UploadException.java b/src/main/java/leets/bookmark/domain/file/application/exception/S3UploadException.java new file mode 100644 index 00000000..996ebd31 --- /dev/null +++ b/src/main/java/leets/bookmark/domain/file/application/exception/S3UploadException.java @@ -0,0 +1,9 @@ +package leets.bookmark.domain.file.application.exception; + +import leets.bookmark.global.common.exception.BusinessException; + +public class S3UploadException extends BusinessException { + public S3UploadException(){ + super(FileErrorCode.S3_UPLOAD_EXCEPTION); + } +} diff --git a/src/main/java/leets/bookmark/domain/file/application/mapper/FileMapper.java b/src/main/java/leets/bookmark/domain/file/application/mapper/FileMapper.java index 9b76da3b..21a66b15 100644 --- a/src/main/java/leets/bookmark/domain/file/application/mapper/FileMapper.java +++ b/src/main/java/leets/bookmark/domain/file/application/mapper/FileMapper.java @@ -21,6 +21,17 @@ public File toFile(User user, Bookmark bookmark, FileSaveRequest fileSaveRequest .build(); } + public File toThumbnailFile(User user, Bookmark bookmark, String fileName, + String s3UrlResponse, FileType type) { + return File.builder() + .user(user) + .bookmark(bookmark) + .fileName(fileName) + .fileUrl(s3UrlResponse) + .fileType(type) + .build(); + } + public FileResponse toFileResponse(File file){ return FileResponse.builder() .fileUrl(file.getFileUrl()) diff --git a/src/main/java/leets/bookmark/domain/file/application/usecase/FileUseCase.java b/src/main/java/leets/bookmark/domain/file/application/usecase/FileUseCase.java index fd059bed..ec0b1507 100644 --- a/src/main/java/leets/bookmark/domain/file/application/usecase/FileUseCase.java +++ b/src/main/java/leets/bookmark/domain/file/application/usecase/FileUseCase.java @@ -26,6 +26,7 @@ public class FileUseCase { private final PreSignedService preSignedService; + private final S3UploadService s3UploadService; private final FileSaveService fileSaveService; private final FileGetService fileGetService; private final FileUpdateService fileUpdateService; @@ -51,6 +52,17 @@ public void saveFile(User user, Bookmark bookmark, @Valid FileSaveRequest fileSa fileSaveService.save(file); } + @Transactional + public void saveThumbnailFile(User user, Bookmark bookmark, String thumbnailUrl) { + String fileName = extractFileNameWithExtension(thumbnailUrl); + FileType type = getValidatedFileType(fileName); + + String s3UrlResponse = s3UploadService.upload(thumbnailUrl); + + File file = fileMapper.toThumbnailFile(user, bookmark, fileName, s3UrlResponse, type); + fileSaveService.save(file); + } + @Transactional(readOnly = true) public FileResponse getFile(User user, Bookmark bookmark) { File file = fileGetService.findByBookmarkId(bookmark.getId()); @@ -67,6 +79,18 @@ public void updateFile(User user, Bookmark bookmark, @Valid FileUpdateRequest fi fileUpdateService.update(file, fileUpdateRequest, getValidatedFileType(fileUpdateRequest.fileName())); } + public void updateThumbnailImage(User user, Bookmark bookmark, String thumbnailUrl) { + File file = fileGetService.findByBookmarkId(bookmark.getId()); + validateFileOwner(user, file); + + String fileName = extractFileNameWithExtension(thumbnailUrl); + FileType type = getValidatedFileType(fileName); + + String s3UrlResponse = s3UploadService.upload(thumbnailUrl); + + fileUpdateService.updateThumbnailImage(file, fileName, s3UrlResponse, type); + } + @Transactional public void deleteFile(User user, Bookmark bookmark) { File file = fileGetService.findByBookmarkId(bookmark.getId()); diff --git a/src/main/java/leets/bookmark/domain/file/domain/entity/enums/FileType.java b/src/main/java/leets/bookmark/domain/file/domain/entity/enums/FileType.java index 27035363..a7878514 100644 --- a/src/main/java/leets/bookmark/domain/file/domain/entity/enums/FileType.java +++ b/src/main/java/leets/bookmark/domain/file/domain/entity/enums/FileType.java @@ -19,7 +19,8 @@ public enum FileType { PPTX(".pptx"), PPT(".ppt"), TXT(".txt"), - WEBP(".webp"); + WEBP(".webp"), + HEIC(".heic"); private final String extension; diff --git a/src/main/java/leets/bookmark/domain/file/domain/service/FileUpdateService.java b/src/main/java/leets/bookmark/domain/file/domain/service/FileUpdateService.java index 0e1f6f6e..3938a229 100644 --- a/src/main/java/leets/bookmark/domain/file/domain/service/FileUpdateService.java +++ b/src/main/java/leets/bookmark/domain/file/domain/service/FileUpdateService.java @@ -13,4 +13,8 @@ public class FileUpdateService { public void update(File file, FileUpdateRequest fileUpdateRequest, FileType fileType) { file.updateFile(fileUpdateRequest.fileName(), fileUpdateRequest.fileUrl(), fileType); } + + public void updateThumbnailImage(File file, String fileName, String s3Url, FileType type) { + file.updateFile(fileName, s3Url, type); + } } diff --git a/src/main/java/leets/bookmark/domain/file/domain/service/S3UploadService.java b/src/main/java/leets/bookmark/domain/file/domain/service/S3UploadService.java new file mode 100644 index 00000000..9806fad3 --- /dev/null +++ b/src/main/java/leets/bookmark/domain/file/domain/service/S3UploadService.java @@ -0,0 +1,61 @@ +package leets.bookmark.domain.file.domain.service; + +import leets.bookmark.domain.file.application.exception.S3UploadException; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.InputStream; +import java.net.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +@RequiredArgsConstructor +@Service +public class S3UploadService { + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + @Value("${cloud.aws.region.static}") + private String region; + + private final S3Client s3Client; + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + + public String upload(String fileUrl) { + try { + URL url = URI.create(fileUrl).toURL(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + try (InputStream inputStream = connection.getInputStream()) { + + String fileName = generateUrl(fileUrl); + + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucket) + .key(fileName) + .contentType(connection.getContentType()) + .build(); + + s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(inputStream, connection.getContentLengthLong())); + + return "https://" + bucket + ".s3." + region + ".amazonaws.com/" + fileName; + } + + } catch (Exception e){ + throw new S3UploadException(); + } + } + + private String generateUrl(String fileName) { + String extension = fileName.substring(fileName.lastIndexOf(".") + 1); + return LocalDateTime.now().format(FORMATTER) + UUID.randomUUID().toString() + "." + extension; + } +} diff --git a/src/main/java/leets/bookmark/domain/file/presentation/FileController.java b/src/main/java/leets/bookmark/domain/file/presentation/FileController.java index 3d87b759..3f7a1f20 100644 --- a/src/main/java/leets/bookmark/domain/file/presentation/FileController.java +++ b/src/main/java/leets/bookmark/domain/file/presentation/FileController.java @@ -20,4 +20,5 @@ public CommonResponse getPresignedUrl(@RequestParam String PresignedUrlResponse response = fileUseCase.getPreSignedUrl(fileName); return CommonResponse.createSuccess(FileResponseCode.PRE_SIGNED_URL_SUCCESS.getMessage(), response); } + } diff --git a/src/main/java/leets/bookmark/domain/file/presentation/FileResponseCode.java b/src/main/java/leets/bookmark/domain/file/presentation/FileResponseCode.java index 6b7b8724..854120ed 100644 --- a/src/main/java/leets/bookmark/domain/file/presentation/FileResponseCode.java +++ b/src/main/java/leets/bookmark/domain/file/presentation/FileResponseCode.java @@ -6,7 +6,8 @@ @Getter @RequiredArgsConstructor public enum FileResponseCode { - PRE_SIGNED_URL_SUCCESS("파일 업로드 URL이 생성되었습니다."); + PRE_SIGNED_URL_SUCCESS("파일 업로드 URL이 생성되었습니다."), + S3_UPLOAD_SUCCESS("S3 업로드에 성공했습니다."); private final String message; } diff --git a/src/main/java/leets/bookmark/global/config/AmazonS3Config.java b/src/main/java/leets/bookmark/global/config/AmazonS3Config.java index b3b05cb8..db0538db 100644 --- a/src/main/java/leets/bookmark/global/config/AmazonS3Config.java +++ b/src/main/java/leets/bookmark/global/config/AmazonS3Config.java @@ -8,6 +8,7 @@ import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.presigner.S3Presigner; @Configuration @@ -34,4 +35,15 @@ public S3Presigner s3Presigner(){ .credentialsProvider(credentialsProvider) .build(); } + + @Bean + public S3Client s3Client() { + AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create(accessKey, secretKey) + ); + return S3Client.builder() + .region(Region.of(region)) + .credentialsProvider(credentialsProvider) + .build(); + } }