From 8d67d1f021544dc2e3652a5a536fa049936afb54 Mon Sep 17 00:00:00 2001 From: ChoiDongKuen Date: Mon, 29 Jan 2024 05:20:56 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EB=A6=AC=EB=B7=B0=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=EC=8B=9C=20=ED=9A=8C=EC=9B=90=20=EB=A7=A4=EB=84=88=20?= =?UTF-8?q?=EC=98=A8=EB=8F=84=20=EB=B0=98=EC=98=81=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A6=AC=EB=B7=B0=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#158)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 회원 리뷰 등록 API 리팩토링 (#157) * feat: 회원 리뷰 등록시, 온도 반영 및 리뷰 피드백 반영 (#157) * feat: User 엔티티 메소드 추가 (#157) * feat: Review enum 필드 추가 (#157) * test: 리뷰 등록시 온도 업데이트에 대한 단위 테스트 (#157) * test: 리뷰 등록 기능 통합 테스트 (#157) * refactor: 회원 리뷰 등록 메소드 수정 (#157) * test: 불필요한 테스트 제거 및 CI 오류 수정 (#157) --- .../user/controller/UserController.java | 2 +- .../java/net/teumteum/user/domain/Review.java | 13 +++- .../java/net/teumteum/user/domain/User.java | 10 ++- .../domain/request/ReviewRegisterRequest.java | 2 +- .../teumteum/user/service/UserService.java | 22 ++++-- .../java/net/teumteum/integration/Api.java | 11 +++ .../net/teumteum/integration/Repository.java | 8 ++ .../teumteum/integration/RequestFixture.java | 15 ++++ .../integration/UserIntegrationTest.java | 78 ++++++++++++++----- .../user/controller/UserControllerTest.java | 26 +++++++ .../unit/user/service/UserServiceTest.java | 28 ++++++- .../net/teumteum/user/domain/UserFixture.java | 4 +- 12 files changed, 180 insertions(+), 39 deletions(-) diff --git a/src/main/java/net/teumteum/user/controller/UserController.java b/src/main/java/net/teumteum/user/controller/UserController.java index 49bbfb8f..d6a28264 100644 --- a/src/main/java/net/teumteum/user/controller/UserController.java +++ b/src/main/java/net/teumteum/user/controller/UserController.java @@ -113,7 +113,7 @@ public void registerReview( @RequestParam Long meetingId, @Valid @RequestBody ReviewRegisterRequest request ) { - userService.registerReview(meetingId, request); + userService.registerReview(meetingId, getCurrentUserId(), request); } @GetMapping("/reviews") diff --git a/src/main/java/net/teumteum/user/domain/Review.java b/src/main/java/net/teumteum/user/domain/Review.java index cb01b81e..a04445aa 100644 --- a/src/main/java/net/teumteum/user/domain/Review.java +++ b/src/main/java/net/teumteum/user/domain/Review.java @@ -1,7 +1,14 @@ package net.teumteum.user.domain; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum Review { - 별로에요, - 좋아요, - 최고에요 + 별로에요(-1), + 좋아요(1), + 최고에요(2); + + private final int score; } diff --git a/src/main/java/net/teumteum/user/domain/User.java b/src/main/java/net/teumteum/user/domain/User.java index bdec964c..c491a028 100644 --- a/src/main/java/net/teumteum/user/domain/User.java +++ b/src/main/java/net/teumteum/user/domain/User.java @@ -122,9 +122,11 @@ public void addFriend(User user) { friends.add(user.id); } - public void addReview(Review review) { - List newReviews = new ArrayList<>(reviews); - newReviews.add(review); - reviews = newReviews; + public void registerReview(Review review) { + reviews.add(review); + } + + public void updateMannerTemperature(Review review) { + mannerTemperature += review.getScore(); } } diff --git a/src/main/java/net/teumteum/user/domain/request/ReviewRegisterRequest.java b/src/main/java/net/teumteum/user/domain/request/ReviewRegisterRequest.java index 60603575..b5cc4bbb 100644 --- a/src/main/java/net/teumteum/user/domain/request/ReviewRegisterRequest.java +++ b/src/main/java/net/teumteum/user/domain/request/ReviewRegisterRequest.java @@ -8,7 +8,7 @@ public record ReviewRegisterRequest( @Valid - @Size(min = 3, max = 6) + @Size(min = 2, max = 5) List reviews ) { diff --git a/src/main/java/net/teumteum/user/service/UserService.java b/src/main/java/net/teumteum/user/service/UserService.java index f690b304..38f9ca02 100644 --- a/src/main/java/net/teumteum/user/service/UserService.java +++ b/src/main/java/net/teumteum/user/service/UserService.java @@ -103,13 +103,15 @@ public void logout(Long userId) { @Transactional - public void registerReview(Long meetingId, ReviewRegisterRequest request) { + public void registerReview(Long meetingId, Long currentUserId, ReviewRegisterRequest request) { checkMeetingExistence(meetingId); + checkUserNotRegisterSelfReview(request, currentUserId); request.reviews() .forEach(userReview -> { User user = getUser(userReview.id()); - user.addReview(userReview.review()); + user.registerReview(userReview.review()); + user.updateMannerTemperature(userReview.review()); }); } @@ -148,8 +150,18 @@ private void checkUserExistence(Authenticated authenticated, String oauthId) { } private void checkMeetingExistence(Long meetingId) { - if (!meetingConnector.existById(meetingId)) { - throw new IllegalArgumentException("meetingId에 해당하는 meeting을 찾을 수 없습니다. \"" + meetingId + "\""); - } + Assert.isTrue(meetingConnector.existById(meetingId), + () -> { + throw new IllegalArgumentException("meetingId에 해당하는 meeting을 찾을 수 없습니다. \"" + meetingId + "\""); + } + ); + } + + private void checkUserNotRegisterSelfReview(ReviewRegisterRequest request, Long currentUserId) { + Assert.isTrue(request.reviews().stream().noneMatch(review -> review.id().equals(currentUserId)), + () -> { + throw new IllegalArgumentException("나의 리뷰에 대한 리뷰를 작성할 수 없습니다."); + } + ); } } diff --git a/src/test/java/net/teumteum/integration/Api.java b/src/test/java/net/teumteum/integration/Api.java index d679e895..225b07de 100644 --- a/src/test/java/net/teumteum/integration/Api.java +++ b/src/test/java/net/teumteum/integration/Api.java @@ -4,6 +4,7 @@ import net.teumteum.meeting.config.PageableHandlerMethodArgumentResolver; import net.teumteum.meeting.domain.Topic; import net.teumteum.teum_teum.domain.request.UserLocationRequest; +import net.teumteum.user.domain.request.ReviewRegisterRequest; import net.teumteum.user.domain.request.UserRegisterRequest; import net.teumteum.user.domain.request.UserUpdateRequest; import net.teumteum.user.domain.request.UserWithdrawRequest; @@ -188,4 +189,14 @@ ResponseSpec getUserReviews(String accessToken) { .header(HttpHeaders.AUTHORIZATION, accessToken) .exchange(); } + + ResponseSpec registerUserReview(String accessToken, Long meetingId, ReviewRegisterRequest request) { + String uri = "/users/reviews?meetingId=" + meetingId; + return webTestClient + .post() + .uri(uri) + .header(HttpHeaders.AUTHORIZATION, accessToken) + .bodyValue(request) + .exchange(); + } } diff --git a/src/test/java/net/teumteum/integration/Repository.java b/src/test/java/net/teumteum/integration/Repository.java index 87f43e8c..7d829ee9 100644 --- a/src/test/java/net/teumteum/integration/Repository.java +++ b/src/test/java/net/teumteum/integration/Repository.java @@ -2,6 +2,7 @@ import java.util.List; +import java.util.stream.IntStream; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import net.teumteum.core.config.AppConfig; @@ -40,6 +41,13 @@ public User saveAndGetUser(Long id) { return userRepository.saveAndFlush(user); } + public List saveAndGetUsers(int size) { + return Stream.generate(UserFixture::getNullIdUser) + .limit(size) + .map(userRepository::saveAndFlush) + .toList(); + } + List getAllUser() { return userRepository.findAll(); } diff --git a/src/test/java/net/teumteum/integration/RequestFixture.java b/src/test/java/net/teumteum/integration/RequestFixture.java index 442560bd..7c6e5dc7 100644 --- a/src/test/java/net/teumteum/integration/RequestFixture.java +++ b/src/test/java/net/teumteum/integration/RequestFixture.java @@ -5,8 +5,10 @@ import static net.teumteum.user.domain.Review.최고에요; import java.util.List; +import java.util.Random; import java.util.UUID; import net.teumteum.core.security.Authenticated; +import net.teumteum.user.domain.Review; import net.teumteum.user.domain.User; import net.teumteum.user.domain.request.ReviewRegisterRequest; import net.teumteum.user.domain.request.ReviewRegisterRequest.UserReviewRegisterRequest; @@ -62,11 +64,24 @@ public static ReviewRegisterRequest reviewRegisterRequest() { return new ReviewRegisterRequest(userReviewRegisterRequests()); } + public static ReviewRegisterRequest reviewRegisterRequest(List users) { + return new ReviewRegisterRequest(userReviewRegisterRequests(users)); + } + private static List userReviewRegisterRequests() { return List.of(new UserReviewRegisterRequest(1L, 별로에요), new UserReviewRegisterRequest(2L, 최고에요), new UserReviewRegisterRequest(3L, 좋아요)); } + private static List userReviewRegisterRequests(List users) { + Review[] reviews = Review.values(); + int length = reviews.length; + + return users.stream() + .map(user -> new UserReviewRegisterRequest(user.getId(), reviews[new Random().nextInt(length)])) + .toList(); + } + private static Job job(User user) { return new Job(user.getJob().getName(), user.getJob().getJobClass(), diff --git a/src/test/java/net/teumteum/integration/UserIntegrationTest.java b/src/test/java/net/teumteum/integration/UserIntegrationTest.java index 67695200..916bb80d 100644 --- a/src/test/java/net/teumteum/integration/UserIntegrationTest.java +++ b/src/test/java/net/teumteum/integration/UserIntegrationTest.java @@ -4,8 +4,10 @@ import java.util.List; import net.teumteum.core.error.ErrorResponse; +import net.teumteum.meeting.domain.Meeting; import net.teumteum.user.domain.User; import net.teumteum.user.domain.UserFixture; +import net.teumteum.user.domain.request.ReviewRegisterRequest; import net.teumteum.user.domain.response.FriendsResponse; import net.teumteum.user.domain.response.UserGetResponse; import net.teumteum.user.domain.response.UserMeGetResponse; @@ -13,6 +15,7 @@ import net.teumteum.user.domain.response.UserReviewsResponse; import net.teumteum.user.domain.response.UsersGetByIdResponse; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -253,25 +256,6 @@ void Withdraw_user_info_api() { assertThatCode(() -> api.withdrawUser(VALID_TOKEN, request)) .doesNotThrowAnyException(); } - - @Test - @DisplayName("해당 회원이 존재하지 않으면, 500 에러를 반환한다.") - void Return_500_error_if_user_not_exist() { - // given - repository.clearUserRepository(); - - var request = RequestFixture.userWithdrawRequest(List.of("쓰지 않는 앱이에요", "오류가 생겨서 쓸 수 없어요")); - - // when - var result = api.withdrawUser(VALID_TOKEN, request); - - // then - Assertions.assertThat(result.expectStatus().is5xxServerError() - .expectBody(ErrorResponse.class) - .returnResult() - .getResponseBody()) - .usingRecursiveComparison().isNull(); - } } @Nested @@ -341,6 +325,9 @@ class Logout_user_api { void Logout_user() { // given var existUser = repository.saveAndGetUser(); + + securityContextSetting.set(existUser.getId()); + redisRepository.saveRedisDataWithExpiration(String.valueOf(existUser.getId()), VALID_TOKEN, DURATION); // when & then @@ -351,11 +338,11 @@ void Logout_user() { @Nested @DisplayName("회원 리뷰 조회 API는") - class Get_user_review_api { + class Get_user_reviews_api { @Test @DisplayName("userId 유저의 리뷰 정보를 가져온다.") - void Get_user_review() { + void Get_user_reviews() { // given var existUser = repository.saveAndGetUser(); @@ -373,4 +360,53 @@ void Get_user_review() { .isNotNull(); } } + + @Nested + @DisplayName("회원 리뷰 등록 API는") + class Register_user_review_api { + + User existUser; + + List users; + + ReviewRegisterRequest request; + + Meeting meeting; + + @BeforeEach + void setUp() { + existUser = repository.saveAndGetUser(); + users = repository.saveAndGetUsers(3); + request = RequestFixture.reviewRegisterRequest(users); + meeting = repository.saveAndGetOpenMeetings(1).get(0); + } + + @Test + @DisplayName("회원 리뷰 등록 요청이 들어오면 리뷰를 등록하고, 200 OK 을 반환한다.") + void Return_200_OK_with_success_register_user_review() { + // given + securityContextSetting.set(existUser.getId()); + + // when + var expected = api.registerUserReview(VALID_TOKEN, meeting.getId(), request); + + // then + Assertions.assertThat(expected.expectStatus().isOk()); + } + + @Test + @DisplayName("현재 로그인한 회원의 id 가 리뷰 등록 요청에 포함된다면, 회원 리뷰 등록을 실패하고 400 bad request 을 반환한다.") + void Return_400_bad_request_if_current_user_id_in_request() { + // given + securityContextSetting.set(users.get(0).getId()); + + // when + var expected = api.registerUserReview(VALID_TOKEN, meeting.getId(), request); + + // then + Assertions.assertThat(expected.expectStatus().isBadRequest() + .expectBody(ErrorResponse.class) + .returnResult().getResponseBody()); + } + } } diff --git a/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java b/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java index b7b47bd0..ff38eb6c 100644 --- a/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java +++ b/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java @@ -7,9 +7,11 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doThrow; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; @@ -163,6 +165,30 @@ void Register_user_review_with_200_ok() throws Exception { .andDo(print()) .andExpect(status().isOk()); } + + @Test + @DisplayName("현재 로그인한 회원의 id 가 리뷰 등록 요청에 포함된다면, 회원 리뷰 등록을 실패하고 400 bad request을 반환한다.") + void Register_reviews_with_400_bad_request() throws Exception { + // given + ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); + + String errorMessage = "나의 리뷰에 대한 리뷰를 작성할 수 없습니다."; + + doThrow(new IllegalArgumentException(errorMessage)) + .when(userService) + .registerReview(anyLong(), anyLong(), any(ReviewRegisterRequest.class)); + + // when & then + mockMvc.perform(post("/users/reviews") + .param("meetingId", String.valueOf(1L)) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reviewRegisterRequest)) + .with(csrf()) + .header(AUTHORIZATION, VALID_ACCESS_TOKEN)) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(result -> assertEquals(errorMessage, result.getResolvedException().getMessage())); + } } diff --git a/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java b/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java index 5733b2de..7a7525b0 100644 --- a/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java +++ b/src/test/java/net/teumteum/unit/user/service/UserServiceTest.java @@ -148,7 +148,9 @@ void Register_user_review_with_200_ok() { Long meetingId = 1L; - Long userId = 1L; + Long userId = 10L; + + Long currentUserId = 20L; given(meetingConnector.existById(anyLong())) .willReturn(true); @@ -157,13 +159,32 @@ void Register_user_review_with_200_ok() { .willReturn(Optional.of(UserFixture.getUserWithId(userId++))); // when - userService.registerReview(meetingId, reviewRegisterRequest); + userService.registerReview(meetingId, currentUserId, reviewRegisterRequest); // then verify(meetingConnector, times(1)).existById(anyLong()); verify(userRepository, times(3)).findById(anyLong()); } + @Test + @DisplayName("회원 id 가 리뷰 정보 요청에 포함되면, 400 Bad Request 와 함께 리뷰 등록을 실패한다.") + void Return_400_bad_request_if_current_user_id_in_request() { + // given + ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); + + Long meetingId = 1L; + + Long currentUserId = reviewRegisterRequest.reviews().get(0).id(); + + given(meetingConnector.existById(anyLong())) + .willReturn(true); + + // when & then + assertThatThrownBy(() -> userService.registerReview(meetingId, currentUserId, reviewRegisterRequest)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("나의 리뷰에 대한 리뷰를 작성할 수 없습니다."); + } + @Test @DisplayName("meeting id 에 해당하는 meeting 이 존재하지 않는 경우, 400 Bad Request 와 함께 리뷰 등록을 실패한다.") void Return_400_bad_request_if_meeting_is_not_exist() { @@ -171,11 +192,12 @@ void Return_400_bad_request_if_meeting_is_not_exist() { ReviewRegisterRequest reviewRegisterRequest = RequestFixture.reviewRegisterRequest(); Long meetingId = 1L; + Long currentUserId = 1L; given(meetingConnector.existById(anyLong())) .willReturn(false); // when & then - assertThatThrownBy(() -> userService.registerReview(meetingId, reviewRegisterRequest)) + assertThatThrownBy(() -> userService.registerReview(meetingId, currentUserId, reviewRegisterRequest)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("meetingId에 해당하는 meeting을 찾을 수 없습니다. \"" + meetingId + "\""); } diff --git a/src/test/java/net/teumteum/user/domain/UserFixture.java b/src/test/java/net/teumteum/user/domain/UserFixture.java index 43616597..2b52ff27 100644 --- a/src/test/java/net/teumteum/user/domain/UserFixture.java +++ b/src/test/java/net/teumteum/user/domain/UserFixture.java @@ -5,6 +5,8 @@ import static net.teumteum.user.domain.Review.좋아요; import static net.teumteum.user.domain.Review.최고에요; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.UUID; @@ -89,7 +91,7 @@ public static class UserBuilder { @Builder.Default private Terms terms = new Terms(true, true); @Builder.Default - private List reviews = List.of(최고에요, 최고에요, 최고에요, 별로에요, 좋아요, 좋아요); + private List reviews = new ArrayList<>(Arrays.asList(최고에요, 최고에요, 최고에요, 별로에요, 좋아요, 좋아요)); } } From 2a1301420dd42b5310dfa6cb8c15eb06bddf54ca Mon Sep 17 00:00:00 2001 From: devxb Date: Mon, 29 Jan 2024 08:57:50 +0900 Subject: [PATCH 2/7] =?UTF-8?q?test:=20MeetingRepository=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EA=B3=BC=20id=EB=B9=84=EA=B5=90=20=EB=B9=84=ED=99=9C=EC=84=9C?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/net/teumteum/meeting/domain/MeetingRepositoryTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/net/teumteum/meeting/domain/MeetingRepositoryTest.java b/src/test/java/net/teumteum/meeting/domain/MeetingRepositoryTest.java index 541f52f3..18d30411 100644 --- a/src/test/java/net/teumteum/meeting/domain/MeetingRepositoryTest.java +++ b/src/test/java/net/teumteum/meeting/domain/MeetingRepositoryTest.java @@ -268,6 +268,7 @@ void Return_meetings_between_start_time_and_end_time() { // then Assertions.assertThat(result) .usingRecursiveComparison() + .ignoringFields("createdAt", "updatedAt", "id", "promiseDateTime") .isEqualTo(expected); } } From d2ff7bf5af9f5ace1f90081380fe20baa703db17 Mon Sep 17 00:00:00 2001 From: ChoiDongKuen Date: Mon, 29 Jan 2024 09:32:51 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20URI=20=EB=B3=80=EA=B2=BD=20(#162)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 회원 탈퇴 URI 수정 (#161) * test: 회원 탈퇴 URI 수정에 대한 테스트 수정 (#161) * fix: CI 에러 수정 (#161) * fix: 필드 값 비교를 위해 deprecated 된 메소드을 대체 (#161) * fix: 필드 값 비교를 위해 deprecated 된 메소드을 대체 (#161) --- src/main/java/net/teumteum/user/controller/UserController.java | 2 +- src/test/java/net/teumteum/integration/Api.java | 2 +- .../net/teumteum/unit/user/controller/UserControllerTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/teumteum/user/controller/UserController.java b/src/main/java/net/teumteum/user/controller/UserController.java index d6a28264..6523683f 100644 --- a/src/main/java/net/teumteum/user/controller/UserController.java +++ b/src/main/java/net/teumteum/user/controller/UserController.java @@ -89,7 +89,7 @@ public InterestQuestionResponse getInterestQuestion(@RequestParam("user-id") Lis return userService.getInterestQuestionByUserIds(userIds, balance); } - @PostMapping("/withdraw") + @PostMapping("/withdraws") @ResponseStatus(HttpStatus.OK) public void withdraw(@Valid @RequestBody UserWithdrawRequest request) { userService.withdraw(request, getCurrentUserId()); diff --git a/src/test/java/net/teumteum/integration/Api.java b/src/test/java/net/teumteum/integration/Api.java index 225b07de..1fd41c22 100644 --- a/src/test/java/net/teumteum/integration/Api.java +++ b/src/test/java/net/teumteum/integration/Api.java @@ -142,7 +142,7 @@ ResponseSpec reissueJwt(String accessToken, String refreshToken) { ResponseSpec withdrawUser(String accessToken, UserWithdrawRequest request) { return webTestClient .post() - .uri("/users/withdraw") + .uri("/users/withdraws") .header(HttpHeaders.AUTHORIZATION, accessToken) .bodyValue(request) .exchange(); diff --git a/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java b/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java index ff38eb6c..5870936e 100644 --- a/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java +++ b/src/test/java/net/teumteum/unit/user/controller/UserControllerTest.java @@ -135,7 +135,7 @@ void Withdraw_user_with_200_ok() throws Exception { = RequestFixture.userWithdrawRequest(List.of("쓰지 않는 앱이에요", "오류가 생겨서 쓸 수 없어요")); // when & then - mockMvc.perform(post("/users/withdraw") + mockMvc.perform(post("/users/withdraws") .content(objectMapper.writeValueAsString(request)) .contentType(APPLICATION_JSON) .with(csrf()) From cbd09bf6bdc317db9be1215f1eb8598f7f8f98f1 Mon Sep 17 00:00:00 2001 From: xb205 <62425964+devxb@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:29:53 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20fcm=20token=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20api=20=EC=B6=94=EA=B0=80=20(#166)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: user_alert ddl의 pk에 auto_increment를 추가한다 * feat: token 업데이트 api를 추가한다 --- .../alert/controller/AlertController.java | 19 ++++++++ .../alert/domain/AlertRepository.java | 10 ++++ .../teumteum/alert/domain/AlertService.java | 18 +++++++- .../net/teumteum/alert/domain/UserAlert.java | 4 ++ .../request/UpdateAlertTokenRequest.java | 10 ++++ .../db/migration/V10__create_alert.sql | 2 +- .../alert/domain/AlertRepositoryTest.java | 46 +++++++++++++++++++ src/test/resources/schema.sql | 2 +- 8 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/teumteum/alert/domain/request/UpdateAlertTokenRequest.java create mode 100644 src/test/java/net/teumteum/alert/domain/AlertRepositoryTest.java diff --git a/src/main/java/net/teumteum/alert/controller/AlertController.java b/src/main/java/net/teumteum/alert/controller/AlertController.java index 349e4df7..bf998ea9 100644 --- a/src/main/java/net/teumteum/alert/controller/AlertController.java +++ b/src/main/java/net/teumteum/alert/controller/AlertController.java @@ -1,11 +1,16 @@ package net.teumteum.alert.controller; +import io.sentry.Sentry; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import net.teumteum.alert.domain.AlertService; import net.teumteum.alert.domain.request.RegisterAlertRequest; +import net.teumteum.alert.domain.request.UpdateAlertTokenRequest; +import net.teumteum.core.error.ErrorResponse; import net.teumteum.core.security.service.SecurityService; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseStatus; @@ -24,4 +29,18 @@ public void registerAlert(@Valid @RequestBody RegisterAlertRequest registerAlert var loginUserId = securityService.getCurrentUserId(); alertService.registerAlert(loginUserId, registerAlertRequest); } + + @PatchMapping("/alerts") + @ResponseStatus(HttpStatus.OK) + public void updateAlert(@Valid @RequestBody UpdateAlertTokenRequest updateAlertTokenRequest) { + var loginUserId = securityService.getCurrentUserId(); + alertService.updateAlertToken(loginUserId, updateAlertTokenRequest); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleIllegalArgumentException(IllegalArgumentException illegalArgumentException) { + Sentry.captureException(illegalArgumentException); + return ErrorResponse.of(illegalArgumentException); + } } diff --git a/src/main/java/net/teumteum/alert/domain/AlertRepository.java b/src/main/java/net/teumteum/alert/domain/AlertRepository.java index 87b2e6d5..33c34cf6 100644 --- a/src/main/java/net/teumteum/alert/domain/AlertRepository.java +++ b/src/main/java/net/teumteum/alert/domain/AlertRepository.java @@ -1,7 +1,10 @@ package net.teumteum.alert.domain; +import jakarta.persistence.LockModeType; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -9,4 +12,11 @@ public interface AlertRepository extends JpaRepository { @Query("select u from user_alert as u where u.userId in :userIds") List findAllByUserId(@Param("userIds") Iterable userIds); + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("select u from user_alert as u where u.userId = :userId") + Optional findByUserIdWithLock(@Param("userId") Long userId); + + Optional findByUserId(@Param("userId") Long userId); + } diff --git a/src/main/java/net/teumteum/alert/domain/AlertService.java b/src/main/java/net/teumteum/alert/domain/AlertService.java index 8d876cf7..65d0b8e8 100644 --- a/src/main/java/net/teumteum/alert/domain/AlertService.java +++ b/src/main/java/net/teumteum/alert/domain/AlertService.java @@ -4,6 +4,7 @@ import java.util.Set; import lombok.RequiredArgsConstructor; import net.teumteum.alert.domain.request.RegisterAlertRequest; +import net.teumteum.alert.domain.request.UpdateAlertTokenRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,8 +17,21 @@ public class AlertService { @Transactional public void registerAlert(Long userId, RegisterAlertRequest registerAlertRequest) { - var alert = new UserAlert(null, userId, registerAlertRequest.token()); - alertRepository.save(alert); + alertRepository.findByUserId(userId) + .ifPresentOrElse(userAlert -> { + throw new IllegalArgumentException("이미 토큰이 생성된 user입니다. \"" + userId +"\""); + }, () -> { + var alert = new UserAlert(null, userId, registerAlertRequest.token()); + alertRepository.save(alert); + }); + } + + @Transactional + public void updateAlertToken(Long userId, UpdateAlertTokenRequest updateAlertTokenRequest) { + var userAlert = alertRepository.findByUserIdWithLock(userId) + .orElseThrow(() -> new IllegalArgumentException("userId에 해당하는 토큰을 찾을 수 없습니다.")); + + userAlert.updateToken(updateAlertTokenRequest.token()); } public List findAllByUserId(Set userIds) { diff --git a/src/main/java/net/teumteum/alert/domain/UserAlert.java b/src/main/java/net/teumteum/alert/domain/UserAlert.java index c5a537ca..ec88c651 100644 --- a/src/main/java/net/teumteum/alert/domain/UserAlert.java +++ b/src/main/java/net/teumteum/alert/domain/UserAlert.java @@ -31,4 +31,8 @@ public class UserAlert { public String getToken() { return token; } + + public void updateToken(String token) { + this.token = token; + } } diff --git a/src/main/java/net/teumteum/alert/domain/request/UpdateAlertTokenRequest.java b/src/main/java/net/teumteum/alert/domain/request/UpdateAlertTokenRequest.java new file mode 100644 index 00000000..921fecad --- /dev/null +++ b/src/main/java/net/teumteum/alert/domain/request/UpdateAlertTokenRequest.java @@ -0,0 +1,10 @@ +package net.teumteum.alert.domain.request; + +import jakarta.validation.constraints.NotNull; + +public record UpdateAlertTokenRequest( + @NotNull + String token +) { + +} diff --git a/src/main/resources/db/migration/V10__create_alert.sql b/src/main/resources/db/migration/V10__create_alert.sql index 40d54db1..ec03a7fa 100644 --- a/src/main/resources/db/migration/V10__create_alert.sql +++ b/src/main/resources/db/migration/V10__create_alert.sql @@ -1,5 +1,5 @@ create table if not exists user_alert( - id bigint primary key, + id bigint primary key auto_increment, user_id bigint unique not null, token text not null ); diff --git a/src/test/java/net/teumteum/alert/domain/AlertRepositoryTest.java b/src/test/java/net/teumteum/alert/domain/AlertRepositoryTest.java new file mode 100644 index 00000000..baff4e60 --- /dev/null +++ b/src/test/java/net/teumteum/alert/domain/AlertRepositoryTest.java @@ -0,0 +1,46 @@ +package net.teumteum.alert.domain; + +import jakarta.persistence.EntityManager; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@DataJpaTest +@ExtendWith(SpringExtension.class) +@DisplayName("AlertRepository 클래스의") +class AlertRepositoryTest { + + @Autowired + private AlertRepository alertRepository; + + @Autowired + private EntityManager entityManager; + + @Nested + @DisplayName("findByUserIdWithLock 메소드는") + class findByUserIdWithLock_method { + + @Test + @DisplayName("userId로 userAlert를 조회한다.") + void find_userAlert_by_userId() { + // given + var userId = 1L; + var userAlert = new UserAlert(1L, userId, "token"); + + alertRepository.saveAndFlush(userAlert); + entityManager.clear(); + + // when + var result = alertRepository.findByUserIdWithLock(userId); + + // then + Assertions.assertThat(result).isPresent(); + } + } + +} diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql index 28efff41..dbf1fcb2 100644 --- a/src/test/resources/schema.sql +++ b/src/test/resources/schema.sql @@ -79,7 +79,7 @@ create table if not exists withdraw_reasons ); create table if not exists user_alert( - id bigint primary key, + id bigint primary key auto_increment, user_id bigint unique not null, token text not null ); From eeb01f59dc281a7afdb54097ac4ba72b98bf5324 Mon Sep 17 00:00:00 2001 From: ddingmin Date: Tue, 30 Jan 2024 20:44:48 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=EB=AA=A8=EC=9E=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EA=B0=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#169)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/net/teumteum/meeting/service/MeetingService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/teumteum/meeting/service/MeetingService.java b/src/main/java/net/teumteum/meeting/service/MeetingService.java index 0a5a2cb3..602a5fb0 100644 --- a/src/main/java/net/teumteum/meeting/service/MeetingService.java +++ b/src/main/java/net/teumteum/meeting/service/MeetingService.java @@ -150,6 +150,7 @@ public void cancelParticipant(Long meetingId, Long userId) { private void uploadMeetingImages(List images, Meeting meeting) { Assert.isTrue(!images.isEmpty() && images.size() <= 5, "이미지는 1개 이상 5개 이하로 업로드해야 합니다."); + meeting.getImageUrls().clear(); images.forEach( image -> meeting.getImageUrls().add( imageUpload.upload(image, meeting.getId().toString()).filePath() From d621bd6af4f37d740ad4589a8823c7130204d6e5 Mon Sep 17 00:00:00 2001 From: xb205 <62425964+devxb@users.noreply.github.com> Date: Thu, 1 Feb 2024 22:36:40 +0900 Subject: [PATCH 6/7] perf: upgrade GPT-3.5 to GPT-4 (#174) --- src/main/java/net/teumteum/user/infra/GptInterestQuestion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/teumteum/user/infra/GptInterestQuestion.java b/src/main/java/net/teumteum/user/infra/GptInterestQuestion.java index c3a4ca1b..355ef7af 100644 --- a/src/main/java/net/teumteum/user/infra/GptInterestQuestion.java +++ b/src/main/java/net/teumteum/user/infra/GptInterestQuestion.java @@ -107,7 +107,7 @@ private record GptQuestionRequest( List messages ) { - private static final String LANGUAGE_MODEL = "gpt-3.5-turbo-1106"; + private static final String LANGUAGE_MODEL = "gpt-4"; private static GptQuestionRequest balanceGame(String interests) { From 98a2037cb84da5542f55cd5c660124fb27ef89fc Mon Sep 17 00:00:00 2001 From: choidongkuen Date: Sat, 3 Feb 2024 14:00:38 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20=EC=9C=84=EC=B9=98=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=92=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EA=B8=B0=EA=B0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/net/teumteum/teum_teum/service/TeumTeumService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java b/src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java index 16ef457e..d8e4d1ff 100644 --- a/src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java +++ b/src/main/java/net/teumteum/teum_teum/service/TeumTeumService.java @@ -26,7 +26,7 @@ public class TeumTeumService { private final RedisService redisService; public UserAroundLocationsResponse saveAndGetUserAroundLocations(UserLocationRequest request) { - redisService.setUserLocation(request.toUserLocation(), 60L); + redisService.setUserLocation(request.toUserLocation(), 86400L); return getUserAroundLocations(request); }