Skip to content

Commit

Permalink
Merge pull request #104 from Katchup-dev/refactor/#96-presigned-url
Browse files Browse the repository at this point in the history
[REFACTOR] 스크린샷 PreSigned-Url 방식 도입
  • Loading branch information
yeseul106 authored Aug 21, 2023
2 parents ba6a669 + f24d881 commit 02c440c
Show file tree
Hide file tree
Showing 20 changed files with 198 additions and 250 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -33,9 +31,8 @@ public class CardController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ApiResponseDto createCard(
@RequestPart List<MultipartFile> fileList,
CardCreateRequestDto cardCreateRequestDto ) {
cardService.createCard(fileList, cardCreateRequestDto);
@Valid @RequestBody CardCreateRequestDto cardCreateRequestDto ) {
cardService.createCard(cardCreateRequestDto);
return ApiResponseDto.success();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Long> keywordIdList;
private List<ScreenshotCreateRequestDto> screenshotList;
private String note;
@NotBlank(message = "CD-114")
private String content;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,6 +14,6 @@ public interface CardService {
List<CardListGetResponseDto> getCardList(Long folderId);
CardGetResponseDto getCard(Long cardId);
void deleteCardList(CardDeleteRequestDto cardDeleteRequestDto);
void createCard(List<MultipartFile> fileList, CardCreateRequestDto cardCreateRequestDto);
void createCard(CardCreateRequestDto cardCreateRequestDto);

}
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
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;
import site.katchup.katchupserver.api.keyword.domain.CardKeyword;
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;
Expand All @@ -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;
Expand All @@ -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<CardListGetResponseDto> getCardList(Long taskId) {
return subTaskRepository.findAllByTaskId(taskId).stream()
.flatMap(subTask -> subTask.getCards().stream())
Expand Down Expand Up @@ -92,9 +78,9 @@ public void deleteCardList(CardDeleteRequestDto cardDeleteRequestDto) {

@Override
@Transactional
public void createCard(List<MultipartFile> 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())
Expand All @@ -106,34 +92,26 @@ public void createCard(List<MultipartFile> 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());
Expand Down Expand Up @@ -172,51 +150,22 @@ 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<KeywordGetResponseDto> getKeywordDtoList(Long cardId) {
return cardKeywordRepository.findAllByCardId(cardId).stream()
.map(cardKeyword -> KeywordGetResponseDto.of(cardKeyword.getKeyword().getId(),
cardKeyword.getKeyword().getName(), cardKeyword.getKeyword().getColor()))
.collect(Collectors.toList());
}
private List<ScreenshotGetResponseDto> 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<FileGetResponseDto> 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("-", "/") + "/";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand All @@ -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;

Expand All @@ -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);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<ScreenshotUploadResponseDto> uploadScreenshot(
Principal principal,
@PathVariable Long cardId,
@RequestPart MultipartFile file
@PostMapping("/screenshots/presigned")
@ResponseStatus(HttpStatus.OK)
public ApiResponseDto<ScreenshotGetPreSignedResponseDto> 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")
Expand All @@ -51,7 +50,7 @@ public ApiResponseDto<ScreenshotUploadResponseDto> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Loading

0 comments on commit 02c440c

Please sign in to comment.