From 47c169911c2890a039063ecffe7be2e8fbb122ca Mon Sep 17 00:00:00 2001 From: hyeonjeongs Date: Sat, 27 Jan 2024 18:51:27 +0900 Subject: [PATCH] =?UTF-8?q?YEL-184=20[feat]=20=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=EC=82=AC=EC=9C=A0=20=EC=A0=80=EC=9E=A5=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/user-data-post.adoc | 35 +++------- .../user/controller/UserController.java | 8 +++ .../dto/request/UserDeleteReasonRequest.java | 10 +++ .../server/domain/user/entity/UserData.java | 8 +++ .../repository/UserDataJpaRepository.java | 8 +++ .../user/repository/UserDataRepository.java | 9 +++ .../repository/UserDataRepositoryImpl.java | 21 ++++++ .../domain/user/service/UserService.java | 24 +++++++ .../static/docs/check-vote-available.html | 2 +- .../resources/static/docs/find-notice.html | 10 +++ src/main/resources/static/docs/google.html | 2 +- .../user/medium/UserControllerTest.java | 66 +++++++++++++++---- 12 files changed, 161 insertions(+), 42 deletions(-) create mode 100644 src/main/java/com/yello/server/domain/user/dto/request/UserDeleteReasonRequest.java create mode 100644 src/main/java/com/yello/server/domain/user/repository/UserDataJpaRepository.java create mode 100644 src/main/java/com/yello/server/domain/user/repository/UserDataRepository.java create mode 100644 src/main/java/com/yello/server/domain/user/repository/UserDataRepositoryImpl.java diff --git a/src/docs/asciidoc/user-data-post.adoc b/src/docs/asciidoc/user-data-post.adoc index fb0a5b0f..613e4d98 100644 --- a/src/docs/asciidoc/user-data-post.adoc +++ b/src/docs/asciidoc/user-data-post.adoc @@ -1,40 +1,20 @@ :reproducible: -== 탈퇴 사유 저장 (명세) +== 탈퇴 & 사유 저장 v2 === 요청 -[http] ----- -POST /api/v1/user/data/withdraw-reason HTTP/1.1 -Authorization: Bearer your-access-token -Content-Type: application-json +include::{snippets}/api/v2/user/http-request.adoc[] -{ - "value": "오류가 많아서" -} ----- +=== 응답 -*필드 타입* +include::{snippets}/api/v2/user/http-response.adoc[] -- "tag": "withdraw-reason" | "account-update-at" | "recommended" -- "value": String -* withdraw-reason 자리가 ENUM으로 대체될 예정입니다. -=== 응답 +*필드 타입* -[http,json] ----- -HTTP/1.1 200 OK -Vary: Origin -Vary: Access-Control-Request-Method -Vary: Access-Control-Request-Headers -Content-Type: application/json +- "value": String +* value는 탈퇴 사유를 보내주시면 됩니다. -{ - "status" : 200, - "message" : "탈퇴 사유 정보 저장에 성공하였습니다." -} ----- *필드 타입* @@ -46,4 +26,5 @@ Content-Type: application/json === CHANGELOG +- 2024.01.27 API 릴리즈 - 2024.01.09 명세 작성 \ No newline at end of file diff --git a/src/main/java/com/yello/server/domain/user/controller/UserController.java b/src/main/java/com/yello/server/domain/user/controller/UserController.java index 7b9ed523..4c324fd2 100644 --- a/src/main/java/com/yello/server/domain/user/controller/UserController.java +++ b/src/main/java/com/yello/server/domain/user/controller/UserController.java @@ -5,6 +5,7 @@ import static com.yello.server.global.common.SuccessCode.READ_USER_SUCCESS; import static com.yello.server.global.common.SuccessCode.UPDATE_DEVICE_TOKEN_USER_SUCCESS; +import com.yello.server.domain.user.dto.request.UserDeleteReasonRequest; import com.yello.server.domain.user.dto.request.UserDeviceTokenRequest; import com.yello.server.domain.user.dto.response.UserDetailResponse; import com.yello.server.domain.user.dto.response.UserDetailV2Response; @@ -70,4 +71,11 @@ public BaseResponse getUserSubscribe(@AccessTokenUs val data = userService.getUserSubscribe(user.getId()); return BaseResponse.success(READ_USER_SUBSCRIBE_SUCCESS, data); } + + @DeleteMapping("/v2/user") + public BaseResponse deleteUserWithReason(@AccessTokenUser User user, @RequestBody + UserDeleteReasonRequest request) { + userService.deleteUserWithReason(user.getId(), request); + return BaseResponse.success(DELETE_USER_SUCCESS); + } } diff --git a/src/main/java/com/yello/server/domain/user/dto/request/UserDeleteReasonRequest.java b/src/main/java/com/yello/server/domain/user/dto/request/UserDeleteReasonRequest.java new file mode 100644 index 00000000..36aa8d43 --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/dto/request/UserDeleteReasonRequest.java @@ -0,0 +1,10 @@ +package com.yello.server.domain.user.dto.request; + +import lombok.Builder; + +@Builder +public record UserDeleteReasonRequest( + String value +) { + +} diff --git a/src/main/java/com/yello/server/domain/user/entity/UserData.java b/src/main/java/com/yello/server/domain/user/entity/UserData.java index ce378fc4..7d54bf04 100644 --- a/src/main/java/com/yello/server/domain/user/entity/UserData.java +++ b/src/main/java/com/yello/server/domain/user/entity/UserData.java @@ -36,4 +36,12 @@ public class UserData { @Column(nullable = false) private String value; + + public static UserData of(UserDataType tag, String value, User user) { + return UserData.builder() + .tag(tag) + .user(user) + .value(value) + .build(); + } } diff --git a/src/main/java/com/yello/server/domain/user/repository/UserDataJpaRepository.java b/src/main/java/com/yello/server/domain/user/repository/UserDataJpaRepository.java new file mode 100644 index 00000000..5177e7b7 --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/repository/UserDataJpaRepository.java @@ -0,0 +1,8 @@ +package com.yello.server.domain.user.repository; + +import com.yello.server.domain.user.entity.UserData; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserDataJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/com/yello/server/domain/user/repository/UserDataRepository.java b/src/main/java/com/yello/server/domain/user/repository/UserDataRepository.java new file mode 100644 index 00000000..a443c3ed --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/repository/UserDataRepository.java @@ -0,0 +1,9 @@ +package com.yello.server.domain.user.repository; + +import com.yello.server.domain.user.entity.UserData; + +public interface UserDataRepository { + + UserData save(UserData userData); + +} diff --git a/src/main/java/com/yello/server/domain/user/repository/UserDataRepositoryImpl.java b/src/main/java/com/yello/server/domain/user/repository/UserDataRepositoryImpl.java new file mode 100644 index 00000000..e67fb051 --- /dev/null +++ b/src/main/java/com/yello/server/domain/user/repository/UserDataRepositoryImpl.java @@ -0,0 +1,21 @@ +package com.yello.server.domain.user.repository; + + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.yello.server.domain.user.entity.UserData; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +@Repository +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserDataRepositoryImpl implements UserDataRepository{ + + private final UserDataJpaRepository userDataJpaRepository; + private final JPAQueryFactory jpaQueryFactory; + @Override + public UserData save(UserData userData) { + return userDataJpaRepository.save(userData); + } +} diff --git a/src/main/java/com/yello/server/domain/user/service/UserService.java b/src/main/java/com/yello/server/domain/user/service/UserService.java index 9878b16c..d513d533 100644 --- a/src/main/java/com/yello/server/domain/user/service/UserService.java +++ b/src/main/java/com/yello/server/domain/user/service/UserService.java @@ -1,5 +1,6 @@ package com.yello.server.domain.user.service; +import static com.yello.server.domain.user.entity.UserDataType.*; import static com.yello.server.global.common.ErrorCode.DEVICE_TOKEN_CONFLICT_USER_EXCEPTION; import com.yello.server.domain.admin.repository.UserAdminRepository; @@ -10,13 +11,17 @@ import com.yello.server.domain.group.repository.UserGroupRepository; import com.yello.server.domain.purchase.entity.Purchase; import com.yello.server.domain.purchase.repository.PurchaseRepository; +import com.yello.server.domain.user.dto.request.UserDeleteReasonRequest; import com.yello.server.domain.user.dto.request.UserDeviceTokenRequest; import com.yello.server.domain.user.dto.response.UserDetailResponse; import com.yello.server.domain.user.dto.response.UserDetailV2Response; import com.yello.server.domain.user.dto.response.UserResponse; import com.yello.server.domain.user.dto.response.UserSubscribeDetailResponse; import com.yello.server.domain.user.entity.User; +import com.yello.server.domain.user.entity.UserData; +import com.yello.server.domain.user.entity.UserDataType; import com.yello.server.domain.user.exception.UserConflictException; +import com.yello.server.domain.user.repository.UserDataRepository; import com.yello.server.domain.user.repository.UserRepository; import com.yello.server.domain.vote.repository.VoteRepository; import com.yello.server.global.common.dto.EmptyObject; @@ -40,6 +45,7 @@ public class UserService { private final UserGroupRepository userGroupRepository; private final UserRepository userRepository; private final VoteRepository voteRepository; + private final UserDataRepository userDataRepository; public UserDetailResponse findMyProfile(Long userId) { final User user = userRepository.getById(userId); @@ -98,4 +104,22 @@ public UserSubscribeDetailResponse getUserSubscribe(Long userId) { return UserSubscribeDetailResponse.of(purchase); } + + @Transactional + public void deleteUserWithReason(Long userId, UserDeleteReasonRequest request) { + final User target = userRepository.getById(userId); + target.delete(); + + friendRepository.findAllByUserId(target.getId()) + .forEach(Friend::delete); + + friendRepository.findAllByTargetId(target.getId()) + .forEach(Friend::delete); + + cooldownRepository.findByUserId(target.getId()) + .ifPresent(Cooldown::delete); + + userDataRepository.save(UserData.of(WITHDRAW_REASON, request.value(), target)); + + } } diff --git a/src/main/resources/static/docs/check-vote-available.html b/src/main/resources/static/docs/check-vote-available.html index 0656394b..5e5ace8a 100644 --- a/src/main/resources/static/docs/check-vote-available.html +++ b/src/main/resources/static/docs/check-vote-available.html @@ -468,7 +468,7 @@

응답

"data" : { "isPossible" : false, "point" : 200, - "createdAt" : "2024-01-27 15:32:41", + "createdAt" : "2024-01-27 17:42:50", "friendStatus" : 1 } } diff --git a/src/main/resources/static/docs/find-notice.html b/src/main/resources/static/docs/find-notice.html index f87223b7..c4cde96c 100644 --- a/src/main/resources/static/docs/find-notice.html +++ b/src/main/resources/static/docs/find-notice.html @@ -525,6 +525,16 @@

