Skip to content

Commit

Permalink
Merge pull request #67 from everymeals/feature/user-withdrawal
Browse files Browse the repository at this point in the history
[Feature/user withdrawal] 마이페이지-회원탈퇴 API
  • Loading branch information
Qbeom0925 committed Nov 26, 2023
2 parents 6d09b76 + 08ed18f commit 666c7dc
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 0 deletions.
17 changes: 17 additions & 0 deletions src/main/java/everymeal/server/user/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import everymeal.server.user.controller.dto.request.UserEmailLoginReq;
import everymeal.server.user.controller.dto.request.UserEmailSingReq;
import everymeal.server.user.controller.dto.request.UserProfileUpdateReq;
import everymeal.server.user.controller.dto.request.WithdrawalReq;
import everymeal.server.user.controller.dto.response.UserEmailAuthRes;
import everymeal.server.user.controller.dto.response.UserLoginRes;
import everymeal.server.user.controller.dto.response.UserProfileRes;
Expand Down Expand Up @@ -164,6 +165,22 @@ public ApplicationResponse<Boolean> updateUserProfile(
userService.updateUserProfile(authenticatedUser, userProfileUpdateReq));
}

@Auth(require = true)
@PostMapping("/withdrawal")
@SecurityRequirement(name = "jwt-user-auth")
@Operation(summary = "회원탈퇴", description = "서비스 회원 탈퇴를 합니다.")
@ApiResponse(
responseCode = "404",
description = """
(U0001)등록된 유저가 아닙니다.<br>
""",
content = @Content(schema = @Schema()))
public ApplicationResponse<Boolean> withdrawal(
@Parameter(hidden = true) @AuthUser AuthenticatedUser authenticatedUser,
@RequestBody WithdrawalReq withdrawalReq) {
return ApplicationResponse.ok(userService.withdrawal(authenticatedUser, withdrawalReq));
}

