Skip to content

Commit

Permalink
πŸ”— :: (#495) Presigned URL 생성 api
Browse files Browse the repository at this point in the history
πŸ”— :: (#495) Presigned URL 생성 api
  • Loading branch information
geunoo authored Dec 14, 2023
2 parents 2bdf26c + c916618 commit df008a6
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package team.retum.jobis.common.util;

import team.retum.jobis.domain.file.exception.InvalidExtensionException;
import team.retum.jobis.domain.file.model.FileType;

import java.util.UUID;

import static team.retum.jobis.domain.file.model.FileType.EXTENSION_FILE;
import static team.retum.jobis.domain.file.model.FileType.LOGO_IMAGE;

public class FileUtil {

public static String generateFullFileName(FileType fileType, String fileName) {
String extension = fileName.substring(fileName.lastIndexOf("."));

boolean isValid = switch (fileType) {
case LOGO_IMAGE -> LOGO_IMAGE.validExtensions.contains(extension);
case EXTENSION_FILE -> EXTENSION_FILE.validExtensions.contains(extension);
};

if (!isValid) {
throw InvalidExtensionException.EXCEPTION;
}

return fileType.name() + "/" + UUID.randomUUID() + "-" + fileName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import lombok.RequiredArgsConstructor;
import team.retum.jobis.common.annotation.ReadOnlyUseCase;
import team.retum.jobis.domain.application.dto.response.QueryEmploymentCountResponse;
import team.retum.jobis.domain.application.model.ApplicationStatus;
import team.retum.jobis.domain.application.spi.QueryApplicationPort;
import team.retum.jobis.domain.student.spi.QueryStudentPort;

import java.time.Year;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package team.retum.jobis.domain.file.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import team.retum.jobis.domain.file.model.FileType;

import java.util.List;

@Getter
@AllArgsConstructor
public class CreateFileUploadUrlRequest {

private final List<FileRequest> files;

@Getter
@AllArgsConstructor
public static class FileRequest {
private final FileType type;
private final String fileName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package team.retum.jobis.domain.file.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.List;

@Getter
@AllArgsConstructor
public class CreateFileUploadUrlResponse {
private final List<UrlResponse> urls;

@Getter
@AllArgsConstructor
public static class UrlResponse {
private final String filePath;
private final String preSignedUrl;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package team.retum.jobis.domain.file.spi;

import team.retum.jobis.domain.file.model.FileType;

import java.io.File;

public interface FilePort {
void uploadFile(File file, String fileName, FileType fileType);
void uploadFile(File file, String fileName);
String generateFileUploadUrl(String fullFileName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package team.retum.jobis.domain.file.usecase;

import lombok.RequiredArgsConstructor;
import team.retum.jobis.common.annotation.Service;
import team.retum.jobis.common.util.FileUtil;
import team.retum.jobis.domain.file.dto.CreateFileUploadUrlRequest;
import team.retum.jobis.domain.file.dto.response.CreateFileUploadUrlResponse;
import team.retum.jobis.domain.file.dto.response.CreateFileUploadUrlResponse.UrlResponse;
import team.retum.jobis.domain.file.spi.FilePort;

@RequiredArgsConstructor
@Service
public class CreateFileUploadUrlUseCase {

private final FilePort filePort;

public CreateFileUploadUrlResponse execute(CreateFileUploadUrlRequest request) {
return new CreateFileUploadUrlResponse(
request.getFiles().stream()
.map(
file -> {
String fullFileName = FileUtil.generateFullFileName(file.getType(), file.getFileName());
String url = filePort.generateFileUploadUrl(fullFileName);
return new UrlResponse(
fullFileName,
url
);
}
).toList()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@

import lombok.RequiredArgsConstructor;
import team.retum.jobis.common.annotation.UseCase;
import team.retum.jobis.common.util.FileUtil;
import team.retum.jobis.domain.file.dto.response.FileUploadResponse;
import team.retum.jobis.domain.file.exception.InvalidExtensionException;
import team.retum.jobis.domain.file.model.FileType;
import team.retum.jobis.domain.file.spi.FilePort;

import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import static team.retum.jobis.domain.file.model.FileType.EXTENSION_FILE;
import static team.retum.jobis.domain.file.model.FileType.LOGO_IMAGE;

@RequiredArgsConstructor
@UseCase
Expand All @@ -25,9 +21,8 @@ public FileUploadResponse execute(List<File> files, FileType fileType) {
List<String> fileUrls = files.stream().filter(Objects::nonNull)
.map(
file -> {
String fileName = fileType + "/" + UUID.randomUUID() + "-" + file.getName();
validateExtension(fileName, fileType);
filePort.uploadFile(file, fileName, fileType);
String fileName = FileUtil.generateFullFileName(fileType, file.getName());
filePort.uploadFile(file, fileName);

return fileName.replace(" ", "+");
}
Expand All @@ -36,16 +31,5 @@ public FileUploadResponse execute(List<File> files, FileType fileType) {
return new FileUploadResponse(fileUrls);
}

private void validateExtension(String fileName, FileType fileType) {
String extension = fileName.substring(fileName.lastIndexOf("."));

boolean isValid = switch (fileType) {
case LOGO_IMAGE -> LOGO_IMAGE.validExtensions.contains(extension);
case EXTENSION_FILE -> EXTENSION_FILE.validExtensions.contains(extension);
};

if (!isValid) {
throw InvalidExtensionException.EXCEPTION;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package team.retum.jobis.domain.application.presentation;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -18,9 +17,9 @@
import team.retum.jobis.common.dto.response.TotalPageCountResponse;
import team.retum.jobis.domain.application.dto.response.CompanyQueryApplicationsResponse;
import team.retum.jobis.domain.application.dto.response.QueryEmploymentCountResponse;
import team.retum.jobis.domain.application.dto.response.QueryMyApplicationsResponse;
import team.retum.jobis.domain.application.dto.response.QueryPassedApplicationStudentsResponse;
import team.retum.jobis.domain.application.dto.response.QueryRejectionReasonResponse;
import team.retum.jobis.domain.application.dto.response.QueryMyApplicationsResponse;
import team.retum.jobis.domain.application.dto.response.TeacherQueryApplicationsResponse;
import team.retum.jobis.domain.application.model.ApplicationStatus;
import team.retum.jobis.domain.application.presentation.dto.request.ChangeApplicationsStatusWebRequest;
Expand All @@ -33,9 +32,9 @@
import team.retum.jobis.domain.application.usecase.CreateApplicationUseCase;
import team.retum.jobis.domain.application.usecase.DeleteApplicationUseCase;
import team.retum.jobis.domain.application.usecase.QueryEmploymentCountUseCase;
import team.retum.jobis.domain.application.usecase.QueryMyApplicationsUseCase;
import team.retum.jobis.domain.application.usecase.QueryPassedApplicationStudentsUseCase;
import team.retum.jobis.domain.application.usecase.QueryRejectionReasonUseCase;
import team.retum.jobis.domain.application.usecase.QueryMyApplicationsUseCase;
import team.retum.jobis.domain.application.usecase.ReapplyUseCase;
import team.retum.jobis.domain.application.usecase.RejectApplicationUseCase;
import team.retum.jobis.domain.application.usecase.TeacherQueryApplicationsUseCase;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package team.retum.jobis.domain.file.presentation;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import team.retum.jobis.domain.file.dto.response.CreateFileUploadUrlResponse;
import team.retum.jobis.domain.file.dto.response.FileUploadResponse;
import team.retum.jobis.domain.file.exception.FileNotFoundException;
import team.retum.jobis.domain.file.exception.FileUploadFailedException;
import team.retum.jobis.domain.file.model.FileType;
import team.retum.jobis.domain.file.presentation.dto.CreatePreSignedUrlWebRequest;
import team.retum.jobis.domain.file.usecase.CreateFileUploadUrlUseCase;
import team.retum.jobis.domain.file.usecase.FileUploadUseCase;

import java.io.File;
Expand All @@ -26,6 +31,7 @@
public class FileWebAdapter {

private final FileUploadUseCase fileUploadUseCase;
private final CreateFileUploadUrlUseCase createFileUploadUrlUseCase;

@ResponseStatus(HttpStatus.CREATED)
@PostMapping
Expand All @@ -52,4 +58,12 @@ public FileUploadResponse uploadFile(
fileType
);
}

@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/pre-signed")
public CreateFileUploadUrlResponse createPreSignedUrl(
@RequestBody @Valid CreatePreSignedUrlWebRequest request
) {
return createFileUploadUrlUseCase.execute(request.toDomainRequest());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package team.retum.jobis.domain.file.presentation.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import team.retum.jobis.domain.file.dto.CreateFileUploadUrlRequest;
import team.retum.jobis.domain.file.dto.CreateFileUploadUrlRequest.FileRequest;
import team.retum.jobis.domain.file.model.FileType;

import java.util.List;

@Getter
@NoArgsConstructor
public class CreatePreSignedUrlWebRequest {

private List<@NotNull FileWebRequest> files;

@Getter
@NoArgsConstructor
public static class FileWebRequest {

@NotNull
private FileType type;

@NotBlank
private String fileName;
}

public CreateFileUploadUrlRequest toDomainRequest() {
return new CreateFileUploadUrlRequest(
files.stream()
.map(file ->
new FileRequest(
file.type,
file.fileName
)
).toList()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

// files
.requestMatchers(HttpMethod.POST, "/files").permitAll()
.requestMatchers(HttpMethod.POST, "/files/pre_signed").permitAll()
.requestMatchers(HttpMethod.DELETE, "/files").permitAll()

// code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package team.retum.jobis.thirdparty.s3;

import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.internal.Mimetypes;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import team.retum.jobis.domain.file.exception.FileUploadFailedException;
import team.retum.jobis.domain.file.model.FileType;
import team.retum.jobis.domain.file.spi.FilePort;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Component
@RequiredArgsConstructor
Expand All @@ -25,7 +30,7 @@ public class S3Adapter implements FilePort {

@Override
@Async("asyncTaskExecutor")
public void uploadFile(File file, String fileName, FileType fileType) {
public void uploadFile(File file, String fileName) {
try {
InputStream inputStream = new FileInputStream(file);
ObjectMetadata objectMetadata = new ObjectMetadata();
Expand All @@ -45,4 +50,30 @@ public void uploadFile(File file, String fileName, FileType fileType) {
throw FileUploadFailedException.EXCEPTION;
}
}

@Override
public String generateFileUploadUrl(String fullFileName) {
return URLDecoder.decode(
amazonS3.generatePresignedUrl(getPreSignedUrlRequest(fullFileName)).toString(), StandardCharsets.UTF_8
);
}

private GeneratePresignedUrlRequest getPreSignedUrlRequest(String filename) {
GeneratePresignedUrlRequest request =
new GeneratePresignedUrlRequest(s3Properties.getBucket(), filename)
.withMethod(HttpMethod.PUT)
.withExpiration(getPreSignedUrlExpiration());
request.addRequestParameter(
Headers.S3_CANNED_ACL,
CannedAccessControlList.PublicRead.toString()
);

return request;
}

private Date getPreSignedUrlExpiration() {
Date expiration = new Date();
expiration.setTime(expiration.getTime() + 1000 * 60 * 2);
return expiration;
}
}

0 comments on commit df008a6

Please sign in to comment.