diff --git a/src/main/java/site/katchup/katchupserver/api/card/controller/CardController.java b/src/main/java/site/katchup/katchupserver/api/card/controller/CardController.java index cd6dd80..91f3858 100644 --- a/src/main/java/site/katchup/katchupserver/api/card/controller/CardController.java +++ b/src/main/java/site/katchup/katchupserver/api/card/controller/CardController.java @@ -5,18 +5,16 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; import site.katchup.katchupserver.api.card.dto.request.CardCreateRequestDto; import site.katchup.katchupserver.api.card.dto.request.CardDeleteRequestDto; import site.katchup.katchupserver.api.card.dto.response.CardGetResponseDto; import site.katchup.katchupserver.api.card.service.CardService; import site.katchup.katchupserver.common.dto.ApiResponseDto; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/cards") @@ -33,9 +31,8 @@ public class CardController { @PostMapping @ResponseStatus(HttpStatus.CREATED) public ApiResponseDto createCard( - @RequestPart List fileList, - CardCreateRequestDto cardCreateRequestDto ) { - cardService.createCard(fileList, cardCreateRequestDto); + @Valid @RequestBody CardCreateRequestDto cardCreateRequestDto ) { + cardService.createCard(cardCreateRequestDto); return ApiResponseDto.success(); } diff --git a/src/main/java/site/katchup/katchupserver/api/card/dto/request/CardCreateRequestDto.java b/src/main/java/site/katchup/katchupserver/api/card/dto/request/CardCreateRequestDto.java index a7b9d6d..51cc0a4 100644 --- a/src/main/java/site/katchup/katchupserver/api/card/dto/request/CardCreateRequestDto.java +++ b/src/main/java/site/katchup/katchupserver/api/card/dto/request/CardCreateRequestDto.java @@ -1,22 +1,25 @@ package site.katchup.katchupserver.api.card.dto.request; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; +import site.katchup.katchupserver.api.screenshot.dto.request.ScreenshotCreateRequestDto; import java.util.List; @AllArgsConstructor(staticName = "of") @Getter public class CardCreateRequestDto { - @NotBlank(message = "CD-110") + @NotNull(message = "CD-110") private Long categoryId; - @NotBlank(message = "CD-111") + @NotNull(message = "CD-111") private Long taskId; - @NotBlank(message = "CD-112") + @NotNull(message = "CD-112") private Long subTaskId; - @NotBlank(message = "CD-113") + @NotNull(message = "CD-113") private List keywordIdList; + private List screenshotList; private String note; @NotBlank(message = "CD-114") private String content; diff --git a/src/main/java/site/katchup/katchupserver/api/card/service/CardService.java b/src/main/java/site/katchup/katchupserver/api/card/service/CardService.java index 6542d9a..1f7ae94 100644 --- a/src/main/java/site/katchup/katchupserver/api/card/service/CardService.java +++ b/src/main/java/site/katchup/katchupserver/api/card/service/CardService.java @@ -1,7 +1,6 @@ package site.katchup.katchupserver.api.card.service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; import site.katchup.katchupserver.api.card.dto.request.CardCreateRequestDto; import site.katchup.katchupserver.api.card.dto.request.CardDeleteRequestDto; import site.katchup.katchupserver.api.card.dto.response.CardGetResponseDto; @@ -15,6 +14,6 @@ public interface CardService { List getCardList(Long folderId); CardGetResponseDto getCard(Long cardId); void deleteCardList(CardDeleteRequestDto cardDeleteRequestDto); - void createCard(List fileList, CardCreateRequestDto cardCreateRequestDto); + void createCard(CardCreateRequestDto cardCreateRequestDto); } diff --git a/src/main/java/site/katchup/katchupserver/api/card/service/Impl/CardServiceImpl.java b/src/main/java/site/katchup/katchupserver/api/card/service/Impl/CardServiceImpl.java index 06f0476..980b751 100644 --- a/src/main/java/site/katchup/katchupserver/api/card/service/Impl/CardServiceImpl.java +++ b/src/main/java/site/katchup/katchupserver/api/card/service/Impl/CardServiceImpl.java @@ -1,19 +1,15 @@ package site.katchup.katchupserver.api.card.service.Impl; -import com.amazonaws.services.s3.model.ObjectMetadata; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; import site.katchup.katchupserver.api.card.domain.Card; -import site.katchup.katchupserver.api.card.domain.File; import site.katchup.katchupserver.api.card.dto.request.CardCreateRequestDto; import site.katchup.katchupserver.api.card.dto.request.CardDeleteRequestDto; import site.katchup.katchupserver.api.card.dto.response.CardGetResponseDto; import site.katchup.katchupserver.api.card.dto.response.CardListGetResponseDto; import site.katchup.katchupserver.api.card.dto.response.FileGetResponseDto; import site.katchup.katchupserver.api.card.repository.CardRepository; -import site.katchup.katchupserver.api.card.repository.FileRepository; import site.katchup.katchupserver.api.card.service.CardService; import site.katchup.katchupserver.api.category.domain.Category; import site.katchup.katchupserver.api.category.repository.CategoryRepository; @@ -21,6 +17,8 @@ import site.katchup.katchupserver.api.keyword.dto.response.KeywordGetResponseDto; import site.katchup.katchupserver.api.keyword.repository.CardKeywordRepository; import site.katchup.katchupserver.api.keyword.repository.KeywordRepository; +import site.katchup.katchupserver.api.screenshot.domain.Screenshot; +import site.katchup.katchupserver.api.screenshot.dto.request.ScreenshotCreateRequestDto; import site.katchup.katchupserver.api.screenshot.dto.response.ScreenshotGetResponseDto; import site.katchup.katchupserver.api.screenshot.repository.ScreenshotRepository; import site.katchup.katchupserver.api.subTask.domain.SubTask; @@ -29,19 +27,14 @@ import site.katchup.katchupserver.api.task.repository.TaskRepository; import site.katchup.katchupserver.api.trash.domain.Trash; import site.katchup.katchupserver.api.trash.repository.TrashRepository; -import site.katchup.katchupserver.common.exception.BadRequestException; -import site.katchup.katchupserver.common.response.ErrorCode; import site.katchup.katchupserver.common.util.S3Util; -import java.io.IOException; -import java.io.InputStream; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class CardServiceImpl implements CardService { private final SubTaskRepository subTaskRepository; @@ -50,18 +43,11 @@ public class CardServiceImpl implements CardService { private final CardRepository cardRepository; private final CategoryRepository categoryRepository; private final TaskRepository taskRepository; - private final FileRepository fileRepository; private final ScreenshotRepository screenshotRepository; private final KeywordRepository keywordRepository; private final S3Util s3Util; - private static final String FILE_FOLDER_NAME = "files/"; - private static final String PDF_TYPE = "application/pdf"; - private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; - private static final long MB = 1024 * 1024; - @Override - @Transactional(readOnly = true) public List getCardList(Long taskId) { return subTaskRepository.findAllByTaskId(taskId).stream() .flatMap(subTask -> subTask.getCards().stream()) @@ -92,9 +78,9 @@ public void deleteCardList(CardDeleteRequestDto cardDeleteRequestDto) { @Override @Transactional - public void createCard(List fileList, CardCreateRequestDto requestDto) { + public void createCard(CardCreateRequestDto requestDto) { - SubTask subTask = subTaskRepository.findByIdOrThrow(requestDto.getTaskId()); + SubTask subTask = subTaskRepository.findByIdOrThrow(requestDto.getSubTaskId()); Card card = Card.builder() .content(requestDto.getContent()) @@ -106,34 +92,26 @@ public void createCard(List fileList, CardCreateRequestDto reques Card savedCard = cardRepository.save(card); for (Long keywordId : requestDto.getKeywordIdList()) { - CardKeyword.builder() + CardKeyword cardKeyword = CardKeyword.builder() .card(savedCard) .keyword(keywordRepository.findByIdOrThrow(keywordId)) .build(); + cardKeywordRepository.save(cardKeyword); } - try { - for (MultipartFile file : fileList) { - String fileName = UUID.randomUUID().toString() + ".pdf"; - validatePdf(file); - validateFileSize(file); - String uploadPath = FILE_FOLDER_NAME + getFoldername() + fileName; - String fileUrl = s3Util.upload(getInputStream(file), uploadPath, getObjectMetadata(file)); - DecimalFormat decimalFormat = new DecimalFormat("#.##"); - fileRepository.save(File.builder() - .card(card) - .name(file.getOriginalFilename()) - .url(fileUrl) - .size( Double.parseDouble(decimalFormat.format((double) file.getSize() / MB))) - .build()); - } - } catch (IOException e) { - throw new BadRequestException(ErrorCode.FILE_UPLOAD_ERROR); + for (ScreenshotCreateRequestDto screenshotInfo : requestDto.getScreenshotList()) { + + Screenshot newScreenshot = Screenshot.builder() + .id(screenshotInfo.getScreenshotUUID()) + .url(screenshotInfo.getScreenshotUrl()) + .card(savedCard) + .build(); + + screenshotRepository.save(newScreenshot); } } @Override - @Transactional(readOnly = true) public CardGetResponseDto getCard(Long cardId) { Card card = cardRepository.findByIdOrThrow(cardId); Task task = taskRepository.findByIdOrThrow(card.getSubTask().getTask().getId()); @@ -172,19 +150,6 @@ private Long getPlacementOrder(SubTask subTask) { } } - private ObjectMetadata getObjectMetadata(MultipartFile file) { - ObjectMetadata objectMetadata = new ObjectMetadata(); - objectMetadata.setContentLength(file.getSize()); - objectMetadata.setContentType(file.getContentType()); - return objectMetadata; - } - - private void validateFileSize(MultipartFile file) { - if (file.getSize() >= MAX_FILE_SIZE) { - throw new BadRequestException(ErrorCode.FILE_SIZE_EXCEED); - } - } - private List getKeywordDtoList(Long cardId) { return cardKeywordRepository.findAllByCardId(cardId).stream() .map(cardKeyword -> KeywordGetResponseDto.of(cardKeyword.getKeyword().getId(), @@ -192,31 +157,15 @@ private List getKeywordDtoList(Long cardId) { .collect(Collectors.toList()); } private List getScreenshotDtoList(Long cardId) { - return screenshotRepository.findAllByCardId(cardId).stream() + return cardRepository.findByIdOrThrow(cardId).getScreenshots().stream() .map(screenshot -> ScreenshotGetResponseDto .of(screenshot.getId(), screenshot.getStickerOrder(), screenshot.getUrl()) ).collect(Collectors.toList()); } private List getFileDtoList(Long cardId) { - return fileRepository.findAllByCardId(cardId).stream() + return cardRepository.findByIdOrThrow(cardId).getFiles().stream() .map(file -> FileGetResponseDto.of(file.getId(), file.getName(), file.getUrl(), file.getSize())) .collect(Collectors.toList()); } - - private void validatePdf(MultipartFile file) { - if (!file.getContentType().equals(PDF_TYPE)) { - throw new BadRequestException(ErrorCode.NOT_PDF_FILE_TYPE); - } - } - - private InputStream getInputStream(MultipartFile file) throws IOException { - return file.getInputStream(); - } - - private String getFoldername() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); - Date date = new Date(); - return sdf.format(date).replace("-", "/") + "/"; - } } diff --git a/src/main/java/site/katchup/katchupserver/api/member/domain/Member.java b/src/main/java/site/katchup/katchupserver/api/member/domain/Member.java index 27da1e5..427097a 100644 --- a/src/main/java/site/katchup/katchupserver/api/member/domain/Member.java +++ b/src/main/java/site/katchup/katchupserver/api/member/domain/Member.java @@ -5,6 +5,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; import site.katchup.katchupserver.common.domain.BaseEntity; + +import java.nio.ByteBuffer; + import static jakarta.persistence.GenerationType.*; import static lombok.AccessLevel.*; @@ -25,6 +28,9 @@ public class Member extends BaseEntity { @Column(name = "image_url") private String imageUrl; + @Column(name = "user_UUID", nullable = false) + private String userUUID; + @Column(name = "is_deleted", nullable = false) private boolean isDeleted; @@ -42,11 +48,19 @@ public Member(String nickname, String email, String imageUrl, boolean isDeleted, this.isDeleted = isDeleted; this.isNewUser = isNewUser; this.refreshToken = refreshToken; + updateUserUUID(); } public void updateMemberStatus(boolean isNewUser, String refreshToken) { this.isNewUser = isNewUser; this.refreshToken = refreshToken; } + + // 10자리의 katchup 유저 코드 생성 + private void updateUserUUID() { + String uuid = java.util.UUID.randomUUID().toString(); + int l = ByteBuffer.wrap(uuid.getBytes()).getInt(); + this.userUUID = Integer.toString(l,9); + } } diff --git a/src/main/java/site/katchup/katchupserver/api/member/service/Impl/MemberServiceImpl.java b/src/main/java/site/katchup/katchupserver/api/member/service/Impl/MemberServiceImpl.java index fdff4f5..3212aaf 100644 --- a/src/main/java/site/katchup/katchupserver/api/member/service/Impl/MemberServiceImpl.java +++ b/src/main/java/site/katchup/katchupserver/api/member/service/Impl/MemberServiceImpl.java @@ -10,11 +10,11 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class MemberServiceImpl implements MemberService { private final MemberRepository memberRepository; @Override - @Transactional(readOnly = true) public MemberProfileGetResponseDto getMemberProfile(Long memberId) { Member member = memberRepository.findByIdOrThrow(memberId); diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/controller/ScreenshotController.java b/src/main/java/site/katchup/katchupserver/api/screenshot/controller/ScreenshotController.java index 1e6afab..c9df011 100644 --- a/src/main/java/site/katchup/katchupserver/api/screenshot/controller/ScreenshotController.java +++ b/src/main/java/site/katchup/katchupserver/api/screenshot/controller/ScreenshotController.java @@ -8,10 +8,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import site.katchup.katchupserver.api.screenshot.dto.response.ScreenshotUploadResponseDto; +import site.katchup.katchupserver.api.screenshot.dto.request.ScreenshotGetPreSignedRequestDto; +import site.katchup.katchupserver.api.screenshot.dto.response.ScreenshotGetPreSignedResponseDto; import site.katchup.katchupserver.api.screenshot.service.ScreenshotService; import site.katchup.katchupserver.common.dto.ApiResponseDto; +import site.katchup.katchupserver.common.util.MemberUtil; import java.security.Principal; @@ -25,21 +26,19 @@ public class ScreenshotController { private final ScreenshotService screenshotService; - @Operation(summary = "스크린샷 업로드 API") + @Operation(summary = "스크린샷 Presigned-Url 요청 API") @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "스크린샷 업로드 성공"), - @ApiResponse(responseCode = "400", description = "스크린샷 업로드 실패", content = @Content), + @ApiResponse(responseCode = "201", description = "스크린샷 Presigned-Url 요청 성공"), + @ApiResponse(responseCode = "400", description = "스크린샷 Presigned-Url 요청 실패", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) } ) - @PostMapping("/cards/{cardId}/screenshot") - @ResponseStatus(HttpStatus.CREATED) - public ApiResponseDto uploadScreenshot( - Principal principal, - @PathVariable Long cardId, - @RequestPart MultipartFile file + @PostMapping("/screenshots/presigned") + @ResponseStatus(HttpStatus.OK) + public ApiResponseDto createPresigned(Principal principal, @RequestBody ScreenshotGetPreSignedRequestDto presignedRequestDto ) { - return success(screenshotService.uploadScreenshot(file, cardId)); + Long memberId = MemberUtil.getMemberId(principal); + return ApiResponseDto.success(screenshotService.getScreenshotPreSignedUrl(memberId, presignedRequestDto)); } @Operation(summary = "스크린샷 삭제 API") @@ -51,7 +50,7 @@ public ApiResponseDto uploadScreenshot( @DeleteMapping("/cards/{cardId}/screenshots/{screenshotId}") @ResponseStatus(HttpStatus.OK) public ApiResponseDto deleteScreenshot( - Principal principal, @PathVariable Long cardId, @PathVariable String screenshotId + @PathVariable Long cardId, @PathVariable String screenshotId ) { screenshotService.deleteScreenshot(cardId, screenshotId); return success(); diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/domain/Screenshot.java b/src/main/java/site/katchup/katchupserver/api/screenshot/domain/Screenshot.java index 1422388..66552f0 100644 --- a/src/main/java/site/katchup/katchupserver/api/screenshot/domain/Screenshot.java +++ b/src/main/java/site/katchup/katchupserver/api/screenshot/domain/Screenshot.java @@ -32,13 +32,8 @@ public class Screenshot extends BaseEntity { @Builder public Screenshot(UUID id, String url, Card card) { this.id = id; - this.url = url; - this.card = card; this.stickerOrder = 0; - } - - public void updateCard(Card card) { + this.url = url; this.card = card; } - } diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/dto/request/ScreenshotCreateRequestDto.java b/src/main/java/site/katchup/katchupserver/api/screenshot/dto/request/ScreenshotCreateRequestDto.java new file mode 100644 index 0000000..7332fdf --- /dev/null +++ b/src/main/java/site/katchup/katchupserver/api/screenshot/dto/request/ScreenshotCreateRequestDto.java @@ -0,0 +1,20 @@ +package site.katchup.katchupserver.api.screenshot.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.UUID; + +@Getter +@AllArgsConstructor +@Schema(description = "스크린샷 업로드 요청 DTO") +public class ScreenshotCreateRequestDto { + @Schema(description = "업로드하는 스크린샷 고유 UUID", example = "ddwd-wdwd-wdwd-wdwdwdwd") + @NotNull + private UUID screenshotUUID; + @Schema(description = "스크린샷 url", example = "https://abde.s3.ap-northeast-2.amazonaws.com/1.png") + @NotNull + private String screenshotUrl; +} diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/dto/request/ScreenshotGetPreSignedRequestDto.java b/src/main/java/site/katchup/katchupserver/api/screenshot/dto/request/ScreenshotGetPreSignedRequestDto.java new file mode 100644 index 0000000..39d4a98 --- /dev/null +++ b/src/main/java/site/katchup/katchupserver/api/screenshot/dto/request/ScreenshotGetPreSignedRequestDto.java @@ -0,0 +1,19 @@ +package site.katchup.katchupserver.api.screenshot.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import static lombok.AccessLevel.PRIVATE; + +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = PRIVATE) +@Schema(description = "스크린샷 Presigned-Url 요청 DTO") +public class ScreenshotGetPreSignedRequestDto { + @Schema(description = "업로드하는 스크린샷 이름", example = "capture.png") + @NotNull + private String screenshotName; +} diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/dto/response/ScreenshotGetPreSignedResponseDto.java b/src/main/java/site/katchup/katchupserver/api/screenshot/dto/response/ScreenshotGetPreSignedResponseDto.java new file mode 100644 index 0000000..e83f14d --- /dev/null +++ b/src/main/java/site/katchup/katchupserver/api/screenshot/dto/response/ScreenshotGetPreSignedResponseDto.java @@ -0,0 +1,21 @@ +package site.katchup.katchupserver.api.screenshot.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import static lombok.AccessLevel.PRIVATE; + +@Getter +@NoArgsConstructor(access = PRIVATE) +@AllArgsConstructor +@Schema(description = "스크린샷 PreSigned-Url 응답 DTO") +public class ScreenshotGetPreSignedResponseDto { + @Schema(description = "스크린샷 UUID", example = "screenshot-uuid") + private String screenshotUUID; + @Schema(description = "스크린샷 PreSigned-Url", example ="preSigned-url") + private String screenshotPreSignedUrl; + + public static ScreenshotGetPreSignedResponseDto of (String screenshotUUID, String screenshotPreSignedUrl) { + return new ScreenshotGetPreSignedResponseDto(screenshotUUID, screenshotPreSignedUrl); + } +} diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/dto/response/ScreenshotUploadResponseDto.java b/src/main/java/site/katchup/katchupserver/api/screenshot/dto/response/ScreenshotUploadResponseDto.java deleted file mode 100644 index 6c5670c..0000000 --- a/src/main/java/site/katchup/katchupserver/api/screenshot/dto/response/ScreenshotUploadResponseDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package site.katchup.katchupserver.api.screenshot.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import lombok.Data; - -@Data -@Schema(description = "스크린샷 업로드 응답 DTO") -public class ScreenshotUploadResponseDto { - @Schema(description = "스크린샷 고유 id", example = "1") - private String id; - @Schema(description = "스크린샷 url", example ="https://~") - private String screenshotUrl; - - @Builder - public ScreenshotUploadResponseDto(String id, String screenshotUrl) { - this.id = id; - this.screenshotUrl = screenshotUrl; - } -} diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/repository/ScreenshotRepository.java b/src/main/java/site/katchup/katchupserver/api/screenshot/repository/ScreenshotRepository.java index c05dbd0..ce95050 100644 --- a/src/main/java/site/katchup/katchupserver/api/screenshot/repository/ScreenshotRepository.java +++ b/src/main/java/site/katchup/katchupserver/api/screenshot/repository/ScreenshotRepository.java @@ -2,6 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import site.katchup.katchupserver.api.screenshot.domain.Screenshot; +import site.katchup.katchupserver.common.exception.NotFoundException; +import site.katchup.katchupserver.common.response.ErrorCode; import java.util.List; import java.util.UUID; @@ -9,4 +11,9 @@ public interface ScreenshotRepository extends JpaRepository { List findAllByCardId(Long cardId); void deleteById(UUID uuid); + + default Screenshot findByIdOrThrow(UUID uuid) { + return findById(uuid).orElseThrow( + () -> new NotFoundException(ErrorCode.NOT_FOUND_SCREENSHOT)); + } } diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/service/ScreenshotService.java b/src/main/java/site/katchup/katchupserver/api/screenshot/service/ScreenshotService.java index dd5fb3c..4d47e5a 100644 --- a/src/main/java/site/katchup/katchupserver/api/screenshot/service/ScreenshotService.java +++ b/src/main/java/site/katchup/katchupserver/api/screenshot/service/ScreenshotService.java @@ -1,11 +1,11 @@ package site.katchup.katchupserver.api.screenshot.service; -import org.springframework.web.multipart.MultipartFile; -import site.katchup.katchupserver.api.screenshot.dto.response.ScreenshotUploadResponseDto; +import site.katchup.katchupserver.api.screenshot.dto.request.ScreenshotGetPreSignedRequestDto; +import site.katchup.katchupserver.api.screenshot.dto.response.ScreenshotGetPreSignedResponseDto; public interface ScreenshotService { - ScreenshotUploadResponseDto uploadScreenshot(MultipartFile file, Long cardId); + ScreenshotGetPreSignedResponseDto getScreenshotPreSignedUrl(Long memberId, ScreenshotGetPreSignedRequestDto requestDto); void deleteScreenshot(Long cardId, String screenshotId); } diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/service/ScreenshotValidator.java b/src/main/java/site/katchup/katchupserver/api/screenshot/service/ScreenshotValidator.java deleted file mode 100644 index 2b0d3a6..0000000 --- a/src/main/java/site/katchup/katchupserver/api/screenshot/service/ScreenshotValidator.java +++ /dev/null @@ -1,30 +0,0 @@ -package site.katchup.katchupserver.api.screenshot.service; - -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -@Component -public class ScreenshotValidator { - private static final Set VALID_CONTENT_TYPES = new HashSet<>(Arrays.asList("image/png", "image/jpg", "image/jpeg", "image/gif","image/tiff", "image/tif")); - - // 5MB - private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; - - public void validate(MultipartFile file) throws IllegalArgumentException{ - if (file.isEmpty()) { - throw new IllegalArgumentException("빈 파일입니다."); - } - - if (!VALID_CONTENT_TYPES.contains(file.getContentType())) { - throw new IllegalArgumentException("지원하지 않는 파일 형식입니다."); - } - - if (file.getSize() > MAX_FILE_SIZE) { - throw new IllegalArgumentException("5MB 미만의 파일을 업로드해야합니다."); - } - } -} diff --git a/src/main/java/site/katchup/katchupserver/api/screenshot/service/impl/ScreenshotServiceImpl.java b/src/main/java/site/katchup/katchupserver/api/screenshot/service/impl/ScreenshotServiceImpl.java index 451c782..cd0ac12 100644 --- a/src/main/java/site/katchup/katchupserver/api/screenshot/service/impl/ScreenshotServiceImpl.java +++ b/src/main/java/site/katchup/katchupserver/api/screenshot/service/impl/ScreenshotServiceImpl.java @@ -1,26 +1,18 @@ package site.katchup.katchupserver.api.screenshot.service.impl; -import com.amazonaws.services.s3.model.ObjectMetadata; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; -import site.katchup.katchupserver.api.card.domain.Card; -import site.katchup.katchupserver.api.card.repository.CardRepository; +import site.katchup.katchupserver.api.member.domain.Member; +import site.katchup.katchupserver.api.member.repository.MemberRepository; import site.katchup.katchupserver.api.screenshot.domain.Screenshot; -import site.katchup.katchupserver.api.screenshot.dto.response.ScreenshotUploadResponseDto; +import site.katchup.katchupserver.api.screenshot.dto.request.ScreenshotGetPreSignedRequestDto; +import site.katchup.katchupserver.api.screenshot.dto.response.ScreenshotGetPreSignedResponseDto; import site.katchup.katchupserver.api.screenshot.repository.ScreenshotRepository; import site.katchup.katchupserver.api.screenshot.service.ScreenshotService; -import site.katchup.katchupserver.api.screenshot.service.ScreenshotValidator; -import site.katchup.katchupserver.common.exception.InternalServerException; -import site.katchup.katchupserver.common.response.ErrorCode; import site.katchup.katchupserver.common.util.S3Util; -import java.io.IOException; -import java.io.InputStream; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; +import java.util.HashMap; import java.util.UUID; @Service @@ -31,40 +23,17 @@ public class ScreenshotServiceImpl implements ScreenshotService { private static final String SCREENSHOT_FOLDER_NAME = "screenshots"; private final S3Util s3Util; - - private final ScreenshotValidator screenshotValidator; - private final ScreenshotRepository screenshotRepository; - - private final CardRepository cardRepository; + private final MemberRepository memberRepository; @Override @Transactional - public ScreenshotUploadResponseDto uploadScreenshot(MultipartFile file, Long cardId) { - screenshotValidator.validate(file); - final String imageId = getUUIDFileName(); - String uploadFilePath = SCREENSHOT_FOLDER_NAME + "/" + getFoldername(); - String uploadFileName = uploadFilePath + "/" + imageId + extractExtension(file); - - try { - String uploadImageUrl = s3Util.upload(getInputStream(file), uploadFileName, getObjectMetadata(file)); - Card card = cardRepository.findByIdOrThrow(cardId); - Screenshot screenshot = Screenshot.builder() - .id(UUID.fromString(imageId)) - .url(uploadImageUrl) - .card(card) - .build(); - - screenshotRepository.save(screenshot); - - return ScreenshotUploadResponseDto.builder() - .id(screenshot.getId().toString()) - .screenshotUrl(screenshot.getUrl()) - .build(); - - } catch (Exception e) { - throw new InternalServerException(ErrorCode.IMAGE_UPLOAD_EXCEPTION); - } + public ScreenshotGetPreSignedResponseDto getScreenshotPreSignedUrl(Long memberId, ScreenshotGetPreSignedRequestDto requestDto) { + Member member = memberRepository.findByIdOrThrow(memberId); + String userUUID = member.getUserUUID(); + String screenshotUploadPrefix = s3Util.makeUploadPrefix(userUUID, SCREENSHOT_FOLDER_NAME); + HashMap preSignedUrlInfo = s3Util.generatePreSignedUrl(screenshotUploadPrefix, requestDto.getScreenshotName()); + return ScreenshotGetPreSignedResponseDto.of(preSignedUrlInfo.get(s3Util.KEY_FILENAME), preSignedUrlInfo.get(s3Util.KEY_PRESIGNED_URL)); } @Override @@ -73,33 +42,4 @@ public void deleteScreenshot(Long cardId, String screenshotId) { screenshotRepository.deleteById(UUID.fromString(screenshotId)); } - - private String getUUIDFileName() { - return UUID.randomUUID().toString(); - } - - private String getFoldername() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); - Date date = new Date(); - return sdf.format(date).replace("-", "/"); - } - - private ObjectMetadata getObjectMetadata(MultipartFile file) { - ObjectMetadata objectMetadata = new ObjectMetadata(); - objectMetadata.setContentLength(file.getSize()); - objectMetadata.setContentType(file.getContentType()); - return objectMetadata; - } - - private InputStream getInputStream(MultipartFile file) throws IOException { - return file.getInputStream(); - } - - private String extractExtension(MultipartFile file) { - try { - return file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); - } catch (StringIndexOutOfBoundsException e) { - throw new InternalServerException(ErrorCode.IMAGE_UPLOAD_EXCEPTION); - } - } } diff --git a/src/main/java/site/katchup/katchupserver/common/response/ErrorCode.java b/src/main/java/site/katchup/katchupserver/common/response/ErrorCode.java index 68c8200..ef44299 100644 --- a/src/main/java/site/katchup/katchupserver/common/response/ErrorCode.java +++ b/src/main/java/site/katchup/katchupserver/common/response/ErrorCode.java @@ -36,6 +36,7 @@ public enum ErrorCode { NOT_FOUND_CARD("CD-304"), DELETED_CARD("CD-305"), NOT_FOUND_KEYWORD("KW-306"), + NOT_FOUND_SCREENSHOT("SS-307"), /** diff --git a/src/main/java/site/katchup/katchupserver/common/util/S3Util.java b/src/main/java/site/katchup/katchupserver/common/util/S3Util.java index 1a8cb38..115bbe5 100644 --- a/src/main/java/site/katchup/katchupserver/common/util/S3Util.java +++ b/src/main/java/site/katchup/katchupserver/common/util/S3Util.java @@ -1,37 +1,73 @@ package site.katchup.katchupserver.common.util; -import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.HttpMethod; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.Headers; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import site.katchup.katchupserver.config.AwsS3Config; +import org.springframework.stereotype.Component; -import java.io.InputStream; -import java.net.URLDecoder; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.UUID; -@Configuration +@Component @RequiredArgsConstructor public class S3Util { - private final Environment env; + public static final String KEY_FILENAME = "fileName"; + public static final String KEY_PRESIGNED_URL = "preSignedUrl"; - private final AwsS3Config awsS3Config; + private final AmazonS3 amazonS3; @Value("${cloud.aws.s3.bucket}") private String bucket; - // 파일 업로드 - public String upload(InputStream file, String fileName, ObjectMetadata objectMetadata) { - awsS3Config.amazonS3Client().putObject(bucket, fileName, file, objectMetadata); - return awsS3Config.amazonS3Client().getUrl(bucket, fileName).toString(); + + public HashMap generatePreSignedUrl(String prefix, String fileName) { + HashMap result = new HashMap<>(); + String uuidFileName = getUUIDFile(); + result.put(KEY_FILENAME, uuidFileName); + String filePath = prefix + "/" + uuidFileName + fileName; + GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(bucket, filePath); + result.put(KEY_PRESIGNED_URL, amazonS3.generatePresignedUrl(generatePresignedUrlRequest).toString()); + return result; + } + + private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(String bucket, String fileName) { + GeneratePresignedUrlRequest generatePresignedUrlRequest = + new GeneratePresignedUrlRequest(bucket, fileName) + .withMethod(HttpMethod.PUT) + .withExpiration(getPreSignedUrlExpiration()); + + generatePresignedUrlRequest.addRequestParameter( + Headers.S3_CANNED_ACL, + CannedAccessControlList.PublicRead.toString()); + + return generatePresignedUrlRequest; + } + + private Date getPreSignedUrlExpiration() { + Date expiration = new Date(); + long expTimeMillis = expiration.getTime(); + expTimeMillis += 1000 * 60 * 2; + expiration.setTime(expTimeMillis); + return expiration; + } + + public String makeUploadPrefix(String userUUID, String folder) { + return String.join("/", userUUID, folder, getDateFolder()); } - // 파일 삭제 - public void delete(String fileName) { - String key = URLDecoder.decode(fileName.split("/")[3]); - awsS3Config.amazonS3Client().deleteObject(bucket, key); + private String getUUIDFile() { + return UUID.randomUUID().toString(); } - public String getImageUrl(String filePath) { - return awsS3Config.amazonS3Client().getUrl(bucket, filePath).toString(); + private String getDateFolder() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + Date date = new Date(); + return sdf.format(date).replace("-", "/"); } -} \ No newline at end of file +} diff --git a/src/main/java/site/katchup/katchupserver/config/AwsS3Config.java b/src/main/java/site/katchup/katchupserver/config/AwsS3Config.java index 64b8c6c..e63588d 100644 --- a/src/main/java/site/katchup/katchupserver/config/AwsS3Config.java +++ b/src/main/java/site/katchup/katchupserver/config/AwsS3Config.java @@ -20,6 +20,7 @@ public class AwsS3Config { @Value("${cloud.aws.credentials.secretKey}") private String secretKey; + @Bean public AmazonS3Client amazonS3Client() { BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey); @@ -29,4 +30,5 @@ public AmazonS3Client amazonS3Client() { .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials)) .build(); } + } diff --git a/src/test/java/site/katchup/katchupserver/api/screenshot/domain/ScreenshotTest.java b/src/test/java/site/katchup/katchupserver/api/screenshot/domain/ScreenshotTest.java index c29f39e..aae37c1 100644 --- a/src/test/java/site/katchup/katchupserver/api/screenshot/domain/ScreenshotTest.java +++ b/src/test/java/site/katchup/katchupserver/api/screenshot/domain/ScreenshotTest.java @@ -32,8 +32,6 @@ public void successSaveScreenshot() { Screenshot screenshot = Screenshot.builder() .id(uuid) - .url("https://example.com/katchup_screenshot.png") - .card(card) .build(); // When @@ -43,7 +41,5 @@ public void successSaveScreenshot() { Assertions.assertNotNull(savedScreenshot.getId()); Assertions.assertEquals(savedScreenshot.getId(), uuid); Assertions.assertEquals(savedScreenshot.getStickerOrder(), 0); - Assertions.assertEquals(savedScreenshot.getUrl(), screenshot.getUrl()); - Assertions.assertEquals(savedScreenshot.getCard().getId(), screenshot.getCard().getId()); } } \ No newline at end of file