Skip to content

Commit d6209f7

Browse files
authored
Merge pull request #26 from aws-cloud-clubs/feature/issue-25
Feature/issue 25
2 parents 9caf897 + 6015076 commit d6209f7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+766
-429
lines changed

build.gradle

+3-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ dependencies {
4444
testImplementation 'org.springframework.boot:spring-boot-starter-test'
4545
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
4646

47-
implementation platform('com.amazonaws:aws-java-sdk-bom:1.12.529')
48-
implementation 'com.amazonaws:aws-java-sdk-s3'
47+
implementation platform('software.amazon.awssdk:bom:2.21.1')
48+
implementation 'software.amazon.awssdk:s3'
49+
implementation 'software.amazon.awssdk:apache-client'
4950

5051
implementation 'org.springframework.boot:spring-boot-starter-aop'
5152
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'

src/main/java/acc/hotsix/file_share/api/FileDeleteController.java

+6-38
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@
22

33
import acc.hotsix.file_share.application.FileDeleteService;
44
import acc.hotsix.file_share.application.FileService;
5-
import acc.hotsix.file_share.dto.DeleteFileReq;
6-
import acc.hotsix.file_share.global.error.FileNotFoundException;
5+
import acc.hotsix.file_share.dto.FilePasswordReq;
6+
import acc.hotsix.file_share.global.error.exception.InvalidPasswordException;
77
import jakarta.validation.Valid;
88
import lombok.RequiredArgsConstructor;
9-
import org.springframework.beans.TypeMismatchException;
10-
import org.springframework.http.HttpStatus;
119
import org.springframework.http.ResponseEntity;
1210
import org.springframework.validation.BindingResult;
13-
import org.springframework.validation.FieldError;
1411
import org.springframework.web.bind.annotation.*;
1512

1613
import java.util.HashMap;
@@ -22,50 +19,21 @@ public class FileDeleteController {
2219
private final FileService fileService;
2320
private final FileDeleteService fileDeleteService;
2421

25-
@PostMapping("/files/{id}/delete")
22+
@PostMapping("/files/delete/{id}")
2623
public ResponseEntity<Map<String, Object>> deleteFile(
2724
@PathVariable("id")Long fileId,
28-
@Valid @ModelAttribute DeleteFileReq req, BindingResult bindingResult
25+
@Valid @ModelAttribute FilePasswordReq req, BindingResult bindingResult
2926
) {
3027
Map<String, Object> resultMap = new HashMap<>();
3128

32-
if (bindingResult.hasErrors()) {
33-
for (FieldError error : bindingResult.getFieldErrors()) {
34-
resultMap.put(error.getField(), error.getDefaultMessage());
35-
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultMap); // TODO 에러 처리 추후 수정 예정
36-
}
37-
}
38-
3929
String password = req.getPassword();
40-
try {
41-
if (!fileService.validateFileAccess(fileId, password)) {
42-
resultMap.put("error", "Access denied: invalid password");
43-
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(resultMap);
44-
}
45-
} catch (FileNotFoundException e) {
46-
resultMap.put("error", e.getMessage());
47-
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultMap);
30+
if (!fileService.validateFileAccess(fileId, password)) {
31+
throw new InvalidPasswordException();
4832
}
4933

5034
fileDeleteService.deleteFile(fileId);
5135

5236
resultMap.put("message", "File deleted successfully");
5337
return ResponseEntity.ok(resultMap);
5438
}
55-
56-
// 파라미터 타입이 불일치
57-
@ExceptionHandler(TypeMismatchException.class)
58-
public ResponseEntity<Map<String, Object>> typeMismatchExceptionHandler (TypeMismatchException e) {
59-
Map<String, Object> resultMap = new HashMap<>();
60-
resultMap.put("error", "Invalid parameter data type");
61-
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultMap);
62-
}
63-
64-
@ExceptionHandler(Exception.class)
65-
public ResponseEntity<Map<String, Object>> exceptionHandler(Exception e) {
66-
Map<String, Object> resultMap = new HashMap<>();
67-
resultMap.put("error", "An unexpected error occurred: " + e.getMessage());
68-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
69-
}
70-
7139
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package acc.hotsix.file_share.api;
2+
3+
import acc.hotsix.file_share.application.FileDownloadService;
4+
import acc.hotsix.file_share.application.FileService;
5+
import acc.hotsix.file_share.dto.FileMetadataResponseDto;
6+
import acc.hotsix.file_share.dto.FilePasswordReq;
7+
import acc.hotsix.file_share.global.error.exception.InvalidPasswordException;
8+
import jakarta.validation.Valid;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.http.HttpHeaders;
12+
import org.springframework.http.HttpStatus;
13+
import org.springframework.http.ResponseEntity;
14+
import org.springframework.validation.BindingResult;
15+
import org.springframework.web.bind.annotation.*;
16+
17+
import java.net.URI;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
21+
@RestController
22+
@RequiredArgsConstructor
23+
@RequestMapping("/files")
24+
public class FileDetailController {
25+
26+
private final FileService fileService;
27+
28+
private final FileDownloadService fileDownloadService;
29+
30+
Map<String, Object> resultMap;
31+
32+
@Value("${cloud.aws.s3.bucket}")
33+
private String bucketName;
34+
35+
// 파일 상세 조회 (메타데이터)
36+
@PostMapping("/detail-meta/{file-id}")
37+
public ResponseEntity getFileMetadata(@PathVariable("file-id") Long fileId,
38+
@Valid @ModelAttribute FilePasswordReq req, BindingResult bindingResult) {
39+
// 비밀번호를 이용한 파일 접근 권한 확인
40+
String password = req.getPassword();
41+
if (!fileService.validateFileAccess(fileId, password)) {
42+
throw new InvalidPasswordException();
43+
}
44+
45+
FileMetadataResponseDto responseDto = fileService.getMetadataById(fileId);
46+
return ResponseEntity.ok().body(responseDto);
47+
}
48+
49+
// 파일 상세 조회(파일)
50+
@PostMapping("/detail-file/{file_id}")
51+
public ResponseEntity<Map<String, Object>> handleGenerateShareLink(@PathVariable(name="file_id") String fileId,
52+
@Valid @ModelAttribute FilePasswordReq req, BindingResult bindingResult) {
53+
resultMap = new HashMap<>();
54+
55+
// 비밀번호를 이용한 파일 접근 권한 확인
56+
String password = req.getPassword();
57+
if (!fileService.validateFileAccess(Long.parseLong(fileId), password)) {
58+
throw new InvalidPasswordException();
59+
}
60+
61+
// get presigned URL 발급
62+
String link = fileDownloadService.createPresignedGetUrl(bucketName, fileId);
63+
64+
// 헤더 설정
65+
HttpHeaders httpHeaders = new HttpHeaders();
66+
httpHeaders.setLocation(URI.create(link));
67+
68+
return ResponseEntity.status(HttpStatus.FOUND).headers(httpHeaders).build();
69+
}
70+
}

src/main/java/acc/hotsix/file_share/api/FileDownloadController.java

+21-41
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
import acc.hotsix.file_share.application.FileDownloadService;
44
import acc.hotsix.file_share.application.FileService;
55
import acc.hotsix.file_share.dto.FileDownloadDto;
6-
import acc.hotsix.file_share.dto.FileMetadataResponseDto;
7-
import acc.hotsix.file_share.global.error.FileNotFoundException;
8-
import jakarta.validation.constraints.NotEmpty;
6+
import acc.hotsix.file_share.global.error.exception.DownloadFileException;
7+
import acc.hotsix.file_share.global.error.exception.InvalidPasswordException;
8+
import jakarta.validation.Valid;
99
import lombok.RequiredArgsConstructor;
1010
import org.springframework.http.HttpHeaders;
1111
import org.springframework.http.HttpStatus;
1212
import org.springframework.http.MediaType;
1313
import org.springframework.http.ResponseEntity;
1414
import org.springframework.web.bind.annotation.*;
1515

16-
import java.io.IOException;
1716
import java.nio.charset.StandardCharsets;
18-
import java.util.HashMap;
17+
import java.nio.file.Files;
18+
import java.nio.file.Paths;
1919

2020
@RestController
2121
@RequiredArgsConstructor
@@ -25,48 +25,28 @@ public class FileDownloadController {
2525
private final FileService fileService;
2626
private final FileDownloadService fileDownloadService;
2727

28-
// 파일 상세 조회 (메타데이터)
29-
@GetMapping("/{file-id}")
30-
public ResponseEntity getFileMetadata(@PathVariable("file-id") Long fileId,
31-
@NotEmpty @RequestParam("password") String password) {
32-
ResponseEntity<HashMap> FORBIDDEN = isPasswordValid(fileId, password);
33-
if (FORBIDDEN != null) return FORBIDDEN;
34-
35-
FileMetadataResponseDto responseDto = fileService.getMetadataById(fileId);
36-
return ResponseEntity.ok().body(responseDto);
37-
}
38-
3928
// 파일 다운로드
40-
@GetMapping("/{file-id}/download")
29+
@PostMapping("/download/{file-id}")
4130
public ResponseEntity downloadFile(@PathVariable("file-id") Long fileId,
42-
@NotEmpty @RequestParam("password") String password) throws IOException {
43-
ResponseEntity<HashMap> FORBIDDEN = isPasswordValid(fileId, password);
44-
if (FORBIDDEN != null) return FORBIDDEN;
45-
46-
FileDownloadDto downloadDto = fileDownloadService.downloadFile(fileId);
31+
@Valid @ModelAttribute("password") String password) {
32+
if (!fileService.validateFileAccess(fileId, password)) {
33+
throw new InvalidPasswordException();
34+
}
4735

48-
byte[] content = downloadDto.getInputStream().readAllBytes();
49-
String filename = downloadDto.getFilename();
36+
try {
37+
FileDownloadDto downloadDto = fileDownloadService.downloadFile(fileId);
5038

51-
HttpHeaders headers = new HttpHeaders();
52-
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
53-
headers.setContentDispositionFormData("attachment", new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
39+
byte[] content = downloadDto.getByteArrayOutputStream().toByteArray();
40+
String filename = downloadDto.getFilename();
41+
String mimeType = Files.probeContentType(Paths.get(filename));
5442

55-
return new ResponseEntity<>(content, headers, HttpStatus.OK);
56-
}
43+
HttpHeaders headers = new HttpHeaders();
44+
headers.setContentType(MediaType.valueOf(mimeType));
45+
headers.setContentDispositionFormData("attachment", filename);
5746

58-
private ResponseEntity<HashMap> isPasswordValid(Long fileId, String password) {
59-
try {
60-
if (!fileService.validateFileAccess(fileId, password)) {
61-
HashMap resultMap = new HashMap<>();
62-
resultMap.put("error", "Access denied: invalid password");
63-
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(resultMap);
64-
}
65-
} catch (FileNotFoundException e) {
66-
HashMap resultMap = new HashMap<>();
67-
resultMap.put("error", e.getMessage());
68-
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultMap);
47+
return new ResponseEntity<>(content, headers, HttpStatus.OK);
48+
} catch (Exception e) {
49+
throw new DownloadFileException();
6950
}
70-
return null;
7151
}
7252
}

src/main/java/acc/hotsix/file_share/api/FileQuerySearchController.java

+11-50
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import acc.hotsix.file_share.dto.FileQueryRequestDTO;
55
import acc.hotsix.file_share.dto.FileQuerySearchResponseDTO;
66
import acc.hotsix.file_share.dto.FileSearchRequestDTO;
7-
import acc.hotsix.file_share.global.error.InvalidQueryReqParamException;
8-
import acc.hotsix.file_share.global.error.MissingSearchReqParamException;
9-
import acc.hotsix.file_share.global.error.NoQueryFilesException;
7+
import acc.hotsix.file_share.global.error.exception.InvalidQueryParamException;
8+
import acc.hotsix.file_share.global.error.exception.MissingSearchParamException;
9+
import acc.hotsix.file_share.global.error.exception.MissingSearchResultException;
1010
import lombok.RequiredArgsConstructor;
1111
import org.springframework.beans.TypeMismatchException;
1212
import org.springframework.data.domain.PageRequest;
@@ -28,11 +28,11 @@ public class FileQuerySearchController {
2828

2929
// 전체 조회
3030
@GetMapping("/all")
31-
public List<FileQuerySearchResponseDTO> queryAllFiles() throws NoQueryFilesException {
31+
public List<FileQuerySearchResponseDTO> queryAllFiles() throws MissingSearchResultException {
3232
List<FileQuerySearchResponseDTO> content = this.fileQuerySearchService.queryAllFile();
3333

3434
if(content.isEmpty()) {
35-
throw new NoQueryFilesException();
35+
throw new MissingSearchResultException();
3636
}
3737

3838
return content;
@@ -44,10 +44,10 @@ public List<FileQuerySearchResponseDTO> queryFilesByPage(
4444
@RequestParam(value = "name", required = false, defaultValue = "asc") String name,
4545
@RequestParam(value = "time", required = false, defaultValue = "asc") String time,
4646
@RequestParam(value = "page", required = false, defaultValue = "0") Integer page
47-
) throws NoQueryFilesException, InvalidQueryReqParamException {
47+
) throws MissingSearchResultException, InvalidQueryParamException {
4848
if((!name.isEmpty() && !name.equals("asc") && !name.equals("desc"))
4949
|| (!time.isEmpty() && !time.equals("asc") && !time.equals("desc"))) {
50-
throw new InvalidQueryReqParamException();
50+
throw new InvalidQueryParamException();
5151
}
5252

5353
FileQueryRequestDTO fileQueryRequestDTO = new FileQueryRequestDTO(name, time);
@@ -56,7 +56,7 @@ public List<FileQuerySearchResponseDTO> queryFilesByPage(
5656
List<FileQuerySearchResponseDTO> content = this.fileQuerySearchService.queryFilesByPage(fileQueryRequestDTO, pageable).getContent();
5757

5858
if(content.isEmpty()) {
59-
throw new NoQueryFilesException();
59+
throw new MissingSearchResultException();
6060
}
6161

6262
return content;
@@ -71,59 +71,20 @@ public List<FileQuerySearchResponseDTO> searchFilesByCriteria (
7171
@RequestParam(value = "after", required = false) LocalDate after,
7272
@RequestParam(value = "type", required = false) String fileType,
7373
@RequestParam(value = "page", required = false, defaultValue = "0") Integer page
74-
) throws MissingSearchReqParamException, NoQueryFilesException {
74+
) throws MissingSearchParamException, MissingSearchResultException {
7575
if(name == null && path == null && before == null && after == null && fileType == null) {
76-
throw new MissingSearchReqParamException();
76+
throw new MissingSearchParamException();
7777
}
7878

7979
FileSearchRequestDTO fileSearchRequestDTO = new FileSearchRequestDTO(name, path, before, after, fileType);
8080
Pageable pageable = PageRequest.of(page, 50);
8181
List<FileQuerySearchResponseDTO> content = this.fileQuerySearchService.searchFilesByCriteria(fileSearchRequestDTO, pageable).getContent();
8282

8383
if(content.isEmpty()) {
84-
throw new NoQueryFilesException();
84+
throw new MissingSearchResultException();
8585
}
8686

8787
return content;
8888
}
8989

90-
// 조회할 값이 없음
91-
@ExceptionHandler(NoQueryFilesException.class)
92-
public ResponseEntity<Map<String, Object>> noQueryFilesExceptionHandler(NoQueryFilesException e) {
93-
Map<String, Object> resultMap = new HashMap<>();
94-
resultMap.put("error", e.getMessage());
95-
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(resultMap);
96-
}
97-
98-
// 검색 조건이 없음
99-
@ExceptionHandler(MissingSearchReqParamException.class)
100-
public ResponseEntity<Map<String, Object>> missingSearchReqParamExceptionHandler(MissingSearchReqParamException e) {
101-
Map<String, Object> resultMap = new HashMap<>();
102-
resultMap.put("error", e.getMessage());
103-
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultMap);
104-
}
105-
106-
// 파라미터가 asc, desc 가 아닌 다른 값임
107-
@ExceptionHandler(InvalidQueryReqParamException.class)
108-
public ResponseEntity<Map<String, Object>> invalidQueryReqParamExceptionHandler(InvalidQueryReqParamException e) {
109-
Map<String, Object> resultMap = new HashMap<>();
110-
resultMap.put("error", e.getMessage());
111-
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultMap);
112-
}
113-
114-
// 파라미터 타입이 불일치
115-
@ExceptionHandler(TypeMismatchException.class)
116-
public ResponseEntity<Map<String, Object>> typeMismatchExceptionHandler (TypeMismatchException e) {
117-
Map<String, Object> resultMap = new HashMap<>();
118-
resultMap.put("error", "Invalid parameter data type");
119-
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resultMap);
120-
}
121-
122-
// 서버 오류 처리
123-
@ExceptionHandler(Exception.class)
124-
public ResponseEntity<Map<String, Object>> exceptionHandler(Exception e) {
125-
Map<String, Object> resultMap = new HashMap<>();
126-
resultMap.put("error", "An unexpected error occurred: " + e.getMessage());
127-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
128-
}
12990
}

0 commit comments

Comments
 (0)