Skip to content

Commit 384e716

Browse files
authored
Merge pull request #152 from slid-todo/feature/auth-withdrawl
feat: 회원 탈퇴
2 parents 0d5c3ce + 216f25a commit 384e716

File tree

12 files changed

+114
-13
lines changed

12 files changed

+114
-13
lines changed

src/main/java/com/codeit/todo/common/config/JwtTokenProvider.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,19 @@
44
import com.codeit.todo.common.exception.payload.ErrorStatus;
55
import io.jsonwebtoken.*;
66
import jakarta.annotation.PostConstruct;
7-
import jakarta.servlet.http.Cookie;
87
import jakarta.servlet.http.HttpServletRequest;
98
import lombok.RequiredArgsConstructor;
109
import org.springframework.beans.factory.annotation.Value;
11-
import org.springframework.http.ResponseCookie;
1210
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1311
import org.springframework.security.core.Authentication;
14-
import org.springframework.security.core.parameters.P;
1512
import org.springframework.security.core.userdetails.UserDetails;
1613
import org.springframework.security.core.userdetails.UserDetailsService;
1714
import org.springframework.stereotype.Component;
1815

1916
import java.util.Base64;
2017
import java.util.Date;
18+
import java.util.HashSet;
19+
import java.util.Set;
2120

2221
@Component
2322
@RequiredArgsConstructor
@@ -91,4 +90,12 @@ public String getUserEmail(String jwtToken) {
9190
}
9291

9392

93+
private final Set<String> tokenBlackList = new HashSet<>();
94+
public void addToBlackList(String currentToken) {
95+
tokenBlackList.add(currentToken);
96+
}
97+
98+
public boolean isTokenBlackListed(String jwtToken){
99+
return tokenBlackList.contains(jwtToken);
100+
}
94101
}

