From 11aa0101b24ffe155154f775d6aae6f284092018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sun, 21 Dec 2025 23:14:31 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20DELETE=20/users/withdraw=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gg/agit/konect/domain/user/controller/UserApi.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/gg/agit/konect/domain/user/controller/UserApi.java b/src/main/java/gg/agit/konect/domain/user/controller/UserApi.java index 98bf07b0..92b8131b 100644 --- a/src/main/java/gg/agit/konect/domain/user/controller/UserApi.java +++ b/src/main/java/gg/agit/konect/domain/user/controller/UserApi.java @@ -1,6 +1,7 @@ package gg.agit.konect.domain.user.controller; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -57,4 +58,8 @@ ResponseEntity updateMyInfo( @PostMapping("/logout") @PublicApi ResponseEntity logout(HttpServletRequest request); + + @Operation(summary = "회원탈퇴를 한다.") + @DeleteMapping("/withdraw") + ResponseEntity logout(@UserId Integer userId); } From 0157b3680cedecbc17935afe73c07fa658bee860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sun, 21 Dec 2025 23:14:54 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20DELETE=20/users/withdraw=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ChatMessageRepository.java | 8 +++++ .../chat/repository/ChatRoomRepository.java | 8 +++++ .../club/repository/ClubApplyRepository.java | 2 ++ .../club/repository/ClubMemberRepository.java | 4 +++ .../CouncilNoticeReadRepository.java | 2 ++ .../user/controller/UserController.java | 7 +++++ .../user/repository/UserRepository.java | 2 ++ .../domain/user/service/UserService.java | 30 +++++++++++++++++++ .../konect/global/code/ApiResponseCode.java | 1 + 9 files changed, 64 insertions(+) diff --git a/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java b/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java index 5a2f6dd7..df864fb1 100644 --- a/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java +++ b/src/main/java/gg/agit/konect/domain/chat/repository/ChatMessageRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.Param; @@ -31,4 +32,11 @@ public interface ChatMessageRepository extends Repository AND cm.isRead = false """) List findUnreadMessages(@Param("chatRoomId") Integer chatRoomId, @Param("receiverId") Integer receiverId); + + @Modifying + @Query(""" + DELETE FROM ChatMessage cm + WHERE cm.sender.id = :userId OR cm.receiver.id = :userId + """) + void deleteByUserId(@Param("userId") Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java index 5a8d1037..d46b067b 100644 --- a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java +++ b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.Param; @@ -40,4 +41,11 @@ public interface ChatRoomRepository extends Repository { OR (cr.sender.id = :userId2 AND cr.receiver.id = :userId1) """) Optional findByTwoUsers(@Param("userId1") Integer userId1, @Param("userId2") Integer userId2); + + @Modifying + @Query(""" + DELETE FROM ChatRoom cr + WHERE cr.sender.id = :userId OR cr.receiver.id = :userId + """) + void deleteByUserId(@Param("userId") Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java index 87debe1f..11cba5c1 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java @@ -9,4 +9,6 @@ public interface ClubApplyRepository extends Repository { boolean existsByClubIdAndUserId(Integer clubId, Integer userId); ClubApply save(ClubApply clubApply); + + void deleteByUserId(Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java index c191fdea..148542ec 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java @@ -33,4 +33,8 @@ public interface ClubMemberRepository extends Repository findPresidentByClubId(@Param("clubId") Integer clubId); boolean existsByClubIdAndUserId(Integer clubId, Integer userId); + + List findByUserId(Integer userId); + + void deleteByUserId(Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/notice/repository/CouncilNoticeReadRepository.java b/src/main/java/gg/agit/konect/domain/notice/repository/CouncilNoticeReadRepository.java index 4554903c..2ff8e81c 100644 --- a/src/main/java/gg/agit/konect/domain/notice/repository/CouncilNoticeReadRepository.java +++ b/src/main/java/gg/agit/konect/domain/notice/repository/CouncilNoticeReadRepository.java @@ -26,4 +26,6 @@ WHERE NOT EXISTS ( ) """) Long countUnreadNoticesByUserId(@Param("userId") Integer userId); + + void deleteByUserId(Integer userId); } diff --git a/src/main/java/gg/agit/konect/domain/user/controller/UserController.java b/src/main/java/gg/agit/konect/domain/user/controller/UserController.java index 5c25a5ba..75b8457d 100644 --- a/src/main/java/gg/agit/konect/domain/user/controller/UserController.java +++ b/src/main/java/gg/agit/konect/domain/user/controller/UserController.java @@ -1,6 +1,7 @@ package gg.agit.konect.domain.user.controller; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -77,4 +78,10 @@ public ResponseEntity logout(HttpServletRequest request) { return ResponseEntity.ok().build(); } + + @DeleteMapping("/withdraw") + public ResponseEntity logout(@UserId Integer userId) { + userService.deleteUser(userId); + return ResponseEntity.notFound().build(); + } } diff --git a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java index 0cb6c800..80ed1843 100644 --- a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java +++ b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java @@ -27,4 +27,6 @@ default User getById(Integer id) { boolean existsByPhoneNumberAndIdNot(String phoneNumber, Integer id); User save(User user); + + void delete(User user); } diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserService.java b/src/main/java/gg/agit/konect/domain/user/service/UserService.java index 0a8e1401..8d295f40 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserService.java @@ -1,9 +1,17 @@ package gg.agit.konect.domain.user.service; +import static gg.agit.konect.global.code.ApiResponseCode.CANNOT_DELETE_CLUB_PRESIDENT; + +import java.util.List; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; +import gg.agit.konect.domain.chat.repository.ChatMessageRepository; +import gg.agit.konect.domain.chat.repository.ChatRoomRepository; +import gg.agit.konect.domain.club.model.ClubMember; +import gg.agit.konect.domain.club.repository.ClubApplyRepository; import gg.agit.konect.domain.club.repository.ClubMemberRepository; import gg.agit.konect.domain.notice.repository.CouncilNoticeReadRepository; import gg.agit.konect.domain.university.model.University; @@ -30,6 +38,9 @@ public class UserService { private final UniversityRepository universityRepository; private final ClubMemberRepository clubMemberRepository; private final CouncilNoticeReadRepository councilNoticeReadRepository; + private final ClubApplyRepository clubApplyRepository; + private final ChatMessageRepository chatMessageRepository; + private final ChatRoomRepository chatRoomRepository; @Transactional public Integer signup(String email, Provider provider, SignupRequest request) { @@ -117,4 +128,23 @@ private void validatePhoneNumberDuplication(User user, UserUpdateRequest request throw CustomException.of(ApiResponseCode.DUPLICATE_PHONE_NUMBER); } } + + @Transactional + public void deleteUser(Integer userId) { + User user = userRepository.getById(userId); + + List clubMembers = clubMemberRepository.findByUserId(userId); + boolean isPresident = clubMembers.stream().anyMatch(ClubMember::isPresident); + if (isPresident) { + throw CustomException.of(CANNOT_DELETE_CLUB_PRESIDENT); + } + + // TODO. 메시지 데이터 히스토리 테이블로 이관 로직 추가 + chatMessageRepository.deleteByUserId(userId); + chatRoomRepository.deleteByUserId(userId); + councilNoticeReadRepository.deleteByUserId(userId); + clubApplyRepository.deleteByUserId(userId); + clubMemberRepository.deleteByUserId(userId); + userRepository.delete(user); + } } diff --git a/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java b/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java index 6b8f3090..8348cb32 100644 --- a/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java +++ b/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java @@ -21,6 +21,7 @@ public enum ApiResponseCode { FAILED_EXTRACT_EMAIL(HttpStatus.BAD_REQUEST, "OAuth 로그인 과정에서 이메일 정보를 가져올 수 없습니다."), CANNOT_CREATE_CHAT_ROOM_WITH_SELF(HttpStatus.BAD_REQUEST, "자기 자신과는 채팅방을 만들 수 없습니다."), REQUIRED_CLUB_APPLY_ANSWER_MISSING(HttpStatus.BAD_REQUEST, "필수 가입 답변이 누락되었습니다."), + CANNOT_DELETE_CLUB_PRESIDENT(HttpStatus.BAD_REQUEST, "동아리 회장인 경우 회장을 양도하고 탈퇴해야 합니다."), // 401 Unauthorized INVALID_SESSION(HttpStatus.UNAUTHORIZED, "올바르지 않은 인증 정보 입니다."),