From a873c69e43b7f2451ae945837de661b3eb02513d Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Sun, 12 Oct 2025 21:40:42 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../token/repository/TokenRepository.java | 2 ++ .../user/controller/UserController.java | 7 ++++++ .../user/dto/UserWithdrawResponseDto.java | 21 ++++++++++++++++ .../soomteum/domain/user/entity/User.java | 4 +++ .../domain/user/service/UserService.java | 25 +++++++++++++++++++ .../soomteum/global/response/ErrorCode.java | 1 + 6 files changed, 60 insertions(+) create mode 100644 src/main/java/com/comma/soomteum/domain/user/dto/UserWithdrawResponseDto.java diff --git a/src/main/java/com/comma/soomteum/domain/token/repository/TokenRepository.java b/src/main/java/com/comma/soomteum/domain/token/repository/TokenRepository.java index 5755141..1838e8d 100644 --- a/src/main/java/com/comma/soomteum/domain/token/repository/TokenRepository.java +++ b/src/main/java/com/comma/soomteum/domain/token/repository/TokenRepository.java @@ -1,6 +1,7 @@ package com.comma.soomteum.domain.token.repository; import com.comma.soomteum.domain.token.entity.Token; +import com.comma.soomteum.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -8,4 +9,5 @@ public interface TokenRepository extends JpaRepository { Optional findByRefreshToken(String refreshToken); + void deleteByUser(User user); } diff --git a/src/main/java/com/comma/soomteum/domain/user/controller/UserController.java b/src/main/java/com/comma/soomteum/domain/user/controller/UserController.java index cca8c0b..3331667 100644 --- a/src/main/java/com/comma/soomteum/domain/user/controller/UserController.java +++ b/src/main/java/com/comma/soomteum/domain/user/controller/UserController.java @@ -4,6 +4,7 @@ import com.comma.soomteum.domain.user.entity.User; import com.comma.soomteum.domain.user.dto.UserNicknameRequestDto; import com.comma.soomteum.domain.user.dto.UserProfileResponseDto; +import com.comma.soomteum.domain.user.dto.UserWithdrawResponseDto; import com.comma.soomteum.domain.user.service.UserService; import com.comma.soomteum.global.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -35,4 +36,10 @@ public ApiResponse getUserProfile(@Parameter(hidden = tr public ApiResponse updateNickname(@Parameter(hidden = true) @LoginUser User user, @Valid @RequestBody UserNicknameRequestDto userNicknameRequestDto) { return ApiResponse.ok(userService.updateNickname(user.getUserId(), userNicknameRequestDto)); } + + @Operation(summary = "회원 탈퇴", description = "사용자의 계정을 탈퇴 처리합니다.") + @DeleteMapping("/withdraw") + public ApiResponse withdrawUser(@Parameter(hidden = true) @LoginUser User user) { + return ApiResponse.ok(userService.withdrawUser(user.getUserId())); + } } diff --git a/src/main/java/com/comma/soomteum/domain/user/dto/UserWithdrawResponseDto.java b/src/main/java/com/comma/soomteum/domain/user/dto/UserWithdrawResponseDto.java new file mode 100644 index 0000000..617a3e6 --- /dev/null +++ b/src/main/java/com/comma/soomteum/domain/user/dto/UserWithdrawResponseDto.java @@ -0,0 +1,21 @@ +package com.comma.soomteum.domain.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserWithdrawResponseDto { + + private String message; + + public static UserWithdrawResponseDto of() { + return UserWithdrawResponseDto.builder() + .message("회원 탈퇴가 완료되었습니다.") + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/comma/soomteum/domain/user/entity/User.java b/src/main/java/com/comma/soomteum/domain/user/entity/User.java index 09a852b..4cc4715 100644 --- a/src/main/java/com/comma/soomteum/domain/user/entity/User.java +++ b/src/main/java/com/comma/soomteum/domain/user/entity/User.java @@ -29,4 +29,8 @@ public class User extends BaseEntity { public void updateNickname(String nickname) { this.nickname = nickname; } + + public void withdraw() { + this.isActive = null; + } } diff --git a/src/main/java/com/comma/soomteum/domain/user/service/UserService.java b/src/main/java/com/comma/soomteum/domain/user/service/UserService.java index 2643a06..4384dd1 100644 --- a/src/main/java/com/comma/soomteum/domain/user/service/UserService.java +++ b/src/main/java/com/comma/soomteum/domain/user/service/UserService.java @@ -1,7 +1,9 @@ package com.comma.soomteum.domain.user.service; +import com.comma.soomteum.domain.token.repository.TokenRepository; import com.comma.soomteum.domain.user.dto.UserNicknameRequestDto; import com.comma.soomteum.domain.user.dto.UserProfileResponseDto; +import com.comma.soomteum.domain.user.dto.UserWithdrawResponseDto; import com.comma.soomteum.domain.user.entity.User; import com.comma.soomteum.domain.user.repository.UserRepository; import com.comma.soomteum.global.response.CustomException; @@ -16,6 +18,7 @@ public class UserService { private final UserRepository userRepository; + private final TokenRepository tokenRepository; /** * 마이페이지 - 이메일, 닉네임 조회 @@ -36,4 +39,26 @@ public UserProfileResponseDto updateNickname(Long userId, UserNicknameRequestDto user.updateNickname(userNicknameRequestDto.getNickname()); return UserProfileResponseDto.fromEntity(user); } + + /** + * 회원 탈퇴 + **/ + @Transactional + public UserWithdrawResponseDto withdrawUser(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + // 이미 탈퇴한 회원인지 확인 + if (user.getIsActive() == null) { + throw new CustomException(ErrorCode.ALREADY_WITHDRAWN_USER); + } + + // 해당 사용자의 모든 토큰 삭제 + tokenRepository.deleteByUser(user); + + // 회원 상태를 탈퇴로 변경 (isActive = null) + user.withdraw(); + + return UserWithdrawResponseDto.of(); + } } diff --git a/src/main/java/com/comma/soomteum/global/response/ErrorCode.java b/src/main/java/com/comma/soomteum/global/response/ErrorCode.java index 0b04e18..fa200d2 100644 --- a/src/main/java/com/comma/soomteum/global/response/ErrorCode.java +++ b/src/main/java/com/comma/soomteum/global/response/ErrorCode.java @@ -63,6 +63,7 @@ public enum ErrorCode { DUPLICATE_EMAIL(409_001, HttpStatus.CONFLICT, "이미 사용 중인 이메일입니다."), ALREADY_LIKED_PLACE(409_002, HttpStatus.CONFLICT, "이미 좋아요를 누른 장소입니다."), NOT_LIKED_PLACE(409_003, HttpStatus.CONFLICT, "좋아요를 누르지 않은 장소입니다."), + ALREADY_WITHDRAWN_USER(409_004, HttpStatus.CONFLICT, "이미 탈퇴한 회원입니다."), // ======================== From c5e936a9eeee69a55ec36cac3e1afb20f80ae67b Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Sun, 12 Oct 2025 21:49:26 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20delete->patch=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comma/soomteum/domain/user/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/comma/soomteum/domain/user/controller/UserController.java b/src/main/java/com/comma/soomteum/domain/user/controller/UserController.java index 3331667..4a0f949 100644 --- a/src/main/java/com/comma/soomteum/domain/user/controller/UserController.java +++ b/src/main/java/com/comma/soomteum/domain/user/controller/UserController.java @@ -38,7 +38,7 @@ public ApiResponse updateNickname(@Parameter(hidden = tr } @Operation(summary = "회원 탈퇴", description = "사용자의 계정을 탈퇴 처리합니다.") - @DeleteMapping("/withdraw") + @PatchMapping("/withdraw") public ApiResponse withdrawUser(@Parameter(hidden = true) @LoginUser User user) { return ApiResponse.ok(userService.withdrawUser(user.getUserId())); } From 2600a0b08ed2acf7e261c308ee5d391aafb70e7d Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Sun, 12 Oct 2025 22:01:30 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=ED=83=88=ED=87=B4=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=EA=B3=84?= =?UTF-8?q?=EC=A0=95=20=EC=9E=90=EB=8F=99=20=EB=B3=B5=EA=B5=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comma/soomteum/domain/auth/service/AuthService.java | 9 ++++++++- .../java/com/comma/soomteum/domain/user/entity/User.java | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/comma/soomteum/domain/auth/service/AuthService.java b/src/main/java/com/comma/soomteum/domain/auth/service/AuthService.java index d71a6f8..748aed5 100644 --- a/src/main/java/com/comma/soomteum/domain/auth/service/AuthService.java +++ b/src/main/java/com/comma/soomteum/domain/auth/service/AuthService.java @@ -41,7 +41,14 @@ public LoginResponseDto socialLogin(String providerId, String email) { .build() )); - boolean isNewUser = user.getCreatedAt() != null && + // 탈퇴한 회원이 다시 로그인하는 경우 계정 복구 + boolean isReturningUser = false; + if (user.getIsActive() == null) { + user.reactivate(); + isReturningUser = true; + } + + boolean isNewUser = !isReturningUser && user.getCreatedAt() != null && user.getUpdatedAt() != null && user.getCreatedAt().equals(user.getUpdatedAt()); diff --git a/src/main/java/com/comma/soomteum/domain/user/entity/User.java b/src/main/java/com/comma/soomteum/domain/user/entity/User.java index 4cc4715..5a2c559 100644 --- a/src/main/java/com/comma/soomteum/domain/user/entity/User.java +++ b/src/main/java/com/comma/soomteum/domain/user/entity/User.java @@ -33,4 +33,8 @@ public void updateNickname(String nickname) { public void withdraw() { this.isActive = null; } + + public void reactivate() { + this.isActive = true; + } } From 5eefbc798dcf3a100eab66bc71f0987e50df7318 Mon Sep 17 00:00:00 2001 From: lilloo04 Date: Sun, 12 Oct 2025 22:12:19 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EA=B4=80=EB=A6=AC=EC=9A=A9=20UserStatus=20enum=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthService.java | 3 +-- .../comma/soomteum/domain/user/entity/User.java | 17 ++++++++++++++--- .../soomteum/domain/user/enums/UserStatus.java | 6 ++++++ .../domain/user/service/UserService.java | 2 +- 4 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/comma/soomteum/domain/user/enums/UserStatus.java diff --git a/src/main/java/com/comma/soomteum/domain/auth/service/AuthService.java b/src/main/java/com/comma/soomteum/domain/auth/service/AuthService.java index 748aed5..feaa5d8 100644 --- a/src/main/java/com/comma/soomteum/domain/auth/service/AuthService.java +++ b/src/main/java/com/comma/soomteum/domain/auth/service/AuthService.java @@ -37,13 +37,12 @@ public LoginResponseDto socialLogin(String providerId, String email) { .providerId(providerId) .nickname("유저" + providerId) .email(email) - .isActive(true) .build() )); // 탈퇴한 회원이 다시 로그인하는 경우 계정 복구 boolean isReturningUser = false; - if (user.getIsActive() == null) { + if (user.isWithdrawn()) { user.reactivate(); isReturningUser = true; } diff --git a/src/main/java/com/comma/soomteum/domain/user/entity/User.java b/src/main/java/com/comma/soomteum/domain/user/entity/User.java index 5a2c559..200c209 100644 --- a/src/main/java/com/comma/soomteum/domain/user/entity/User.java +++ b/src/main/java/com/comma/soomteum/domain/user/entity/User.java @@ -1,6 +1,7 @@ package com.comma.soomteum.domain.user.entity; import com.comma.soomteum.domain.BaseEntity; +import com.comma.soomteum.domain.user.enums.UserStatus; import jakarta.persistence.*; import lombok.*; @@ -24,17 +25,27 @@ public class User extends BaseEntity { private String email; - private Boolean isActive; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private UserStatus status = UserStatus.ACTIVE; public void updateNickname(String nickname) { this.nickname = nickname; } public void withdraw() { - this.isActive = null; + this.status = UserStatus.WITHDRAWN; } public void reactivate() { - this.isActive = true; + this.status = UserStatus.ACTIVE; + } + + public boolean isWithdrawn() { + return this.status == UserStatus.WITHDRAWN; + } + + public boolean isActive() { + return this.status == UserStatus.ACTIVE; } } diff --git a/src/main/java/com/comma/soomteum/domain/user/enums/UserStatus.java b/src/main/java/com/comma/soomteum/domain/user/enums/UserStatus.java new file mode 100644 index 0000000..c2d65d6 --- /dev/null +++ b/src/main/java/com/comma/soomteum/domain/user/enums/UserStatus.java @@ -0,0 +1,6 @@ +package com.comma.soomteum.domain.user.enums; + +public enum UserStatus { + ACTIVE, // 활성 회원 + WITHDRAWN // 탈퇴 회원 +} \ No newline at end of file diff --git a/src/main/java/com/comma/soomteum/domain/user/service/UserService.java b/src/main/java/com/comma/soomteum/domain/user/service/UserService.java index 4384dd1..9e0c1be 100644 --- a/src/main/java/com/comma/soomteum/domain/user/service/UserService.java +++ b/src/main/java/com/comma/soomteum/domain/user/service/UserService.java @@ -49,7 +49,7 @@ public UserWithdrawResponseDto withdrawUser(Long userId) { .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); // 이미 탈퇴한 회원인지 확인 - if (user.getIsActive() == null) { + if (user.isWithdrawn()) { throw new CustomException(ErrorCode.ALREADY_WITHDRAWN_USER); }