From 72df590b751897ad7e45229a7f81a3821ba5c8c1 Mon Sep 17 00:00:00 2001 From: jwooo Date: Thu, 11 Apr 2024 11:02:36 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=EC=B6=94=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../userlibrary/UserLibraryController.java | 30 ++++++++++++++ .../request/UserLibraryCreateRequest.java | 32 +++++++++++++++ .../userlibrary/UserLibraryService.java | 39 +++++++++++++++++++ .../UserLibraryCreateServiceRequest.java | 32 +++++++++++++++ .../response/UserLibraryResponse.java | 30 ++++++++++++++ .../com/jisungin/domain/ReadingStatus.java | 5 +++ .../repository/UserLibraryRepository.java | 1 + 7 files changed, 169 insertions(+) create mode 100644 src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java create mode 100644 src/main/java/com/jisungin/api/userlibrary/request/UserLibraryCreateRequest.java create mode 100644 src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java create mode 100644 src/main/java/com/jisungin/application/userlibrary/request/UserLibraryCreateServiceRequest.java create mode 100644 src/main/java/com/jisungin/application/userlibrary/response/UserLibraryResponse.java diff --git a/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java b/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java new file mode 100644 index 0000000..70fc378 --- /dev/null +++ b/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java @@ -0,0 +1,30 @@ +package com.jisungin.api.userlibrary; + +import com.jisungin.api.ApiResponse; +import com.jisungin.api.oauth.Auth; +import com.jisungin.api.userlibrary.request.UserLibraryCreateRequest; +import com.jisungin.application.userlibrary.UserLibraryService; +import com.jisungin.application.userlibrary.response.UserLibraryResponse; +import jakarta.validation.Valid; +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("/v1") +public class UserLibraryController { + + private final UserLibraryService userLibraryService; + + @PostMapping("/user-libraries") + public ApiResponse createUserLibraryResponse( + @Valid @RequestBody UserLibraryCreateRequest request, + @Auth Long userId + ) { + return ApiResponse.ok(userLibraryService.createUserLibrary(request.toServiceRequest(), userId)); + } + +} diff --git a/src/main/java/com/jisungin/api/userlibrary/request/UserLibraryCreateRequest.java b/src/main/java/com/jisungin/api/userlibrary/request/UserLibraryCreateRequest.java new file mode 100644 index 0000000..2203b73 --- /dev/null +++ b/src/main/java/com/jisungin/api/userlibrary/request/UserLibraryCreateRequest.java @@ -0,0 +1,32 @@ +package com.jisungin.api.userlibrary.request; + +import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class UserLibraryCreateRequest { + + @NotBlank(message = "책 isbn 입력은 필수 입니다.") + private String isbn; + + @NotBlank(message = "독서 상태 정보 입력은 필수 입니다.") + private String readingStatus; + + @Builder + private UserLibraryCreateRequest(String isbn, String readingStatus) { + this.isbn = isbn; + this.readingStatus = readingStatus; + } + + public UserLibraryCreateServiceRequest toServiceRequest() { + return UserLibraryCreateServiceRequest.builder() + .isbn(isbn) + .readingStatus(readingStatus) + .build(); + } + +} diff --git a/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java b/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java new file mode 100644 index 0000000..3fd1188 --- /dev/null +++ b/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java @@ -0,0 +1,39 @@ +package com.jisungin.application.userlibrary; + +import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest; +import com.jisungin.application.userlibrary.response.UserLibraryResponse; +import com.jisungin.domain.book.Book; +import com.jisungin.domain.book.repository.BookRepository; +import com.jisungin.domain.mylibrary.UserLibrary; +import com.jisungin.domain.mylibrary.repository.UserLibraryRepository; +import com.jisungin.domain.user.User; +import com.jisungin.domain.user.repository.UserRepository; +import com.jisungin.exception.BusinessException; +import com.jisungin.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserLibraryService { + + private final UserRepository userRepository; + private final BookRepository bookRepository; + private final UserLibraryRepository userLibraryRepository; + + @Transactional + public UserLibraryResponse createUserLibrary(UserLibraryCreateServiceRequest request, Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + Book book = bookRepository.findById(request.getIsbn()) + .orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND)); + + UserLibrary savedUserLibrary = userLibraryRepository.save(request.toEntity(user, book)); + + return UserLibraryResponse.of(savedUserLibrary); + } + +} diff --git a/src/main/java/com/jisungin/application/userlibrary/request/UserLibraryCreateServiceRequest.java b/src/main/java/com/jisungin/application/userlibrary/request/UserLibraryCreateServiceRequest.java new file mode 100644 index 0000000..8208854 --- /dev/null +++ b/src/main/java/com/jisungin/application/userlibrary/request/UserLibraryCreateServiceRequest.java @@ -0,0 +1,32 @@ +package com.jisungin.application.userlibrary.request; + +import com.jisungin.domain.ReadingStatus; +import com.jisungin.domain.book.Book; +import com.jisungin.domain.mylibrary.UserLibrary; +import com.jisungin.domain.user.User; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class UserLibraryCreateServiceRequest { + + private String isbn; + private String readingStatus; + + @Builder + private UserLibraryCreateServiceRequest(String isbn, String readingStatus) { + this.isbn = isbn; + this.readingStatus = readingStatus; + } + + public UserLibrary toEntity(User user, Book book) { + return UserLibrary.builder() + .user(user) + .book(book) + .status(ReadingStatus.createReadingStatus(readingStatus)) + .build(); + } + +} diff --git a/src/main/java/com/jisungin/application/userlibrary/response/UserLibraryResponse.java b/src/main/java/com/jisungin/application/userlibrary/response/UserLibraryResponse.java new file mode 100644 index 0000000..f2789ae --- /dev/null +++ b/src/main/java/com/jisungin/application/userlibrary/response/UserLibraryResponse.java @@ -0,0 +1,30 @@ +package com.jisungin.application.userlibrary.response; + + +import com.jisungin.domain.mylibrary.UserLibrary; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class UserLibraryResponse { + + private Long id; + private String status; + + @Builder + private UserLibraryResponse(Long id, String status) { + this.id = id; + this.status = status; + } + + public static UserLibraryResponse of(UserLibrary userLibrary) { + return UserLibraryResponse.builder() + .id(userLibrary.getId()) + .status(userLibrary.getStatus().getText()) + .build(); + + } + +} diff --git a/src/main/java/com/jisungin/domain/ReadingStatus.java b/src/main/java/com/jisungin/domain/ReadingStatus.java index 318d4dd..ec18eef 100644 --- a/src/main/java/com/jisungin/domain/ReadingStatus.java +++ b/src/main/java/com/jisungin/domain/ReadingStatus.java @@ -3,6 +3,7 @@ import com.jisungin.exception.BusinessException; import com.jisungin.exception.ErrorCode; import java.util.List; +import java.util.Locale; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -18,6 +19,10 @@ public enum ReadingStatus { private final String text; + public static ReadingStatus createReadingStatus(String status) { + return ReadingStatus.valueOf(status.toUpperCase(Locale.ENGLISH)); + } + public static List createReadingStatus(List statusList) { if (statusList == null) { throw new BusinessException(ErrorCode.PARTICIPATION_CONDITION_ERROR); diff --git a/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java b/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java index 881ceac..f7ae75a 100644 --- a/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java +++ b/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java @@ -12,4 +12,5 @@ public interface UserLibraryRepository extends JpaRepository "SELECT ul.status FROM UserLibrary ul JOIN ul.user u WHERE u.id = :id" ) ReadingStatus findByUserId(@Param("id") Long userId); + } From 9e47155a4061caac3facaf1a6e937b6766557dfd Mon Sep 17 00:00:00 2001 From: jwooo Date: Thu, 11 Apr 2024 11:03:04 +0900 Subject: [PATCH 2/8] =?UTF-8?q?test:=20=EC=84=9C=EC=9E=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jisungin/ControllerTestSupport.java | 8 +- .../UserLibraryControllerTest.java | 72 ++++++++++ .../userlibrary/UserLibraryServiceTest.java | 130 ++++++++++++++++++ 3 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java create mode 100644 src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java diff --git a/src/test/java/com/jisungin/ControllerTestSupport.java b/src/test/java/com/jisungin/ControllerTestSupport.java index 1ea8f4d..4c5b67e 100644 --- a/src/test/java/com/jisungin/ControllerTestSupport.java +++ b/src/test/java/com/jisungin/ControllerTestSupport.java @@ -12,6 +12,7 @@ import com.jisungin.api.talkroom.TalkRoomController; import com.jisungin.api.talkroomlike.TalkRoomLikeController; import com.jisungin.api.user.UserController; +import com.jisungin.api.userlibrary.UserLibraryController; import com.jisungin.application.book.BestSellerService; import com.jisungin.application.book.BookService; import com.jisungin.application.comment.CommentService; @@ -23,6 +24,7 @@ import com.jisungin.application.talkroom.TalkRoomService; import com.jisungin.application.talkroomlike.TalkRoomLikeService; import com.jisungin.application.user.UserService; +import com.jisungin.application.userlibrary.UserLibraryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -38,7 +40,8 @@ BookController.class, ReviewLikeController.class, ImageController.class, - SearchController.class + SearchController.class, + UserLibraryController.class }) public abstract class ControllerTestSupport { @@ -84,4 +87,7 @@ public abstract class ControllerTestSupport { @MockBean protected SearchService searchService; + @MockBean + protected UserLibraryService userLibraryService; + } diff --git a/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java b/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java new file mode 100644 index 0000000..4969fda --- /dev/null +++ b/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java @@ -0,0 +1,72 @@ +package com.jisungin.api.userlibrary; + +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.jisungin.ControllerTestSupport; +import com.jisungin.api.userlibrary.request.UserLibraryCreateRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class UserLibraryControllerTest extends ControllerTestSupport { + + @Test + @DisplayName("서재 정보를 생성한다.") + public void createUseLibrary() throws Exception { + // given + UserLibraryCreateRequest request = UserLibraryCreateRequest.builder() + .isbn("00001") + .readingStatus("want") + .build(); + + // when // then + mockMvc.perform(post("/v1/user-libraries") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")) + .andDo(print()); + } + + @Test + @DisplayName("서재 정보 등록 시 isbn 입력은 필수이다.") + public void createUserLibraryWithoutIsbn() throws Exception { + // given + UserLibraryCreateRequest request = UserLibraryCreateRequest.builder() + .readingStatus("want") + .build(); + + // when // then + mockMvc.perform(post("/v1/user-libraries") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("400")) + .andExpect(jsonPath("$.message").value("책 isbn 입력은 필수 입니다.")) + .andDo(print()); + } + + @Test + @DisplayName("사재 정보 등록 시 독서 상태 입력은 필수이다.") + public void createUserLibraryWithoutReadingStatus() throws Exception { + // given + UserLibraryCreateRequest request = UserLibraryCreateRequest.builder() + .isbn("00001") + .build(); + + // when // then + mockMvc.perform(post("/v1/user-libraries") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("400")) + .andExpect(jsonPath("$.message").value("독서 상태 정보 입력은 필수 입니다.")) + .andDo(print()); + } + +} diff --git a/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java b/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java new file mode 100644 index 0000000..1bb3f5f --- /dev/null +++ b/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java @@ -0,0 +1,130 @@ +package com.jisungin.application.userlibrary; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.jisungin.ServiceTestSupport; +import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest; +import com.jisungin.application.userlibrary.response.UserLibraryResponse; +import com.jisungin.domain.ReadingStatus; +import com.jisungin.domain.book.Book; +import com.jisungin.domain.book.repository.BookRepository; +import com.jisungin.domain.mylibrary.UserLibrary; +import com.jisungin.domain.mylibrary.repository.UserLibraryRepository; +import com.jisungin.domain.oauth.OauthId; +import com.jisungin.domain.oauth.OauthType; +import com.jisungin.domain.user.User; +import com.jisungin.domain.user.repository.UserRepository; +import com.jisungin.exception.BusinessException; +import java.time.LocalDateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class UserLibraryServiceTest extends ServiceTestSupport { + + @Autowired + private UserRepository userRepository; + + @Autowired + private BookRepository bookRepository; + + @Autowired + private UserLibraryRepository userLibraryRepository; + + @Autowired + private UserLibraryService userLibraryService; + + @BeforeEach + public void tearDown() { + userLibraryRepository.deleteAllInBatch(); + bookRepository.deleteAllInBatch(); + userRepository.deleteAllInBatch(); + } + + @Test + @DisplayName("사용자가 서재 정보를 생성한다.") + public void createUserLibrary() { + // given + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + + UserLibraryCreateServiceRequest request = UserLibraryCreateServiceRequest.builder() + .isbn(book.getIsbn()) + .readingStatus("want") + .build(); + + // when + UserLibraryResponse response = userLibraryService.createUserLibrary(request, user.getId()); + + // then + UserLibrary savedUserLibrary = userLibraryRepository.findAll().get(0); + + assertThat(response.getId()).isEqualTo(savedUserLibrary.getId()); + assertThat(response.getStatus()).isEqualTo(ReadingStatus.WANT.getText()); + } + + @Test + @DisplayName("서재 등록시 사용자 정보가 존재해야 한다.") + public void createUserLibraryWithoutUser() { + // given + Long invalidUserId = -1L; + Book book = bookRepository.save(createBook()); + + UserLibraryCreateServiceRequest request = UserLibraryCreateServiceRequest.builder() + .isbn(book.getIsbn()) + .readingStatus("want") + .build(); + + // when // then + assertThatThrownBy(() -> userLibraryService.createUserLibrary(request, invalidUserId)) + .isInstanceOf(BusinessException.class) + .hasMessage("사용자를 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 등록 시 책 정보가 존재해야 한다.") + public void createUserLibraryWithoutBook() { + // given + String invalidIsbn = "XXXXXXXXXXX"; + User user = userRepository.save(createUser()); + + UserLibraryCreateServiceRequest request = UserLibraryCreateServiceRequest.builder() + .isbn(invalidIsbn) + .readingStatus("want") + .build(); + + // when // then + assertThatThrownBy(() -> userLibraryService.createUserLibrary(request, user.getId())) + .isInstanceOf(BusinessException.class) + .hasMessage("책을 찾을 수 없습니다."); + } + + private static User createUser() { + return User.builder() + .name("user@gmail.com") + .profileImage("image") + .oauthId( + OauthId.builder() + .oauthId("oauthId") + .oauthType(OauthType.KAKAO) + .build() + ) + .build(); + } + + private static Book createBook() { + return Book.builder() + .title("제목") + .content("내용") + .authors("작가") + .isbn("11111") + .publisher("publisher") + .dateTime(LocalDateTime.now()) + .imageUrl("www") + .thumbnail("이미지") + .build(); + } + +} From 98df6339f87f8bcd9e366cb52bbaa2f48ad1e1e3 Mon Sep 17 00:00:00 2001 From: jwooo Date: Thu, 11 Apr 2024 16:41:27 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=EC=B6=94=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/jisungin/api/ApiResponse.java | 4 +++ .../userlibrary/UserLibraryController.java | 18 +++++++++-- .../request/UserLibraryEditRequest.java | 32 +++++++++++++++++++ .../userlibrary/UserLibraryService.java | 26 +++++++++++++++ .../UserLibraryEditServiceRequest.java | 21 ++++++++++++ .../java/com/jisungin/domain/book/Book.java | 4 +++ .../domain/mylibrary/UserLibrary.java | 12 +++++++ .../repository/UserLibraryRepository.java | 9 ++++++ .../com/jisungin/exception/ErrorCode.java | 3 +- 9 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/jisungin/api/userlibrary/request/UserLibraryEditRequest.java create mode 100644 src/main/java/com/jisungin/application/userlibrary/request/UserLibraryEditServiceRequest.java diff --git a/src/main/java/com/jisungin/api/ApiResponse.java b/src/main/java/com/jisungin/api/ApiResponse.java index 9ac65ed..d47a599 100644 --- a/src/main/java/com/jisungin/api/ApiResponse.java +++ b/src/main/java/com/jisungin/api/ApiResponse.java @@ -24,4 +24,8 @@ public static ApiResponse ok(T data) { return new ApiResponse<>(HttpStatus.OK, HttpStatus.OK.name(), data); } + public static ApiResponse ok() { + return new ApiResponse<>(HttpStatus.OK, HttpStatus.OK.name(), null); + } + } diff --git a/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java b/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java index 70fc378..9ebc726 100644 --- a/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java +++ b/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java @@ -3,10 +3,13 @@ import com.jisungin.api.ApiResponse; import com.jisungin.api.oauth.Auth; import com.jisungin.api.userlibrary.request.UserLibraryCreateRequest; +import com.jisungin.api.userlibrary.request.UserLibraryEditRequest; import com.jisungin.application.userlibrary.UserLibraryService; import com.jisungin.application.userlibrary.response.UserLibraryResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -20,11 +23,20 @@ public class UserLibraryController { private final UserLibraryService userLibraryService; @PostMapping("/user-libraries") - public ApiResponse createUserLibraryResponse( - @Valid @RequestBody UserLibraryCreateRequest request, - @Auth Long userId + public ApiResponse createUserLibrary(@Valid @RequestBody UserLibraryCreateRequest request, + @Auth Long userId ) { return ApiResponse.ok(userLibraryService.createUserLibrary(request.toServiceRequest(), userId)); } + @PatchMapping("/user-libraries/{userLibraryId}") + public ApiResponse editUserLibrary(@PathVariable("userLibraryId") Long userLibraryId, + @Valid @RequestBody UserLibraryEditRequest request, + @Auth Long userId + ) { + userLibraryService.editUserLibrary(userLibraryId, userId, request.toServiceRequest()); + + return ApiResponse.ok(); + } + } diff --git a/src/main/java/com/jisungin/api/userlibrary/request/UserLibraryEditRequest.java b/src/main/java/com/jisungin/api/userlibrary/request/UserLibraryEditRequest.java new file mode 100644 index 0000000..664983e --- /dev/null +++ b/src/main/java/com/jisungin/api/userlibrary/request/UserLibraryEditRequest.java @@ -0,0 +1,32 @@ +package com.jisungin.api.userlibrary.request; + +import com.jisungin.application.userlibrary.request.UserLibraryEditServiceRequest; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class UserLibraryEditRequest { + + @NotBlank(message = "책 isbn 입력은 필수 입니다.") + private String isbn; + + @NotBlank(message = "독서 상태 정보 입력은 필수 입니다.") + private String readingStatus; + + @Builder + private UserLibraryEditRequest(String isbn, String readingStatus) { + this.isbn = isbn; + this.readingStatus = readingStatus; + } + + public UserLibraryEditServiceRequest toServiceRequest() { + return UserLibraryEditServiceRequest.builder() + .isbn(isbn) + .readingStatus(readingStatus) + .build(); + } + +} diff --git a/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java b/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java index 3fd1188..97ec4d4 100644 --- a/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java +++ b/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java @@ -1,7 +1,9 @@ package com.jisungin.application.userlibrary; import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest; +import com.jisungin.application.userlibrary.request.UserLibraryEditServiceRequest; import com.jisungin.application.userlibrary.response.UserLibraryResponse; +import com.jisungin.domain.ReadingStatus; import com.jisungin.domain.book.Book; import com.jisungin.domain.book.repository.BookRepository; import com.jisungin.domain.mylibrary.UserLibrary; @@ -11,9 +13,11 @@ import com.jisungin.exception.BusinessException; import com.jisungin.exception.ErrorCode; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -36,4 +40,26 @@ public UserLibraryResponse createUserLibrary(UserLibraryCreateServiceRequest req return UserLibraryResponse.of(savedUserLibrary); } + @Transactional + public void editUserLibrary(Long userLibraryId, Long userId, UserLibraryEditServiceRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + Book book = bookRepository.findById(request.getIsbn()) + .orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND)); + + UserLibrary userLibrary = userLibraryRepository.findByIdWithBookAndUser(userLibraryId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_LIBRARY_NOT_FOUND)); + + if (!userLibrary.isUserLibraryOwner(user.getId())) { + throw new BusinessException(ErrorCode.UNAUTHORIZED_REQUEST); + } + + if (!userLibrary.isSameBook(book.getIsbn())) { + throw new BusinessException(ErrorCode.BOOK_INVALID_INFO); + } + + userLibrary.editReadingStatus(ReadingStatus.createReadingStatus(request.getReadingStatus())); + } + } diff --git a/src/main/java/com/jisungin/application/userlibrary/request/UserLibraryEditServiceRequest.java b/src/main/java/com/jisungin/application/userlibrary/request/UserLibraryEditServiceRequest.java new file mode 100644 index 0000000..e0fc085 --- /dev/null +++ b/src/main/java/com/jisungin/application/userlibrary/request/UserLibraryEditServiceRequest.java @@ -0,0 +1,21 @@ +package com.jisungin.application.userlibrary.request; + +import com.jisungin.api.userlibrary.request.UserLibraryCreateRequest; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class UserLibraryEditServiceRequest { + + private String isbn; + private String readingStatus; + + @Builder + private UserLibraryEditServiceRequest(String isbn, String readingStatus) { + this.isbn = isbn; + this.readingStatus = readingStatus; + } + +} diff --git a/src/main/java/com/jisungin/domain/book/Book.java b/src/main/java/com/jisungin/domain/book/Book.java index 0d10a28..a77f06a 100644 --- a/src/main/java/com/jisungin/domain/book/Book.java +++ b/src/main/java/com/jisungin/domain/book/Book.java @@ -56,4 +56,8 @@ private Book(String isbn, String title, String content, String authors, String p this.dateTime = dateTime; } + public boolean isSame(String isbn) { + return this.isbn.equals(isbn); + } + } diff --git a/src/main/java/com/jisungin/domain/mylibrary/UserLibrary.java b/src/main/java/com/jisungin/domain/mylibrary/UserLibrary.java index 7bebbe8..71c6776 100644 --- a/src/main/java/com/jisungin/domain/mylibrary/UserLibrary.java +++ b/src/main/java/com/jisungin/domain/mylibrary/UserLibrary.java @@ -39,4 +39,16 @@ private UserLibrary(User user, Book book, ReadingStatus status) { this.status = status; } + public boolean isUserLibraryOwner(Long userId) { + return user.isMe(userId); + } + + public boolean isSameBook(String isbn) { + return book.isSame(isbn); + } + + public void editReadingStatus(ReadingStatus status) { + this.status = status; + } + } diff --git a/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java b/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java index f7ae75a..e25da8a 100644 --- a/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java +++ b/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java @@ -2,6 +2,7 @@ import com.jisungin.domain.ReadingStatus; import com.jisungin.domain.mylibrary.UserLibrary; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,4 +14,12 @@ public interface UserLibraryRepository extends JpaRepository ) ReadingStatus findByUserId(@Param("id") Long userId); + @Query( + "SELECT ul FROM UserLibrary ul " + + "JOIN FETCH ul.book " + + "JOIN FETCH ul.user " + + "WHERE ul.id = :id" + ) + Optional findByIdWithBookAndUser(@Param("id") Long id); + } diff --git a/src/main/java/com/jisungin/exception/ErrorCode.java b/src/main/java/com/jisungin/exception/ErrorCode.java index 7b86805..16f6411 100644 --- a/src/main/java/com/jisungin/exception/ErrorCode.java +++ b/src/main/java/com/jisungin/exception/ErrorCode.java @@ -26,7 +26,8 @@ public enum ErrorCode { IMAGE_NOT_FOUND(400, "파일이 없습니다."), S3_UPLOAD_FAIL(400, "이미지 업로드가 실패되었습니다."), NOT_IMAGE(400, "이미지 파일이 아닙니다."), - UNABLE_WRITE_COMMENT(400, "의견을 쓸 권한이 없습니다."); + UNABLE_WRITE_COMMENT(400, "의견을 쓸 권한이 없습니다."), + USER_LIBRARY_NOT_FOUND(404, "서재 정보를 찾을 수 없습니다."); private final int code; private final String message; From 7faee25f238b304696665f38414bdf6da25d3012 Mon Sep 17 00:00:00 2001 From: jwooo Date: Thu, 11 Apr 2024 16:42:16 +0900 Subject: [PATCH 4/8] =?UTF-8?q?test:=20=EC=84=9C=EC=9E=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserLibraryControllerTest.java | 64 +++++++ .../userlibrary/UserLibraryServiceTest.java | 160 ++++++++++++++++++ 2 files changed, 224 insertions(+) diff --git a/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java b/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java index 4969fda..83050a0 100644 --- a/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java +++ b/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java @@ -1,6 +1,7 @@ package com.jisungin.api.userlibrary; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -8,6 +9,7 @@ import com.jisungin.ControllerTestSupport; import com.jisungin.api.userlibrary.request.UserLibraryCreateRequest; +import com.jisungin.api.userlibrary.request.UserLibraryEditRequest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -69,4 +71,66 @@ public void createUserLibraryWithoutReadingStatus() throws Exception { .andDo(print()); } + @Test + @DisplayName("서재 정보를 수정한다.") + public void editUserLibrary() throws Exception { + // given + Long userLibraryId = 1L; + + UserLibraryEditRequest request = UserLibraryEditRequest.builder() + .isbn("00001") + .readingStatus("want") + .build(); + + // when // then + mockMvc.perform(patch("/v1/user-libraries/{userLibraryId}", userLibraryId) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")) + .andDo(print()); + } + + @Test + @DisplayName("서재 정보를 수정시 isbn 입력은 필수이다.") + public void editUserLibraryWithoutIsbn() throws Exception { + // given + Long userLibraryId = 1L; + + UserLibraryEditRequest request = UserLibraryEditRequest.builder() + .readingStatus("want") + .build(); + + // when // then + mockMvc.perform(patch("/v1/user-libraries/{userLibraryId}", userLibraryId) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("400")) + .andExpect(jsonPath("$.message").value("책 isbn 입력은 필수 입니다.")) + .andDo(print()); + } + + @Test + @DisplayName("서재 정보를 수정시 독서 상태 정보 입력은 필수이다.") + public void editUserLibraryWithoutReadingStatus() throws Exception { + // given + Long userLibraryId = 1L; + + UserLibraryEditRequest request = UserLibraryEditRequest.builder() + .isbn("00001") + .build(); + + // when // then + mockMvc.perform(patch("/v1/user-libraries/{userLibraryId}", userLibraryId) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("400")) + .andExpect(jsonPath("$.message").value("독서 상태 정보 입력은 필수 입니다.")) + .andDo(print()); + } + } diff --git a/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java b/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java index 1bb3f5f..2649518 100644 --- a/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java +++ b/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java @@ -5,6 +5,7 @@ import com.jisungin.ServiceTestSupport; import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest; +import com.jisungin.application.userlibrary.request.UserLibraryEditServiceRequest; import com.jisungin.application.userlibrary.response.UserLibraryResponse; import com.jisungin.domain.ReadingStatus; import com.jisungin.domain.book.Book; @@ -17,6 +18,7 @@ import com.jisungin.domain.user.repository.UserRepository; import com.jisungin.exception.BusinessException; import java.time.LocalDateTime; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -101,6 +103,130 @@ public void createUserLibraryWithoutBook() { .hasMessage("책을 찾을 수 없습니다."); } + @Test + @DisplayName("서재 정보를 수정한다.") + public void editUserLibrary() { + // given + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + UserLibrary userLibrary = userLibraryRepository.save(create(user, book)); + + UserLibraryEditServiceRequest request = UserLibraryEditServiceRequest.builder() + .isbn(book.getIsbn()) + .readingStatus("read") + .build(); + + // when + userLibraryService.editUserLibrary(userLibrary.getId(), user.getId(), request); + + // then + Optional savedLibrary = userLibraryRepository.findById(userLibrary.getId()); + + assertThat(savedLibrary).isNotEmpty(); + assertThat(savedLibrary.get().getStatus()).isEqualTo(ReadingStatus.READ); + } + + @Test + @DisplayName("서재 정보 수정 시 사용자 정보가 존재해야 한다.") + public void editUserLibraryWithoutUser() { + // given + Long userLibraryId = 1L; + Long userId = 1L; + Book book = bookRepository.save(createBook()); + + UserLibraryEditServiceRequest request = UserLibraryEditServiceRequest.builder() + .isbn(book.getIsbn()) + .readingStatus("read") + .build(); + + // when // then + assertThatThrownBy(() -> userLibraryService.editUserLibrary(userLibraryId, userId, request)) + .isInstanceOf(BusinessException.class) + .hasMessage("사용자를 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 정보 수정 시 책 정보가 존재해야 한다.") + public void editUserLibraryWithoutBook() { + // given + Long userLibraryId = 1L; + String bookIsbn = "0000X"; + User user = userRepository.save(createUser()); + + UserLibraryEditServiceRequest request = UserLibraryEditServiceRequest.builder() + .isbn(bookIsbn) + .readingStatus("read") + .build(); + + // when // then + assertThatThrownBy(() -> userLibraryService.editUserLibrary(userLibraryId, user.getId(), request)) + .isInstanceOf(BusinessException.class) + .hasMessage("책을 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 정보 수정 시 서재 정보가 존재해야 한다.") + public void editUserLibraryWithoutUserLibrary() { + // given + Long userLibraryId = 1L; + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + + UserLibraryEditServiceRequest request = UserLibraryEditServiceRequest.builder() + .isbn(book.getIsbn()) + .readingStatus("read") + .build(); + + // when // then + assertThatThrownBy(() -> userLibraryService.editUserLibrary(userLibraryId, user.getId(), request)) + .isInstanceOf(BusinessException.class) + .hasMessage("서재 정보를 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 정보 수정 시 서재 정보와 사용자 정보는 일치해야 한다.") + public void editUserLibraryInvalidUser() { + // given + User user = userRepository.save(createUser()); + User anotherUser = userRepository.save(createAnotherUser()); + + Book book = bookRepository.save(createBook()); + + UserLibrary userLibrary = userLibraryRepository.save(create(user, book)); + + UserLibraryEditServiceRequest request = UserLibraryEditServiceRequest.builder() + .isbn(book.getIsbn()) + .readingStatus("read") + .build(); + + // when // then + assertThatThrownBy(() -> userLibraryService.editUserLibrary(userLibrary.getId(), anotherUser.getId(), request)) + .isInstanceOf(BusinessException.class) + .hasMessage("권한이 없는 사용자입니다."); + } + + @Test + @DisplayName("서재 정보 수정 시 서재 정보와 도서 정보는 일치해야 한다.") + public void editUserLibraryInvalidBook() { + // given + User user = userRepository.save(createUser()); + + Book book = bookRepository.save(createBookWithIsbn("00001")); + Book anotherBook = bookRepository.save(createBookWithIsbn("00002")); + + UserLibrary userLibrary = userLibraryRepository.save(create(user, book)); + + UserLibraryEditServiceRequest request = UserLibraryEditServiceRequest.builder() + .isbn(anotherBook.getIsbn()) + .readingStatus("read") + .build(); + + // when // then + assertThatThrownBy(() -> userLibraryService.editUserLibrary(userLibrary.getId(), user.getId(), request)) + .isInstanceOf(BusinessException.class) + .hasMessage("올바르지 않은 책 정보 입니다."); + } + private static User createUser() { return User.builder() .name("user@gmail.com") @@ -114,6 +240,19 @@ private static User createUser() { .build(); } + private static User createAnotherUser() { + return User.builder() + .name("another@gmail.com") + .profileImage("image") + .oauthId( + OauthId.builder() + .oauthId("anotherOauthId") + .oauthType(OauthType.KAKAO) + .build() + ) + .build(); + } + private static Book createBook() { return Book.builder() .title("제목") @@ -127,4 +266,25 @@ private static Book createBook() { .build(); } + private static Book createBookWithIsbn(String isbn) { + return Book.builder() + .title("제목") + .content("내용") + .authors("작가") + .isbn(isbn) + .publisher("publisher") + .dateTime(LocalDateTime.now()) + .imageUrl("www") + .thumbnail("이미지") + .build(); + } + + public static UserLibrary create(User user, Book book) { + return UserLibrary.builder() + .user(user) + .book(book) + .status(ReadingStatus.WANT) + .build(); + } + } From 57f42040379f54e98d2a32e7ae5fc36c66bd070a Mon Sep 17 00:00:00 2001 From: jwooo Date: Thu, 11 Apr 2024 20:17:52 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20API=20=EC=B6=94=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jisungin/api/GlobalExceptionHandler.java | 11 +++++++++ .../userlibrary/UserLibraryController.java | 12 ++++++++++ .../userlibrary/UserLibraryService.java | 23 +++++++++++++++++++ .../com/jisungin/exception/ErrorCode.java | 3 ++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/jisungin/api/GlobalExceptionHandler.java b/src/main/java/com/jisungin/api/GlobalExceptionHandler.java index 6ca8ef9..157311f 100644 --- a/src/main/java/com/jisungin/api/GlobalExceptionHandler.java +++ b/src/main/java/com/jisungin/api/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package com.jisungin.api; import com.jisungin.exception.BusinessException; +import com.jisungin.exception.ErrorCode; import com.jisungin.exception.ErrorResponse; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -9,6 +10,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -31,6 +33,15 @@ public ResponseEntity handleMethodArgumentNotValidException(Metho .body(ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), firstErrorMessage)); } + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity handleMissionServletRequestParamException( + MissingServletRequestParameterException e + ) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ErrorResponse.of(ErrorCode.INVALID_PARAMS_VALUE.getCode(), + ErrorCode.INVALID_PARAMS_VALUE.getMessage())); + } + @ExceptionHandler(BusinessException.class) public ResponseEntity handleBusinessException(BusinessException e) { log.info("BusinessException: {}", e.getMessage()); diff --git a/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java b/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java index 9ebc726..6f1ccda 100644 --- a/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java +++ b/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java @@ -8,11 +8,13 @@ import com.jisungin.application.userlibrary.response.UserLibraryResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -39,4 +41,14 @@ public ApiResponse editUserLibrary(@PathVariable("userLibraryId") Long use return ApiResponse.ok(); } + @DeleteMapping("/user-libraries/{userLibraryId}") + public ApiResponse deleteUserLibrary(@PathVariable("userLibraryId") Long userLibraryId, + @RequestParam String isbn, + @Auth Long userId + ) { + userLibraryService.deleteUserLibrary(userLibraryId, userId, isbn); + + return ApiResponse.ok(); + } + } diff --git a/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java b/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java index 97ec4d4..7dcda01 100644 --- a/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java +++ b/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java @@ -1,5 +1,6 @@ package com.jisungin.application.userlibrary; +import com.amazonaws.services.kms.model.EnableKeyRotationRequest; import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest; import com.jisungin.application.userlibrary.request.UserLibraryEditServiceRequest; import com.jisungin.application.userlibrary.response.UserLibraryResponse; @@ -62,4 +63,26 @@ public void editUserLibrary(Long userLibraryId, Long userId, UserLibraryEditServ userLibrary.editReadingStatus(ReadingStatus.createReadingStatus(request.getReadingStatus())); } + @Transactional + public void deleteUserLibrary(Long userLibraryId, Long userId, String isbn) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + Book book = bookRepository.findById(isbn) + .orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND)); + + UserLibrary userLibrary = userLibraryRepository.findByIdWithBookAndUser(userLibraryId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_LIBRARY_NOT_FOUND)); + + if (!userLibrary.isUserLibraryOwner(user.getId())) { + throw new BusinessException(ErrorCode.UNAUTHORIZED_REQUEST); + } + + if (!userLibrary.isSameBook(book.getIsbn())) { + throw new BusinessException(ErrorCode.BOOK_INVALID_INFO); + } + + userLibraryRepository.deleteById(userLibrary.getId()); + } + } diff --git a/src/main/java/com/jisungin/exception/ErrorCode.java b/src/main/java/com/jisungin/exception/ErrorCode.java index 16f6411..4609689 100644 --- a/src/main/java/com/jisungin/exception/ErrorCode.java +++ b/src/main/java/com/jisungin/exception/ErrorCode.java @@ -27,7 +27,8 @@ public enum ErrorCode { S3_UPLOAD_FAIL(400, "이미지 업로드가 실패되었습니다."), NOT_IMAGE(400, "이미지 파일이 아닙니다."), UNABLE_WRITE_COMMENT(400, "의견을 쓸 권한이 없습니다."), - USER_LIBRARY_NOT_FOUND(404, "서재 정보를 찾을 수 없습니다."); + USER_LIBRARY_NOT_FOUND(404, "서재 정보를 찾을 수 없습니다."), + INVALID_PARAMS_VALUE(400, "유효하지 않은 파라미터 입니다."); private final int code; private final String message; From 02190608df3de0ce7ce0eaf35570888d0d86b600 Mon Sep 17 00:00:00 2001 From: jwooo Date: Thu, 11 Apr 2024 20:19:09 +0900 Subject: [PATCH 6/8] =?UTF-8?q?test:=20=EC=84=9C=EC=9E=AC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserLibraryControllerTest.java | 31 ++++++ .../userlibrary/UserLibraryServiceTest.java | 96 +++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java b/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java index 83050a0..7a904ed 100644 --- a/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java +++ b/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java @@ -1,6 +1,7 @@ package com.jisungin.api.userlibrary; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -133,4 +134,34 @@ public void editUserLibraryWithoutReadingStatus() throws Exception { .andDo(print()); } + @Test + @DisplayName("서재 정보를 삭제한다.") + public void deleteUserLibrary() throws Exception { + // given + Long userLibraryId = 1L; + + // when // then + mockMvc.perform(delete("/v1/user-libraries/{userLibraryId}", userLibraryId) + .param("isbn", "0000X")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")) + .andDo(print()); + } + + @Test + @DisplayName("서재 정보 삭제 시 책 isbn 입력은 필수이다.") + public void deleteUserLibraryWithoutIsbn() throws Exception { + // given + Long userLibraryId = 1L; + + // when // then + mockMvc.perform(delete("/v1/user-libraries/{userLibraryId}", userLibraryId)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("400")) + .andExpect(jsonPath("$.message").value("유효하지 않은 파라미터 입니다.")) + .andDo(print()); + } + } diff --git a/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java b/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java index 2649518..acbd089 100644 --- a/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java +++ b/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java @@ -18,6 +18,7 @@ import com.jisungin.domain.user.repository.UserRepository; import com.jisungin.exception.BusinessException; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -227,6 +228,101 @@ public void editUserLibraryInvalidBook() { .hasMessage("올바르지 않은 책 정보 입니다."); } + @Test + @DisplayName("서재 정보를 삭제한다.") + public void deleteUserLibrary() { + // given + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + UserLibrary userLibrary = userLibraryRepository.save(create(user, book)); + + // when + userLibraryService.deleteUserLibrary(userLibrary.getId(), user.getId(), book.getIsbn()); + + // then + List response = userLibraryRepository.findAll(); + + assertThat(response).isEmpty(); + } + + @Test + @DisplayName("서재 정보 삭제 시 사용자 정보가 존재해야 한다.") + public void deleteUserLibraryWithoutUser() { + // given + Long userLibraryId = 1L; + Long userId = 1L; + Book book = bookRepository.save(createBook()); + + // when // then + assertThatThrownBy(() -> userLibraryService.deleteUserLibrary(userLibraryId, userId, book.getIsbn())) + .isInstanceOf(BusinessException.class) + .hasMessage("사용자를 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 정보 삭제 시 책 정보가 존재해야 한다.") + public void deleteUserLibraryWithoutBook() { + // given + Long userLibraryId = 1L; + String bookIsbn = "0000X"; + User user = userRepository.save(createUser()); + + // when // then + assertThatThrownBy(() -> userLibraryService.deleteUserLibrary(userLibraryId, user.getId(), bookIsbn)) + .isInstanceOf(BusinessException.class) + .hasMessage("책을 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 정보 삭제 시 서재 정보가 존재해야 한다.") + public void deleteUserLibraryWithoutUserLibrary() { + // given + Long userLibraryId = 1L; + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + + // when // then + assertThatThrownBy(() -> userLibraryService.deleteUserLibrary(userLibraryId, user.getId(), book.getIsbn())) + .isInstanceOf(BusinessException.class) + .hasMessage("서재 정보를 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 정보 삭제 시 서재 정보와 사용자 정보는 일치해야 한다.") + public void deleteUserLibraryInvalidUser() { + // given + User user = userRepository.save(createUser()); + User anotherUser = userRepository.save(createAnotherUser()); + + Book book = bookRepository.save(createBook()); + + UserLibrary userLibrary = userLibraryRepository.save(create(user, book)); + + // when // then + assertThatThrownBy( + () -> userLibraryService.deleteUserLibrary(userLibrary.getId(), anotherUser.getId(), book.getIsbn())) + .isInstanceOf(BusinessException.class) + .hasMessage("권한이 없는 사용자입니다."); + } + + @Test + @DisplayName("서재 정보 삭제 시 서재 정보와 도서 정보는 일치해야 한다.") + public void deleteUserLibraryInvalidBook() { + // given + User user = userRepository.save(createUser()); + + Book book = bookRepository.save(createBookWithIsbn("00001")); + Book anotherBook = bookRepository.save(createBookWithIsbn("00002")); + + UserLibrary userLibrary = userLibraryRepository.save(create(user, book)); + + // when // then + assertThatThrownBy( + () -> userLibraryService.deleteUserLibrary(userLibrary.getId(), user.getId(), anotherBook.getIsbn())) + .isInstanceOf(BusinessException.class) + .hasMessage("올바르지 않은 책 정보 입니다."); + } + private static User createUser() { return User.builder() .name("user@gmail.com") From c37cc85c0a5edc2d479846e036c197e5a468816d Mon Sep 17 00:00:00 2001 From: jwooo Date: Fri, 12 Apr 2024 16:01:33 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=B6=94=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/userlibrary/UserLibraryController.java | 8 ++++++++ .../userlibrary/UserLibraryService.java | 17 ++++++++++++++++- .../response/UserLibraryResponse.java | 14 +++++++++++++- .../repository/UserLibraryRepository.java | 4 ++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java b/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java index 6f1ccda..4e96e4b 100644 --- a/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java +++ b/src/main/java/com/jisungin/api/userlibrary/UserLibraryController.java @@ -9,6 +9,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -24,6 +25,13 @@ public class UserLibraryController { private final UserLibraryService userLibraryService; + @GetMapping("/user-libraries") + public ApiResponse getUserLibrary(@RequestParam String isbn, + @Auth Long userId + ) { + return ApiResponse.ok(userLibraryService.getUserLibrary(userId, isbn)); + } + @PostMapping("/user-libraries") public ApiResponse createUserLibrary(@Valid @RequestBody UserLibraryCreateRequest request, @Auth Long userId diff --git a/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java b/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java index 7dcda01..3cac752 100644 --- a/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java +++ b/src/main/java/com/jisungin/application/userlibrary/UserLibraryService.java @@ -1,6 +1,5 @@ package com.jisungin.application.userlibrary; -import com.amazonaws.services.kms.model.EnableKeyRotationRequest; import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest; import com.jisungin.application.userlibrary.request.UserLibraryEditServiceRequest; import com.jisungin.application.userlibrary.response.UserLibraryResponse; @@ -28,6 +27,22 @@ public class UserLibraryService { private final BookRepository bookRepository; private final UserLibraryRepository userLibraryRepository; + public UserLibraryResponse getUserLibrary(Long userId, String isbn) { + if (userId == null || isbn == null) { + return UserLibraryResponse.empty(); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + Book book = bookRepository.findById(isbn) + .orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND)); + + UserLibrary userLibrary = userLibraryRepository.findByUserAndBook(user, book); + + return UserLibraryResponse.of(userLibrary); + } + @Transactional public UserLibraryResponse createUserLibrary(UserLibraryCreateServiceRequest request, Long userId) { User user = userRepository.findById(userId) diff --git a/src/main/java/com/jisungin/application/userlibrary/response/UserLibraryResponse.java b/src/main/java/com/jisungin/application/userlibrary/response/UserLibraryResponse.java index f2789ae..6b89e24 100644 --- a/src/main/java/com/jisungin/application/userlibrary/response/UserLibraryResponse.java +++ b/src/main/java/com/jisungin/application/userlibrary/response/UserLibraryResponse.java @@ -12,19 +12,31 @@ public class UserLibraryResponse { private Long id; private String status; + private Boolean hasReadingStatus; @Builder - private UserLibraryResponse(Long id, String status) { + private UserLibraryResponse(Long id, String status, Boolean hasReadingStatus) { this.id = id; this.status = status; + this.hasReadingStatus = hasReadingStatus; } public static UserLibraryResponse of(UserLibrary userLibrary) { + if (userLibrary == null) { + return empty(); + } + return UserLibraryResponse.builder() .id(userLibrary.getId()) .status(userLibrary.getStatus().getText()) + .hasReadingStatus(true) .build(); + } + public static UserLibraryResponse empty() { + return UserLibraryResponse.builder() + .hasReadingStatus(false) + .build(); } } diff --git a/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java b/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java index e25da8a..9ba6f96 100644 --- a/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java +++ b/src/main/java/com/jisungin/domain/mylibrary/repository/UserLibraryRepository.java @@ -1,7 +1,9 @@ package com.jisungin.domain.mylibrary.repository; import com.jisungin.domain.ReadingStatus; +import com.jisungin.domain.book.Book; import com.jisungin.domain.mylibrary.UserLibrary; +import com.jisungin.domain.user.User; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -22,4 +24,6 @@ public interface UserLibraryRepository extends JpaRepository ) Optional findByIdWithBookAndUser(@Param("id") Long id); + UserLibrary findByUserAndBook(User user, Book book); + } From 07c5b0c5fd817133456cd17b8c8c797906eb5064 Mon Sep 17 00:00:00 2001 From: jwooo Date: Fri, 12 Apr 2024 16:02:17 +0900 Subject: [PATCH 8/8] =?UTF-8?q?test:=20=EC=84=9C=EC=9E=AC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserLibraryControllerTest.java | 30 +++++++ .../userlibrary/UserLibraryServiceTest.java | 90 +++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java b/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java index 7a904ed..6d7534f 100644 --- a/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java +++ b/src/test/java/com/jisungin/api/userlibrary/UserLibraryControllerTest.java @@ -2,6 +2,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -16,6 +17,35 @@ public class UserLibraryControllerTest extends ControllerTestSupport { + @Test + @DisplayName("서재 정보를 조회한다.") + public void getUserLibrary() throws Exception { + // given + String isbn = "00001"; + + // when // then + mockMvc.perform(get("/v1/user-libraries") + .param("isbn", isbn) + .accept(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")) + .andDo(print()); + } + + @Test + @DisplayName("서재 정보 조회 시 책 isbn 입력은 필수이다.") + public void getUserLibraryWithoutIsbn() throws Exception { + // when // then + mockMvc.perform(get("/v1/user-libraries") + .accept(APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("400")) + .andExpect(jsonPath("$.message").value("유효하지 않은 파라미터 입니다.")) + .andDo(print()); + } + @Test @DisplayName("서재 정보를 생성한다.") public void createUseLibrary() throws Exception { diff --git a/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java b/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java index acbd089..45c063c 100644 --- a/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java +++ b/src/test/java/com/jisungin/application/userlibrary/UserLibraryServiceTest.java @@ -46,6 +46,96 @@ public void tearDown() { userRepository.deleteAllInBatch(); } + @Test + @DisplayName("사용자가 서재 정보를 조회한다.") + public void getUserLibrary() { + // given + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + UserLibrary userLibrary = userLibraryRepository.save(create(user, book)); + + // when + UserLibraryResponse response = userLibraryService.getUserLibrary(user.getId(), book.getIsbn()); + + // then + assertThat(response.getId()).isEqualTo(userLibrary.getId()); + assertThat(response.getStatus()).isEqualTo(userLibrary.getStatus().getText()); + assertThat(response.getHasReadingStatus()).isTrue(); + } + + @Test + @DisplayName("비로그인으로 서재 정보 조회시 빈 응답을 받는다.") + public void getUserLibraryForUnAuthenticatedUser() { + // given + String bookIsbn = "0000X"; + + // when + UserLibraryResponse response = userLibraryService.getUserLibrary(null, bookIsbn); + + // then + assertThat(response).isNotNull(); + assertThat(response.getId()).isNull(); + assertThat(response.getStatus()).isNull(); + assertThat(response.getHasReadingStatus()).isFalse(); + } + + @Test + @DisplayName("서재 정보 조회 시 isbn이 없는 경우 빈 응답을 받는다.") + public void getUserLibraryWithNonIsbn() { + // given + User user = userRepository.save(createUser()); + + // when + UserLibraryResponse response = userLibraryService.getUserLibrary(user.getId(), null); + + // then + assertThat(response).isNotNull(); + assertThat(response.getHasReadingStatus()).isFalse(); + } + + @Test + @DisplayName("서재 정보 조회 시 사용자 정보가 존재해야 한다.") + public void getUserLibraryWithoutUser() { + // given + Long invalidUserId = -1L; + Book book = bookRepository.save(createBook()); + + // when // then + assertThatThrownBy(() -> userLibraryService.getUserLibrary(invalidUserId, book.getIsbn())) + .isInstanceOf(BusinessException.class) + .hasMessage("사용자를 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 정보 조회 시 책 정보가 존재해야 한다.") + public void getUserLibraryWithoutBook() { + // given + String invalidIsbn = "0000X"; + User user = userRepository.save(createUser()); + + // when // then + assertThatThrownBy(() -> userLibraryService.getUserLibrary(user.getId(), invalidIsbn)) + .isInstanceOf(BusinessException.class) + .hasMessage("책을 찾을 수 없습니다."); + } + + @Test + @DisplayName("서재 정보 조회 시 서재 정보가 없을 시 빈 응답을 받는다.") + public void getUserLibraryWhenLibraryEmpty() { + // given + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + + // when + UserLibraryResponse response = userLibraryService.getUserLibrary(user.getId(), book.getIsbn()); + + // then + assertThat(response).isNotNull(); + assertThat(response.getId()).isNull(); + assertThat(response.getStatus()).isNull(); + assertThat(response.getHasReadingStatus()).isFalse(); + } + @Test @DisplayName("사용자가 서재 정보를 생성한다.") public void createUserLibrary() {