diff --git a/src/main/java/org/ai/roboadvisor/domain/chat/controller/ChatController.java b/src/main/java/org/ai/roboadvisor/domain/chat/controller/ChatController.java index ca0cdf3..987f16a 100644 --- a/src/main/java/org/ai/roboadvisor/domain/chat/controller/ChatController.java +++ b/src/main/java/org/ai/roboadvisor/domain/chat/controller/ChatController.java @@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ai.roboadvisor.domain.chat.dto.request.ClearRequest; +import org.ai.roboadvisor.domain.chat.dto.request.MessageClearRequest; import org.ai.roboadvisor.domain.chat.dto.request.MessageRequest; import org.ai.roboadvisor.domain.chat.dto.response.ChatResponse; import org.ai.roboadvisor.domain.chat.dto.response.ChatResult; @@ -32,9 +32,11 @@ public class ChatController { private final ChatService chatService; @Operation(summary = "채팅 서비스 입장", description = """ - 사용자가 채팅 서비스 입장 시, 기존의 대화 내용을 불러온다. + 사용자가 채팅 서비스에 입장할 때 요청되는 함수 - 기존에 대화 내용이 존재하지 않는다면(처음 입장하는 경우), 초기 메시지(Welcome Message)를 보내준다. + case 1: 처음 입장하는 경우 - 서버에서 챗봇 초기 메시지(Welcome Message)를 보내준다. + + case 2: 기존에 대화 내용이 존재하는 경우 - 기존에 존재하는 대화 메시지 전체를 불러온다. order : 대화 내용을 불러오는 경우에 숫자가 클수록 최근에 한 대화이다. @@ -44,8 +46,8 @@ public class ChatController { @getAllChats_CREATED @ApiResponse_Internal_Server_Error @GetMapping(value = "/{nickname}") - public ResponseEntity> getAllChats(@PathVariable("nickname") String nickname) { - ChatResult chatResult = chatService.getAllChats(nickname); + public ResponseEntity> enter(@PathVariable("nickname") String nickname) { + ChatResult chatResult = chatService.enter(nickname); if (checkIfChatOrderResponseIsPresent(chatResult)) { // case LOAD_CHAT_SUCCESS return ResponseEntity.status(HttpStatus.OK) @@ -57,32 +59,41 @@ public ResponseEntity> getAllChats(@PathVariable( } } - @Operation(summary = "채팅 메시지 전송", description = "클라이언트로부터 사용자의 메시지를 받아서, ChatGPT로 보내고, 응답 결과를 받아 클라이언트로 전달한다") + @Operation(summary = "채팅 메시지 전송", description = """ + 사용자가 입력한 채팅 메시지를 전달한다. + + 프론트에서는 채팅 메시지를 서버로 보냄과 동시에 화면에 사용자가 보낸 채팅 메시지를 직접 띄우는 식으로 구현되어 있다. + + 서버에서는 사용자가 입력한 메시지를 ChatGPT API를 사용하여 전달한 다음, 응답 결과를 받아 반환한다. + + Swagger 문서 하단의 Schemas 중 RequestBody로 'MessageRequest' 를 사용한다. + """) @sendMessage_CREATED @sendMessage_BAD_REQUEST @ApiResponse_Internal_Server_Error @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> sendMessage(@RequestBody MessageRequest messageRequest) { chatService.save(messageRequest); - ChatResponse result = chatService.getMessageFromApi(messageRequest.getNickname(), messageRequest.getContent()); + return ResponseEntity.status(HttpStatus.CREATED) .body(SuccessApiResponse.success(SuccessCode.CHAT_CREATED_SUCCESS, new ChatResult(result))); } @Operation(summary = "대화 내용 삭제", description = """ - 요청을 보내면, 기존 대화 내용이 전부 삭제된다. + 기존 대화 내용이 전부 삭제된다. 대화 내용이 삭제된 이후, data에 초기 메시지(Welcome Message)를 리스트 형식으로 담아서 다시 보내준다. + + Swagger 문서 하단의 Schemas 중 RequestBody로 'MessageClearRequest' 를 사용한다. """) @clear_CREATED @ApiResponse_Internal_Server_Error @PostMapping(value = "/clear") - public ResponseEntity> clear(@RequestBody ClearRequest clearRequest) { - String userNickname = clearRequest.getNickname(); + public ResponseEntity> clear(@RequestBody MessageClearRequest messageClearRequest) { return ResponseEntity.status(HttpStatus.CREATED) .body(SuccessApiResponse.success(SuccessCode.CHAT_DELETED_SUCCESS, - chatService.clear(userNickname))); + chatService.clear(messageClearRequest))); } protected boolean checkIfChatOrderResponseIsPresent(ChatResult chatResult) { diff --git a/src/main/java/org/ai/roboadvisor/domain/chat/dto/request/ClearRequest.java b/src/main/java/org/ai/roboadvisor/domain/chat/dto/request/MessageClearRequest.java similarity index 78% rename from src/main/java/org/ai/roboadvisor/domain/chat/dto/request/ClearRequest.java rename to src/main/java/org/ai/roboadvisor/domain/chat/dto/request/MessageClearRequest.java index cf0a903..fcb76ba 100644 --- a/src/main/java/org/ai/roboadvisor/domain/chat/dto/request/ClearRequest.java +++ b/src/main/java/org/ai/roboadvisor/domain/chat/dto/request/MessageClearRequest.java @@ -11,13 +11,13 @@ @NoArgsConstructor @AllArgsConstructor @Schema(description = "채팅 대화 내역 삭제를 요청할 때 사용하는 JSON 요청 예시") -public class ClearRequest { +public class MessageClearRequest { @Schema(description = "사용자 정보: 닉네임", example = "testUser") @NotBlank private String nickname; - public static ClearRequest of(String nickname) { - return new ClearRequest(nickname); + public static MessageClearRequest of(String nickname) { + return new MessageClearRequest(nickname); } } diff --git a/src/main/java/org/ai/roboadvisor/domain/chat/service/ChatService.java b/src/main/java/org/ai/roboadvisor/domain/chat/service/ChatService.java index d78a156..d13004c 100644 --- a/src/main/java/org/ai/roboadvisor/domain/chat/service/ChatService.java +++ b/src/main/java/org/ai/roboadvisor/domain/chat/service/ChatService.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.ai.roboadvisor.domain.chat.dto.Message; import org.ai.roboadvisor.domain.chat.dto.request.ChatGptRequest; +import org.ai.roboadvisor.domain.chat.dto.request.MessageClearRequest; import org.ai.roboadvisor.domain.chat.dto.request.MessageRequest; import org.ai.roboadvisor.domain.chat.dto.response.ChatGptResponse; import org.ai.roboadvisor.domain.chat.dto.response.ChatOrderResponse; @@ -11,6 +12,7 @@ import org.ai.roboadvisor.domain.chat.dto.response.ChatResult; import org.ai.roboadvisor.domain.chat.entity.Chat; import org.ai.roboadvisor.domain.chat.repository.ChatRepository; +import org.ai.roboadvisor.domain.user.repository.UserRepository; import org.ai.roboadvisor.global.exception.CustomException; import org.ai.roboadvisor.global.exception.ErrorCode; import org.springframework.beans.factory.annotation.Value; @@ -34,6 +36,7 @@ public class ChatService { private final ChatGPTService chatGPTService; + private final UserRepository userRepository; private final ChatRepository chatRepository; private final MongoTemplate mongoTemplate; private final String ROLE_USER = "user"; @@ -45,7 +48,7 @@ public class ChatService { private String OPEN_AI_MODEL; @Transactional - public ChatResult getAllChats(String nickname) { + public ChatResult enter(String nickname) { if (!checkIfChatExistsInDb(nickname)) { return new ChatResult(createAndSaveWelcomeMessage(nickname)); } else { @@ -71,7 +74,14 @@ public ChatResult getAllChats(String nickname) { } } + private boolean checkIfChatExistsInDb(String email) { + return chatRepository.existsChatByNickname(email); + } + public void save(MessageRequest messageRequest) { + // 사용자 닉네임 검증 + checkUserIsExisted(messageRequest.getNickname()); + // MessageRequest time format 검증 LocalDateTime dateTime = parseDateTime(messageRequest.getTime()) .orElseThrow(() -> new CustomException(ErrorCode.TIME_INPUT_INVALID)); @@ -80,6 +90,7 @@ public void save(MessageRequest messageRequest) { saveChat(userChat); } + public ChatResponse getMessageFromApi(String nickname, String message) { ChatGptRequest chatGptRequest = ChatGptRequest .builder() @@ -106,13 +117,7 @@ public ChatResponse getMessageFromApi(String nickname, String message) { .message(responseMessage) .time(now) .build(); - - try { - chat.setTimeZone(chat.getTime(), KST_TO_UTC); // KST -> UTC - chatRepository.save(chat); - } catch (RuntimeException e) { - throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR); - } + saveChat(chat); // 2. Return Message DTO to client return ChatResponse.builder() @@ -123,7 +128,10 @@ public ChatResponse getMessageFromApi(String nickname, String message) { } } - public ChatResult clear(String nickname) { + public ChatResult clear(MessageClearRequest clearRequest) { + String nickname = clearRequest.getNickname(); + checkUserIsExisted(nickname); + // Clear All data try { chatRepository.deleteAllByNickname(nickname); @@ -134,7 +142,7 @@ public ChatResult clear(String nickname) { } public ChatResponse createAndSaveWelcomeMessage(String nickname) { - String WELCOME_MESSAGE = "안녕하세요, 저는 AI로보어드바이저의 ChatGPT 서비스에요! 궁금한 점을 입력해주세요"; + String WELCOME_MESSAGE = "안녕하세요, 저는 알파몬의 ChatGPT 서비스에요! 궁금한 점을 입력해주세요"; // 1. Create Chat Entity and Save LocalDateTime now = LocalDateTime.now().withNano(0); // ignore milliseconds @@ -144,7 +152,6 @@ public ChatResponse createAndSaveWelcomeMessage(String nickname) { .message(WELCOME_MESSAGE) .time(now) .build(); - saveChat(chat); // 2. Entity -> DTO and Return Welcome Message @@ -171,7 +178,10 @@ private Optional parseDateTime(String timeString) { } } - private boolean checkIfChatExistsInDb(String email) { - return chatRepository.existsChatByNickname(email); + private void checkUserIsExisted(String nickname) { + userRepository.findUserByNickname(nickname) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_EXISTED)); } + + } \ No newline at end of file diff --git a/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/getAllChats/getAllChats_CREATED.java b/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/getAllChats/getAllChats_CREATED.java index 5cb1984..9b4ded4 100644 --- a/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/getAllChats/getAllChats_CREATED.java +++ b/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/getAllChats/getAllChats_CREATED.java @@ -11,22 +11,26 @@ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited -@ApiResponse(responseCode = "201", description = "사용자가 채팅방에 처음 입장한 경우, 챗봇의 Welcome Message를 보낸다.", +@ApiResponse(responseCode = "201", description = """ + case 1: 처음 입장하는 경우 - 서버에서 챗봇 초기 메시지(Welcome Message)를 보내준다. + + 이 경우는 data 안에 chatResponse 객체를 반환한다. + """, content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), examples = @ExampleObject(name = "example", description = "처음 입장한 경우 응답 예시", value = """ { - "code": 201, - "message": "채팅방 입장에 성공하셨습니다", - "data": { - "chatResponse": { - "role": "assistant", - "content": "안녕하세요, 저는 AI로보어드바이저의 ChatGPT 서비스에요! 궁금한 점을 입력해주세요", - "time": "2023-09-22 14:32:58" - } - } - } + "code": 201, + "message": "채팅방 입장에 성공하셨습니다", + "data": { + "chatResponse": { + "role": "assistant", + "content": "안녕하세요, 저는 알파몬의 ChatGPT 서비스에요! 궁금한 점을 입력해주세요", + "time": "2023-10-19 00:53:00" + } + } + } """ ))) public @interface getAllChats_CREATED { diff --git a/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/getAllChats/getAllChats_OK.java b/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/getAllChats/getAllChats_OK.java index b367f08..733cdb1 100644 --- a/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/getAllChats/getAllChats_OK.java +++ b/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/getAllChats/getAllChats_OK.java @@ -12,16 +12,17 @@ @Retention(RetentionPolicy.RUNTIME) @Inherited @ApiResponse(responseCode = "200", description = """ - 사용자가 채팅방에 처음 입장한 경우, 기존의 대화 내용을 보낸다. + case 2: 기존에 대화 내용이 존재하는 경우 - 기존에 존재하는 대화 메시지 전체를 불러온다. + + 이 경우는 data 안에 chatOrderResponse 객체를 반환한다. chatOrderResponse Array 객체 안에, order, role, content, time 정보가 각각 담긴다. - order 값이 높을 수록, 나중에 작성된 메시지이다. 따라서 order 값을 기준으로 정렬하면 되며, + role: assistant(ChatGPT 답변 내용), role: user(사용자가 입력한 대화) - 혹은 time 값을 DateTime 타입으로 바꿔서 정렬도 가능하다. + order 값이 높을 수록, 최근에 작성된 메시지이다. 따라서 order 값을 기준으로 정렬하면 되며, 혹은 time 값을 DateTime 타입으로 바꿔서 정렬도 가능하다. - '현재는 Pagination을 따로 사용하지 않고, 전체 데이터를 한 번에 모두 보내도록 구현되어 있음' - + '현재는 Pagination을 따로 사용하지 않고, 전체 데이터를 모두 보내도록 구현되어 있음' """, content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), examples = @ExampleObject(name = "example", diff --git a/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/sendMessage/sendMessage_BAD_REQUEST.java b/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/sendMessage/sendMessage_BAD_REQUEST.java index 1a922f3..55e4cde 100644 --- a/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/sendMessage/sendMessage_BAD_REQUEST.java +++ b/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/sendMessage/sendMessage_BAD_REQUEST.java @@ -12,12 +12,23 @@ @Retention(RetentionPolicy.RUNTIME) @Inherited @ApiResponse(responseCode = "400", description = """ - 시간 형식을 잘못 입력한 경우 + 1. 사용자의 닉네임이 DB에 존재하지 않는 경우 -> example1 - e.g) 2023-03-03 11:11:11 (사이에 띄어쓰기가 두 칸인 경우), 2023-03-03T11:11:11 (사이에 문자가 끼워져 있는 경우), ... + 2. 시간 형식을 잘못 입력한 경우 -> example2 + + e.g) 2023-03-03 11:11:11 (사이에 띄어쓰기가 두 칸인 경우), 2023-03-03T11:11:11 (사이에 문자가 끼워져 있는 경우), etc.. """, content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), - examples = @ExampleObject(name = "example", + examples = {@ExampleObject(name = "example1", + description = "사용자의 닉네임이 DB에 존재하지 않는 경우 예시", + value = """ + { + "code": 400, + "message": "가입된 사용자의 정보가 존재하지 않습니다", + "data": null + } + """ + ), @ExampleObject(name = "example2", description = "시간 형식을 잘못 입력한 경우 예시", value = """ { @@ -26,6 +37,7 @@ "data": null } """ - ))) + )} + )) public @interface sendMessage_BAD_REQUEST { } diff --git a/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/sendMessage/sendMessage_CREATED.java b/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/sendMessage/sendMessage_CREATED.java index 2fdb8da..fffdac0 100644 --- a/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/sendMessage/sendMessage_CREATED.java +++ b/src/main/java/org/ai/roboadvisor/domain/chat/swagger_annotation/sendMessage/sendMessage_CREATED.java @@ -22,8 +22,8 @@ "data": { "chatResponse": { "role": "assistant", - "content": "주식에서 선물거래는 미래에 일정한 날짜에 주식을 사거나 팔 수 있는 계약을 말합니다. 이러한 계약은 특정 주식의 가격을 현재의 가격에서 미리 확정하고 미래에 거래가 이루어지도록 하는 것이 목적입니다. 주식 선물거래는 투자자들이 주식 시장의 가격 변동에 대해 원하는 방향으로 수익을 얻거나 손실을 최소화하기 위해 사용됩니다. 예를 들어, 주식 가격이 상승할 것으로 예상되는 투자자는 주식 선물계약을 매수하여 주식을 미리 구매하는 것으로서, 가격이 상승하면 이익을 얻을 수 있습니다. 반대로, 주식 가격이 하락할 것으로 예상되는 투자자는 주식 선물계약을 매도하여 주식을 미리 판매하는 것으로서, 가격이 하락하면 이익을 얻을 수 있습니다. 선물거래는 주식 가격 변동에 대한 투자자의 예측을 기반으로 이루어지므로, 주식 시장에서 높은 위험성을 가지고 있습니다.", - "time": "2023-09-23 13:24:10" + "content": "주식에서 선물거래란 특정 기간 동안 미리 약정된 가격으로 주식을 매매하는 거래 방식을 의미합니다. 즉, 주식 선물거래는 약정된 가격으로 미래의 특정 시점에 주식을 사거나 팔 수 있는 계약을 말합니다. 이러한 선물거래는 투자자들이 주식 가격의 변동성으로부터 보호받을 수 있고, 투자 전략을 구성할 때 사용할 수 있는 도구로 이용될 수 있습니다. 주식 선물거래는 주식시장의 투기성과 리스크를 관리하고자 하는 투자자들에게 유용한 도구로 활용됩니다.", + "time": "2023-10-19 00:56:53" } } } diff --git a/src/main/java/org/ai/roboadvisor/domain/community/service/CommentService.java b/src/main/java/org/ai/roboadvisor/domain/community/service/CommentService.java index 554341d..44633c4 100644 --- a/src/main/java/org/ai/roboadvisor/domain/community/service/CommentService.java +++ b/src/main/java/org/ai/roboadvisor/domain/community/service/CommentService.java @@ -120,7 +120,7 @@ private Comment findExistingCommentById(Long postId, Long commentId) { Post existPost = postRepository.findPostById(postId) .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_EXISTED)); return commentRepository.findCommentByIdAndPost(commentId, existPost) - .orElseThrow(() -> new CustomException(ErrorCode.INTERNAL_SERVER_ERROR)); + .orElseThrow(() -> new CustomException(ErrorCode.COMMENT_NOT_EXISTED)); } private void validateUserHasAuthority(String requestNickname, Comment existingComment) { diff --git a/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/comment/delete/delete_BAD_REQUEST.java b/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/comment/delete/delete_BAD_REQUEST.java index 7e67dc9..a21645c 100644 --- a/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/comment/delete/delete_BAD_REQUEST.java +++ b/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/comment/delete/delete_BAD_REQUEST.java @@ -11,19 +11,33 @@ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited -@ApiResponse(responseCode = "400", description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우", +@ApiResponse(responseCode = "400", description = """ + 1. 게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 -> example1 + + 2. 댓글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 -> example2 + + """, content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), - examples = - @ExampleObject(name = "example", - description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 예시", + examples = { + @ExampleObject(name = "example1", + description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 예시", + value = """ + { + "code": 400, + "message": "요청하신 게시글 id가 존재하지 않습니다", + "data": null + } + """ + ), @ExampleObject(name = "example2", + description = "댓글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 예시", value = """ { "code": 400, - "message": "요청하신 게시글 id가 존재하지 않습니다.", + "message": "요청하신 댓글 id가 존재하지 않습니다", "data": null } """ - ) + )} )) public @interface delete_BAD_REQUEST { } diff --git a/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/comment/update/update_BAD_REQUEST.java b/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/comment/update/update_BAD_REQUEST.java index 960e242..61e65aa 100644 --- a/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/comment/update/update_BAD_REQUEST.java +++ b/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/comment/update/update_BAD_REQUEST.java @@ -11,19 +11,33 @@ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited -@ApiResponse(responseCode = "400", description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우", +@ApiResponse(responseCode = "400", description = """ + 1. 게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 -> example1 + + 2. 댓글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 -> example2 + + """, content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), - examples = - @ExampleObject(name = "example", - description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 예시", + examples = { + @ExampleObject(name = "example1", + description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 예시", + value = """ + { + "code": 400, + "message": "요청하신 게시글 id가 존재하지 않습니다", + "data": null + } + """ + ), @ExampleObject(name = "example2", + description = "댓글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 예시", value = """ { "code": 400, - "message": "요청하신 게시글 id가 존재하지 않습니다.", + "message": "요청하신 댓글 id가 존재하지 않습니다", "data": null } """ - ) + )} )) public @interface update_BAD_REQUEST { } diff --git a/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/post/update/update_BAD_REQUEST.java b/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/post/update/update_BAD_REQUEST.java index 863f965..d191a82 100644 --- a/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/post/update/update_BAD_REQUEST.java +++ b/src/main/java/org/ai/roboadvisor/domain/community/swagger_annotation/post/update/update_BAD_REQUEST.java @@ -11,20 +11,34 @@ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited -@ApiResponse(responseCode = "400", description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우", +@ApiResponse(responseCode = "400", description = """ + 1. 사용자의 닉네임이 DB에 존재하지 않는 경우, 2. 게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 + + 사용자의 닉네임이 DB에 존재하지 않는 경우 -> example1 + + 게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 -> example2 + """, content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), - examples = - @ExampleObject(name = "example", - description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 예시", + examples = {@ExampleObject(name = "example1", + description = "사용자의 닉네임이 DB에 존재하지 않는 경우 예시", value = """ { "code": 400, - "message": "요청하신 게시글 id가 존재하지 않습니다.", + "message": "가입된 사용자의 정보가 존재하지 않습니다", "data": null - } + } """ - ) - + ), + @ExampleObject(name = "example2", + description = "게시글 번호가 잘못 입력된 경우, 혹은 존재하지 않는 경우 예시", + value = """ + { + "code": 400, + "message": "요청하신 게시글 id가 존재하지 않습니다.", + "data": null + } + """ + )} )) public @interface update_BAD_REQUEST { } diff --git a/src/main/java/org/ai/roboadvisor/domain/tendency/controller/TendencyController.java b/src/main/java/org/ai/roboadvisor/domain/tendency/controller/TendencyController.java index dc91718..9cb4d79 100644 --- a/src/main/java/org/ai/roboadvisor/domain/tendency/controller/TendencyController.java +++ b/src/main/java/org/ai/roboadvisor/domain/tendency/controller/TendencyController.java @@ -1,24 +1,19 @@ package org.ai.roboadvisor.domain.tendency.controller; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.ExampleObject; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ai.roboadvisor.domain.tendency.dto.TendencyUpdateDto; import org.ai.roboadvisor.domain.tendency.service.TendencyService; +import org.ai.roboadvisor.domain.tendency.swagger_annotation.updateTendency.updateTendency_BAD_REQUEST; +import org.ai.roboadvisor.domain.tendency.swagger_annotation.updateTendency.updateTendency_CREATED; import org.ai.roboadvisor.global.common.dto.SuccessApiResponse; import org.ai.roboadvisor.global.exception.SuccessCode; import org.ai.roboadvisor.global.swagger_annotation.ApiResponse_Internal_Server_Error; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -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 org.springframework.web.bind.annotation.*; @Slf4j @RequiredArgsConstructor @@ -29,44 +24,19 @@ public class TendencyController { private final TendencyService tendencyService; - @Operation(summary = "투자 성향 등록", description = "사용자의 투자 성향을 등록한다") - @ApiResponse(responseCode = "201", description = """ - 사용자가 입력한 투자 성향을 등록한다. - + @Operation(summary = "투자 성향 등록", description = """ + 사용자의 투자 성향을 등록한다 + 투자 성향 종류: LION(공격투자형), SNAKE(적극투자형), MONKEY(위험중립형), SHEEP(안정추구형) - data 값으로 등록된 투자 성향의 결과를 반환한다""", - content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), - examples = @ExampleObject(name = "example", - description = "정상 응답 예시", - value = """ - { - "code": 201, - "message": "투자 성향 테스트 결과가 정상적으로 등록되었습니다", - "data": { - "nickname": "testUser", - "tendency": "SHEEP" - } - } - """ - ))) - @ApiResponse(responseCode = "400", description = "잘못된 투자 성향이 입력된 경우", - content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), - examples = @ExampleObject(name = "example", - description = "잘못된 투자 성향이 입력된 경우 예시", - value = """ - { - "code": 400, - "message": "잘못된 투자 성향 형식이 입력되었습니다", - "data": null - } - """ - ))) + Swagger 문서 하단의 Schemas 중 RequestBody로 'TendencyUpdateDto'를 사용한다. + """) + @updateTendency_CREATED + @updateTendency_BAD_REQUEST @ApiResponse_Internal_Server_Error @PostMapping("/update") public ResponseEntity> updateTendency(@RequestBody TendencyUpdateDto tendencyUpdateDto) { return ResponseEntity.status(HttpStatus.CREATED) .body(SuccessApiResponse.success(SuccessCode.TENDENCY_UPDATE_SUCCESS, tendencyService.updateTendency(tendencyUpdateDto))); } - } diff --git a/src/main/java/org/ai/roboadvisor/domain/tendency/service/TendencyService.java b/src/main/java/org/ai/roboadvisor/domain/tendency/service/TendencyService.java index 44a17f2..cd3703c 100644 --- a/src/main/java/org/ai/roboadvisor/domain/tendency/service/TendencyService.java +++ b/src/main/java/org/ai/roboadvisor/domain/tendency/service/TendencyService.java @@ -21,22 +21,23 @@ public class TendencyService { @Transactional public TendencyUpdateDto updateTendency(TendencyUpdateDto tendencyUpdateDto) { String userNickname = tendencyUpdateDto.getNickname(); + User user = userRepository.findUserByNickname(userNickname) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_EXISTED)); Tendency updateTendency = tendencyUpdateDto.getTendency(); - if (checkTendencyIsValid(updateTendency)) { - throw new CustomException(ErrorCode.TENDENCY_INPUT_INVALID); - } - - User user = userRepository.findUserByNickname(userNickname).orElse(null); - if (user == null) { - throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR); - } - user.setTendency(updateTendency); + checkTendencyIsValid(updateTendency); + updateTendencyOfUser(user, updateTendency); return TendencyUpdateDto.of(userNickname, updateTendency); } - private boolean checkTendencyIsValid(Tendency tendency) { - return tendency == Tendency.TYPE_NOT_EXISTS; + private void checkTendencyIsValid(Tendency tendency) { + if (tendency == Tendency.TYPE_NOT_EXISTS) { + throw new CustomException(ErrorCode.TENDENCY_INPUT_INVALID); + } + } + + private void updateTendencyOfUser(User user, Tendency tendency) { + user.setTendency(tendency); } } diff --git a/src/main/java/org/ai/roboadvisor/domain/tendency/swagger_annotation/updateTendency/updateTendency_BAD_REQUEST.java b/src/main/java/org/ai/roboadvisor/domain/tendency/swagger_annotation/updateTendency/updateTendency_BAD_REQUEST.java new file mode 100644 index 0000000..1886de6 --- /dev/null +++ b/src/main/java/org/ai/roboadvisor/domain/tendency/swagger_annotation/updateTendency/updateTendency_BAD_REQUEST.java @@ -0,0 +1,43 @@ +package org.ai.roboadvisor.domain.tendency.swagger_annotation.updateTendency; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.ai.roboadvisor.global.common.dto.SuccessApiResponse; + +import java.lang.annotation.*; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@ApiResponse(responseCode = "400", description = """ + 사용자의 닉네임이 DB에 존재하지 않는 경우, 혹은 잘못된 투자 성향이 입력된 경우 + + 사용자의 닉네임이 DB에 존재하지 않는 경우 -> example1 + + 잘못된 투자 성향이 입력된 경우 -> example2 + """, + content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), + examples = {@ExampleObject(name = "example1", + description = "사용자의 닉네임이 DB에 존재하지 않는 경우 예시", + value = """ + { + "code": 400, + "message": "가입된 사용자의 정보가 존재하지 않습니다", + "data": null + } + """ + ), @ExampleObject(name = "example2", + description = "잘못된 투자 성향이 입력된 경우 예시", + value = """ + { + "code": 400, + "message": "잘못된 투자 성향 형식이 입력되었습니다", + "data": null + } + """)} + + )) +public @interface updateTendency_BAD_REQUEST { +} diff --git a/src/main/java/org/ai/roboadvisor/domain/tendency/swagger_annotation/updateTendency/updateTendency_CREATED.java b/src/main/java/org/ai/roboadvisor/domain/tendency/swagger_annotation/updateTendency/updateTendency_CREATED.java new file mode 100644 index 0000000..64550ac --- /dev/null +++ b/src/main/java/org/ai/roboadvisor/domain/tendency/swagger_annotation/updateTendency/updateTendency_CREATED.java @@ -0,0 +1,34 @@ +package org.ai.roboadvisor.domain.tendency.swagger_annotation.updateTendency; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.ai.roboadvisor.global.common.dto.SuccessApiResponse; + +import java.lang.annotation.*; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@ApiResponse(responseCode = "201", description = """ + 사용자가 입력한 투자 성향을 등록한다. + + data 값으로 사용자 닉네임 및 등록된 투자 성향 결과를 반환한다. + """, + content = @Content(schema = @Schema(implementation = SuccessApiResponse.class), + examples = @ExampleObject(name = "example", + description = "정상 응답 예시", + value = """ + { + "code": 201, + "message": "투자 성향 테스트 결과가 정상적으로 등록되었습니다", + "data": { + "nickname": "testUser", + "tendency": "SHEEP" + } + } + """ + ))) +public @interface updateTendency_CREATED { +} diff --git a/src/main/java/org/ai/roboadvisor/global/exception/ErrorCode.java b/src/main/java/org/ai/roboadvisor/global/exception/ErrorCode.java index 41b7c35..f225f73 100644 --- a/src/main/java/org/ai/roboadvisor/global/exception/ErrorCode.java +++ b/src/main/java/org/ai/roboadvisor/global/exception/ErrorCode.java @@ -21,11 +21,11 @@ public enum ErrorCode { TIME_INPUT_INVALID(HttpStatus.BAD_REQUEST, "CH01", "time 형식을 yyyy-MM-dd HH:mm:ss으로 작성해 주세요"), // community - POST_NOT_EXISTED(HttpStatus.BAD_REQUEST, "C001", "요청하신 게시글 id가 존재하지 않습니다."), + POST_NOT_EXISTED(HttpStatus.BAD_REQUEST, "C001", "요청하신 게시글 id가 존재하지 않습니다"), USER_HAS_NOT_AUTHORIZED(HttpStatus.UNAUTHORIZED, "CO02", "사용자에게 권한이 존재하지 않습니다"), TENDENCY_NOT_MATCH_BETWEEN_POST_AND_COMMENT(HttpStatus.UNAUTHORIZED, "CO03", "게시글과 사용자의 투자 성향이 달라서 댓글을 작성할 수 없습니다"), - COMMENT_NOT_EXISTED(HttpStatus.BAD_REQUEST, "C004", "요청하신 댓글 id가 존재하지 않습니다."), + COMMENT_NOT_EXISTED(HttpStatus.BAD_REQUEST, "C004", "요청하신 댓글 id가 존재하지 않습니다"), // tendency TENDENCY_INPUT_INVALID(HttpStatus.BAD_REQUEST, "TE01", "잘못된 투자 성향 형식이 입력되었습니다"); diff --git a/src/test/java/org/ai/roboadvisor/domain/chat/controller/ChatControllerTest.java b/src/test/java/org/ai/roboadvisor/domain/chat/controller/ChatControllerTest.java index 9102d38..aae26d9 100644 --- a/src/test/java/org/ai/roboadvisor/domain/chat/controller/ChatControllerTest.java +++ b/src/test/java/org/ai/roboadvisor/domain/chat/controller/ChatControllerTest.java @@ -1,7 +1,7 @@ package org.ai.roboadvisor.domain.chat.controller; import com.google.gson.Gson; -import org.ai.roboadvisor.domain.chat.dto.request.ClearRequest; +import org.ai.roboadvisor.domain.chat.dto.request.MessageClearRequest; import org.ai.roboadvisor.domain.chat.dto.request.MessageRequest; import org.ai.roboadvisor.domain.chat.dto.response.ChatOrderResponse; import org.ai.roboadvisor.domain.chat.dto.response.ChatResponse; @@ -56,11 +56,11 @@ class ChatControllerTest { private final String WELCOME_MESSAGE = "안녕하세요, 저는 AI로보어드바이저의 ChatGPT 서비스에요! 궁금한 점을 입력해주세요"; /** - * getAllChats + * enter */ @Test @DisplayName("처음 대화방에 입장한 경우") - void getAllChats_when_data_is_not_exists() throws Exception { + void enter_when_data_is_not_exists() throws Exception { // given String testNickname = TEST_USER_NICKNAME; LocalDateTime now = LocalDateTime.now().withNano(0); @@ -71,7 +71,7 @@ void getAllChats_when_data_is_not_exists() throws Exception { .build(); // when - Mockito.when(chatService.getAllChats(testNickname)).thenReturn(new ChatResult(chatResponse)); + Mockito.when(chatService.enter(testNickname)).thenReturn(new ChatResult(chatResponse)); // then mvc.perform(MockMvcRequestBuilders.get("/api/chat/{nickname}", testNickname)) @@ -82,7 +82,7 @@ void getAllChats_when_data_is_not_exists() throws Exception { .andExpect(jsonPath("$.data.chatResponse.role").value(ROLE_ASSISTANT)) .andDo(print()); - verify(chatService, times(1)).getAllChats(testNickname); + verify(chatService, times(1)).enter(testNickname); } @Test @@ -100,7 +100,7 @@ void getMessageList_one_chat_already_saved_in_db() throws Exception { .build()); // when - Mockito.when(chatService.getAllChats(testNickname)).thenReturn(new ChatResult(responseList)); + Mockito.when(chatService.enter(testNickname)).thenReturn(new ChatResult(responseList)); // then mvc.perform(MockMvcRequestBuilders.get("/api/chat/{nickname}", testNickname)) @@ -111,12 +111,12 @@ void getMessageList_one_chat_already_saved_in_db() throws Exception { .andExpect(jsonPath("$.data.chatOrderResponse[0].role").value(ROLE_ASSISTANT)) .andDo(print()); - verify(chatService, times(1)).getAllChats(testNickname); + verify(chatService, times(1)).enter(testNickname); } @Test @DisplayName("db에 대화 내용 3개가 저장되어 있는 경우") - void getAllChats_three_chat_already_saved_in_db() throws Exception { + void enter_three_chat_already_saved_in_db() throws Exception { // given String testNickname = TEST_USER_NICKNAME; LocalDateTime now = LocalDateTime.now().withNano(0); @@ -142,7 +142,7 @@ void getAllChats_three_chat_already_saved_in_db() throws Exception { ); // when - Mockito.when(chatService.getAllChats(testNickname)).thenReturn(new ChatResult(responseList)); + Mockito.when(chatService.enter(testNickname)).thenReturn(new ChatResult(responseList)); // then mvc.perform(MockMvcRequestBuilders.get("/api/chat/{nickname}", testNickname)) @@ -157,7 +157,7 @@ void getAllChats_three_chat_already_saved_in_db() throws Exception { .andExpect(jsonPath("$.data.chatOrderResponse[2].role").value(ROLE_ASSISTANT)) .andDo(print()); - verify(chatService, times(1)).getAllChats(testNickname); + verify(chatService, times(1)).enter(testNickname); } @@ -240,7 +240,7 @@ void sendChatBotMessage_should_throw_CustomException_when_save_fails() throws Ex @DisplayName("정상적으로 db에서 데이터가 제거된 경우, 다시 Welcome Message를 리턴한다") void clear() throws Exception { // given - ClearRequest clearRequest = ClearRequest.of(TEST_USER_NICKNAME); + MessageClearRequest messageClearRequest = MessageClearRequest.of(TEST_USER_NICKNAME); LocalDateTime now = LocalDateTime.now().withNano(0); ChatResponse chatResponse = ChatResponse.builder() @@ -252,10 +252,10 @@ void clear() throws Exception { ChatResult chatResult = new ChatResult(chatResponse); Gson gson = new Gson(); - String content = gson.toJson(clearRequest); + String content = gson.toJson(messageClearRequest); // when - Mockito.when(chatService.clear(TEST_USER_NICKNAME)).thenReturn(chatResult); + Mockito.when(chatService.clear(messageClearRequest)).thenReturn(chatResult); // then mvc.perform(MockMvcRequestBuilders.post("/api/chat/clear") @@ -268,19 +268,19 @@ void clear() throws Exception { .andExpect(jsonPath("$.data.chatResponse.content").value(WELCOME_MESSAGE)) .andDo(print()); - verify(chatService, times(1)).clear(TEST_USER_NICKNAME); + verify(chatService, times(1)).clear(messageClearRequest); } @Test @DisplayName("서버, db 오류로 인해 정상적으로 데이터가 지워지지 않은 경우, CustomException 리턴") void clear_should_throw_CustomException_when_delete_fails() throws Exception { // given - ClearRequest clearRequest = ClearRequest.of(TEST_USER_NICKNAME); + MessageClearRequest messageClearRequest = MessageClearRequest.of(TEST_USER_NICKNAME); Gson gson = new Gson(); - String content = gson.toJson(clearRequest); + String content = gson.toJson(messageClearRequest); // when - Mockito.when(chatService.clear(TEST_USER_NICKNAME)) + Mockito.when(chatService.clear(messageClearRequest)) .thenThrow(new CustomException(ErrorCode.INTERNAL_SERVER_ERROR)); // then @@ -293,6 +293,6 @@ void clear_should_throw_CustomException_when_delete_fails() throws Exception { .andExpect(jsonPath("$.data").doesNotExist()) // check data is null .andDo(print()); - verify(chatService, times(1)).clear(TEST_USER_NICKNAME); + verify(chatService, times(1)).clear(messageClearRequest); } } \ No newline at end of file diff --git a/src/test/java/org/ai/roboadvisor/domain/chat/service/ChatServiceTest.java b/src/test/java/org/ai/roboadvisor/domain/chat/service/ChatServiceTest.java index 41b6693..d3bf9a3 100644 --- a/src/test/java/org/ai/roboadvisor/domain/chat/service/ChatServiceTest.java +++ b/src/test/java/org/ai/roboadvisor/domain/chat/service/ChatServiceTest.java @@ -1,11 +1,13 @@ package org.ai.roboadvisor.domain.chat.service; +import org.ai.roboadvisor.domain.chat.dto.request.MessageClearRequest; import org.ai.roboadvisor.domain.chat.dto.request.MessageRequest; import org.ai.roboadvisor.domain.chat.dto.response.ChatOrderResponse; import org.ai.roboadvisor.domain.chat.dto.response.ChatResponse; import org.ai.roboadvisor.domain.chat.dto.response.ChatResult; import org.ai.roboadvisor.domain.chat.entity.Chat; import org.ai.roboadvisor.domain.chat.repository.ChatRepository; +import org.ai.roboadvisor.domain.user.repository.UserRepository; import org.ai.roboadvisor.global.exception.CustomException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -32,6 +34,9 @@ class ChatServiceTest { @Autowired private ChatService chatService; + @Autowired + private UserRepository userRepository; + @Autowired private ChatRepository chatRepository; @@ -51,16 +56,16 @@ void tearDown() { } /** - * getAllChats + * enter */ @Test @DisplayName("case 1: db에 대화내용이 존재하지 않는 경우, Welcome Message를 Controller로 전달한다") - void getAllChats_when_data_is_null() { + void enter_when_data_is_null() { // given String testNickname = TEST_USER_NICKNAME; // when - ChatResult chatResult = chatService.getAllChats(testNickname); + ChatResult chatResult = chatService.enter(testNickname); // then assertThat(chatResult.getChatOrderResponse()).isNull(); @@ -91,7 +96,7 @@ void getChatList_when_data_already_exists_in_db() { chatRepository.saveAll(chats); // when - ChatResult chatResult = chatService.getAllChats(testNickname); + ChatResult chatResult = chatService.enter(testNickname); // then assertThat(chatResult.getChatOrderResponse()).isNotNull(); @@ -137,7 +142,7 @@ void getChatList_when_datas_already_exists_in_db() { chatRepository.saveAll(chats); // when - ChatResult chatResult = chatService.getAllChats(testNickname); + ChatResult chatResult = chatService.enter(testNickname); // then assertThat(chatResult.getChatOrderResponse()).isNotNull(); @@ -237,6 +242,7 @@ void save_fail_when_time_format_is_wrong() { void clear() { // given String testNickname = TEST_USER_NICKNAME; + MessageClearRequest clearRequest = new MessageClearRequest(testNickname); LocalDateTime now = LocalDateTime.now(); List chatList = Arrays.asList( Chat.builder() @@ -256,7 +262,7 @@ void clear() { // when // delete All data, and save one welcome message data - ChatResult chatResult = chatService.clear(testNickname); + ChatResult chatResult = chatService.clear(clearRequest); // then List chats = chatRepository.findAll();