src/main/java/com/codeit/todo/domain/User.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ public class User {
3131
@Column(name = "profile_pic", nullable = false)
3232
private String profilePic;
3333

34+
@Column(name = "user_status", nullable = false)
35+
private String userStatus;
36+
37+
3438
@OneToMany(mappedBy= "user", cascade = CascadeType.REMOVE, orphanRemoval = true)
3539
private List<Goal> goals = new ArrayList<>();
3640

@@ -43,12 +47,13 @@ public class User {
4347
private List<Follow> followees = new ArrayList<>();
4448

4549
@Builder
46-
public User(int userId, String name, String email, String password, String profilePic) {
50+
public User(int userId, String name, String email, String password, String profilePic, String userStatus) {
4751
this.userId = userId;
4852
this.name = name;
4953
this.email = email;
5054
this.password = password;
5155
this.profilePic = profilePic;
56+
this.userStatus = userStatus;
5257
}
5358

5459
public void updateProfilePic(String completePicUrl){
@@ -58,4 +63,6 @@ public void updateProfilePic(String completePicUrl){
5863
public void updatePassword(String password){
5964
this.password = password;
6065
}
66+
67+
public void updateStatus(){this.userStatus = "탈퇴";}
6168
}

src/main/java/com/codeit/todo/repository/CommentRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
import com.codeit.todo.domain.Comment;
55
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
import java.util.List;
8+
69
public interface CommentRepository extends JpaRepository<Comment, Integer> {
710

11+
List<Comment> findByUser_UserId(int userId);
812
}
913

src/main/java/com/codeit/todo/repository/FollowRepository.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import com.codeit.todo.domain.Follow;
55
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Modifying;
67
import org.springframework.data.jpa.repository.Query;
78
import org.springframework.data.repository.query.Param;
89

@@ -31,5 +32,17 @@ public interface FollowRepository extends JpaRepository<Follow, Integer> {
3132
List<Integer> findFolloweeIdsByFollowerId(@Param("userId") int userId);
3233

3334
Optional<Follow> findByFollower_UserIdAndFollowee_UserId(int followerId, int followeeId);
35+
36+
@Modifying
37+
@Query
38+
("DELETE FROM Follow f " +
39+
"WHERE f.followee.userId = :userId ")
40+
void deleteByFolloweeUserId(@Param("userId") int userId);
41+
42+
@Modifying
43+
@Query(
44+
"DELETE FROM Follow f " +
45+
"WHERE f.follower.userId = :userId ")
46+
void deleteByFollowerUserId(@Param("userId") int userId);
3447
}
3548

src/main/java/com/codeit/todo/repository/GoalRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.springframework.data.domain.Pageable;
55
import org.springframework.data.domain.Slice;
66
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Modifying;
78
import org.springframework.data.jpa.repository.Query;
89
import org.springframework.data.repository.query.Param;
910

@@ -40,4 +41,5 @@ public interface GoalRepository extends JpaRepository<Goal, Integer> {
4041
Slice<Goal> findByUserAndHasTodosAfterLastGoalId(@Param("lastGoalId") Integer lastGoalId, @Param("userId") int userId, Pageable pageable, @Param("today") LocalDate today);
4142

4243
List<Goal> findByGoalTitleContains(@Param("keyword") String keyword);
44+
4345
}

src/main/java/com/codeit/todo/repository/LikesRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import com.codeit.todo.domain.Likes;
44
import org.springframework.data.jpa.repository.JpaRepository;
55

6+
import java.util.List;
67
import java.util.Optional;
78

89
public interface LikesRepository extends JpaRepository<Likes, Integer> {
910
Boolean existsByUser_UserIdAndComplete_CompleteId(int userId, int completeId);
1011

1112
Optional<Likes> findByComplete_CompleteIdAndUser_UserId(int completeId, int userId);
13+
14+
List<Likes> findByUser_UserId(int userId);
1215
}

src/main/java/com/codeit/todo/service/user/UserService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ public interface UserService {
2121
ReadTargetUserResponse findTargetUserProfile(int userId, int targetUserId);
2222

2323
ReadMyPageResponse findUserInfoAndFollows(int userId);
24+
25+
UpdateUserStatusResponse updateUserStatus(int userId);
2426
}

src/main/java/com/codeit/todo/service/user/impl/UserServiceImpl.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
import com.codeit.todo.common.config.JwtTokenProvider;
44
import com.codeit.todo.common.exception.ApplicationException;
5+
import com.codeit.todo.common.exception.auth.AuthorizationDeniedException;
56
import com.codeit.todo.common.exception.payload.ErrorStatus;
67
import com.codeit.todo.common.exception.user.SignUpException;
78
import com.codeit.todo.common.exception.user.UpdatePasswordException;
89
import com.codeit.todo.common.exception.user.UserNotFoundException;
910
import com.codeit.todo.domain.User;
10-
import com.codeit.todo.repository.FollowRepository;
11-
import com.codeit.todo.repository.GoalRepository;
12-
import com.codeit.todo.repository.UserRepository;
11+
import com.codeit.todo.repository.*;
1312
import com.codeit.todo.service.storage.StorageService;
1413
import com.codeit.todo.service.user.UserService;
1514
import com.codeit.todo.web.dto.request.auth.LoginRequest;
@@ -37,6 +36,8 @@ public class UserServiceImpl implements UserService {
3736
private final UserRepository userRepository;
3837
private final GoalRepository goalRepository;
3938
private final FollowRepository followRepository;
39+
private final LikesRepository likesRepository;
40+
private final CommentRepository commentRepository;
4041
private final AuthenticationManager authenticationManager;
4142
private final JwtTokenProvider jwtTokenProvider;
4243
private final PasswordEncoder passwordEncoder;
@@ -78,12 +79,16 @@ public String login(LoginRequest loginRequest){
7879
try{
7980
User user = userRepository.findByEmail(email)
8081
.orElseThrow(()-> new UserNotFoundException(email, "User"));
82+
if(user.getUserStatus().equals("탈퇴")) throw new AuthorizationDeniedException("탈퇴한 회원입니다. 로그인 권한이 없습니다.");
8183

8284
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, password));
8385
SecurityContextHolder.getContext().setAuthentication(authentication);
8486

8587
return jwtTokenProvider.createToken(email);
86-
}catch(Exception e){
88+
} catch(AuthorizationDeniedException e){
89+
e.printStackTrace();
90+
throw new AuthorizationDeniedException("탈퇴한 회원입니다. 로그인 권한이 없습니다.");
91+
} catch(Exception e){
8792
e.printStackTrace();
8893
throw new ApplicationException(new ErrorStatus(
8994
"로그인 과정에서 에러 발생",
@@ -162,6 +167,22 @@ public ReadMyPageResponse findUserInfoAndFollows(int userId) {
162167
return ReadMyPageResponse.from(followerCount, followeeCount);
163168
}
164169

170+
@Transactional
171+
@Override
172+
public UpdateUserStatusResponse updateUserStatus(int userId) {
173+
User user = getUser(userId);
174+
user.updateStatus();
175+
176+
followRepository.deleteByFolloweeUserId(userId);
177+
followRepository.deleteByFollowerUserId(userId);
178+
179+
goalRepository.findByUser_UserId(userId).forEach(goalRepository::delete);
180+
likesRepository.findByUser_UserId(userId).forEach(likesRepository::delete);
181+
commentRepository.findByUser_UserId(userId).forEach(commentRepository::delete);
182+
183+
return UpdateUserStatusResponse.from(userId);
184+
}
185+
165186
public User getUser(int userId){
166187
User user = userRepository.findById(userId)
167188
.orElseThrow(()-> new UserNotFoundException(String.valueOf(userId), "User"));

src/main/java/com/codeit/todo/web/controller/AuthController.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
package com.codeit.todo.web.controller;
22

3+
import com.codeit.todo.common.config.JwtTokenProvider;
34
import com.codeit.todo.repository.CustomUserDetails;
45
import com.codeit.todo.service.user.UserService;
56
import com.codeit.todo.web.dto.request.auth.LoginRequest;
67
import com.codeit.todo.web.dto.request.auth.SignUpRequest;
78
import com.codeit.todo.web.dto.request.auth.UpdatePasswordRequest;
89
import com.codeit.todo.web.dto.request.auth.UpdatePictureRequest;
910
import com.codeit.todo.web.dto.response.Response;
10-
import com.codeit.todo.web.dto.response.auth.ReadMyPageResponse;
11-
import com.codeit.todo.web.dto.response.auth.ReadTargetUserResponse;
12-
import com.codeit.todo.web.dto.response.auth.UpdatePasswordResponse;
13-
import com.codeit.todo.web.dto.response.auth.UpdatePictureResponse;
11+
import com.codeit.todo.web.dto.response.auth.*;
1412
import io.swagger.v3.oas.annotations.Operation;
1513
import io.swagger.v3.oas.annotations.responses.ApiResponse;
1614
import io.swagger.v3.oas.annotations.responses.ApiResponses;
15+
import jakarta.servlet.http.HttpServletRequest;
1716
import jakarta.servlet.http.HttpServletResponse;
1817
import lombok.RequiredArgsConstructor;
1918
import org.springframework.http.ResponseEntity;
2019
import org.springframework.security.core.annotation.AuthenticationPrincipal;
20+
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
2121
import org.springframework.web.bind.annotation.*;
2222

2323
@RestController
@@ -26,7 +26,7 @@
2626
public class AuthController {
2727

2828
private final UserService userService;
29-
29+
private final JwtTokenProvider jwtTokenProvider;
3030
@Operation(summary = "회원가입", description = "이름, 이메일, 비밀번호로 회원가입")
3131
@ApiResponses(value = {
3232
@ApiResponse(responseCode = "200", description = "회원가입 성공")
@@ -116,4 +116,23 @@ public Response<ReadMyPageResponse> getMyPage(@AuthenticationPrincipal CustomUse
116116
return Response.ok( userService.findUserInfoAndFollows(userId));
117117
}
118118

119+
@Operation(
120+
summary = "회원 탈퇴",
121+
description = "user_status를 '탈퇴'로 변경하고 목표, 할일, 인증, 좋아요, 댓글, 팔로워, 팔로이 모두 삭제"
122+
)
123+
@ApiResponses(value = {
124+
@ApiResponse(responseCode = "200", description = "탈퇴 성공")
125+
})
126+
@DeleteMapping("/withdrawl")
127+
public Response<UpdateUserStatusResponse> userWithdraw(
128+
HttpServletRequest request,
129+
@AuthenticationPrincipal CustomUserDetails userDetails
130+
) {
131+
String currentToken = request.getHeader("token");
132+
jwtTokenProvider.addToBlackList(currentToken);
133+
134+
int userId = userDetails.getUserId();
135+
return Response.ok(userService.updateUserStatus(userId));
136+
}
137+
119138
}

src/main/java/com/codeit/todo/web/dto/request/auth/SignUpRequest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public User toEntity(String encodedPassword, String profilePic) {
2323
.email(this.email)
2424
.password(encodedPassword)
2525
.profilePic(profilePic)
26+
.userStatus("가입")
2627
.build();
2728
}
2829

0 commit comments

Comments
 (0)