From 3cfc2bf7af99970064369cdf85f17df9d7c39a1e Mon Sep 17 00:00:00 2001 From: soohyun Date: Sat, 28 Jun 2025 19:15:35 +0900 Subject: [PATCH 1/8] =?UTF-8?q?fix:=20=EB=B9=88=20url=20=EC=A4=84=20?= =?UTF-8?q?=EB=95=8C=20=EB=8C=80=EC=9D=91=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/app/toaster/exception/Error.java | 3 + .../toaster/toast/service/ToastService.java | 5 +- .../toast/service/ToastServiceTest.java | 143 ++++++++++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java diff --git a/linkmind/src/main/java/com/app/toaster/exception/Error.java b/linkmind/src/main/java/com/app/toaster/exception/Error.java index 164c3f96..90d2c1c4 100644 --- a/linkmind/src/main/java/com/app/toaster/exception/Error.java +++ b/linkmind/src/main/java/com/app/toaster/exception/Error.java @@ -22,11 +22,14 @@ public enum Error { NOT_FOUND_TIMER(HttpStatus.NOT_FOUND, "찾을 수 없는 타이머입니다."), NOT_FOUND_POPUP_EXCEPTION(HttpStatus.NOT_FOUND, "유효하지 않은 팝업입니다."), + /** * 400 BAD REQUEST EXCEPTION */ BAD_REQUEST_ISREAD(HttpStatus.BAD_REQUEST, "isRead 값이 잘못요청 되었습니다."), BAD_REQUEST_ID(HttpStatus.BAD_REQUEST, "잘못된 id값입니다."), + EMPTY_URL(HttpStatus.BAD_REQUEST, "빈 url로는 저장할 수 없습니다."), + BAD_REQUEST_VALIDATION(HttpStatus.BAD_REQUEST, "유효한 값으로 요청을 다시 보내주세요."), BAD_REQUEST_FILE_EXTENSION(HttpStatus.BAD_REQUEST, "파일형식이 잘못된 것 같습니다."), BAD_REQUEST_FILE_SIZE(HttpStatus.BAD_REQUEST, "파일크기가 잘못된 것 같습니다. 최대 5MB"), diff --git a/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java b/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java index 646d55b5..be47419d 100644 --- a/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java +++ b/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java @@ -51,9 +51,11 @@ public class ToastService { public void createToast(Long userId, SaveToastDto saveToastDto){ //해당 유저 탐색 User presentUser = findUser(userId); + if (saveToastDto.linkUrl() ==null || saveToastDto.linkUrl().isBlank()){ + throw new CustomException(Error.EMPTY_URL, Error.EMPTY_URL.getMessage()); + } //토스트 생성 try { - System.out.println(saveToastDto.linkUrl()); OgResponse res = parsingService.getOg(saveToastDto.linkUrl()); //byte 배열로 읽어들임. log.info(res.titleAdvanced()); @@ -63,6 +65,7 @@ public void createToast(Long userId, SaveToastDto saveToastDto){ // log.info(realRes.fileName()); // log.info(realRes.preSignedUrl()); + //presigned url Toast toast = Toast.builder() .user(presentUser) diff --git a/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java b/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java new file mode 100644 index 00000000..bdb1e194 --- /dev/null +++ b/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java @@ -0,0 +1,143 @@ +package com.app.toaster.toast.service; + +import com.app.toaster.category.domain.Category; +import com.app.toaster.category.infrastructure.CategoryRepository; +import com.app.toaster.category.service.CategoryService; +import com.app.toaster.exception.Error; +import com.app.toaster.exception.model.CustomException; +import com.app.toaster.parse.controller.response.OgResponse; +import com.app.toaster.parse.service.ParsingService; +import com.app.toaster.toast.controller.request.SaveToastDto; +import com.app.toaster.toast.domain.Toast; +import com.app.toaster.toast.infrastructure.ToastRepository; +import com.app.toaster.user.domain.User; +import com.app.toaster.user.infrastructure.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ToastServiceTest { + + @Mock + private ToastRepository toastRepository; + + @Mock + private ParsingService parsingService; + + @Mock + private CategoryRepository categoryRepository; + + @Mock + private UserRepository userRepository; // findUser 메소드를 위해 필요할 수 있음 + + @InjectMocks + private ToastService toastService; + + private User testUser; + + private Category testCategory; + private SaveToastDto validSaveToastDto; + private OgResponse mockOgResponse; + + @BeforeEach + void setUp() { + testUser = User.builder() + .socialId("testUser") + .nickname("test") + .build(); + + validSaveToastDto = new SaveToastDto("https://example.com", 1L); + + mockOgResponse = OgResponse.of("Test Title", "https://example.com/image.jpg"); + + testCategory = Category.builder() + .priority(0) + .title("hi") + .user(testUser) + .build(); + } + + @Test + @DisplayName("Toast 생성 성공 테스트") + void createToast_Success() throws IOException { + // given + Long userId = 1L; + + + when(userRepository.findByUserId(userId)).thenReturn(Optional.of(testUser)); + when(parsingService.getOg(validSaveToastDto.linkUrl())).thenReturn(mockOgResponse); + when(categoryRepository.findById(userId)).thenReturn(Optional.of(testCategory)); + + // when + toastService.createToast(userId, validSaveToastDto); + + // then + verify(parsingService, times(1)).getOg(validSaveToastDto.linkUrl()); + verify(toastRepository, times(1)).save(any(Toast.class)); + + // Toast 객체가 올바르게 생성되는지 확인 + ArgumentCaptor toastCaptor = ArgumentCaptor.forClass(Toast.class); + verify(toastRepository).save(toastCaptor.capture()); + + Toast savedToast = toastCaptor.getValue(); + assertThat(savedToast.getUser()).isEqualTo(testUser); + assertThat(savedToast.getLinkUrl()).isEqualTo(validSaveToastDto.linkUrl()); + assertThat(savedToast.getTitle()).isEqualTo(mockOgResponse.titleAdvanced()); + } + + @Test + @DisplayName("URL이 null인 경우 EMPTY_URL 에러를 반환한다.") + void createToast_Fail_NullUrl() { + // given + Long userId = 1L; + + SaveToastDto invalidDto = new SaveToastDto(null, 1L); + + when(userRepository.findByUserId(userId)).thenReturn(Optional.of(testUser)); + + // when & then + CustomException exception = assertThrows(CustomException.class, + () -> toastService.createToast(userId, invalidDto)); + + assertThat(exception.getError()).isEqualTo(Error.EMPTY_URL); + } + + @Test + @DisplayName("URL이 빈 문자열인 경우 EMPTY_URL에러를 반환한다.") + void createToast_Fail_EmptyUrl() { + // given + Long userId = 1L; + + SaveToastDto invalidDto = new SaveToastDto("", 1L); + SaveToastDto invalidDto2 = new SaveToastDto(" ", 1L); + + + when(userRepository.findByUserId(userId)).thenReturn(Optional.of(testUser)); + + // when & then + CustomException exception1 = assertThrows(CustomException.class, + () -> toastService.createToast(userId, invalidDto)); + CustomException exception2 = assertThrows(CustomException.class, + () -> toastService.createToast(userId, invalidDto2)); + + assertThat(exception1.getError()).isEqualTo(Error.EMPTY_URL); + assertThat(exception2.getError()).isEqualTo(Error.EMPTY_URL); + + } + + + +} \ No newline at end of file From 0d14bf9d84835eccaf8c956818a48d324856f304 Mon Sep 17 00:00:00 2001 From: soohyun Date: Sat, 28 Jun 2025 19:23:33 +0900 Subject: [PATCH 2/8] =?UTF-8?q?chore:=20=EC=97=90=EB=9F=AC=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/app/toaster/exception/Error.java | 2 +- .../java/com/app/toaster/toast/service/ToastService.java | 2 +- .../com/app/toaster/toast/service/ToastServiceTest.java | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/linkmind/src/main/java/com/app/toaster/exception/Error.java b/linkmind/src/main/java/com/app/toaster/exception/Error.java index 90d2c1c4..fb30c9fc 100644 --- a/linkmind/src/main/java/com/app/toaster/exception/Error.java +++ b/linkmind/src/main/java/com/app/toaster/exception/Error.java @@ -28,7 +28,7 @@ public enum Error { */ BAD_REQUEST_ISREAD(HttpStatus.BAD_REQUEST, "isRead 값이 잘못요청 되었습니다."), BAD_REQUEST_ID(HttpStatus.BAD_REQUEST, "잘못된 id값입니다."), - EMPTY_URL(HttpStatus.BAD_REQUEST, "빈 url로는 저장할 수 없습니다."), + BAD_REQUEST_EMPTY_URL(HttpStatus.BAD_REQUEST, "빈 url로는 저장할 수 없습니다."), BAD_REQUEST_VALIDATION(HttpStatus.BAD_REQUEST, "유효한 값으로 요청을 다시 보내주세요."), BAD_REQUEST_FILE_EXTENSION(HttpStatus.BAD_REQUEST, "파일형식이 잘못된 것 같습니다."), diff --git a/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java b/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java index be47419d..1b401f4f 100644 --- a/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java +++ b/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java @@ -52,7 +52,7 @@ public void createToast(Long userId, SaveToastDto saveToastDto){ //해당 유저 탐색 User presentUser = findUser(userId); if (saveToastDto.linkUrl() ==null || saveToastDto.linkUrl().isBlank()){ - throw new CustomException(Error.EMPTY_URL, Error.EMPTY_URL.getMessage()); + throw new CustomException(Error.BAD_REQUEST_EMPTY_URL, Error.BAD_REQUEST_EMPTY_URL.getMessage()); } //토스트 생성 try { diff --git a/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java b/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java index bdb1e194..18e4938d 100644 --- a/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java +++ b/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java @@ -2,7 +2,6 @@ import com.app.toaster.category.domain.Category; import com.app.toaster.category.infrastructure.CategoryRepository; -import com.app.toaster.category.service.CategoryService; import com.app.toaster.exception.Error; import com.app.toaster.exception.model.CustomException; import com.app.toaster.parse.controller.response.OgResponse; @@ -112,7 +111,7 @@ void createToast_Fail_NullUrl() { CustomException exception = assertThrows(CustomException.class, () -> toastService.createToast(userId, invalidDto)); - assertThat(exception.getError()).isEqualTo(Error.EMPTY_URL); + assertThat(exception.getError()).isEqualTo(Error.BAD_REQUEST_EMPTY_URL); } @Test @@ -133,8 +132,8 @@ void createToast_Fail_EmptyUrl() { CustomException exception2 = assertThrows(CustomException.class, () -> toastService.createToast(userId, invalidDto2)); - assertThat(exception1.getError()).isEqualTo(Error.EMPTY_URL); - assertThat(exception2.getError()).isEqualTo(Error.EMPTY_URL); + assertThat(exception1.getError()).isEqualTo(Error.BAD_REQUEST_EMPTY_URL); + assertThat(exception2.getError()).isEqualTo(Error.BAD_REQUEST_EMPTY_URL); } From 9320d4557ed7f784f7b8f6839429c42e6ec92c31 Mon Sep 17 00:00:00 2001 From: sss4920 Date: Thu, 3 Jul 2025 11:01:04 +0900 Subject: [PATCH 3/8] =?UTF-8?q?fix/#269:=20redirect=20302=EB=A5=BC=20?= =?UTF-8?q?=EB=9D=84=EC=9A=B0=EB=8A=94=20url=EC=9D=B8=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=20=EB=81=9D=EA=B9=8C=EC=A7=80=20=EB=93=A4=EC=96=B4=EA=B0=80?= =?UTF-8?q?=EC=84=9C=20=ED=97=A4=EB=8D=94=ED=8C=8C=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../toaster/parse/service/ParsingService.java | 15 ++++- .../parse/service/ParsingServiceTest.java | 62 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 linkmind/src/test/java/com/app/toaster/parse/service/ParsingServiceTest.java diff --git a/linkmind/src/main/java/com/app/toaster/parse/service/ParsingService.java b/linkmind/src/main/java/com/app/toaster/parse/service/ParsingService.java index 758674ff..4023a716 100644 --- a/linkmind/src/main/java/com/app/toaster/parse/service/ParsingService.java +++ b/linkmind/src/main/java/com/app/toaster/parse/service/ParsingService.java @@ -54,7 +54,12 @@ public OgResponse getOg(String linkUrl) throws IOException { private String getTitle(String linkUrl) throws IOException { try { - Document doc = Jsoup.connect(linkUrl).get(); + Document doc = Jsoup.connect(linkUrl) + .followRedirects(true) // 리다이렉션 자동 따라가기 + .maxBodySize(1024*1024) // 페이지 크기 제한 없음 + .timeout(10000) + .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") + .get(); Elements ogTitleElements = doc.select("meta[property=og:title]"); Elements titleElements = doc.select("head").select("title"); if (ogTitleElements.isEmpty() && titleElements.isEmpty()) { @@ -69,7 +74,13 @@ private String getTitle(String linkUrl) throws IOException { private String getImage(String linkUrl){ try { - Document doc = Jsoup.connect(linkUrl).get(); + Document doc = Jsoup.connect(linkUrl) + .followRedirects(true) // 리다이렉션 자동 따라가기 + .maxBodySize(1024*1024) // 페이지 크기 제한 없음 + .timeout(10000) + .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") + .get(); + Elements iframes = doc.select("iframe"); Elements ogBlogImage = new Elements(); if (!iframes.isEmpty()){ diff --git a/linkmind/src/test/java/com/app/toaster/parse/service/ParsingServiceTest.java b/linkmind/src/test/java/com/app/toaster/parse/service/ParsingServiceTest.java new file mode 100644 index 00000000..cd71a3f1 --- /dev/null +++ b/linkmind/src/test/java/com/app/toaster/parse/service/ParsingServiceTest.java @@ -0,0 +1,62 @@ +package com.app.toaster.parse.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.app.toaster.parse.controller.response.OgResponse; +import com.app.toaster.toast.controller.request.SaveToastDto; +import java.io.IOException; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; +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.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ParsingServiceTest { + + @InjectMocks + private ParsingService parsingService; + + @Test + @DisplayName("리다이렉션 Url에 대해서도 open graph 파싱이 잘된다.") + void getOgWhenRedirect302Url() throws IOException { + // given + String redirectUrl = createRedirect302CaseFixture().linkUrl(); + + // when - 직접 Jsoup으로 테스트 + Document doc = Jsoup.connect(redirectUrl) + .followRedirects(true) + .maxBodySize(1024 * 1024) + .timeout(10000) + .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") + .get(); + + Elements ogTitleElements = doc.select("meta[property=og:title]"); + Elements ogImageElements = doc.select("meta[property=og:image]"); + Elements titleElements = doc.select("title"); + + // then - Jsoup 레벨에서 먼저 검증 + System.out.println("OG Title: " + (ogTitleElements.isEmpty() ? "없음" : ogTitleElements.attr("content"))); + System.out.println("OG Image: " + (ogImageElements.isEmpty() ? "없음" : ogImageElements.attr("content"))); + System.out.println("Title: " + (titleElements.isEmpty() ? "없음" : titleElements.text())); + + // when - 서비스 레벨 테스트 + OgResponse result = parsingService.getOg(redirectUrl); + + // then + assertThat(result).isNotNull(); + assertThat(result.titleAdvanced()).isNotBlank(); + assertThat(result.imageAdvanced()).isNotBlank(); + + // 추가 검증 + assertThat(result.titleAdvanced()).isNotEqualTo("기본 토스트 제목"); + assertThat(result.imageAdvanced()).isNotEqualTo("BASIC_THUMBNAIL_URL"); + } + + private SaveToastDto createRedirect302CaseFixture(){ + return new SaveToastDto("https://digital.mk.co.kr/news_link.php?year=2025&no=469576", 1L); + } +} \ No newline at end of file From 49deb7815a55d3fbea0c4b903c84b1a9334bab38 Mon Sep 17 00:00:00 2001 From: sss4920 Date: Thu, 3 Jul 2025 15:23:23 +0900 Subject: [PATCH 4/8] =?UTF-8?q?chore/#269:=20IOException=20throws=EB=A1=9C?= =?UTF-8?q?=20=EC=9D=B4=EC=83=81=ED=95=98=EA=B2=8C=20=EB=8D=98=EC=A7=80?= =?UTF-8?q?=EB=8D=98=EA=B1=B0=20try-catch=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../toaster/parse/service/ParsingService.java | 44 ++++++++++--------- .../toast/controller/ToastController.java | 4 +- .../toaster/toast/service/ToastService.java | 39 +++++++--------- .../toast/service/ToastServiceTest.java | 2 +- 4 files changed, 42 insertions(+), 47 deletions(-) diff --git a/linkmind/src/main/java/com/app/toaster/parse/service/ParsingService.java b/linkmind/src/main/java/com/app/toaster/parse/service/ParsingService.java index 4023a716..f7f63abf 100644 --- a/linkmind/src/main/java/com/app/toaster/parse/service/ParsingService.java +++ b/linkmind/src/main/java/com/app/toaster/parse/service/ParsingService.java @@ -31,28 +31,22 @@ public ParsingService(@Value("${static-image.url}") final String basicThumbnail) this.BASIC_THUMBNAIL = basicThumbnail; } - public OgResponse getOg(String linkUrl) throws IOException { - try { - String title = getTitle(linkUrl); - log.info(title); - String image = getImage(linkUrl); - log.info(image); - return OgResponse.of( - title == null || title.isBlank() ? "기본 토스트 제목" : title, - image == null || image.isBlank() ? BASIC_THUMBNAIL : image - ); - }catch (HttpStatusException | SSLHandshakeException e){ - return OgResponse.of("15자 내로 제목을 지어주세요.", BASIC_THUMBNAIL); - }catch (ConnectException e){ - throw new BadRequestException(Error.BAD_REQUEST_URL, Error.BAD_REQUEST_URL.getMessage()); - } + public OgResponse getOg(String linkUrl) { + String title = getTitle(linkUrl); + log.info(title); + String image = getImage(linkUrl); + log.info(image); + return OgResponse.of( + title == null || title.isBlank() ? "기본 토스트 제목" : title, + image == null || image.isBlank() ? BASIC_THUMBNAIL : image + ); } // public String getOg(String linkUrl) throws IOException { // String image = getImage(linkUrl); // return image == null || image.isBlank() ? BASIC_THUMBNAIL : image; // } - private String getTitle(String linkUrl) throws IOException { + private String getTitle(String linkUrl) { try { Document doc = Jsoup.connect(linkUrl) .followRedirects(true) // 리다이렉션 자동 따라가기 @@ -63,13 +57,20 @@ private String getTitle(String linkUrl) throws IOException { Elements ogTitleElements = doc.select("meta[property=og:title]"); Elements titleElements = doc.select("head").select("title"); if (ogTitleElements.isEmpty() && titleElements.isEmpty()) { - return null; + log.info("[NOT FOUND] og 데이터, html header 뜯었는데 결과 없음."); + return "15자 내로 제목을 지어주세요."; } return ogTitleElements.isEmpty()?titleElements.get(0).text(): ogTitleElements.get(0).attr("content"); }catch (org.jsoup.HttpStatusException e){ - return null; + log.info("[ERROR] title 파싱 중 http status 에러 발생"); + return "15자 내로 제목을 지어주세요."; + } catch (SSLHandshakeException e){ + log.info("[ERROR] 너무 오래된 사이트라 handshake 규칙이 맞지 않습니다."); + return "15자 내로 제목을 지어주세요."; + } catch (IOException e){ + log.info("[ERROR] title 파싱 중 에러 발생"); + return "15자 내로 제목을 지어주세요."; } - } private String getImage(String linkUrl){ @@ -93,7 +94,10 @@ private String getImage(String linkUrl){ return findImageAnywhere(ogImageElements, ogImage, ogBlogImage); }catch (MalformedURLException e){ throw new CustomException(Error.MALFORMED_URL_EXEPTION,Error.MALFORMED_URL_EXEPTION.getMessage()); - }catch (org.jsoup.HttpStatusException e){ + }catch (org.jsoup.HttpStatusException e) { + return null; + }catch (SSLHandshakeException e){ + log.info("[ERROR] 너무 오래된 사이트라 handshake 규칙이 맞지 않습니다."); return null; }catch (IOException e){ throw new CustomException(Error.NOT_FOUND_IMAGE_EXCEPTION, Error.NOT_FOUND_IMAGE_EXCEPTION.getMessage()); diff --git a/linkmind/src/main/java/com/app/toaster/toast/controller/ToastController.java b/linkmind/src/main/java/com/app/toaster/toast/controller/ToastController.java index 096b9265..6b029a34 100644 --- a/linkmind/src/main/java/com/app/toaster/toast/controller/ToastController.java +++ b/linkmind/src/main/java/com/app/toaster/toast/controller/ToastController.java @@ -42,7 +42,7 @@ public class ToastController { @Deprecated public ApiResponse getOgAdvanced( @RequestBody OgRequestDto ogRequestDto - ) throws IOException { + ) { return ApiResponse.success(Success.PARSING_OG_SUCCESS, parsingService.getOg(ogRequestDto.linkUrl())); } @@ -72,7 +72,7 @@ public ApiResponse updateIsRead( public ApiResponse deleteToast( //나중에 softDelete로 변경 @UserId Long userId, @RequestParam Long toastId - ) throws IOException { + ) { toastService.deleteToast(userId, toastId); return ApiResponse.success(Success.DELETE_TOAST_SUCCESS); } diff --git a/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java b/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java index 1b401f4f..3091da1f 100644 --- a/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java +++ b/linkmind/src/main/java/com/app/toaster/toast/service/ToastService.java @@ -55,30 +55,21 @@ public void createToast(Long userId, SaveToastDto saveToastDto){ throw new CustomException(Error.BAD_REQUEST_EMPTY_URL, Error.BAD_REQUEST_EMPTY_URL.getMessage()); } //토스트 생성 - try { - OgResponse res = parsingService.getOg(saveToastDto.linkUrl()); - //byte 배열로 읽어들임. - log.info(res.titleAdvanced()); - log.info(res.imageAdvanced()); - String imageString = checkIsBasicImage(res.imageAdvanced()); - // // ImagePresignedUrlResponse realRes = getUploadPreSignedUrl(res.imageAdvanced()); - // log.info(realRes.fileName()); - // log.info(realRes.preSignedUrl()); - - - //presigned url - Toast toast = Toast.builder() - .user(presentUser) - .linkUrl(saveToastDto.linkUrl()) - .title(res.titleAdvanced()) - .thumbnailUrl(imageString) - .build(); - // 만약 유저에게 만들어져있는 카테고리가 없는지 확인하고 - checkCategoryIsEmpty(toast, saveToastDto.categoryId()); - toastRepository.save(toast); - } catch (IOException e ) { //여기서 에러 발생 시 외부 s3 문제일 수 도 있으므로 500으로 에러 예상 범위 알림. - throw new CustomException(Error.CREATE_TOAST_PROCCESS_EXCEPTION, Error.CREATE_TOAST_PROCCESS_EXCEPTION.getMessage()); - } + OgResponse res = parsingService.getOg(saveToastDto.linkUrl()); + //byte 배열로 읽어들임. + log.info(res.titleAdvanced()); + log.info(res.imageAdvanced()); + String imageString = checkIsBasicImage(res.imageAdvanced()); + + Toast toast = Toast.builder() + .user(presentUser) + .linkUrl(saveToastDto.linkUrl()) + .title(res.titleAdvanced()) + .thumbnailUrl(imageString) + .build(); + // 만약 유저에게 만들어져있는 카테고리가 없는지 확인하고 + checkCategoryIsEmpty(toast, saveToastDto.categoryId()); + toastRepository.save(toast); } @Transactional diff --git a/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java b/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java index 18e4938d..bae388e5 100644 --- a/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java +++ b/linkmind/src/test/java/com/app/toaster/toast/service/ToastServiceTest.java @@ -71,7 +71,7 @@ void setUp() { @Test @DisplayName("Toast 생성 성공 테스트") - void createToast_Success() throws IOException { + void createToast_Success() { // given Long userId = 1L; From 345c9b32f1491f84ec4219e841e7fd3ceccef4e8 Mon Sep 17 00:00:00 2001 From: mmihye Date: Mon, 14 Jul 2025 18:33:15 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[#271]=20fix:=20title=20column=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20text=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- linkmind/src/main/java/com/app/toaster/toast/domain/Toast.java | 1 + 1 file changed, 1 insertion(+) diff --git a/linkmind/src/main/java/com/app/toaster/toast/domain/Toast.java b/linkmind/src/main/java/com/app/toaster/toast/domain/Toast.java index 07918e50..6aa2e9a0 100644 --- a/linkmind/src/main/java/com/app/toaster/toast/domain/Toast.java +++ b/linkmind/src/main/java/com/app/toaster/toast/domain/Toast.java @@ -27,6 +27,7 @@ public class Toast { @JoinColumn(name = "category_id") private Category category; + @Column(columnDefinition = "TEXT") private String title; @Column(columnDefinition = "TEXT") From e760a9cd8ed1af454b570432e2fe9c001cb61a82 Mon Sep 17 00:00:00 2001 From: soohyun Date: Sun, 31 Aug 2025 14:28:45 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feature:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=EC=8B=9C=20ios=20=EC=9A=94=EC=B2=AD=EC=9D=B4=EB=A9=B4?= =?UTF-8?q?=20os=20=ED=95=84=EB=93=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=95=88=EB=93=9C=EA=B0=80=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=90=A0=EC=A7=80=20=EB=AA=B0=EB=9D=BC?= =?UTF-8?q?=EC=84=9C=20enum=EC=9C=BC=EB=A1=9C=20=EB=B9=BC=EB=8A=94?= =?UTF-8?q?=EA=B1=B4=20=EC=95=A0=EB=A7=A4=ED=95=B4=EC=84=9C=20=EC=9D=BC?= =?UTF-8?q?=EB=8B=A8=20String=EC=9C=BC=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/toaster/auth/controller/AuthController.java | 4 +++- .../auth/controller/response/SignInResponseDto.java | 11 ++++++----- .../com/app/toaster/auth/service/AuthService.java | 12 +++++++++--- .../main/java/com/app/toaster/user/domain/User.java | 7 +++++++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/linkmind/src/main/java/com/app/toaster/auth/controller/AuthController.java b/linkmind/src/main/java/com/app/toaster/auth/controller/AuthController.java index 9d53f49c..dbb666fa 100644 --- a/linkmind/src/main/java/com/app/toaster/auth/controller/AuthController.java +++ b/linkmind/src/main/java/com/app/toaster/auth/controller/AuthController.java @@ -2,6 +2,7 @@ import java.io.IOException; +import jakarta.annotation.Nullable; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -33,9 +34,10 @@ public class AuthController { @ResponseStatus(HttpStatus.OK) public ApiResponse signIn( @RequestHeader("Authorization") String socialAccessToken, + @RequestHeader("TOASTER-OS") @Nullable String os, @RequestBody SignInRequestDto requestDto ) throws IOException { - return ApiResponse.success(Success.LOGIN_SUCCESS, authService.signIn(socialAccessToken, requestDto)); + return ApiResponse.success(Success.LOGIN_SUCCESS, authService.signIn(socialAccessToken, requestDto, os)); } @PostMapping("/token") diff --git a/linkmind/src/main/java/com/app/toaster/auth/controller/response/SignInResponseDto.java b/linkmind/src/main/java/com/app/toaster/auth/controller/response/SignInResponseDto.java index a7269605..31aeb3b7 100644 --- a/linkmind/src/main/java/com/app/toaster/auth/controller/response/SignInResponseDto.java +++ b/linkmind/src/main/java/com/app/toaster/auth/controller/response/SignInResponseDto.java @@ -1,8 +1,9 @@ package com.app.toaster.auth.controller.response; -public record SignInResponseDto(Long userId, String accessToken, String refreshToken, String fcmToken, Boolean isRegistered,Boolean fcmIsAllowed, String profile) { - public static SignInResponseDto of(Long userId, String accessToken, String refreshToken, String fcmToken, - Boolean isRegistered, Boolean fcmIsAllowed, String profile){ - return new SignInResponseDto(userId,accessToken, refreshToken,fcmToken,isRegistered,fcmIsAllowed,profile); - } +public record SignInResponseDto(Long userId, String accessToken, String refreshToken, String fcmToken, + Boolean isRegistered, Boolean fcmIsAllowed, String profile, String os) { + public static SignInResponseDto of(Long userId, String accessToken, String refreshToken, String fcmToken, + Boolean isRegistered, Boolean fcmIsAllowed, String profile, String os) { + return new SignInResponseDto(userId, accessToken, refreshToken, fcmToken, isRegistered, fcmIsAllowed, profile, os); + } } diff --git a/linkmind/src/main/java/com/app/toaster/auth/service/AuthService.java b/linkmind/src/main/java/com/app/toaster/auth/service/AuthService.java index 497234f8..38e6de75 100644 --- a/linkmind/src/main/java/com/app/toaster/auth/service/AuthService.java +++ b/linkmind/src/main/java/com/app/toaster/auth/service/AuthService.java @@ -63,7 +63,7 @@ public class AuthService { private final TimerRepository timerRepository; @Transactional - public SignInResponseDto signIn(String socialAccessToken, SignInRequestDto requestDto) throws IOException { + public SignInResponseDto signIn(String socialAccessToken, SignInRequestDto requestDto, String os) throws IOException { SocialType socialType = SocialType.valueOf(requestDto.socialType()); LoginResult loginResult = login(socialType, socialAccessToken); String socialId = loginResult.id(); @@ -99,8 +99,14 @@ public SignInResponseDto signIn(String socialAccessToken, SignInRequestDto reque if (nickname!=null){ //탈퇴 안했던 유저들도 수정될 수 있도록 변경 user.updateNickname(nickname); } - return SignInResponseDto.of(user.getUserId(), accessToken, refreshToken, fcmToken, isRegistered,user.getFcmIsAllowed(), - user.getProfile()); + System.out.println(os); + + if (os != null && os.equals("IOS")){ + user.updateOs(os); + } + + return SignInResponseDto.of(user.getUserId(), accessToken, refreshToken, fcmToken, isRegistered, user.getFcmIsAllowed(), + user.getProfile(), user.getOs()); } @Transactional diff --git a/linkmind/src/main/java/com/app/toaster/user/domain/User.java b/linkmind/src/main/java/com/app/toaster/user/domain/User.java index a09dec12..80f09506 100644 --- a/linkmind/src/main/java/com/app/toaster/user/domain/User.java +++ b/linkmind/src/main/java/com/app/toaster/user/domain/User.java @@ -45,6 +45,9 @@ public class User { @Column(nullable = true) private String profile; + @Column(nullable = true) + private String os; + @Builder public User(String nickname, String socialId, SocialType socialType) { this.nickname = nickname; @@ -75,4 +78,8 @@ public void updateProfile(String profile){ this.profile = profile; } + public void updateOs(String os){ + this.os = os; + } + } From 9b49954177085687d361c1a4fec5f5d76485f367 Mon Sep 17 00:00:00 2001 From: soohyun Date: Mon, 1 Sep 2025 00:02:16 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=EC=97=91=EC=84=B8=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EB=B0=9C=EA=B8=89=20=EC=8B=9C=EC=97=90?= =?UTF-8?q?=EB=8F=84=20ios=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20os=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 64 +++++++++---------- .../app/toaster/auth/service/AuthService.java | 5 +- .../toaster/common/config/jwt/JwtService.java | 2 + 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/linkmind/src/main/java/com/app/toaster/auth/controller/AuthController.java b/linkmind/src/main/java/com/app/toaster/auth/controller/AuthController.java index dbb666fa..29cad484 100644 --- a/linkmind/src/main/java/com/app/toaster/auth/controller/AuthController.java +++ b/linkmind/src/main/java/com/app/toaster/auth/controller/AuthController.java @@ -28,41 +28,41 @@ @RequiredArgsConstructor @RequestMapping("/auth") public class AuthController { - private final AuthService authService; + private final AuthService authService; - @PostMapping - @ResponseStatus(HttpStatus.OK) - public ApiResponse signIn( - @RequestHeader("Authorization") String socialAccessToken, - @RequestHeader("TOASTER-OS") @Nullable String os, - @RequestBody SignInRequestDto requestDto - ) throws IOException { - return ApiResponse.success(Success.LOGIN_SUCCESS, authService.signIn(socialAccessToken, requestDto, os)); - } + @PostMapping + @ResponseStatus(HttpStatus.OK) + public ApiResponse signIn( + @RequestHeader("Authorization") String socialAccessToken, + @RequestHeader("TOASTER-OS") @Nullable String os, + @RequestBody SignInRequestDto requestDto + ) throws IOException { + return ApiResponse.success(Success.LOGIN_SUCCESS, authService.signIn(socialAccessToken, requestDto, os)); + } - @PostMapping("/token") - @ResponseStatus(HttpStatus.OK) - public ApiResponse reissueToken(@RequestHeader String refreshToken) { - return ApiResponse.success(Success.RE_ISSUE_TOKEN_SUCCESS, authService.issueToken(refreshToken)); - } + @PostMapping("/token") + @ResponseStatus(HttpStatus.OK) + public ApiResponse reissueToken(@RequestHeader String refreshToken, @RequestHeader("TOASTER-OS") @Nullable String os) { + return ApiResponse.success(Success.RE_ISSUE_TOKEN_SUCCESS, authService.issueToken(refreshToken, os)); + } - @PostMapping("/sign-out") - @ResponseStatus(HttpStatus.OK) - public ApiResponse signOut(@UserId Long userId) { - authService.signOut(userId); - return ApiResponse.success(Success.SIGNOUT_SUCCESS); - } + @PostMapping("/sign-out") + @ResponseStatus(HttpStatus.OK) + public ApiResponse signOut(@UserId Long userId) { + authService.signOut(userId); + return ApiResponse.success(Success.SIGNOUT_SUCCESS); + } - @DeleteMapping("/withdraw") - @ResponseStatus(HttpStatus.OK) - public ApiResponse withdraw(@UserId Long userId) { - authService.withdraw(userId); - return ApiResponse.success(Success.DELETE_USER_SUCCESS); - } + @DeleteMapping("/withdraw") + @ResponseStatus(HttpStatus.OK) + public ApiResponse withdraw(@UserId Long userId) { + authService.withdraw(userId); + return ApiResponse.success(Success.DELETE_USER_SUCCESS); + } - @PostMapping("/token/health") - @ResponseStatus(HttpStatus.OK) - public ApiResponse checkHealthOfToken(@RequestHeader String token) { - return ApiResponse.success(Success.TOKEN_HEALTH_CHECKED_SUCCESS, authService.checkHealthOfToken(token)); - } + @PostMapping("/token/health") + @ResponseStatus(HttpStatus.OK) + public ApiResponse checkHealthOfToken(@RequestHeader String token) { + return ApiResponse.success(Success.TOKEN_HEALTH_CHECKED_SUCCESS, authService.checkHealthOfToken(token)); + } } diff --git a/linkmind/src/main/java/com/app/toaster/auth/service/AuthService.java b/linkmind/src/main/java/com/app/toaster/auth/service/AuthService.java index 38e6de75..1d5ff810 100644 --- a/linkmind/src/main/java/com/app/toaster/auth/service/AuthService.java +++ b/linkmind/src/main/java/com/app/toaster/auth/service/AuthService.java @@ -110,7 +110,7 @@ public SignInResponseDto signIn(String socialAccessToken, SignInRequestDto reque } @Transactional - public TokenResponseDto issueToken(String refreshToken) { + public TokenResponseDto issueToken(String refreshToken, String os) { jwtService.verifyToken(refreshToken); User user = userRepository.findByRefreshToken(refreshToken) @@ -122,6 +122,9 @@ public TokenResponseDto issueToken(String refreshToken) { user.updateRefreshToken(newRefreshToken); + if (os != null && os.equals("IOS")){ + user.updateOs(os); + } return TokenResponseDto.of(newAccessToken, newRefreshToken); } diff --git a/linkmind/src/main/java/com/app/toaster/common/config/jwt/JwtService.java b/linkmind/src/main/java/com/app/toaster/common/config/jwt/JwtService.java index a7770db4..a1130c0c 100644 --- a/linkmind/src/main/java/com/app/toaster/common/config/jwt/JwtService.java +++ b/linkmind/src/main/java/com/app/toaster/common/config/jwt/JwtService.java @@ -62,10 +62,12 @@ private SecretKey getSigningKey() { // JWT 토큰 검증 public boolean verifyToken(String token) { try { + System.out.println(token); final Claims claims = getBody(token); return true; } catch (RuntimeException e) { if (e instanceof ExpiredJwtException) { + System.out.println("여기1"); throw new UnauthorizedException(Error.TOKEN_TIME_EXPIRED_EXCEPTION, Error.TOKEN_TIME_EXPIRED_EXCEPTION.getMessage()); } throw new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage()); From ccebc998e50803a575104e1241547c0e9d5bcf36 Mon Sep 17 00:00:00 2001 From: soohyun Date: Mon, 1 Sep 2025 01:41:08 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feature:=20share=20clip=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/app/toaster/exception/Success.java | 1 + .../share_clip/ShareClipController.java | 23 +++++ .../client/share_clip/ShareClipService.java | 85 +++++++++++++++++++ .../request/ClipInfoRequestDto.java | 9 ++ .../request/CreateShareClipRequestDto.java | 8 ++ .../request/ShareToastInfoRequestDto.java | 8 ++ .../request/UserInfoRequestDto.java | 7 ++ .../response/ShareClipResponseDto.java | 15 ++++ .../toast/infrastructure/ToastRepository.java | 2 + 9 files changed, 158 insertions(+) create mode 100644 linkmind/src/main/java/com/app/toaster/external/client/share_clip/ShareClipController.java create mode 100644 linkmind/src/main/java/com/app/toaster/external/client/share_clip/ShareClipService.java create mode 100644 linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/ClipInfoRequestDto.java create mode 100644 linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/CreateShareClipRequestDto.java create mode 100644 linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/ShareToastInfoRequestDto.java create mode 100644 linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/UserInfoRequestDto.java create mode 100644 linkmind/src/main/java/com/app/toaster/external/client/share_clip/response/ShareClipResponseDto.java diff --git a/linkmind/src/main/java/com/app/toaster/exception/Success.java b/linkmind/src/main/java/com/app/toaster/exception/Success.java index 34055ba3..a4ef20bc 100644 --- a/linkmind/src/main/java/com/app/toaster/exception/Success.java +++ b/linkmind/src/main/java/com/app/toaster/exception/Success.java @@ -15,6 +15,7 @@ public enum Success { CREATE_TOAST_SUCCESS(HttpStatus.CREATED, "토스트 저장이 완료 되었습니다."), CREATE_CATEGORY_SUCCESS(HttpStatus.CREATED, "새 카테고리 추가 성공"), CREATE_TIMER_SUCCESS(HttpStatus.CREATED, "새 타이머 생성 성공"), + CREATE_SHARE_CLIP(HttpStatus.CREATED, "공유클립 생성 성공"), /** * 200 OK diff --git a/linkmind/src/main/java/com/app/toaster/external/client/share_clip/ShareClipController.java b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/ShareClipController.java new file mode 100644 index 00000000..7e263ad3 --- /dev/null +++ b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/ShareClipController.java @@ -0,0 +1,23 @@ +package com.app.toaster.external.client.share_clip; + +import com.app.toaster.common.dto.ApiResponse; +import com.app.toaster.exception.Success; +import com.app.toaster.external.client.share_clip.request.CreateShareClipRequestDto; +import lombok.RequiredArgsConstructor; +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.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/share-clip") +public class ShareClipController { + + private final ShareClipService shareClipService; + + @PostMapping + public ApiResponse createShareClip(@RequestBody CreateShareClipRequestDto createShareClipRequestDto){ + return ApiResponse.success(Success.CREATE_SHARE_CLIP, shareClipService.createShareClip(createShareClipRequestDto)); + } +} diff --git a/linkmind/src/main/java/com/app/toaster/external/client/share_clip/ShareClipService.java b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/ShareClipService.java new file mode 100644 index 00000000..8be57d63 --- /dev/null +++ b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/ShareClipService.java @@ -0,0 +1,85 @@ +package com.app.toaster.external.client.share_clip; + +import com.app.toaster.category.domain.Category; +import com.app.toaster.category.infrastructure.CategoryRepository; +import com.app.toaster.exception.Error; +import com.app.toaster.exception.model.CustomException; +import com.app.toaster.external.client.share_clip.request.ClipInfoRequestDto; +import com.app.toaster.external.client.share_clip.request.CreateShareClipRequestDto; +import com.app.toaster.external.client.share_clip.request.UserInfoRequestDto; +import com.app.toaster.external.client.share_clip.response.ShareClipResponseDto; +import com.app.toaster.toast.domain.Toast; +import com.app.toaster.toast.infrastructure.ToastRepository; +import com.app.toaster.user.domain.SocialType; +import com.app.toaster.user.domain.User; +import com.app.toaster.user.infrastructure.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + + +@Service +@RequiredArgsConstructor +public class ShareClipService { + + private final CategoryRepository categoryRepository; + private final ToastRepository toastRepository; + private final UserRepository userRepository; + private final static int MAX_CATERGORY_NUMBER = 15; + + @Transactional + public ShareClipResponseDto createShareClip(CreateShareClipRequestDto createShareClipRequestDto){ + try { + UserInfoRequestDto userInfoRequestDto = createShareClipRequestDto.userInfoRequestDto(); + ClipInfoRequestDto clipInfoRequestDto = createShareClipRequestDto.clipDto(); + + User user = userRepository.findBySocialIdAndSocialType(userInfoRequestDto.receiverSocialId(), SocialType.valueOf(userInfoRequestDto.receiverSocialType())) + .orElseThrow(() -> new CustomException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage())); + + Category category = createCategory(user, clipInfoRequestDto); + + List toastList = clipInfoRequestDto.toasts().stream().map( + (toast) -> Toast.builder() + .thumbnailUrl(toast.linkUrl()) + .linkUrl(toast.linkUrl()) + .title(toast.title()) + .category(category) + .user(user) + .build() + ).toList(); + + toastRepository.saveAll(toastList); + return ShareClipResponseDto.success(user.getUserId(), category.getCategoryId()); + }catch (Exception e){ + return ShareClipResponseDto.fail(e.getMessage()); + } + } + + private Category createCategory(User presentUser, ClipInfoRequestDto clipDto){ + val maxPriority = categoryRepository.findMaxPriorityByUser(presentUser); + + val categoryNum = categoryRepository.countAllByUser(presentUser); + System.out.println(categoryNum); + + if (categoryNum >= MAX_CATERGORY_NUMBER) { + throw new CustomException(Error.BAD_REQUEST_CREATE_CLIP_EXCEPTION, + Error.BAD_REQUEST_CREATE_CLIP_EXCEPTION.getMessage()); + } + + if(categoryRepository.countAllByTitleAndUser(clipDto.clipTitle(),presentUser)>0){ + throw new CustomException(Error.UNPROCESSABLE_CREATE_TIMER_EXCEPTION, Error.UNPROCESSABLE_CREATE_TIMER_EXCEPTION.getMessage()); + } + + //카테고리 생성 + Category newCategory = Category.builder() + .title(clipDto.clipTitle()) + .user(presentUser) + .priority(maxPriority + 1) + .build(); + return categoryRepository.save(newCategory); + } + +} diff --git a/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/ClipInfoRequestDto.java b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/ClipInfoRequestDto.java new file mode 100644 index 00000000..0b08bd24 --- /dev/null +++ b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/ClipInfoRequestDto.java @@ -0,0 +1,9 @@ +package com.app.toaster.external.client.share_clip.request; + +import java.util.List; + +public record ClipInfoRequestDto( + String clipTitle, + List toasts +) { +} diff --git a/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/CreateShareClipRequestDto.java b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/CreateShareClipRequestDto.java new file mode 100644 index 00000000..65bac344 --- /dev/null +++ b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/CreateShareClipRequestDto.java @@ -0,0 +1,8 @@ +package com.app.toaster.external.client.share_clip.request; + + +public record CreateShareClipRequestDto( + UserInfoRequestDto userInfoRequestDto, + ClipInfoRequestDto clipDto +) { +} diff --git a/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/ShareToastInfoRequestDto.java b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/ShareToastInfoRequestDto.java new file mode 100644 index 00000000..fc099611 --- /dev/null +++ b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/ShareToastInfoRequestDto.java @@ -0,0 +1,8 @@ +package com.app.toaster.external.client.share_clip.request; + +public record ShareToastInfoRequestDto( + String title, + String thumbnail, + String linkUrl +) { +} diff --git a/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/UserInfoRequestDto.java b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/UserInfoRequestDto.java new file mode 100644 index 00000000..26c06bc3 --- /dev/null +++ b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/request/UserInfoRequestDto.java @@ -0,0 +1,7 @@ +package com.app.toaster.external.client.share_clip.request; + +public record UserInfoRequestDto( + String receiverSocialId, + String receiverSocialType +) { +} diff --git a/linkmind/src/main/java/com/app/toaster/external/client/share_clip/response/ShareClipResponseDto.java b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/response/ShareClipResponseDto.java new file mode 100644 index 00000000..6bafd7ed --- /dev/null +++ b/linkmind/src/main/java/com/app/toaster/external/client/share_clip/response/ShareClipResponseDto.java @@ -0,0 +1,15 @@ +package com.app.toaster.external.client.share_clip.response; + +public record ShareClipResponseDto( + String result, + Long clipId, + Long userId +) { + public static ShareClipResponseDto success(Long userId, Long clipId){ + return new ShareClipResponseDto("Y", userId, clipId); + } + + public static ShareClipResponseDto fail(String message){ + return new ShareClipResponseDto(message, null, null); + } +} diff --git a/linkmind/src/main/java/com/app/toaster/toast/infrastructure/ToastRepository.java b/linkmind/src/main/java/com/app/toaster/toast/infrastructure/ToastRepository.java index 6be78d39..2a51d470 100644 --- a/linkmind/src/main/java/com/app/toaster/toast/infrastructure/ToastRepository.java +++ b/linkmind/src/main/java/com/app/toaster/toast/infrastructure/ToastRepository.java @@ -20,6 +20,8 @@ public interface ToastRepository extends JpaRepository { ArrayList findByIsReadAndCategory(Boolean isRead, Category category); + ArrayList findByUser(User user); + ArrayList getAllByUser(User user); List getAllByUserOrderByCreatedAtDesc(User user);