private ResponseEntity<ApplicationResponse<UserLoginRes>> setRefreshToken(
UserLoginRes response) {
ResponseCookie cookie =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package everymeal.server.user.controller.dto.request;


import everymeal.server.user.entity.WithdrawalReason;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

public record WithdrawalReq(
@NotBlank
@Schema(
description = "탈퇴 사유를 입력해주세요.",
example = "NOT_USE_USUALLY",
allowableValues = {
"NOT_USE_USUALLY",
"INCONVENIENT_IN_TERMS_OF_USABILITY",
"ERRORS_OCCUR_FREQUENTLY",
"MY_SCHOOL_HAS_CHANGED",
"ETC"
})
WithdrawalReason withdrawalReason,
@Schema(description = "사유가 '기타'일 경우, 추가 이유 입력해주세요.", example = "다른 서비스를 사용하게 되었다.")
String etcReason) {}
8 changes: 8 additions & 0 deletions src/main/java/everymeal/server/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import jakarta.persistence.Index;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -53,6 +54,9 @@ public class User extends BaseEntity {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
Set<ReviewMark> reviewMarks = new HashSet<>();

@OneToOne(mappedBy = "user")
private Withdrawal withdrawal;

@Builder
public User(String nickname, String email, String profileImgUrl, University university) {
this.nickname = nickname;
Expand All @@ -70,4 +74,8 @@ public void updateProfile(String nickname, String profileImgUrl) {
this.nickname = nickname;
this.profileImgUrl = profileImgUrl;
}
/** 회원 탈퇴 */
public void setIsDeleted() {
this.isDeleted = Boolean.TRUE;
}
}
42 changes: 42 additions & 0 deletions src/main/java/everymeal/server/user/entity/Withdrawal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package everymeal.server.user.entity;


import everymeal.server.global.entity.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.MapsId;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Withdrawal extends BaseEntity {
@Id private Long userIdx;

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private WithdrawalReason withdrawalReason;

@Column(nullable = true, columnDefinition = "TEXT")
private String etcReason;

@MapsId
@OneToOne
@JoinColumn(name = "user_idx", referencedColumnName = "idx")
private User user;

@Builder
public Withdrawal(WithdrawalReason withdrawalReason, String etcReason, User user) {
this.withdrawalReason = withdrawalReason;
this.etcReason = etcReason;
this.user = user;
}
}
17 changes: 17 additions & 0 deletions src/main/java/everymeal/server/user/entity/WithdrawalReason.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package everymeal.server.user.entity;


import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum WithdrawalReason {
NOT_USE_USUALLY("앱을 잘 쓰지 않아요"),
INCONVENIENT_IN_TERMS_OF_USABILITY("사용성이 불편해요"),
ERRORS_OCCUR_FREQUENTLY("오류가 자주 발생해요"),
MY_SCHOOL_HAS_CHANGED("학교가 바뀌었어요"),
ETC("기타");

public final String MESSAGE;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package everymeal.server.user.repository;


import everymeal.server.user.entity.Withdrawal;
import org.springframework.data.jpa.repository.JpaRepository;

public interface WithdrawalRepository extends JpaRepository<Withdrawal, Long> {}
3 changes: 3 additions & 0 deletions src/main/java/everymeal/server/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import everymeal.server.user.controller.dto.request.UserEmailLoginReq;
import everymeal.server.user.controller.dto.request.UserEmailSingReq;
import everymeal.server.user.controller.dto.request.UserProfileUpdateReq;
import everymeal.server.user.controller.dto.request.WithdrawalReq;
import everymeal.server.user.controller.dto.response.UserEmailAuthRes;
import everymeal.server.user.controller.dto.response.UserLoginRes;
import everymeal.server.user.controller.dto.response.UserProfileRes;
Expand All @@ -26,4 +27,6 @@ public interface UserService {

Boolean updateUserProfile(
AuthenticatedUser authenticatedUser, UserProfileUpdateReq userProfileUpdateReq);

Boolean withdrawal(AuthenticatedUser authenticatedUser, WithdrawalReq request);
}
31 changes: 31 additions & 0 deletions src/main/java/everymeal/server/user/service/UserServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@
import everymeal.server.user.controller.dto.request.UserEmailLoginReq;
import everymeal.server.user.controller.dto.request.UserEmailSingReq;
import everymeal.server.user.controller.dto.request.UserProfileUpdateReq;
import everymeal.server.user.controller.dto.request.WithdrawalReq;
import everymeal.server.user.controller.dto.response.UserEmailAuthRes;
import everymeal.server.user.controller.dto.response.UserLoginRes;
import everymeal.server.user.controller.dto.response.UserProfileRes;
import everymeal.server.user.entity.User;
import everymeal.server.user.entity.Withdrawal;
import everymeal.server.user.entity.WithdrawalReason;
import everymeal.server.user.repository.UserMapper;
import everymeal.server.user.repository.UserRepository;
import everymeal.server.user.repository.WithdrawalRepository;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Map;
Expand All @@ -39,6 +43,7 @@ public class UserServiceImpl implements UserService {
private final MailUtil mailUtil;
private final S3Util s3Util;
private final UserMapper userMapper;
private final WithdrawalRepository withdrawalRepository;

@Override
@Transactional
Expand Down Expand Up @@ -175,4 +180,30 @@ public Boolean updateUserProfile(
user.updateProfile(request.nickName(), request.profileImageKey());
return true;
}

@Override
@Transactional
public Boolean withdrawal(AuthenticatedUser authenticatedUser, WithdrawalReq request) {
User user =
userRepository
.findById(authenticatedUser.getIdx())
.orElseThrow(() -> new ApplicationException(ExceptionList.USER_NOT_FOUND));
Withdrawal withdrawal;
if (request.withdrawalReason() != WithdrawalReason.ETC) { // 기타를 제외한 경우
withdrawal =
Withdrawal.builder()
.withdrawalReason(request.withdrawalReason())
.user(user)
.build();
} else // 기타를 선택한 경우
withdrawal =
Withdrawal.builder()
.withdrawalReason(request.withdrawalReason())
.etcReason(request.etcReason())
.user(user)
.build();
withdrawalRepository.save(withdrawal); // 탈퇴 관련 정보 저장
user.setIsDeleted(); // 논리 삭제
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import everymeal.server.user.controller.dto.request.UserEmailLoginReq;
import everymeal.server.user.controller.dto.request.UserEmailSingReq;
import everymeal.server.user.controller.dto.request.UserProfileUpdateReq;
import everymeal.server.user.controller.dto.request.WithdrawalReq;
import everymeal.server.user.controller.dto.response.UserLoginRes;
import everymeal.server.user.entity.WithdrawalReason;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
Expand Down Expand Up @@ -155,4 +157,23 @@ void updateUserProfile() throws Exception {
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("OK"));
}

@DisplayName("회원 탈퇴")
@Test
void withdrawal() throws Exception {
// given
WithdrawalReq request = new WithdrawalReq(WithdrawalReason.ERRORS_OCCUR_FREQUENTLY, "");

given(userJwtResolver.resolveArgument(any(), any(), any(), any()))
.willReturn(AuthenticatedUser.builder().idx(1L).build());

// when-then
mockMvc.perform(
post("/api/v1/users/withdrawal")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("OK"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
import everymeal.server.user.controller.dto.request.UserEmailLoginReq;
import everymeal.server.user.controller.dto.request.UserEmailSingReq;
import everymeal.server.user.controller.dto.request.UserProfileUpdateReq;
import everymeal.server.user.controller.dto.request.WithdrawalReq;
import everymeal.server.user.controller.dto.response.UserEmailAuthRes;
import everymeal.server.user.controller.dto.response.UserLoginRes;
import everymeal.server.user.entity.User;
import everymeal.server.user.entity.WithdrawalReason;
import everymeal.server.user.repository.UserRepository;
import everymeal.server.user.repository.WithdrawalRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -34,9 +37,11 @@ class UserServiceImplTest extends IntegrationTestSupport {
@MockBean private MailUtil mailUtil;
@Autowired private UniversityRepository universityRepository;
@Autowired private S3Util s3Util;
@Autowired private WithdrawalRepository withdrawalRepository;

@AfterEach
void tearDown() {
withdrawalRepository.deleteAllInBatch();
userRepository.deleteAllInBatch();
}

Expand Down Expand Up @@ -314,6 +319,34 @@ void updateUserProfile_duplicated() {
ExceptionList.NICKNAME_ALREADY_EXIST.getCODE());
}

@DisplayName("회원 탈퇴 - 정해진 사유를 선택한 경우")
@Test
void withdrawal() {
// given
String token = jwtUtil.generateEmailToken("[email protected]", "12345");

University university =
universityRepository.save(
University.builder().name("명지대학교").campusName("인문캠퍼스").build());
UserEmailSingReq request =
new UserEmailSingReq("연유크림", token, "12345", university.getIdx(), "imageKey");

UserLoginRes userLoginRes = userService.signUp(request);

AuthenticatedUser user =
jwtUtil.getAuthenticateUserFromAccessToken(userLoginRes.accessToken());

WithdrawalReq withdrawalReq =
new WithdrawalReq(WithdrawalReason.ERRORS_OCCUR_FREQUENTLY, "");

// when then
var result = userService.withdrawal(user, withdrawalReq);
var withdrawalUser = userRepository.findByNickname("연유크림").get();

assertEquals(result, Boolean.TRUE);
assertEquals(withdrawalUser.getIsDeleted(), Boolean.TRUE);
}

private User createUser(String email, String nickname) {
return User.builder().email(email).nickname(nickname).build();
}
Expand Down

0 comments on commit 666c7dc

Please sign in to comment.