Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

πŸ”— :: (#495) Presigned URL 생성 api #496

Merged
merged 12 commits into from
Dec 14, 2023
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;
}
}
Loading