응답

  • "isAvailable": Boolean

  • +
  • +

    "type" : String

    +
    +
      +
    • +

      ENUM 값 → "NOTICE" | "BANNER"

      +
    • +
    +
    +
  • diff --git a/src/main/resources/static/docs/google.html b/src/main/resources/static/docs/google.html index f0317bf7..312d7846 100644 --- a/src/main/resources/static/docs/google.html +++ b/src/main/resources/static/docs/google.html @@ -481,7 +481,7 @@

    응답

    "message" : "구글 구독 결제 검증 및 반영에 성공하였습니다.", "data" : { "productId" : "productId", - "expiredAt" : "2024-01-27T15:32:31.145173" + "expiredAt" : "2024-01-27T17:42:38.846267" } } diff --git a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java index eba0fecf..f0f7bb34 100644 --- a/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java +++ b/src/test/java/com/yello/server/domain/user/medium/UserControllerTest.java @@ -4,6 +4,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.operation.preprocess.Preprocessors.removeHeaders; @@ -17,6 +18,7 @@ import com.yello.server.domain.authorization.filter.JwtFilter; import com.yello.server.domain.group.entity.UserGroupType; import com.yello.server.domain.user.controller.UserController; +import com.yello.server.domain.user.dto.request.UserDeleteReasonRequest; import com.yello.server.domain.user.dto.request.UserDeviceTokenRequest; import com.yello.server.domain.user.dto.response.UserDetailResponse; import com.yello.server.domain.user.dto.response.UserDetailV2Response; @@ -63,8 +65,9 @@ class UserControllerTest { final String[] excludeRequestHeaders = {"X-CSRF-TOKEN", "Host"}; - final String[] excludeResponseHeaders = {"X-Content-Type-Options", "X-XSS-Protection", "Cache-Control", "Pragma", - "Expires", "X-Frame-Options", "Content-Length"}; + final String[] excludeResponseHeaders = + {"X-Content-Type-Options", "X-XSS-Protection", "Cache-Control", "Pragma", + "Expires", "X-Frame-Options", "Content-Length"}; @Autowired private MockMvc mockMvc; @@ -107,8 +110,10 @@ void init() { ) .andDo(print()) .andDo(document("api/v1/user/findUser", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } @@ -132,8 +137,10 @@ void init() { ) .andDo(print()) .andDo(document("api/v2/user", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } @@ -162,8 +169,10 @@ void init() { ) .andDo(print()) .andDo(document("api/v1/user/findUserById", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders)), + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders)), pathParameters(parameterWithName("userId").description("유저 아이디 값"))) ) .andExpect(MockMvcResultMatchers.status().isOk()); @@ -189,8 +198,10 @@ void init() { .content(objectMapper.writeValueAsString(userDeviceTokenRequest))) .andDo(print()) .andDo(document("api/v1/user/updateUserDeviceToken", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } @@ -206,8 +217,10 @@ void init() { ) .andDo(print()) .andDo(document("api/v1/user/deleteUser", - Preprocessors.preprocessRequest(prettyPrint(), removeHeaders(excludeRequestHeaders)), - Preprocessors.preprocessResponse(prettyPrint(), removeHeaders(excludeResponseHeaders))) + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) ) .andExpect(MockMvcResultMatchers.status().isOk()); } @@ -216,7 +229,8 @@ void init() { void 유저_구독_정보_조회에_성공합니다() throws Exception { // given - final UserSubscribeDetailResponse userSubscribeDetailResponse = UserSubscribeDetailResponse.of(testDataUtil.generatePurchase(1L, user)); + final UserSubscribeDetailResponse userSubscribeDetailResponse = + UserSubscribeDetailResponse.of(testDataUtil.generatePurchase(1L, user)); // when given(userService.getUserSubscribe(anyLong())) .willReturn(userSubscribeDetailResponse); @@ -237,4 +251,30 @@ void init() { } + @Test + void 유저_탈퇴_v2_성공합니다() throws Exception { + // given + final UserDeleteReasonRequest request = + UserDeleteReasonRequest.builder().value("오류가 많아서").build(); +/* + doNothing() + .when(userService) + .deleteUserWithReason(anyLong(), request);*/ + // when + // then + mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/v2/user") + .with(csrf().asHeader()) + .header(HttpHeaders.AUTHORIZATION, "Bearer your-access-token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("api/v2/user", + Preprocessors.preprocessRequest(prettyPrint(), + removeHeaders(excludeRequestHeaders)), + Preprocessors.preprocessResponse(prettyPrint(), + removeHeaders(excludeResponseHeaders))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + }