From bad14cc370c421485a14d73097dbb7c6969896c1 Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Tue, 2 Apr 2024 20:17:31 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20s3?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- .../jisungin/api/image/ImageController.java | 26 +++++ .../application/image/ImageService.java | 22 ++++ .../java/com/jisungin/config/S3Config.java | 32 ++++++ .../com/jisungin/infra/s3/S3FileManager.java | 102 ++++++++++++++++++ src/main/resources/application-local.yml | 8 +- src/main/resources/application.yml | 3 +- src/test/resources/application.yml | 15 ++- 8 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/jisungin/api/image/ImageController.java create mode 100644 src/main/java/com/jisungin/application/image/ImageService.java create mode 100644 src/main/java/com/jisungin/config/S3Config.java create mode 100644 src/main/java/com/jisungin/infra/s3/S3FileManager.java diff --git a/.gitignore b/.gitignore index b7a6e16..213c1d7 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ application-dev.yml application-prod.yml application-jwt.yml application-oauth.yml -application-crawler.yml \ No newline at end of file +application-crawler.yml +application-s3.yml \ No newline at end of file diff --git a/src/main/java/com/jisungin/api/image/ImageController.java b/src/main/java/com/jisungin/api/image/ImageController.java new file mode 100644 index 0000000..9da5aca --- /dev/null +++ b/src/main/java/com/jisungin/api/image/ImageController.java @@ -0,0 +1,26 @@ +package com.jisungin.api.image; + +import com.jisungin.api.ApiResponse; +import com.jisungin.application.image.ImageService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RequestMapping("/v1") +@RequiredArgsConstructor +@RestController +public class ImageController { + + private final ImageService imageService; + + @PostMapping("/s3/upload") + public ApiResponse> upload(@RequestPart List files, @RequestParam String dirName) { + return ApiResponse.ok(imageService.upload(files, dirName)); + } + +} diff --git a/src/main/java/com/jisungin/application/image/ImageService.java b/src/main/java/com/jisungin/application/image/ImageService.java new file mode 100644 index 0000000..f695571 --- /dev/null +++ b/src/main/java/com/jisungin/application/image/ImageService.java @@ -0,0 +1,22 @@ +package com.jisungin.application.image; + +import com.jisungin.infra.s3.S3FileManager; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@RequiredArgsConstructor +@Service +public class ImageService { + + private final S3FileManager s3FileManager; + + public List upload(List multipartFiles, String dirName) { + return multipartFiles.stream() + .map(multipartFile -> s3FileManager.upload(multipartFile, dirName)) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/jisungin/config/S3Config.java b/src/main/java/com/jisungin/config/S3Config.java new file mode 100644 index 0000000..10491f3 --- /dev/null +++ b/src/main/java/com/jisungin/config/S3Config.java @@ -0,0 +1,32 @@ +package com.jisungin.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3Client() { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } + +} diff --git a/src/main/java/com/jisungin/infra/s3/S3FileManager.java b/src/main/java/com/jisungin/infra/s3/S3FileManager.java new file mode 100644 index 0000000..52586b2 --- /dev/null +++ b/src/main/java/com/jisungin/infra/s3/S3FileManager.java @@ -0,0 +1,102 @@ +package com.jisungin.infra.s3; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.amazonaws.util.IOUtils; +import com.jisungin.exception.BusinessException; +import com.jisungin.exception.ErrorCode; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +@RequiredArgsConstructor +@Component +public class S3FileManager { + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + private final AmazonS3 amazonS3; + + public String upload(MultipartFile multipartFiles, String dirName) { + + validateMultipartFile(multipartFiles); + + return this.uploadImage(multipartFiles, dirName); + } + + private void validateMultipartFile(MultipartFile multipartFile) { + if (multipartFile.isEmpty() || Objects.isNull(multipartFile.getOriginalFilename())) { + throw new BusinessException(ErrorCode.IMAGE_NOT_FOUND); + } + } + + private String uploadImage(MultipartFile multipartFile, String dirName) { + this.validateImageFileExtension(multipartFile.getOriginalFilename()); + try { + return this.uploadImageToS3(multipartFile, dirName); + } catch (IOException e) { + throw new BusinessException(ErrorCode.S3_UPLOAD_FAIL); + } + } + + private String uploadImageToS3(MultipartFile multipartFile, String dirName) throws IOException { + String originalFilename = multipartFile.getOriginalFilename(); + String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + + String s3FileName = createS3FileName(originalFilename, dirName); + + InputStream is = multipartFile.getInputStream(); + byte[] bytes = IOUtils.toByteArray(is); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType("image/" + extension); + metadata.setContentLength(bytes.length); + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); + + try { + PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, s3FileName, byteArrayInputStream, metadata) + .withCannedAcl(CannedAccessControlList.PublicRead); + + amazonS3.putObject(putObjectRequest); + } catch (Exception e) { + throw new BusinessException(ErrorCode.S3_UPLOAD_FAIL); + } finally { + byteArrayInputStream.close(); + is.close(); + } + + return amazonS3.getUrl(bucket, s3FileName).toString(); + } + + private static String createS3FileName(String originalFilename, String dirName) { + String s3FileName = dirName + "/" + UUID.randomUUID().toString().substring(0, 10) + originalFilename; + return s3FileName; + } + + private void validateImageFileExtension(String filename) { + int lastDotIndex = filename.lastIndexOf("."); + if (lastDotIndex == -1) { + throw new BusinessException(ErrorCode.NOT_IMAGE); + } + + String extension = filename.substring(lastDotIndex + 1).toLowerCase(); + List allowedExtensions = Arrays.asList("jpg", "jpeg", "png", "gif"); + + if (!allowedExtensions.contains(extension)) { + throw new BusinessException(ErrorCode.NOT_IMAGE); + } + } + +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 2bed404..4efb13a 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -31,4 +31,10 @@ spring: sql: init: - mode: always \ No newline at end of file + mode: always + + servlet: + multipart: + max-file-size: 10MB + max-request-size: 100MB + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 34de983..14f77ca 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,4 +11,5 @@ spring: - prod include: - oauth - - crawler \ No newline at end of file + - crawler + - s3 \ No newline at end of file diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 1222ab8..535ca5e 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -41,4 +41,17 @@ crawler: bookJsonCss: "script[type=application/ld+json]" bestRankingCss: "div.img_upper > em.ico.rank" bestIdCss: "ul#yesBestList > li" - bestIdAttrs: "data-goods-no" \ No newline at end of file + bestIdAttrs: "data-goods-no" + +cloud: + aws: + credentials: + access-key: AKIAYS2NTQWYEAO33I44 + secret-key: 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 + s3: + bucket: jisungin-bucket + region: + static: ap-northeast-2 + auto: false + stack: + auto: false \ No newline at end of file From 0521203522f02ce4d475267729648addf6e94699 Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Tue, 2 Apr 2024 21:07:13 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20s3?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jisungin/api/image/ImageController.java | 12 ++++++++ .../application/image/ImageService.java | 6 +++- .../com/jisungin/infra/s3/S3FileManager.java | 29 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/jisungin/api/image/ImageController.java b/src/main/java/com/jisungin/api/image/ImageController.java index 9da5aca..b6fd5c0 100644 --- a/src/main/java/com/jisungin/api/image/ImageController.java +++ b/src/main/java/com/jisungin/api/image/ImageController.java @@ -4,6 +4,8 @@ import com.jisungin.application.image.ImageService; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -23,4 +25,14 @@ public ApiResponse> upload(@RequestPart List files, return ApiResponse.ok(imageService.upload(files, dirName)); } + @DeleteMapping("/s3/remove") + public ApiResponse removeFile(@RequestParam String fileName) { + imageService.removeFile(fileName); + + return ApiResponse.builder() + .message("삭제 성공") + .status(HttpStatus.OK) + .build(); + } + } diff --git a/src/main/java/com/jisungin/application/image/ImageService.java b/src/main/java/com/jisungin/application/image/ImageService.java index f695571..6cf2c81 100644 --- a/src/main/java/com/jisungin/application/image/ImageService.java +++ b/src/main/java/com/jisungin/application/image/ImageService.java @@ -18,5 +18,9 @@ public List upload(List multipartFiles, String dirName) { .map(multipartFile -> s3FileManager.upload(multipartFile, dirName)) .collect(Collectors.toList()); } - + + public void removeFile(String fileName) { + s3FileManager.removeFile(fileName); + } + } diff --git a/src/main/java/com/jisungin/infra/s3/S3FileManager.java b/src/main/java/com/jisungin/infra/s3/S3FileManager.java index 52586b2..624be39 100644 --- a/src/main/java/com/jisungin/infra/s3/S3FileManager.java +++ b/src/main/java/com/jisungin/infra/s3/S3FileManager.java @@ -1,7 +1,9 @@ package com.jisungin.infra.s3; +import com.amazonaws.SdkClientException; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.DeleteObjectRequest; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.util.IOUtils; @@ -10,6 +12,10 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -35,6 +41,29 @@ public String upload(MultipartFile multipartFiles, String dirName) { return this.uploadImage(multipartFiles, dirName); } + public void removeFile(String fileName) { + String key = getKeyFromImage(fileName); + try { + amazonS3.deleteObject(new DeleteObjectRequest(bucket, key)); + } catch (SdkClientException e) { + throw new BusinessException(ErrorCode.IMAGE_NOT_FOUND); + } + } + + private String getKeyFromImage(String fileName) { + try { + URL url = new URL(fileName); + String path = url.getPath(); + String desiredPart = path.replaceFirst("^/[^/]*/[^/]*/", ""); + if (desiredPart.startsWith("/")) { // 맨 앞이 "/"로 시작하는 경우 첫 번째 문자를 제거 + desiredPart = desiredPart.substring(1); + } + return URLDecoder.decode(desiredPart, "UTF-8"); + } catch (MalformedURLException | UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + private void validateMultipartFile(MultipartFile multipartFile) { if (multipartFile.isEmpty() || Objects.isNull(multipartFile.getOriginalFilename())) { throw new BusinessException(ErrorCode.IMAGE_NOT_FOUND); From 733b80ecd944ca1e1637c62139760f44c7e76acc Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Wed, 3 Apr 2024 16:40:53 +0900 Subject: [PATCH 03/11] =?UTF-8?q?test:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20s3?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../jisungin/api/image/ImageController.java | 9 +-- .../api/image/ImageControllerTest.java | 59 +++++++++++++++++++ .../application/image/ImageServiceTest.java | 51 ++++++++++++++++ 4 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/jisungin/api/image/ImageControllerTest.java create mode 100644 src/test/java/com/jisungin/application/image/ImageServiceTest.java diff --git a/build.gradle b/build.gradle index b54c7b1..effb485 100644 --- a/build.gradle +++ b/build.gradle @@ -71,6 +71,8 @@ dependencies { // Test Container testImplementation "org.junit.jupiter:junit-jupiter:5.8.1" testImplementation "org.testcontainers:junit-jupiter:1.17.3" + // S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('bootBuildImage') { diff --git a/src/main/java/com/jisungin/api/image/ImageController.java b/src/main/java/com/jisungin/api/image/ImageController.java index b6fd5c0..278121e 100644 --- a/src/main/java/com/jisungin/api/image/ImageController.java +++ b/src/main/java/com/jisungin/api/image/ImageController.java @@ -20,13 +20,14 @@ public class ImageController { private final ImageService imageService; - @PostMapping("/s3/upload") - public ApiResponse> upload(@RequestPart List files, @RequestParam String dirName) { + @PostMapping("/s3") + public ApiResponse> upload(@RequestPart List files, + @RequestParam("dirName") String dirName) { return ApiResponse.ok(imageService.upload(files, dirName)); } - @DeleteMapping("/s3/remove") - public ApiResponse removeFile(@RequestParam String fileName) { + @DeleteMapping("/s3") + public ApiResponse removeFile(@RequestParam("fileName") String fileName) { imageService.removeFile(fileName); return ApiResponse.builder() diff --git a/src/test/java/com/jisungin/api/image/ImageControllerTest.java b/src/test/java/com/jisungin/api/image/ImageControllerTest.java new file mode 100644 index 0000000..0a1a0af --- /dev/null +++ b/src/test/java/com/jisungin/api/image/ImageControllerTest.java @@ -0,0 +1,59 @@ +package com.jisungin.api.image; + +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.jisungin.ControllerTestSupport; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; + +class ImageControllerTest extends ControllerTestSupport { + + @Test + @DisplayName("이미지 업로드하기") + void uploadImage() throws Exception { + // given + String fileName = "image.jpg"; + byte[] imageByte = "image".getBytes(); + + MockMultipartFile multipartFile = new MockMultipartFile("files", fileName, "image/jpg", imageByte); + + given(imageService.upload(anyList(), anyString())) + .willReturn(List.of("image.jpg")); + + // when // then + mockMvc.perform(multipart("/v1/s3") + .file(multipartFile) + .param("dirName", "dirName") + ) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")); + } + + @Test + @DisplayName("이미지 제거하기") + void removeImage() throws Exception { + // given + String fileName = "image.jpg"; + + // when // then + mockMvc.perform(delete("/v1/s3") + .param("fileName", fileName)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("삭제 성공")); + + } + +} \ No newline at end of file diff --git a/src/test/java/com/jisungin/application/image/ImageServiceTest.java b/src/test/java/com/jisungin/application/image/ImageServiceTest.java new file mode 100644 index 0000000..b0d8425 --- /dev/null +++ b/src/test/java/com/jisungin/application/image/ImageServiceTest.java @@ -0,0 +1,51 @@ +package com.jisungin.application.image; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; + +import com.jisungin.infra.s3.S3FileManager; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +@ExtendWith(MockitoExtension.class) +class ImageServiceTest { + + @InjectMocks + ImageService imageService; + + @Mock + S3FileManager s3FileManager; + + @Test + @DisplayName("이미지를 업로드 테스트") + void uploadImage() throws Exception { + // given + MockMultipartFile multipartFile = getMockMultipartFile(); + + given(s3FileManager.upload(any(MultipartFile.class), anyString())) + .willReturn("image.jpg"); + // when + List images = imageService.upload(List.of(multipartFile), "talkroom"); + + // then + Assertions.assertThat("image.jpg").isEqualTo(images.get(0)); + } + + private static MockMultipartFile getMockMultipartFile() { + String fileName = "image.jpg"; + byte[] imageByte = "image".getBytes(); + + MockMultipartFile multipartFile = new MockMultipartFile("file", fileName, "image", imageByte); + return multipartFile; + } + +} \ No newline at end of file From 227f4c55ea9b1c10d26747a592444f670cea7a1d Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Wed, 3 Apr 2024 16:43:29 +0900 Subject: [PATCH 04/11] =?UTF-8?q?docs:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20s3?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20REST=20Docs=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api/image/image.adoc | 26 ++++ src/docs/asciidoc/index.adoc | 7 +- .../docs/image/ImageControllerDocsTest.java | 117 ++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/docs/asciidoc/api/image/image.adoc create mode 100644 src/test/java/com/jisungin/docs/image/ImageControllerDocsTest.java diff --git a/src/docs/asciidoc/api/image/image.adoc b/src/docs/asciidoc/api/image/image.adoc new file mode 100644 index 0000000..e66f323 --- /dev/null +++ b/src/docs/asciidoc/api/image/image.adoc @@ -0,0 +1,26 @@ +[[image-upload]] +=== 이미지 업로드 + +==== HTTP Request + +include::{snippets}/image/upload/http-request.adoc[] +include::{snippets}/image/upload/request-parts.adoc[] +include::{snippets}/image/upload/form-parameters.adoc[] + +==== HTTP Response + +include::{snippets}/image/upload/http-response.adoc[] +include::{snippets}/image/upload/response-fields.adoc[] + +[[image-remove]] +=== 이미지 제거 + +==== HTTP Request + +include::{snippets}/image/delete/http-request.adoc[] +include::{snippets}/image/delete/form-parameters.adoc[] + +==== HTTP Response + +include::{snippets}/image/delete/http-response.adoc[] +include::{snippets}/image/delete/response-fields.adoc[] diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 7194ac2..44a8e6d 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -27,4 +27,9 @@ include::api/talkroomlike/talkroomlike.adoc[] [[CommentLike-API]] == CommentLike API -include::api/commentlike/commentlike.adoc[] \ No newline at end of file +include::api/commentlike/commentlike.adoc[] + +[[Image-API]] +== Image API + +include::api/image/image.adoc[] \ No newline at end of file diff --git a/src/test/java/com/jisungin/docs/image/ImageControllerDocsTest.java b/src/test/java/com/jisungin/docs/image/ImageControllerDocsTest.java new file mode 100644 index 0000000..6c5e68e --- /dev/null +++ b/src/test/java/com/jisungin/docs/image/ImageControllerDocsTest.java @@ -0,0 +1,117 @@ +package com.jisungin.docs.image; + +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.multipart; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.formParameters; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.partWithName; +import static org.springframework.restdocs.request.RequestDocumentation.requestParts; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.jisungin.api.image.ImageController; +import com.jisungin.application.image.ImageService; +import com.jisungin.docs.RestDocsSupport; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.restdocs.payload.JsonFieldType; + +public class ImageControllerDocsTest extends RestDocsSupport { + + private final ImageService imageService = mock(ImageService.class); + + @Override + protected Object initController() { + return new ImageController(imageService); + } + + @Test + @DisplayName("이미지를 업로드 하는 API") + void uploadImage() throws Exception { + String fileName = "image.jpg"; + byte[] imageByte = "image".getBytes(); + + MockMultipartFile multipartFile = new MockMultipartFile("files", fileName, "image/jpg", imageByte); + + String dirName = "dirName"; + + given(imageService.upload(anyList(), anyString())) + .willReturn(List.of("image.jpg")); + + mockMvc.perform(multipart("/v1/s3") + .file(multipartFile) + .param("dirName", dirName) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.MULTIPART_FORM_DATA) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + ) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(document("image/upload", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestParts( + partWithName("files").description("이미지") + ), + formParameters( + parameterWithName("dirName") + .description("디렉토리명 (ex: talkroom, comment, review ...)") + ), + responseFields( + fieldWithPath("code").type(JsonFieldType.NUMBER) + .description("코드"), + fieldWithPath("status").type(JsonFieldType.STRING) + .description("상태"), + fieldWithPath("message").type(JsonFieldType.STRING) + .description("메시지"), + fieldWithPath("data").type(JsonFieldType.ARRAY) + .description("이미지 URL") + ) + )); + } + + @Test + @DisplayName("이미지를 삭제하는 API") + void removeImage() throws Exception { + String fileName = "fileName"; + + mockMvc.perform(delete("/v1/s3") + .param("fileName", fileName) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + ) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(document("image/delete", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + formParameters( + parameterWithName("fileName") + .description("이미지 URL") + ), + responseFields( + fieldWithPath("code").type(JsonFieldType.NUMBER) + .description("코드"), + fieldWithPath("status").type(JsonFieldType.STRING) + .description("상태"), + fieldWithPath("message").type(JsonFieldType.STRING) + .description("메시지"), + fieldWithPath("data").type(JsonFieldType.NULL) + .description("return data null") + ) + )); + } + +} From d4bbf28137eaa832e3114289acbc44b007b673d9 Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Wed, 3 Apr 2024 16:44:38 +0900 Subject: [PATCH 05/11] =?UTF-8?q?deploy:=20S3=20=EC=97=B0=EB=8F=99=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/jisungin_dev.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/jisungin_dev.yml b/.github/workflows/jisungin_dev.yml index cacd6a0..c1c971c 100644 --- a/.github/workflows/jisungin_dev.yml +++ b/.github/workflows/jisungin_dev.yml @@ -56,6 +56,14 @@ jobs: DEV_SECRET_DIR_FILE_NAME: application-crawler.yml run: echo $DEV_SECRET | base64 --decode >> $DEV_SECRET_DIR/$DEV_SECRET_DIR_FILE_NAME + # application-s3.yml + - name: Copy s3 Secret + env: + DEV_SECRET: ${{ secrets.APPLICATION_S3_YML }} + DEV_SECRET_DIR: src/main/resources + DEV_SECRET_DIR_FILE_NAME: application-s3.yml + run: echo $DEV_SECRET | base64 --decode >> $DEV_SECRET_DIR/$DEV_SECRET_DIR_FILE_NAME + # application-jwt.yml # - name: Copy jwt Secret # env: From 8f1ae755d7aa80f0c32eea4456f143b31857c742 Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Wed, 3 Apr 2024 16:54:20 +0900 Subject: [PATCH 06/11] =?UTF-8?q?Refactor:=20=EB=B3=91=ED=95=A9=20?= =?UTF-8?q?=EC=8B=9C=20=EC=B6=A9=EB=8F=8C=ED=95=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/jisungin/exception/ErrorCode.java | 5 ++++- src/test/java/com/jisungin/ControllerTestSupport.java | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jisungin/exception/ErrorCode.java b/src/main/java/com/jisungin/exception/ErrorCode.java index 8ca14b7..b1b05bc 100644 --- a/src/main/java/com/jisungin/exception/ErrorCode.java +++ b/src/main/java/com/jisungin/exception/ErrorCode.java @@ -22,7 +22,10 @@ public enum ErrorCode { LIKE_EXIST(400, "이미 좋아요를 눌렀습니다."), REQUEST_TIME_OUT(408, "요청 시간이 만료 되었습니다."), COMMENT_LIKE_NOT_FOUND(404, "의견 좋아요를 찾을 수 없습니다."), - REVIEW_LIKE_NOT_FOUND(404, "리뷰 좋아요를 찾을 수 없습니다."); + REVIEW_LIKE_NOT_FOUND(404, "리뷰 좋아요를 찾을 수 없습니다."), + IMAGE_NOT_FOUND(400, "파일이 없습니다."), + S3_UPLOAD_FAIL(400, "이미지 업로드가 실패되었습니다."), + NOT_IMAGE(400, "이미지 파일이 아닙니다."); private final int code; diff --git a/src/test/java/com/jisungin/ControllerTestSupport.java b/src/test/java/com/jisungin/ControllerTestSupport.java index d5d51d0..3090214 100644 --- a/src/test/java/com/jisungin/ControllerTestSupport.java +++ b/src/test/java/com/jisungin/ControllerTestSupport.java @@ -4,6 +4,7 @@ import com.jisungin.api.book.BookController; import com.jisungin.api.comment.CommentController; import com.jisungin.api.commentlike.CommentLikeController; +import com.jisungin.api.image.ImageController; import com.jisungin.api.oauth.AuthContext; import com.jisungin.api.review.ReviewController; import com.jisungin.api.reviewlike.ReviewLikeController; @@ -14,6 +15,7 @@ import com.jisungin.application.book.BookService; import com.jisungin.application.comment.CommentService; import com.jisungin.application.commentlike.CommentLikeService; +import com.jisungin.application.image.ImageService; import com.jisungin.application.review.ReviewService; import com.jisungin.application.reviewlike.ReviewLikeService; import com.jisungin.application.talkroom.TalkRoomService; @@ -32,7 +34,8 @@ CommentLikeController.class, UserController.class, BookController.class, - ReviewLikeController.class + ReviewLikeController.class, + ImageController.class }) public abstract class ControllerTestSupport { @@ -72,4 +75,7 @@ public abstract class ControllerTestSupport { @MockBean protected ReviewLikeService reviewLikeService; + @MockBean + protected ImageService imageService; + } From a3c6efd73d3d17a788f8bee91c5ad620912972cb Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Wed, 3 Apr 2024 17:18:15 +0900 Subject: [PATCH 07/11] =?UTF-8?q?bugFix:=20TEST=20=EC=8B=9C=20S3=20AWS=20k?= =?UTF-8?q?ey=20=EC=A3=BC=EC=9E=85=20=EC=97=90=EB=9F=AC=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/jisungin/config/S3Config.java | 2 ++ .../com/jisungin/JisunginApplicationTests.java | 5 +++++ .../java/com/jisungin/ServiceTestSupport.java | 5 +++++ .../service/book/BestSellerServiceTest.java | 4 ++++ src/test/resources/application.yml | 15 +-------------- .../restdocs/templates/path-parameters.snippet | 10 ---------- .../restdocs/templates/query-parameters.snippet | 11 ----------- .../restdocs/templates/request-fields.snippet | 14 -------------- .../restdocs/templates/response-fields.snippet | 14 -------------- 9 files changed, 17 insertions(+), 63 deletions(-) delete mode 100644 src/test/resources/org/springframework/restdocs/templates/path-parameters.snippet delete mode 100644 src/test/resources/org/springframework/restdocs/templates/query-parameters.snippet delete mode 100644 src/test/resources/org/springframework/restdocs/templates/request-fields.snippet delete mode 100644 src/test/resources/org/springframework/restdocs/templates/response-fields.snippet diff --git a/src/main/java/com/jisungin/config/S3Config.java b/src/main/java/com/jisungin/config/S3Config.java index 10491f3..e5c6aa0 100644 --- a/src/main/java/com/jisungin/config/S3Config.java +++ b/src/main/java/com/jisungin/config/S3Config.java @@ -7,8 +7,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; @Configuration +@Profile("!default") public class S3Config { @Value("${cloud.aws.credentials.access-key}") diff --git a/src/test/java/com/jisungin/JisunginApplicationTests.java b/src/test/java/com/jisungin/JisunginApplicationTests.java index 15a8558..3f9f5cf 100644 --- a/src/test/java/com/jisungin/JisunginApplicationTests.java +++ b/src/test/java/com/jisungin/JisunginApplicationTests.java @@ -1,11 +1,16 @@ package com.jisungin; +import com.jisungin.infra.s3.S3FileManager; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; @SpringBootTest class JisunginApplicationTests { + @MockBean + private S3FileManager s3FileManager; + @Test void contextLoads() { } diff --git a/src/test/java/com/jisungin/ServiceTestSupport.java b/src/test/java/com/jisungin/ServiceTestSupport.java index 2cd047e..3b246d1 100644 --- a/src/test/java/com/jisungin/ServiceTestSupport.java +++ b/src/test/java/com/jisungin/ServiceTestSupport.java @@ -1,7 +1,12 @@ package com.jisungin; +import com.jisungin.infra.s3.S3FileManager; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; @SpringBootTest public abstract class ServiceTestSupport { + + @MockBean + protected S3FileManager s3FileManager; } diff --git a/src/test/java/com/jisungin/application/service/book/BestSellerServiceTest.java b/src/test/java/com/jisungin/application/service/book/BestSellerServiceTest.java index 87390b4..c007f7f 100644 --- a/src/test/java/com/jisungin/application/service/book/BestSellerServiceTest.java +++ b/src/test/java/com/jisungin/application/service/book/BestSellerServiceTest.java @@ -17,6 +17,7 @@ import com.jisungin.domain.book.repository.BookRepository; import com.jisungin.infra.crawler.Crawler; import com.jisungin.infra.crawler.CrawlingBook; +import com.jisungin.infra.s3.S3FileManager; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -51,6 +52,9 @@ public class BestSellerServiceTest extends RedisTestContainer { @MockBean private Crawler crawler; + @MockBean + private S3FileManager s3FileManager; + @AfterEach public void tearDown() { bookRepository.deleteAllInBatch(); diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 535ca5e..1222ab8 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -41,17 +41,4 @@ crawler: bookJsonCss: "script[type=application/ld+json]" bestRankingCss: "div.img_upper > em.ico.rank" bestIdCss: "ul#yesBestList > li" - bestIdAttrs: "data-goods-no" - -cloud: - aws: - credentials: - access-key: AKIAYS2NTQWYEAO33I44 - secret-key: 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 - s3: - bucket: jisungin-bucket - region: - static: ap-northeast-2 - auto: false - stack: - auto: false \ No newline at end of file + bestIdAttrs: "data-goods-no" \ No newline at end of file diff --git a/src/test/resources/org/springframework/restdocs/templates/path-parameters.snippet b/src/test/resources/org/springframework/restdocs/templates/path-parameters.snippet deleted file mode 100644 index c23456d..0000000 --- a/src/test/resources/org/springframework/restdocs/templates/path-parameters.snippet +++ /dev/null @@ -1,10 +0,0 @@ -==== Request Fields -|=== -|Parameter|Description - -{{#parameters}} -|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} -|{{#tableCellContent}}{{description}}{{/tableCellContent}} - -{{/parameters}} -|=== \ No newline at end of file diff --git a/src/test/resources/org/springframework/restdocs/templates/query-parameters.snippet b/src/test/resources/org/springframework/restdocs/templates/query-parameters.snippet deleted file mode 100644 index 5e2b2f8..0000000 --- a/src/test/resources/org/springframework/restdocs/templates/query-parameters.snippet +++ /dev/null @@ -1,11 +0,0 @@ -==== Request Fields -|=== -|Parameter|Optional|Description - - {{#parameters}} - |{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} - |{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} - |{{#tableCellContent}}{{description}}{{/tableCellContent}} - -{{/parameters}} -|=== \ No newline at end of file diff --git a/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet deleted file mode 100644 index 96c1a7a..0000000 --- a/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet +++ /dev/null @@ -1,14 +0,0 @@ -==== Request Fields -|=== -|Path|Type|Optional|Description - -{{#fields}} - -|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} -|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} -|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} -|{{#tableCellContent}}{{description}}{{/tableCellContent}} - -{{/fields}} - -|=== \ No newline at end of file diff --git a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet deleted file mode 100644 index f3cc493..0000000 --- a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet +++ /dev/null @@ -1,14 +0,0 @@ -==== Response Fields -|=== -|Path|Type|Optional|Description - -{{#fields}} - -|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} -|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} -|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} -|{{#tableCellContent}}{{description}}{{/tableCellContent}} - -{{/fields}} - -|=== \ No newline at end of file From 0ff66594205847a88456bba3bf70c08cc980edbe Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Wed, 3 Apr 2024 17:34:14 +0900 Subject: [PATCH 08/11] =?UTF-8?q?docs:=20snippet=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../restdocs/templates/path-parameters.snippet | 10 ++++++++++ .../restdocs/templates/query-parameters.snippet | 11 +++++++++++ .../restdocs/templates/request-fields.snippet | 14 ++++++++++++++ .../restdocs/templates/respnse-fields.snippet | 14 ++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 src/test/resources/org.springframework/restdocs/templates/path-parameters.snippet create mode 100644 src/test/resources/org.springframework/restdocs/templates/query-parameters.snippet create mode 100644 src/test/resources/org.springframework/restdocs/templates/request-fields.snippet create mode 100644 src/test/resources/org.springframework/restdocs/templates/respnse-fields.snippet diff --git a/src/test/resources/org.springframework/restdocs/templates/path-parameters.snippet b/src/test/resources/org.springframework/restdocs/templates/path-parameters.snippet new file mode 100644 index 0000000..c23456d --- /dev/null +++ b/src/test/resources/org.springframework/restdocs/templates/path-parameters.snippet @@ -0,0 +1,10 @@ +==== Request Fields +|=== +|Parameter|Description + +{{#parameters}} +|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/src/test/resources/org.springframework/restdocs/templates/query-parameters.snippet b/src/test/resources/org.springframework/restdocs/templates/query-parameters.snippet new file mode 100644 index 0000000..b59f61d --- /dev/null +++ b/src/test/resources/org.springframework/restdocs/templates/query-parameters.snippet @@ -0,0 +1,11 @@ +==== Request Fields +|=== +|Parameter|Optional|Description + +{{#parameters}} +|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/src/test/resources/org.springframework/restdocs/templates/request-fields.snippet b/src/test/resources/org.springframework/restdocs/templates/request-fields.snippet new file mode 100644 index 0000000..96c1a7a --- /dev/null +++ b/src/test/resources/org.springframework/restdocs/templates/request-fields.snippet @@ -0,0 +1,14 @@ +==== Request Fields +|=== +|Path|Type|Optional|Description + +{{#fields}} + +|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} +|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/fields}} + +|=== \ No newline at end of file diff --git a/src/test/resources/org.springframework/restdocs/templates/respnse-fields.snippet b/src/test/resources/org.springframework/restdocs/templates/respnse-fields.snippet new file mode 100644 index 0000000..f3cc493 --- /dev/null +++ b/src/test/resources/org.springframework/restdocs/templates/respnse-fields.snippet @@ -0,0 +1,14 @@ +==== Response Fields +|=== +|Path|Type|Optional|Description + +{{#fields}} + +|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} +|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{^optional}}O{{/optional}}{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/fields}} + +|=== \ No newline at end of file From b4fa74bad5e971d227d0e5720489c2d46666f6de Mon Sep 17 00:00:00 2001 From: AHNYUNKI Date: Wed, 3 Apr 2024 19:39:04 +0900 Subject: [PATCH 09/11] =?UTF-8?q?bugFix:=20AWS=20key=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application.yml | 44 ------------------------------ 1 file changed, 44 deletions(-) delete mode 100644 src/test/resources/application.yml diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml deleted file mode 100644 index 1222ab8..0000000 --- a/src/test/resources/application.yml +++ /dev/null @@ -1,44 +0,0 @@ -spring: - jpa: - open-in-view: false - hibernate: - ddl-auto: create-drop - properties: - hibernate: - format_sql: true - show_sql: true - jackson: - default-property-inclusion: non_null - - h2: - console: - enabled: true - path: /h2-console - - datasource: - url: jdbc:h2:mem:jisungin - username: sa - password: - driver-class-name: org.h2.Driver - - data: - redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} - password: ${REDIS_PASSWORD:test} - -crawler: - yes24: - fetcher: - isbnUrl: "https://www.yes24.com/Product/Search?domain=BOOK&query=" - bookUrl: "https://www.yes24.com/Product/Goods/" - bestBookUrl: "https://www.yes24.com/Product/Category/BestSeller?categoryNumber=001&pageNumber=1&pageSize=100" - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36" - parser: - isbnCss: "ul#yesSchList > li" - isbnAttr: "data-goods-no" - bookContentCss: "div.infoWrap_txt > div.infoWrap_txtInner" - bookJsonCss: "script[type=application/ld+json]" - bestRankingCss: "div.img_upper > em.ico.rank" - bestIdCss: "ul#yesBestList > li" - bestIdAttrs: "data-goods-no" \ No newline at end of file From dfeddb30c63d57c6bfa84832b14cd26bb18c1562 Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Thu, 4 Apr 2024 15:44:11 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/jisungin/infra/s3/S3FileManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/jisungin/infra/s3/S3FileManager.java b/src/main/java/com/jisungin/infra/s3/S3FileManager.java index 624be39..0e10664 100644 --- a/src/main/java/com/jisungin/infra/s3/S3FileManager.java +++ b/src/main/java/com/jisungin/infra/s3/S3FileManager.java @@ -60,7 +60,7 @@ private String getKeyFromImage(String fileName) { } return URLDecoder.decode(desiredPart, "UTF-8"); } catch (MalformedURLException | UnsupportedEncodingException e) { - throw new RuntimeException(e); + throw new BusinessException(ErrorCode.NOT_IMAGE); } } From f19d8899a2850cdf663db02d73bdf5a28c8de84f Mon Sep 17 00:00:00 2001 From: ahnyunki Date: Thu, 4 Apr 2024 15:48:52 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20yaml=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application.yml | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/test/resources/application.yml diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..1222ab8 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,44 @@ +spring: + jpa: + open-in-view: false + hibernate: + ddl-auto: create-drop + properties: + hibernate: + format_sql: true + show_sql: true + jackson: + default-property-inclusion: non_null + + h2: + console: + enabled: true + path: /h2-console + + datasource: + url: jdbc:h2:mem:jisungin + username: sa + password: + driver-class-name: org.h2.Driver + + data: + redis: + host: ${REDIS_HOST:localhost} + port: ${REDIS_PORT:6379} + password: ${REDIS_PASSWORD:test} + +crawler: + yes24: + fetcher: + isbnUrl: "https://www.yes24.com/Product/Search?domain=BOOK&query=" + bookUrl: "https://www.yes24.com/Product/Goods/" + bestBookUrl: "https://www.yes24.com/Product/Category/BestSeller?categoryNumber=001&pageNumber=1&pageSize=100" + userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36" + parser: + isbnCss: "ul#yesSchList > li" + isbnAttr: "data-goods-no" + bookContentCss: "div.infoWrap_txt > div.infoWrap_txtInner" + bookJsonCss: "script[type=application/ld+json]" + bestRankingCss: "div.img_upper > em.ico.rank" + bestIdCss: "ul#yesBestList > li" + bestIdAttrs: "data-goods-no" \ No newline at end of file