From 6c351966ddbb768afa24a0eb25e14ace20f7121c Mon Sep 17 00:00:00 2001 From: lsn5963 <77337977+lsn5963@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:48:38 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[FIX]=20=ED=86=A0=ED=81=B0=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=201=EB=8B=AC=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 --- .../com/movelog/domain/auth/presentation/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/movelog/domain/auth/presentation/AuthController.java b/src/main/java/com/movelog/domain/auth/presentation/AuthController.java index dcb0d63..d2b6d2a 100644 --- a/src/main/java/com/movelog/domain/auth/presentation/AuthController.java +++ b/src/main/java/com/movelog/domain/auth/presentation/AuthController.java @@ -61,7 +61,7 @@ public ResponseEntity login(@RequestBody IdTokenReq idTokenReq) { authService.findOrCreateUser(idTokenReq.provider(), idTokenReq.idToken()); String email = authService.findEmail(providerId); - String accessToken = jwtUtil.createJwt("access", providerId, "ROLE_USER", 3600000L, email); + String accessToken = jwtUtil.createJwt("access", providerId, "ROLE_USER", 2592000000L, email); System.out.println("accessToken = " + accessToken); User user = userRepository.findByProviderId(providerId) .orElseThrow(EntityNotFoundException::new); From 216de5bdbbd4e18ce69077f17a3e69f6d53ae92a Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Wed, 8 Jan 2025 22:04:13 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[DOCS]=20=EB=89=B4=EC=8A=A4=20=ED=97=A4?= =?UTF-8?q?=EB=93=9C=EB=9D=BC=EC=9D=B8=20=EC=9A=94=EC=B2=AD=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=86=8C=EB=AC=B8=EC=9E=90=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/openapi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/openapi.json b/src/main/resources/static/openapi.json index 0017c56..4a3411a 100644 --- a/src/main/resources/static/openapi.json +++ b/src/main/resources/static/openapi.json @@ -1140,7 +1140,7 @@ "NewsHeadLineReq": { "type": "object", "properties": { - "Option": { + "option": { "type": "string", "description": "뉴스 헤드라인 고정 옵션" }, From 98d0504372a2fd2717b5a3668c184f4ff82c6dd7 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Sun, 12 Jan 2025 17:32:03 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[FEAT]=20User=20Exception=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/application/UserService.java | 9 ++++++--- .../user/exception/UserDuplicateException.java | 11 +++++++++++ .../user/exception/UserNotFoundException.java | 10 ++++++++++ .../global/exception/DuplicateException.java | 17 +++++++++++++++++ .../global/exception/NotFoundException.java | 17 +++++++++++++++++ 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/movelog/domain/user/exception/UserDuplicateException.java create mode 100644 src/main/java/com/movelog/domain/user/exception/UserNotFoundException.java create mode 100644 src/main/java/com/movelog/global/exception/DuplicateException.java create mode 100644 src/main/java/com/movelog/global/exception/NotFoundException.java diff --git a/src/main/java/com/movelog/domain/user/application/UserService.java b/src/main/java/com/movelog/domain/user/application/UserService.java index 1cdd3be..2d099c1 100644 --- a/src/main/java/com/movelog/domain/user/application/UserService.java +++ b/src/main/java/com/movelog/domain/user/application/UserService.java @@ -1,13 +1,13 @@ package com.movelog.domain.user.application; +import com.movelog.domain.user.domain.User; import com.movelog.domain.user.domain.repository.UserRepository; -import com.movelog.global.payload.Message; -import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; + +import java.util.Optional; @RequiredArgsConstructor @Service @@ -16,4 +16,7 @@ public class UserService { private final UserRepository userRepository; + public Optional findById(Long id) { + return userRepository.findById(id); + } } diff --git a/src/main/java/com/movelog/domain/user/exception/UserDuplicateException.java b/src/main/java/com/movelog/domain/user/exception/UserDuplicateException.java new file mode 100644 index 0000000..4224b43 --- /dev/null +++ b/src/main/java/com/movelog/domain/user/exception/UserDuplicateException.java @@ -0,0 +1,11 @@ +package com.movelog.domain.user.exception; + + +import com.movelog.global.exception.DuplicateException; + +public class UserDuplicateException extends DuplicateException { + + public UserDuplicateException() { + super("U002", "이미 존재하는 사용자입니다."); + } +} diff --git a/src/main/java/com/movelog/domain/user/exception/UserNotFoundException.java b/src/main/java/com/movelog/domain/user/exception/UserNotFoundException.java new file mode 100644 index 0000000..8606c12 --- /dev/null +++ b/src/main/java/com/movelog/domain/user/exception/UserNotFoundException.java @@ -0,0 +1,10 @@ +package com.movelog.domain.user.exception; + +import com.movelog.global.exception.NotFoundException; + +public class UserNotFoundException extends NotFoundException { + + public UserNotFoundException() { + super("U001", "사용자를 찾을 수 없습니다."); + } +} diff --git a/src/main/java/com/movelog/global/exception/DuplicateException.java b/src/main/java/com/movelog/global/exception/DuplicateException.java new file mode 100644 index 0000000..f307054 --- /dev/null +++ b/src/main/java/com/movelog/global/exception/DuplicateException.java @@ -0,0 +1,17 @@ +package com.movelog.global.exception; + +import lombok.Getter; + +@Getter +public class DuplicateException extends RuntimeException { + private final int statusCode; + private final String code; + private final String message; + + public DuplicateException(String code, String message) { + super(message); + this.statusCode = 409; // Conflict status code + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/com/movelog/global/exception/NotFoundException.java b/src/main/java/com/movelog/global/exception/NotFoundException.java new file mode 100644 index 0000000..b59c5d8 --- /dev/null +++ b/src/main/java/com/movelog/global/exception/NotFoundException.java @@ -0,0 +1,17 @@ +package com.movelog.global.exception; + +import lombok.Getter; + +@Getter +public class NotFoundException extends RuntimeException { + private final int statusCode; + private final String code; + private final String message; + + public NotFoundException(String code, String message) { + super(message); + this.statusCode = 404; + this.code = code; + this.message = message; + } +} From 55699e562ff661951a9ccc19d63eef3f794e0547 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Sun, 12 Jan 2025 17:33:17 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[DOCS]=20=ED=97=A4=EB=93=9C=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=83=9D=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=A0=95=EB=B3=B4=20=EB=AC=B8=EC=84=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/openapi.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/resources/static/openapi.json b/src/main/resources/static/openapi.json index 4a3411a..40ea835 100644 --- a/src/main/resources/static/openapi.json +++ b/src/main/resources/static/openapi.json @@ -672,7 +672,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Message" + "type": "array", + "items": { + "$ref": "#/components/schemas/NewsHeadLineRes" + } } } } @@ -1154,6 +1157,15 @@ } } }, + "NewsHeadLineRes": { + "type": "object", + "properties": { + "headline": { + "type": "string", + "description": "뉴스 헤드라인" + } + } + }, "WordRecommendRes": { "type": "object", "properties": { From ac6115c2fda90c92b5743e3f9655dab653211478 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Sun, 12 Jan 2025 17:34:31 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[FEAT]=20=ED=97=A4=EB=93=9C=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?GPT=20API=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/gpt/application/GptService.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/main/java/com/movelog/domain/gpt/application/GptService.java diff --git a/src/main/java/com/movelog/domain/gpt/application/GptService.java b/src/main/java/com/movelog/domain/gpt/application/GptService.java new file mode 100644 index 0000000..51ceb0b --- /dev/null +++ b/src/main/java/com/movelog/domain/gpt/application/GptService.java @@ -0,0 +1,63 @@ +package com.movelog.domain.gpt.application; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +@Slf4j +public class GptService { + + private final WebClient gptWebClient; // @Qualifier 제거하고 빈 이름에 맞는 생성자 주입 + @Value("${chatgpt.api-key}") + private String apiKey; + @Value("${chatgpt.model}") + private String model; + @Value("${chatgpt.max-tokens}") + private Integer maxTokens; + @Value("${chatgpt.temperature}") + private Double temperature; + @Value("${chatgpt.top-p}") + private Double topP; + + public Mono callChatGpt(String prompt) { + return gptWebClient.post() + .uri("/chat/completions") // 경로에 대한 수정 + .header("Authorization", "Bearer " + apiKey) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(buildRequestBody(prompt)) + .retrieve() + .bodyToMono(JsonNode.class) + .doOnSuccess(response -> log.info("GPT API 응답 성공: {}", response)) + .doOnError(error -> log.error("GPT API 호출 중 오류 발생: {}", error.getMessage())) + .onErrorResume(error -> { + log.error("GPT API 호출 중 예외 처리 발생: {}", error.getMessage()); + return Mono.error(new RuntimeException("GPT API 호출 실패", error)); + }); + } + + private Map buildRequestBody(String prompt) { + Map bodyMap = new HashMap<>(); + bodyMap.put("model", model); + bodyMap.put("max_tokens", maxTokens); + bodyMap.put("temperature", temperature); + bodyMap.put("top_p", topP); + bodyMap.put("messages", List.of( + Map.of( + "role", "user", + "content", prompt + ) + )); + return bodyMap; + } +} From 6321c190006bc90701d48376dc4b8ee8741c9700 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Sun, 12 Jan 2025 17:34:49 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[FEAT]=20=ED=97=A4=EB=93=9C=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?GPT=20=ED=94=84=EB=A1=AC=ED=8F=AC=ED=8A=B8=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=ED=8C=8C=EC=8B=B1=20=EB=A9=94=EC=86=8C=EB=93=9C?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/HeadlineGeneratorService.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/main/java/com/movelog/domain/news/application/HeadlineGeneratorService.java diff --git a/src/main/java/com/movelog/domain/news/application/HeadlineGeneratorService.java b/src/main/java/com/movelog/domain/news/application/HeadlineGeneratorService.java new file mode 100644 index 0000000..ad7493f --- /dev/null +++ b/src/main/java/com/movelog/domain/news/application/HeadlineGeneratorService.java @@ -0,0 +1,85 @@ +package com.movelog.domain.news.application; + +import com.fasterxml.jackson.databind.JsonNode; +import com.movelog.domain.gpt.application.GptService; +import com.movelog.domain.news.dto.response.HeadLineRes; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class HeadlineGeneratorService { + + private final GptService gptService; + + public List generateHeadLine(String option, String verb, String noun) { + // 프롬프트 생성 + String prompt = generatePrompt(option, verb, noun); + + // 비동기 방식으로 GPT API 호출 + return gptService.callChatGpt(prompt) + .flatMap(response -> { + if (response == null) { + return Mono.error(new RuntimeException("GPT API 응답이 없습니다.")); + } + + // 응답 파싱하여 List 반환 + List practiceResList = parseResponse(response); + return Mono.just(practiceResList); + }) + .doOnError(error -> log.error("문제 생성 중 오류 발생: ", error)).block(); + } + + + // 프롬프트 생성 + private String generatePrompt(String option, String verb, String noun) { + return String.format( + "다음 내용을 바탕으로 뉴스 헤드라인을 생성해주세요: %s\n" + + "당신은 뉴스 헤드라인을 작성하는 역할이며, 다음 형식을 엄격히 따라 뉴스 헤드라인을 제공해 주세요:\n\n" + + "이 뉴스 헤드라인의 목적은 사용자의 기록을 기반으로 약간의 재미 요소가 들어간 헤드라인을 제공하는 것입니다.\n" + + "각 뉴스 헤드라인은 반드시 격식있는 어체로 구성되어야 하며, 반드시 올바른 어법을 준수해야 합니다.\n" + + "각 헤드라인의 내용은 주어진 옵션, 동사, 명사를 기반으로 생성되어야 하며, 헤드라인 내용에 직접적인 내용이 들어가지 않아도 됩니다." + + "뉴스 헤드라인은 반드시 3개의 후보를 제공해야 하며, 각 후보는 1개의 줄바꿈으로 구분됩니다.\n" + + "각 헤드라인 내용의 구조는 반드시 쉼표로 나뉘어야 하며, 쉼표 이전의 내용에는 헤드라인 강조 옵션에 대한 내용이 포함되어야 합니다." + + "쉼표 앞 뒤의 내용에 대한 글자 수는 각각 16자 이하여야 하며, 쉼표는 반드시 한글자로만 구성되어야 합니다.\n\n" + + + "다음은 제공된 정보를 통해 출력해야 하는 뉴스 헤드라인의 출력 예시입니다:\n\n" + + "꾸준한 노력 끝에 완성한 마라톤 기록, 도전의 의미는?\n오랜만의 첫 도전, 무엇이 그를 움직이게 했나?\n드디어 끊어낸 술, 운동으로 극복해내" + + + "다음은 제공된 정보를 통해 출력해야 하는 뉴스 헤드라인의 출력 형식입니다:\n\n" + + "첫번째 헤드라인 후보 내용\n두번째 헤드라인 후보 내용\n세번째 헤드라인 후보 내용\n" + + + "반드시 뉴스 헤드라인 형식을 준수해 주세요. 정해진 형식을 따르지 않으면 응답을 처리할 수 없습니다." + + "만약 결과 헤드라인 내용이 위 형식과 다르다면, 다시 요청하여 주세요.", + option, verb, noun + ); + } + + // GPT 응답 파싱 + private List parseResponse(JsonNode response) { + List headLineResList = new ArrayList<>(); + + // GPT 응답에서 질문, 답변, 해설 추출 + String textResponse = response.path("choices").get(0).path("message").path("content").asText().trim(); + log.info("Full Text Response: " + textResponse); // 전체 응답 확인 + + String[] headLines = textResponse.split("\n"); + // 헤드라인 생성 결과가 3개가 아닌 경우 예외 처리 + if (headLines.length != 3) { + throw new RuntimeException("생성된 헤드라인이 3개가 아닙니다."); + } + + // 헤드라인 후보 파싱 결과를 리스트에 저장 후 응답 리턴 + for (String headLine : headLines) { + headLineResList.add(HeadLineRes.builder().headLine(headLine).build()); + } + + return headLineResList; + } +} From c44ee800bbb9a03af8f570cee40464ad89a3f27b Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Sun, 12 Jan 2025 17:35:13 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[FEAT]=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=EB=82=B4=EC=9A=A9=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=ED=97=A4=EB=93=9C=EB=9D=BC=EC=9D=B8=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/news/application/NewsService.java | 43 +++++++++++++++++ .../news/dto/request/NewsHeadLineReq.java | 24 ++++++++++ .../domain/news/dto/response/HeadLineRes.java | 16 +++++++ .../news/presentation/NewsController.java | 46 +++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 src/main/java/com/movelog/domain/news/application/NewsService.java create mode 100644 src/main/java/com/movelog/domain/news/dto/request/NewsHeadLineReq.java create mode 100644 src/main/java/com/movelog/domain/news/dto/response/HeadLineRes.java create mode 100644 src/main/java/com/movelog/domain/news/presentation/NewsController.java diff --git a/src/main/java/com/movelog/domain/news/application/NewsService.java b/src/main/java/com/movelog/domain/news/application/NewsService.java new file mode 100644 index 0000000..e782e45 --- /dev/null +++ b/src/main/java/com/movelog/domain/news/application/NewsService.java @@ -0,0 +1,43 @@ +package com.movelog.domain.news.application; + +import com.movelog.domain.news.dto.request.NewsHeadLineReq; +import com.movelog.domain.news.dto.response.HeadLineRes; +import com.movelog.domain.user.application.UserService; +import com.movelog.domain.user.domain.User; +import com.movelog.domain.user.domain.repository.UserRepository; +import com.movelog.domain.user.exception.UserNotFoundException; +import com.movelog.global.DefaultAssert; +import com.movelog.global.config.security.token.UserPrincipal; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class NewsService { + private final HeadlineGeneratorService headlineGeneratorService; + private final UserService userService; + private final UserRepository userRepository; + + public List createHeadLine(UserPrincipal userPrincipal, NewsHeadLineReq newsHeadLineReq) { + User user = validateUser(userPrincipal); + // id가 5인 유저 정보(테스트용) + // User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new); + String option = newsHeadLineReq.getOption(); + String verb = newsHeadLineReq.getVerb(); + String noun = newsHeadLineReq.getNoun(); + return headlineGeneratorService.generateHeadLine(option, verb, noun); + } + + + private User validateUser(UserPrincipal userPrincipal) { + Optional userOptional = userService.findById(userPrincipal.getId()); + DefaultAssert.isOptionalPresent(userOptional); + return userOptional.get(); + } +} diff --git a/src/main/java/com/movelog/domain/news/dto/request/NewsHeadLineReq.java b/src/main/java/com/movelog/domain/news/dto/request/NewsHeadLineReq.java new file mode 100644 index 0000000..0c332e8 --- /dev/null +++ b/src/main/java/com/movelog/domain/news/dto/request/NewsHeadLineReq.java @@ -0,0 +1,24 @@ +package com.movelog.domain.news.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class NewsHeadLineReq { + @Schema( type = "String", example ="첫 도전, 오랜만에 다시, 꾸준히 이어온 기록, 끊어낸 습관 중 택 1", description="뉴스 헤드라인 고정 옵션입니다.") + private String option; + + @Schema( type = "String", example ="했어요, 먹었어요, 갔어요 중 택 1", description="사용자가 선택한 동사 정보입니다.") + private String verb; + + @Schema( type = "String", example ="클라이밍", description="사용자가 선택한 명사 정보입니다.") + private String noun; + + +} diff --git a/src/main/java/com/movelog/domain/news/dto/response/HeadLineRes.java b/src/main/java/com/movelog/domain/news/dto/response/HeadLineRes.java new file mode 100644 index 0000000..3636da4 --- /dev/null +++ b/src/main/java/com/movelog/domain/news/dto/response/HeadLineRes.java @@ -0,0 +1,16 @@ +package com.movelog.domain.news.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class HeadLineRes { + @Schema( type = "String", example ="5년 만의 첫 도전, 무엇이 그를 움직이게 했나?", description="뉴스 헤드라인 추천 내용입니다.") + private String headLine; +} diff --git a/src/main/java/com/movelog/domain/news/presentation/NewsController.java b/src/main/java/com/movelog/domain/news/presentation/NewsController.java new file mode 100644 index 0000000..06ffa67 --- /dev/null +++ b/src/main/java/com/movelog/domain/news/presentation/NewsController.java @@ -0,0 +1,46 @@ +package com.movelog.domain.news.presentation; + +import com.movelog.domain.news.application.NewsService; +import com.movelog.domain.news.dto.request.NewsHeadLineReq; +import com.movelog.domain.news.dto.response.HeadLineRes; +import com.movelog.global.config.security.token.CurrentUser; +import com.movelog.global.config.security.token.UserPrincipal; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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; + +import java.util.List; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/news") +@Tag(name = "News", description = "뉴스 관련 API입니다.") +public class NewsController { + + private final NewsService newsService; + + @Operation(summary = "뉴스 헤드라인 생성 API", description = "뉴스 헤드라인을 생성하는 API입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "뉴스 헤드라인 생성 성공"), + @ApiResponse(responseCode = "400", description = "뉴스 헤드라인 생성 실패") + }) + @PostMapping("/headline") + public List createHeadLine( + @Parameter(description = "Access Token을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal, + @Parameter(description = "뉴스 헤드라인 생성 요청", required = true) @RequestBody NewsHeadLineReq newsHeadLineReq + ) { + return newsService.createHeadLine(userPrincipal, newsHeadLineReq); + } + + + +} From 1c8724f73492460f017a2f84117e37925202c1b9 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 19:15:22 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[REFACTOR]=20VerbType=20enum=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../movelog/domain/record/domain/Record.java | 9 ++---- .../domain/record/domain/VerbType.java | 25 ++++++++++++++++ .../dto/{req => request}/CreateRecordReq.java | 6 ++-- .../record/presentation/RecordController.java | 3 +- .../domain/record/service/RecordService.java | 30 ++++++++++++------- 5 files changed, 52 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/movelog/domain/record/domain/VerbType.java rename src/main/java/com/movelog/domain/record/dto/{req => request}/CreateRecordReq.java (66%) diff --git a/src/main/java/com/movelog/domain/record/domain/Record.java b/src/main/java/com/movelog/domain/record/domain/Record.java index 377d999..eb801f1 100644 --- a/src/main/java/com/movelog/domain/record/domain/Record.java +++ b/src/main/java/com/movelog/domain/record/domain/Record.java @@ -28,8 +28,8 @@ public class Record extends BaseEntity { @JoinColumn(name = "keyword_id") private Keyword keyword; - @Column(name = "verb_type") - private Long verbType; + @Enumerated(EnumType.STRING) // Enum을 문자열로 저장 + private VerbType verbType; @Column(name = "record_image") private String recordImage; @@ -38,14 +38,11 @@ public class Record extends BaseEntity { private java.time.LocalDateTime actionTime; @Builder - public Record(User user, Keyword keyword, Long verbType, String recordImage) { + public Record(User user, Keyword keyword, VerbType verbType, String recordImage) { this.user = user; this.keyword = keyword; this.verbType = verbType; this.recordImage = recordImage; this.actionTime = actionTime == null? LocalDateTime.now():actionTime; } - - - } diff --git a/src/main/java/com/movelog/domain/record/domain/VerbType.java b/src/main/java/com/movelog/domain/record/domain/VerbType.java new file mode 100644 index 0000000..16a2e20 --- /dev/null +++ b/src/main/java/com/movelog/domain/record/domain/VerbType.java @@ -0,0 +1,25 @@ +package com.movelog.domain.record.domain; + +import lombok.Getter; + +@Getter +public enum VerbType { + // 했어요, 먹었어요, 갔어요 + DO("했어요"), EAT("먹었어요"), GO("갔어요"); + + private final String verbType; + + VerbType(String verbType) { + this.verbType = verbType; + } + + // Enum 매핑 메서드 추가 + public static VerbType fromValue(String value) { + for (VerbType type : values()) { + if (type.getVerbType().equals(value)) { + return type; + } + } + throw new IllegalArgumentException("No enum constant for value: " + value); + } +} diff --git a/src/main/java/com/movelog/domain/record/dto/req/CreateRecordReq.java b/src/main/java/com/movelog/domain/record/dto/request/CreateRecordReq.java similarity index 66% rename from src/main/java/com/movelog/domain/record/dto/req/CreateRecordReq.java rename to src/main/java/com/movelog/domain/record/dto/request/CreateRecordReq.java index 1790248..30e25e9 100644 --- a/src/main/java/com/movelog/domain/record/dto/req/CreateRecordReq.java +++ b/src/main/java/com/movelog/domain/record/dto/request/CreateRecordReq.java @@ -1,4 +1,4 @@ -package com.movelog.domain.record.dto.req; +package com.movelog.domain.record.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -11,8 +11,8 @@ @NoArgsConstructor @Getter public class CreateRecordReq { - @Schema(type = "Long", example = "0", description = "0 -> 했어요, 1 -> 먹었어요, 2 -> 갔어요") - private Long verbType; + @Schema(type = "String", example = "했어요", description = "했어요, 먹었어요, 갔어요") + private String verbType; @Schema(type = "String", example = "헬스", description = "명사 작성") private String noun; diff --git a/src/main/java/com/movelog/domain/record/presentation/RecordController.java b/src/main/java/com/movelog/domain/record/presentation/RecordController.java index ae71c53..8c31610 100644 --- a/src/main/java/com/movelog/domain/record/presentation/RecordController.java +++ b/src/main/java/com/movelog/domain/record/presentation/RecordController.java @@ -1,7 +1,6 @@ package com.movelog.domain.record.presentation; -import com.movelog.domain.record.dto.req.CreateRecordReq; -import com.movelog.domain.record.repository.RecordRepository; +import com.movelog.domain.record.dto.request.CreateRecordReq; import com.movelog.domain.record.service.RecordService; import com.movelog.global.config.security.token.UserPrincipal; import com.movelog.global.payload.ApiResponse; diff --git a/src/main/java/com/movelog/domain/record/service/RecordService.java b/src/main/java/com/movelog/domain/record/service/RecordService.java index a90d174..ab5e3f3 100644 --- a/src/main/java/com/movelog/domain/record/service/RecordService.java +++ b/src/main/java/com/movelog/domain/record/service/RecordService.java @@ -2,7 +2,8 @@ import com.movelog.domain.record.domain.Keyword; import com.movelog.domain.record.domain.Record; -import com.movelog.domain.record.dto.req.CreateRecordReq; +import com.movelog.domain.record.domain.VerbType; +import com.movelog.domain.record.dto.request.CreateRecordReq; import com.movelog.domain.record.repository.KeywordRepository; import com.movelog.domain.record.repository.RecordRepository; import com.movelog.domain.user.domain.User; @@ -10,6 +11,7 @@ import com.movelog.global.DefaultAssert; import com.movelog.global.util.S3Util; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -24,6 +26,7 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) +@Slf4j public class RecordService { private final RecordRepository recordRepository; private final UserRepository userRepository; @@ -34,30 +37,37 @@ public class RecordService { public void createRecord(Long userId, CreateRecordReq createRecordReq, MultipartFile img) { User user = validUserById(userId); String recordImgUrl = s3Util.upload(img); + log.info("recordImgUrl: {}", recordImgUrl); Keyword keyword = Keyword.builder() .keyword(createRecordReq.getNoun()) .build(); keywordRepository.save(keyword); + String verb = createRecordReq.getVerbType(); + try { + VerbType verbType = VerbType.fromValue(verb); - Record record = Record.builder() - .user(user) - .keyword(keyword) - .verbType(createRecordReq.getVerbType()) - .recordImage(recordImgUrl) + Record record = Record.builder() + .user(user) + .keyword(keyword) + .verbType(verbType) + .recordImage(recordImgUrl) // .actionTime(LocalDateTime.now()) - .build(); + .build(); + + recordRepository.save(record); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Invalid verb type: " + verb, e); + } - recordRepository.save(record); } private User validUserById(Long userId) { Optional userOptional = userRepository.findById(userId); - DefaultAssert.isOptionalPresent(userOptional); return userOptional.get(); } - public List retrieveTodayRecord(Long userId) { + public List retrieveTodayRecord(Long userId) { User user = validUserById(userId); // 현재 날짜 가져오기 LocalDate today = LocalDate.now(); // 오늘 날짜 (2025-01-05 기준)