Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT]: 회원 탈퇴 API 구현 #100

Merged
merged 20 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
497711d
refactor: 사용하지 않는 service 제거
twoosky Sep 6, 2023
84d1071
refactor: user 관련 조회 쿼리 queryHandler로 통일
twoosky Sep 6, 2023
11bd647
feat: user 조회 쿼리에 status 조건 추가
twoosky Sep 6, 2023
9d1ea3a
feat: user status enum 구현
twoosky Sep 6, 2023
5755e09
feat: user entity에 status 컬럼 추가
twoosky Sep 6, 2023
b1a354c
feat: active user 조회 쿼리 handler 메서드 추가
twoosky Sep 6, 2023
7383cf5
fix: comment 조회 응답 userId 필드 수정
twoosky Sep 6, 2023
2ab3b21
fix: article 조회 응답 userId 필드 수정
twoosky Sep 6, 2023
335d23d
fix: user 조회 -> active user 조회로 수정
twoosky Sep 6, 2023
ac8b0b7
feat: 진행 중인 그룹 존재 여부 반환 쿼리 작성
twoosky Sep 6, 2023
10911be
feat: 진행 중인 그룹 여부 반환 쿼리 handler 메서드 작성
twoosky Sep 6, 2023
cb63da0
feat: wait 상태 지원서 존재 여부 반환 jpa 쿼리 메서드 작성
twoosky Sep 6, 2023
212a910
feat: wait 상태 지원서 존재 여부 반환 쿼리 handler 작성
twoosky Sep 6, 2023
513450f
feat: 회원 탈퇴 api 구현
twoosky Sep 6, 2023
65ccdab
feat: signout validator 작성
twoosky Sep 6, 2023
0ae734a
refactor: findByPhone 쿼리 handler 메서드 호출로 리팩토링
twoosky Sep 6, 2023
0b73c33
feat: 회원 탈퇴 비즈니스 로직 구현
twoosky Sep 6, 2023
387114f
feat: 회원 탈퇴 관련 errorCode 작성
twoosky Sep 6, 2023
fae21d0
feat: 회원 탈퇴 관련 exception 정의
twoosky Sep 6, 2023
19a75a8
fix: 구문 error 해결
twoosky Sep 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.gloddy.server.apply.domain.Apply;
import com.gloddy.server.apply.domain.vo.Status;
import com.gloddy.server.auth.domain.User;
import com.gloddy.server.group.domain.Group;

import java.util.List;
Expand All @@ -25,4 +26,6 @@ public interface ApplyQueryHandler {
Apply findById(Long applyId);

Boolean existsByUserIdAndGroupIdAndStatus(Long userId, Long groupId, Status status);

Boolean existsByUserAndStatus(User user, Status status);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.gloddy.server.apply.domain.handler.ApplyQueryHandler;
import com.gloddy.server.apply.exception.NotFoundApplyException;
import com.gloddy.server.apply.infra.repository.ApplyJpaRepository;
import com.gloddy.server.auth.domain.User;
import com.gloddy.server.group.domain.Group;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
Expand Down Expand Up @@ -59,4 +60,9 @@ public Apply findById(Long applyId) {
public Boolean existsByUserIdAndGroupIdAndStatus(Long userId, Long groupId, Status status) {
return applyJpaRepository.existsByUserIdAndGroupIdAndStatus(userId, groupId, status);
}

@Override
public Boolean existsByUserAndStatus(User user, Status status) {
return applyJpaRepository.existsByUserAndStatus(user, status);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.gloddy.server.apply.domain.Apply;
import com.gloddy.server.apply.domain.vo.Status;
import com.gloddy.server.apply.infra.repository.custom.ApplyJpaRepositoryCustom;
import com.gloddy.server.auth.domain.User;
import com.gloddy.server.group.domain.Group;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -25,4 +26,6 @@ public interface ApplyJpaRepository extends JpaRepository<Apply, Long>, ApplyJpa
Apply findFirstByOrderByIdDesc();

Boolean existsByUserIdAndGroupIdAndStatus(Long userId, Long groupId, Status status);

Boolean existsByUserAndStatus(User user, Status status);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static GetArticle getArticleDto(Article article, User currentUser) {
Profile profile = user.getProfile();

return new GetArticle(
currentUser.getId(),
user.getId(),
article.getId(),
profile.getImageUrl(),
profile.getNickname(),
Expand Down
22 changes: 13 additions & 9 deletions src/main/java/com/gloddy/server/auth/api/AuthApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,51 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
@RequestMapping("/api/v1/auth")
public class AuthApi {

private final AuthService authService;

@Operation(security = {})
@PostMapping("/auth/email/check")
@PostMapping("/email/check")
public ResponseEntity<AuthResponse.Whether> emailCheck(@RequestBody AuthRequest.EmailCheck req) {
AuthResponse.Whether response = authService.emailCheck(req.getEmail());

return ApiResponse.ok(response);
}

@Operation(security = {})
@PostMapping("/auth/sign-up")
@PostMapping("/sign-up")
public ResponseEntity<AuthResponse.SignUp> signUp(@RequestBody AuthRequest.SignUp req) {
AuthResponse.SignUp response = authService.signUp(req);

return ApiResponse.ok(response);
}

@Operation(security = {})
@PostMapping("/auth/login")
@PostMapping("/login")
public ResponseEntity<AuthResponse.Login> login(@RequestBody AuthRequest.Login req) {
AuthResponse.Login response = authService.login(req);

return ApiResponse.ok(response);
}

@Operation(security = {})
@PostMapping("/auth/token-reissue")
@PostMapping("/token-reissue")
public ResponseEntity<AuthResponse.Token> tokenReissue(@RequestBody AuthRequest.ReIssueToken req) {
AuthResponse.Token response = authService.reissueToken(req.getAccessToken(), req.getRefreshToken());
return ApiResponse.ok(response);
}

@PatchMapping("/sign-out")
public ResponseEntity<Void> signOut(
@AuthenticationPrincipal Long userId
) {
authService.signOut(userId);
return ApiResponse.noContent();
}
}
24 changes: 18 additions & 6 deletions src/main/java/com/gloddy/server/auth/application/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package com.gloddy.server.auth.application;

import com.gloddy.server.apply.domain.handler.ApplyQueryHandler;
import com.gloddy.server.auth.domain.User;
import com.gloddy.server.auth.domain.service.UserFactory;
import com.gloddy.server.auth.domain.service.UserSignOutPolicy;
import com.gloddy.server.auth.domain.vo.Phone;
import com.gloddy.server.auth.domain.vo.kind.Status;
import com.gloddy.server.auth.exception.WithdrawRequirementsNotMetException;
import com.gloddy.server.auth.jwt.JwtToken;
import com.gloddy.server.auth.jwt.JwtTokenBuilder;
import com.gloddy.server.auth.jwt.JwtTokenIssuer;
import com.gloddy.server.core.error.handler.errorCode.ErrorCode;
import com.gloddy.server.group_member.domain.handler.GroupMemberQueryHandler;
import com.gloddy.server.user.domain.handler.UserCommandHandler;
import com.gloddy.server.user.domain.handler.UserQueryHandler;
import com.gloddy.server.user.event.producer.UserEventProducer;
import com.gloddy.server.user.infra.repository.UserJpaRepository;
import com.gloddy.server.auth.domain.dto.AuthRequest;
import com.gloddy.server.auth.domain.dto.AuthResponse;
import com.gloddy.server.user.event.UserCreateEvent;
Expand All @@ -23,10 +27,11 @@
@RequiredArgsConstructor
public class AuthService {

private final UserJpaRepository userJpaRepository;
private final UserCommandHandler userCommandHandler;
private final UserQueryHandler userQueryHandler;
private final JwtTokenIssuer jwtTokenIssuer;
private final UserEventProducer userEventProducer;
private final UserSignOutPolicy userSignOutPolicy;
private final UserFactory userFactory;

@Transactional
Expand All @@ -41,15 +46,15 @@ public AuthResponse.SignUp signUp(AuthRequest.SignUp req) {

@Transactional(readOnly = true)
public AuthResponse.Login login(AuthRequest.Login req) {
Optional<User> findUser = userJpaRepository.findByPhone(new Phone(req.getPhoneNumber()));
Phone phone = new Phone(req.getPhoneNumber());
Optional<User> findUser = userQueryHandler.findByPhone(phone);
return findUser.map(user -> AuthResponse.Login.from(user, jwtTokenIssuer))
.orElseGet(AuthResponse.Login::fail);
}

@Transactional(readOnly = true)
public AuthResponse.Whether emailCheck(String email) {

Optional<User> findUser = userJpaRepository.findByEmail(email);
Optional<User> findUser = userQueryHandler.findByEmail(email);

if (findUser.isEmpty()) {
return new AuthResponse.Whether(false);
Expand All @@ -63,4 +68,11 @@ public AuthResponse.Token reissueToken(String accessToken, String refreshToken)
JwtToken token = jwtTokenIssuer.reIssueToken(accessToken, refreshToken);
return new AuthResponse.Token(token);
}

@Transactional
public void signOut(Long userId) {
User user = userQueryHandler.findByIdAndStatus(userId, Status.ACTIVE);
userSignOutPolicy.validate(user);
user.withDraw();
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/gloddy/server/auth/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.gloddy.server.auth.domain.vo.kind.Authority;
import com.gloddy.server.auth.domain.vo.kind.Gender;
import com.gloddy.server.auth.domain.vo.kind.Personality;
import com.gloddy.server.auth.domain.vo.kind.Status;
import com.gloddy.server.core.entity.common.BaseTimeEntity;
import com.gloddy.server.core.event.GroupParticipateEvent;
import com.gloddy.server.group.event.GroupCreateEvent;
Expand Down Expand Up @@ -39,6 +40,10 @@ public class User extends BaseTimeEntity {
@Column(name = "authority")
private Authority authority;

@Enumerated(EnumType.STRING)
@Column(name = "status")
private Status status;

@Embedded
private Phone phone;

Expand All @@ -59,6 +64,7 @@ public User(Phone phone, School school, Profile profile) {
this.phone = phone;
this.school = school;
this.profile = profile;
this.status = Status.ACTIVE;
authorityDefault();
}

Expand Down Expand Up @@ -131,4 +137,8 @@ public LocalDate getBirth() {
public LocalDate getJoinAt() {
return this.getCreatedAt().toLocalDate();
}

public void withDraw() {
this.status = Status.WITHDRAW;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.gloddy.server.auth.domain.service;

import com.gloddy.server.apply.domain.handler.ApplyQueryHandler;
import com.gloddy.server.auth.domain.User;
import com.gloddy.server.auth.exception.WithdrawRequirementsNotMetException;
import com.gloddy.server.core.error.handler.errorCode.ErrorCode;
import com.gloddy.server.group_member.domain.handler.GroupMemberQueryHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class UserSignOutPolicy {

private final GroupMemberQueryHandler groupMemberQueryHandler;
private final ApplyQueryHandler applyQueryHandler;

public void validate(User user) {
validateGroup(user);
validateApply(user);
}

private void validateGroup(User user) {
if (existsParticipationGroup(user)) {
throw new WithdrawRequirementsNotMetException(ErrorCode.EXISTS_PARTICIPATING_GROUP);
}
}

private void validateApply(User user) {
if (existsWaitApply(user)) {
throw new WithdrawRequirementsNotMetException(ErrorCode.EXISTS_WAIT_APPLY);
}
}

private boolean existsParticipationGroup(User user) {
return groupMemberQueryHandler.existsByUserAndGroupEndTimeBefore(user);
}

private boolean existsWaitApply(User user) {
return applyQueryHandler.existsByUserAndStatus(user, com.gloddy.server.apply.domain.vo.Status.WAIT);
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
package com.gloddy.server.auth.domain.service;

import com.gloddy.server.auth.domain.User;
import com.gloddy.server.auth.domain.vo.Phone;
import com.gloddy.server.auth.exception.AlreadyUserSignUpException;
import com.gloddy.server.user.infra.repository.UserJpaRepository;
import com.gloddy.server.user.domain.handler.UserQueryHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
@RequiredArgsConstructor
public class UserSignUpPolicy {

private final UserJpaRepository userJpaRepository;
private final UserQueryHandler userQueryHandler;

public void validate(String phoneNumber) {
Phone phone = new Phone(phoneNumber);

Optional<User> user = userJpaRepository.findByPhone(phone);

if (user.isPresent()) {
throw new AlreadyUserSignUpException();
}
userQueryHandler.findByPhone(phone)
.ifPresent(user -> {throw new AlreadyUserSignUpException();});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.gloddy.server.auth.domain.vo.kind;

public enum Status {
ACTIVE,
WITHDRAW,
;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.gloddy.server.auth.exception;

import com.gloddy.server.core.error.handler.errorCode.ErrorCode;
import com.gloddy.server.core.error.handler.exception.BaseBusinessException;

public class WithdrawRequirementsNotMetException extends BaseBusinessException {
public WithdrawRequirementsNotMetException(ErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.gloddy.server.auth.domain.User;
import com.gloddy.server.auth.domain.vo.Phone;
import com.gloddy.server.auth.jwt.JwtUserAdapter;
import com.gloddy.server.user.infra.repository.UserJpaRepository;
import com.gloddy.server.user.domain.handler.UserQueryHandler;
import com.gloddy.server.core.error.handler.errorCode.ErrorCode;
import com.gloddy.server.core.error.handler.exception.UserBusinessException;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,12 +17,12 @@
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

private final UserJpaRepository userJpaRepository;
private final UserQueryHandler userQueryHandler;

@Override
public UserDetails loadUserByUsername(String phoneNumber) throws UsernameNotFoundException {

User user = userJpaRepository.findByPhone(new Phone(phoneNumber))
User user = userQueryHandler.findByPhone(new Phone(phoneNumber))
.orElseThrow(() -> new UserBusinessException(ErrorCode.USER_NOT_FOUND));

return JwtUserAdapter.from(user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static List<GetComment> mapToGetCommentListFrom(List<Comment> comments, U

private static GetComment getComment(Comment comment, User user) {
return new GetComment(
user.getId(),
comment.getUser().getId(),
comment.getId(),
comment.getWriterImageUrl(),
comment.getWriterNickName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum ErrorCode {
NOT_EXIST_RELIABILITY_LEVEL(404, "존재하지 않는 신뢰도 레벨입니다."),

GROUP_TIME_INVALID(400, "올바르지 않은 그룹 시작 시간입니다."),
EXISTS_PARTICIPATING_GROUP(400, "참여 중인 모임이 존재합니다."),

EMAIL_INVALID(404, "유효하지 않은 이메일 형식입니다."),
PHONE_NUMBER_INVALID(400, "유효하지 않은 휴대폰 번호입니다."),
Expand All @@ -45,6 +46,7 @@ public enum ErrorCode {
GROUP_NOT_FOUND(404, "존재하지 않는 그룹입니다."),
GROUP_NOT_CAPTAIN(403, "권한이 없습니다."),
APPLY_NOT_FOUND(404, "존재하지 않는 지원서입니다."),
EXISTS_WAIT_APPLY(400, "대기 상태인 지원서가 존재합니다."),
NO_TOTAL_GROUP_MEMBER_PRAISE(400, "모든 그룹 참여자에 대해 칭찬 하지 않았습니다"),

SMS_BAD_REQUEST(400, "잘못된 형식의 SMS 요청입니다."),
Expand Down
Loading
Loading