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
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 CreatePreSignedUrlRequest {

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 CreatePreSignedUrlResponse {
private final List<PreSignedUrlResponse> urls;

@Getter
@AllArgsConstructor
public static class PreSignedUrlResponse {
private String filePath;
private String preSignedUrl;
geunoo marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@
import java.io.File;

public interface FilePort {
void uploadFile(File file, String fileName, FileType fileType);
void uploadFile(File file, String fileName);

String getPreSignedUrl(String fullFileName);
geunoo marked this conversation as resolved.
Show resolved Hide resolved

void validateExtension(String fileName, FileType fileType);

String getFullFileName(FileType fileType, String fileName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package team.retum.jobis.domain.file.usecase;

import lombok.RequiredArgsConstructor;
import team.retum.jobis.common.annotation.UseCase;
import team.retum.jobis.domain.file.dto.CreatePreSignedUrlRequest;
import team.retum.jobis.domain.file.dto.response.CreatePreSignedUrlResponse;
import team.retum.jobis.domain.file.dto.response.CreatePreSignedUrlResponse.PreSignedUrlResponse;
import team.retum.jobis.domain.file.spi.FilePort;

@RequiredArgsConstructor
@UseCase
public class CreatePreSignedUrlUseCase {
geunoo marked this conversation as resolved.
Show resolved Hide resolved

private final FilePort filePort;

public CreatePreSignedUrlResponse execute(CreatePreSignedUrlRequest request) {
return new CreatePreSignedUrlResponse(
request.getFiles().stream()
.map(
file -> {
String fileName = filePort.getFullFileName(file.getType(), file.getFileName());
geunoo marked this conversation as resolved.
Show resolved Hide resolved
String url = filePort.getPreSignedUrl(fileName);
return new PreSignedUrlResponse(
fileName,
url
);
}
).toList()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,12 @@
import lombok.RequiredArgsConstructor;
import team.retum.jobis.common.annotation.UseCase;
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 +20,9 @@ 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 = filePort.getFullFileName(fileType, file.getName());
filePort.validateExtension(fileName, fileType);
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.CreatePreSignedUrlResponse;
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.CreatePreSignedUrlUseCase;
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 CreatePreSignedUrlUseCase createPreSignedUrlUseCase;

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

@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/pre_signed")
geunoo marked this conversation as resolved.
Show resolved Hide resolved
public CreatePreSignedUrlResponse createPreSignedUrl(
@RequestBody @Valid CreatePreSignedUrlWebRequest request
) {
return createPreSignedUrlUseCase.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.CreatePreSignedUrlRequest;
import team.retum.jobis.domain.file.dto.CreatePreSignedUrlRequest.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 CreatePreSignedUrlRequest toDomainRequest() {
return new CreatePreSignedUrlRequest(
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,31 @@
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.exception.InvalidExtensionException;
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;
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;

@Component
@RequiredArgsConstructor
Expand All @@ -25,7 +36,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 +56,49 @@ public void uploadFile(File file, String fileName, FileType fileType) {
throw FileUploadFailedException.EXCEPTION;
}
}

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

@Override
public 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;
}
}
geunoo marked this conversation as resolved.
Show resolved Hide resolved

@Override
public String getFullFileName(FileType fileType, String fileName) {
return fileType.name() + "/" + UUID.randomUUID() + "-" + fileName;
}

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