From cbd625cbe02a6fcbf2a19209af8c371cb8a777c7 Mon Sep 17 00:00:00 2001 From: msk226 Date: Wed, 31 Dec 2025 22:42:34 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[Feature]=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/application/token/LogoutService.java | 18 ++++++++++++++++++ .../kr/spot/presentation/AuthController.java | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 modules/auth/src/main/java/kr/spot/application/token/LogoutService.java diff --git a/modules/auth/src/main/java/kr/spot/application/token/LogoutService.java b/modules/auth/src/main/java/kr/spot/application/token/LogoutService.java new file mode 100644 index 0000000..43f39dd --- /dev/null +++ b/modules/auth/src/main/java/kr/spot/application/token/LogoutService.java @@ -0,0 +1,18 @@ +package kr.spot.application.token; + +import kr.spot.infrastructure.jpa.RefreshTokenRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class LogoutService { + + private final RefreshTokenRepository refreshTokenRepository; + + @Transactional + public void logout(long memberId) { + refreshTokenRepository.deleteByMemberId(memberId); + } +} diff --git a/modules/auth/src/main/java/kr/spot/presentation/AuthController.java b/modules/auth/src/main/java/kr/spot/presentation/AuthController.java index cd4e0f1..8bd8a2f 100644 --- a/modules/auth/src/main/java/kr/spot/presentation/AuthController.java +++ b/modules/auth/src/main/java/kr/spot/presentation/AuthController.java @@ -6,6 +6,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import kr.spot.ApiResponse; +import kr.spot.annotations.CurrentMember; +import kr.spot.application.token.LogoutService; import kr.spot.application.token.TokenReissueService; import kr.spot.code.status.SuccessStatus; import kr.spot.presentation.command.dto.TokenDTO; @@ -23,6 +25,7 @@ public class AuthController { private final TokenReissueService tokenReissueService; + private final LogoutService logoutService; @Operation(summary = "토큰 재발급", description = """ ## 리프레시 토큰을 통해 새로운 액세스 토큰과 리프레시 토큰을 발급받습니다. @@ -45,4 +48,20 @@ public ResponseEntity> reissueToken( return ResponseEntity.ok( ApiResponse.onSuccess(SuccessStatus._OK, tokenReissueService.reissueToken(refreshToken))); } + + @Operation(summary = "로그아웃", description = """ + ## 로그아웃을 수행합니다. + - 서버에 저장된 리프레시 토큰을 삭제합니다. + - 액세스 토큰은 만료될 때까지 유효하므로, 클라이언트에서도 토큰을 삭제해주세요. + """) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), + }) + @PostMapping("/logout") + public ResponseEntity> logout( + @CurrentMember long memberId + ) { + logoutService.logout(memberId); + return ResponseEntity.ok(ApiResponse.onSuccess(SuccessStatus._OK, null)); + } } From b53eab0e27f4cd9988078a00f0bfd6f94e0afbb3 Mon Sep 17 00:00:00 2001 From: msk226 Date: Wed, 31 Dec 2025 22:43:48 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[Feature]=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/token/LogoutServiceTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 modules/auth/src/test/java/kr/spot/application/token/LogoutServiceTest.java diff --git a/modules/auth/src/test/java/kr/spot/application/token/LogoutServiceTest.java b/modules/auth/src/test/java/kr/spot/application/token/LogoutServiceTest.java new file mode 100644 index 0000000..1b320c6 --- /dev/null +++ b/modules/auth/src/test/java/kr/spot/application/token/LogoutServiceTest.java @@ -0,0 +1,36 @@ +package kr.spot.application.token; + +import static kr.spot.common.AuthFixture.MEMBER_ID; +import static org.mockito.Mockito.verify; + +import kr.spot.infrastructure.jpa.RefreshTokenRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class LogoutServiceTest { + + @Mock + RefreshTokenRepository refreshTokenRepository; + + LogoutService service; + + @BeforeEach + void setUp() { + service = new LogoutService(refreshTokenRepository); + } + + @Test + @DisplayName("로그아웃 시 해당 회원의 리프레시 토큰을 삭제한다") + void should_deleteRefreshToken_when_logout() { + // when + service.logout(MEMBER_ID); + + // then + verify(refreshTokenRepository).deleteByMemberId(MEMBER_ID); + } +} From 0ab2187da5c462a1432977671453e596668585bb Mon Sep 17 00:00:00 2001 From: msk226 Date: Wed, 31 Dec 2025 23:17:25 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[Feature]=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 --- .../java/kr/spot/code/status/ErrorStatus.java | 1 + .../event/RevokeTokenOnMemberWithdrawn.java | 20 +++++++++++ .../command/MemberWithdrawService.java | 33 +++++++++++++++++++ .../command/MemberCommandController.java | 20 +++++++++++ .../CleanupNotificationOnMemberWithdrawn.java | 20 +++++++++++ .../jpa/NotificationRepository.java | 1 + .../event/CleanupPostOnMemberWithdrawn.java | 27 +++++++++++++++ .../infrastructure/jpa/CommentRepository.java | 2 ++ .../jpa/PostLikeRepository.java | 4 +++ .../infrastructure/jpa/PostRepository.java | 2 ++ .../domain/events/MemberWithdrawnEvent.java | 5 +++ .../ports/HasActiveStudyAsLeaderPort.java | 6 ++++ .../CleanupStudyPostOnMemberWithdrawn.java | 29 ++++++++++++++++ .../infrastructure/jpa/CommentRepository.java | 1 + .../jpa/PostLikeRepository.java | 4 +++ .../infrastructure/jpa/PostRepository.java | 2 ++ .../event/CleanupReviewOnMemberWithdrawn.java | 26 +++++++++++++++ .../jpa/ReviewReactionRepository.java | 4 +++ .../infrastructure/jpa/ReviewRepository.java | 2 ++ .../CleanupAttendanceOnMemberWithdrawn.java | 22 +++++++++++++ .../jpa/AttendanceRepository.java | 2 ++ ...eanupStudyMembershipOnMemberWithdrawn.java | 26 +++++++++++++++ .../ports/HasActiveStudyAsLeaderService.java | 20 +++++++++++ .../infrastructure/jpa/StudyRepository.java | 2 ++ .../jpa/associations/StudyLikeRepository.java | 4 +++ .../associations/StudyMemberRepository.java | 1 + .../event/CleanupTodoOnMemberWithdrawn.java | 22 +++++++++++++ .../infrastructure/jpa/TodoRepository.java | 2 ++ 28 files changed, 310 insertions(+) create mode 100644 modules/auth/src/main/java/kr/spot/application/event/RevokeTokenOnMemberWithdrawn.java create mode 100644 modules/member/src/main/java/kr/spot/application/command/MemberWithdrawService.java create mode 100644 modules/notification/src/main/java/kr/spot/application/event/CleanupNotificationOnMemberWithdrawn.java create mode 100644 modules/post/src/main/java/kr/spot/application/event/CleanupPostOnMemberWithdrawn.java create mode 100644 modules/shared/src/main/java/kr/spot/domain/events/MemberWithdrawnEvent.java create mode 100644 modules/study-api/src/main/java/kr/spot/ports/HasActiveStudyAsLeaderPort.java create mode 100644 modules/study/src/main/java/kr/spot/post/application/event/CleanupStudyPostOnMemberWithdrawn.java create mode 100644 modules/study/src/main/java/kr/spot/review/application/event/CleanupReviewOnMemberWithdrawn.java create mode 100644 modules/study/src/main/java/kr/spot/schedule/application/event/CleanupAttendanceOnMemberWithdrawn.java create mode 100644 modules/study/src/main/java/kr/spot/study/application/event/CleanupStudyMembershipOnMemberWithdrawn.java create mode 100644 modules/study/src/main/java/kr/spot/study/application/ports/HasActiveStudyAsLeaderService.java create mode 100644 modules/study/src/main/java/kr/spot/todo/application/event/CleanupTodoOnMemberWithdrawn.java diff --git a/common/api/src/main/java/kr/spot/code/status/ErrorStatus.java b/common/api/src/main/java/kr/spot/code/status/ErrorStatus.java index 058033b..668c7ef 100644 --- a/common/api/src/main/java/kr/spot/code/status/ErrorStatus.java +++ b/common/api/src/main/java/kr/spot/code/status/ErrorStatus.java @@ -32,6 +32,7 @@ public enum ErrorStatus implements BaseErrorCode { _MEMBER_UNSUPPORTED_LOGIN_TYPE(400, "MEMBER4003", "지원하지 않는 로그인 타입입니다."), _MEMBER_EMAIL_EXIST(400, "MEMBER4004", "이미 해당 방식으로 가입 내역이 존재하는 이메일입니다."), _FAIL_TO_UPDATE_NAME(400, "MEMBER4005", "이름 변경에 실패했습니다."), + _CANNOT_WITHDRAW_WITH_ACTIVE_STUDY(400, "MEMBER4006", "운영 중인 스터디가 있어 탈퇴할 수 없습니다."), // 스터디 관련 _MAX_MEMBERS_MUST_BE_POSITIVE(400, "STUDY4000", "최대 인원 수는 양수여야 합니다."), diff --git a/modules/auth/src/main/java/kr/spot/application/event/RevokeTokenOnMemberWithdrawn.java b/modules/auth/src/main/java/kr/spot/application/event/RevokeTokenOnMemberWithdrawn.java new file mode 100644 index 0000000..75ecf4a --- /dev/null +++ b/modules/auth/src/main/java/kr/spot/application/event/RevokeTokenOnMemberWithdrawn.java @@ -0,0 +1,20 @@ +package kr.spot.application.event; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.infrastructure.jpa.RefreshTokenRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class RevokeTokenOnMemberWithdrawn { + + private final RefreshTokenRepository refreshTokenRepository; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handle(MemberWithdrawnEvent event) { + refreshTokenRepository.deleteByMemberId(event.memberId()); + } +} diff --git a/modules/member/src/main/java/kr/spot/application/command/MemberWithdrawService.java b/modules/member/src/main/java/kr/spot/application/command/MemberWithdrawService.java new file mode 100644 index 0000000..97b1071 --- /dev/null +++ b/modules/member/src/main/java/kr/spot/application/command/MemberWithdrawService.java @@ -0,0 +1,33 @@ +package kr.spot.application.command; + +import kr.spot.code.status.ErrorStatus; +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.exception.GeneralException; +import kr.spot.infrastructure.jpa.MemberRepository; +import kr.spot.ports.HasActiveStudyAsLeaderPort; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MemberWithdrawService { + + private final MemberRepository memberRepository; + private final ApplicationEventPublisher eventPublisher; + private final HasActiveStudyAsLeaderPort hasActiveStudyAsLeaderPort; + + @Transactional + public void withdraw(long memberId) { + validateCanWithdraw(memberId); + memberRepository.deleteById(memberId); + eventPublisher.publishEvent(new MemberWithdrawnEvent(memberId)); + } + + private void validateCanWithdraw(long memberId) { + if (hasActiveStudyAsLeaderPort.hasActiveStudyAsLeader(memberId)) { + throw new GeneralException(ErrorStatus._CANNOT_WITHDRAW_WITH_ACTIVE_STUDY); + } + } +} diff --git a/modules/member/src/main/java/kr/spot/presentation/command/MemberCommandController.java b/modules/member/src/main/java/kr/spot/presentation/command/MemberCommandController.java index 1b2d155..4ffa7e8 100644 --- a/modules/member/src/main/java/kr/spot/presentation/command/MemberCommandController.java +++ b/modules/member/src/main/java/kr/spot/presentation/command/MemberCommandController.java @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import kr.spot.ApiResponse; import kr.spot.annotations.CurrentMember; +import kr.spot.application.command.MemberWithdrawService; import kr.spot.application.command.RegisterMemberInfoService; import kr.spot.application.command.RegisterPreferredCategoryService; import kr.spot.application.command.RegisterPreferredRegionService; @@ -17,6 +18,7 @@ import kr.spot.presentation.command.dto.request.UpdateMemberNameRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -31,6 +33,7 @@ public class MemberCommandController { private final RegisterPreferredCategoryService registerPreferredCategoryService; private final RegisterPreferredRegionService registerPreferredRegionService; private final RegisterMemberInfoService registerMemberInfoService; + private final MemberWithdrawService memberWithdrawService; @Operation(summary = "선호 카테고리 등록", description = "회원의 선호 카테고리를 등록합니다. 한 번 저장 후 다시 호출하면 덮어씁니다. (이전 데이터 삭제)") @ApiResponses({ @@ -83,4 +86,21 @@ public ResponseEntity> updateMemberName( registerMemberInfoService.updateMemberName(memberId, request); return ResponseEntity.ok(ApiResponse.onSuccess(SuccessStatus._NO_CONTENT)); } + + @Operation(summary = "회원 탈퇴", description = """ + ## 회원 탈퇴를 수행합니다. + - 회원 정보 및 관련 데이터가 모두 삭제됩니다. + - 클라이언트에서도 저장된 토큰을 삭제해주세요. + """) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "탈퇴 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증되지 않은 사용자입니다.", content = @Content(schema = @Schema(implementation = kr.spot.ApiResponse.class))) + }) + @DeleteMapping("/me") + public ResponseEntity> withdraw( + @CurrentMember @Parameter(hidden = true) long memberId + ) { + memberWithdrawService.withdraw(memberId); + return ResponseEntity.ok(ApiResponse.onSuccess(SuccessStatus._OK, null)); + } } diff --git a/modules/notification/src/main/java/kr/spot/application/event/CleanupNotificationOnMemberWithdrawn.java b/modules/notification/src/main/java/kr/spot/application/event/CleanupNotificationOnMemberWithdrawn.java new file mode 100644 index 0000000..de746d6 --- /dev/null +++ b/modules/notification/src/main/java/kr/spot/application/event/CleanupNotificationOnMemberWithdrawn.java @@ -0,0 +1,20 @@ +package kr.spot.application.event; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.infrastructure.jpa.NotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class CleanupNotificationOnMemberWithdrawn { + + private final NotificationRepository notificationRepository; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handle(MemberWithdrawnEvent event) { + notificationRepository.deleteByTargetTargetMemberId(event.memberId()); + } +} diff --git a/modules/notification/src/main/java/kr/spot/infrastructure/jpa/NotificationRepository.java b/modules/notification/src/main/java/kr/spot/infrastructure/jpa/NotificationRepository.java index fd7191e..eadf8a0 100644 --- a/modules/notification/src/main/java/kr/spot/infrastructure/jpa/NotificationRepository.java +++ b/modules/notification/src/main/java/kr/spot/infrastructure/jpa/NotificationRepository.java @@ -5,4 +5,5 @@ public interface NotificationRepository extends JpaRepository { + void deleteByTargetTargetMemberId(long targetMemberId); } diff --git a/modules/post/src/main/java/kr/spot/application/event/CleanupPostOnMemberWithdrawn.java b/modules/post/src/main/java/kr/spot/application/event/CleanupPostOnMemberWithdrawn.java new file mode 100644 index 0000000..3c75196 --- /dev/null +++ b/modules/post/src/main/java/kr/spot/application/event/CleanupPostOnMemberWithdrawn.java @@ -0,0 +1,27 @@ +package kr.spot.application.event; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.infrastructure.jpa.CommentRepository; +import kr.spot.infrastructure.jpa.PostLikeRepository; +import kr.spot.infrastructure.jpa.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class CleanupPostOnMemberWithdrawn { + + private final PostLikeRepository postLikeRepository; + private final CommentRepository commentRepository; + private final PostRepository postRepository; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handle(MemberWithdrawnEvent event) { + long memberId = event.memberId(); + postLikeRepository.deleteAllByMemberId(memberId); + commentRepository.deleteByWriterInfoWriterId(memberId); + postRepository.deleteByWriterInfoWriterId(memberId); + } +} diff --git a/modules/post/src/main/java/kr/spot/infrastructure/jpa/CommentRepository.java b/modules/post/src/main/java/kr/spot/infrastructure/jpa/CommentRepository.java index 3913596..0b832fd 100644 --- a/modules/post/src/main/java/kr/spot/infrastructure/jpa/CommentRepository.java +++ b/modules/post/src/main/java/kr/spot/infrastructure/jpa/CommentRepository.java @@ -17,4 +17,6 @@ default Comment getById(Long id) { return findById(id) .orElseThrow(() -> new GeneralException(ErrorStatus._COMMENT_NOT_FOUND)); } + + void deleteByWriterInfoWriterId(long writerId); } diff --git a/modules/post/src/main/java/kr/spot/infrastructure/jpa/PostLikeRepository.java b/modules/post/src/main/java/kr/spot/infrastructure/jpa/PostLikeRepository.java index 94236e0..c53364f 100644 --- a/modules/post/src/main/java/kr/spot/infrastructure/jpa/PostLikeRepository.java +++ b/modules/post/src/main/java/kr/spot/infrastructure/jpa/PostLikeRepository.java @@ -26,4 +26,8 @@ int savePostLike(@Param("id") long id, """, nativeQuery = true) int hardDelete(@Param("postId") Long postId, @Param("memberId") Long memberId); + + @Modifying + @Query(value = "DELETE FROM post_like WHERE member_id = :memberId", nativeQuery = true) + void deleteAllByMemberId(@Param("memberId") long memberId); } diff --git a/modules/post/src/main/java/kr/spot/infrastructure/jpa/PostRepository.java b/modules/post/src/main/java/kr/spot/infrastructure/jpa/PostRepository.java index 050a127..a29917a 100644 --- a/modules/post/src/main/java/kr/spot/infrastructure/jpa/PostRepository.java +++ b/modules/post/src/main/java/kr/spot/infrastructure/jpa/PostRepository.java @@ -65,4 +65,6 @@ default void validateExists(long postId) { throw new GeneralException(ErrorStatus._POST_NOT_FOUND); } } + + void deleteByWriterInfoWriterId(long writerId); } diff --git a/modules/shared/src/main/java/kr/spot/domain/events/MemberWithdrawnEvent.java b/modules/shared/src/main/java/kr/spot/domain/events/MemberWithdrawnEvent.java new file mode 100644 index 0000000..704a866 --- /dev/null +++ b/modules/shared/src/main/java/kr/spot/domain/events/MemberWithdrawnEvent.java @@ -0,0 +1,5 @@ +package kr.spot.domain.events; + +public record MemberWithdrawnEvent(long memberId) { + +} diff --git a/modules/study-api/src/main/java/kr/spot/ports/HasActiveStudyAsLeaderPort.java b/modules/study-api/src/main/java/kr/spot/ports/HasActiveStudyAsLeaderPort.java new file mode 100644 index 0000000..c21e050 --- /dev/null +++ b/modules/study-api/src/main/java/kr/spot/ports/HasActiveStudyAsLeaderPort.java @@ -0,0 +1,6 @@ +package kr.spot.ports; + +public interface HasActiveStudyAsLeaderPort { + + boolean hasActiveStudyAsLeader(long memberId); +} diff --git a/modules/study/src/main/java/kr/spot/post/application/event/CleanupStudyPostOnMemberWithdrawn.java b/modules/study/src/main/java/kr/spot/post/application/event/CleanupStudyPostOnMemberWithdrawn.java new file mode 100644 index 0000000..c99bfe6 --- /dev/null +++ b/modules/study/src/main/java/kr/spot/post/application/event/CleanupStudyPostOnMemberWithdrawn.java @@ -0,0 +1,29 @@ +package kr.spot.post.application.event; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.post.infrastructure.jpa.CommentRepository; +import kr.spot.post.infrastructure.jpa.PostLikeRepository; +import kr.spot.post.infrastructure.jpa.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Order(5) +@Component +@RequiredArgsConstructor +public class CleanupStudyPostOnMemberWithdrawn { + + private final PostLikeRepository postLikeRepository; + private final CommentRepository commentRepository; + private final PostRepository postRepository; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handle(MemberWithdrawnEvent event) { + long memberId = event.memberId(); + postLikeRepository.deleteAllByMemberId(memberId); + commentRepository.deleteByWriterInfoWriterId(memberId); + postRepository.deleteByWriterInfoWriterId(memberId); + } +} diff --git a/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/CommentRepository.java b/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/CommentRepository.java index d0e9a0a..0221f94 100644 --- a/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/CommentRepository.java +++ b/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/CommentRepository.java @@ -14,4 +14,5 @@ default Comment getById(long id) { .orElseThrow(() -> new GeneralException(ErrorStatus._COMMENT_NOT_FOUND)); } + void deleteByWriterInfoWriterId(long writerId); } diff --git a/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/PostLikeRepository.java b/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/PostLikeRepository.java index 4bb4ee2..36e6609 100644 --- a/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/PostLikeRepository.java +++ b/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/PostLikeRepository.java @@ -28,4 +28,8 @@ int savePostLike(@Param("id") long id, """, nativeQuery = true) int hardDelete(@Param("postId") Long postId, @Param("memberId") Long memberId); + + @Modifying + @Query(value = "DELETE FROM study_post_like WHERE member_id = :memberId", nativeQuery = true) + void deleteAllByMemberId(@Param("memberId") long memberId); } diff --git a/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/PostRepository.java b/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/PostRepository.java index 61fa9cf..5ecea14 100644 --- a/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/PostRepository.java +++ b/modules/study/src/main/java/kr/spot/post/infrastructure/jpa/PostRepository.java @@ -13,4 +13,6 @@ default Post getById(long postId) { return findById(postId).orElseThrow( () -> new GeneralException(ErrorStatus._POST_NOT_FOUND)); } + + void deleteByWriterInfoWriterId(long writerId); } diff --git a/modules/study/src/main/java/kr/spot/review/application/event/CleanupReviewOnMemberWithdrawn.java b/modules/study/src/main/java/kr/spot/review/application/event/CleanupReviewOnMemberWithdrawn.java new file mode 100644 index 0000000..62914fe --- /dev/null +++ b/modules/study/src/main/java/kr/spot/review/application/event/CleanupReviewOnMemberWithdrawn.java @@ -0,0 +1,26 @@ +package kr.spot.review.application.event; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.review.infrastructure.jpa.ReviewReactionRepository; +import kr.spot.review.infrastructure.jpa.ReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Order(4) +@Component +@RequiredArgsConstructor +public class CleanupReviewOnMemberWithdrawn { + + private final ReviewReactionRepository reviewReactionRepository; + private final ReviewRepository reviewRepository; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handle(MemberWithdrawnEvent event) { + long memberId = event.memberId(); + reviewReactionRepository.deleteAllByMemberId(memberId); + reviewRepository.deleteByWriterInfoWriterId(memberId); + } +} diff --git a/modules/study/src/main/java/kr/spot/review/infrastructure/jpa/ReviewReactionRepository.java b/modules/study/src/main/java/kr/spot/review/infrastructure/jpa/ReviewReactionRepository.java index 53d8139..9d53b42 100644 --- a/modules/study/src/main/java/kr/spot/review/infrastructure/jpa/ReviewReactionRepository.java +++ b/modules/study/src/main/java/kr/spot/review/infrastructure/jpa/ReviewReactionRepository.java @@ -22,4 +22,8 @@ boolean existsByReviewIdAndMemberIdAndReaction( int hardDelete(@Param("reviewId") Long reviewId, @Param("memberId") Long memberId, @Param("reaction") String reaction); + + @Modifying + @Query(value = "DELETE FROM review_reaction WHERE member_id = :memberId", nativeQuery = true) + void deleteAllByMemberId(@Param("memberId") long memberId); } diff --git a/modules/study/src/main/java/kr/spot/review/infrastructure/jpa/ReviewRepository.java b/modules/study/src/main/java/kr/spot/review/infrastructure/jpa/ReviewRepository.java index 866fdcf..d6daf77 100644 --- a/modules/study/src/main/java/kr/spot/review/infrastructure/jpa/ReviewRepository.java +++ b/modules/study/src/main/java/kr/spot/review/infrastructure/jpa/ReviewRepository.java @@ -17,4 +17,6 @@ default void validateExists(long reviewId) { throw new GeneralException(ErrorStatus._REVIEW_NOT_FOUND); } } + + void deleteByWriterInfoWriterId(long writerId); } diff --git a/modules/study/src/main/java/kr/spot/schedule/application/event/CleanupAttendanceOnMemberWithdrawn.java b/modules/study/src/main/java/kr/spot/schedule/application/event/CleanupAttendanceOnMemberWithdrawn.java new file mode 100644 index 0000000..9c11f2e --- /dev/null +++ b/modules/study/src/main/java/kr/spot/schedule/application/event/CleanupAttendanceOnMemberWithdrawn.java @@ -0,0 +1,22 @@ +package kr.spot.schedule.application.event; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.schedule.infrastructure.jpa.AttendanceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Order(2) +@Component +@RequiredArgsConstructor +public class CleanupAttendanceOnMemberWithdrawn { + + private final AttendanceRepository attendanceRepository; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handle(MemberWithdrawnEvent event) { + attendanceRepository.deleteByMemberInfoMemberId(event.memberId()); + } +} diff --git a/modules/study/src/main/java/kr/spot/schedule/infrastructure/jpa/AttendanceRepository.java b/modules/study/src/main/java/kr/spot/schedule/infrastructure/jpa/AttendanceRepository.java index 188b44e..20fd815 100644 --- a/modules/study/src/main/java/kr/spot/schedule/infrastructure/jpa/AttendanceRepository.java +++ b/modules/study/src/main/java/kr/spot/schedule/infrastructure/jpa/AttendanceRepository.java @@ -12,4 +12,6 @@ public interface AttendanceRepository extends JpaRepository { boolean existsByScheduleIdAndMemberInfoMemberId(Long scheduleId, Long memberId); List findAllByScheduleId(Long scheduleId); + + void deleteByMemberInfoMemberId(long memberId); } diff --git a/modules/study/src/main/java/kr/spot/study/application/event/CleanupStudyMembershipOnMemberWithdrawn.java b/modules/study/src/main/java/kr/spot/study/application/event/CleanupStudyMembershipOnMemberWithdrawn.java new file mode 100644 index 0000000..9fb887a --- /dev/null +++ b/modules/study/src/main/java/kr/spot/study/application/event/CleanupStudyMembershipOnMemberWithdrawn.java @@ -0,0 +1,26 @@ +package kr.spot.study.application.event; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.study.infrastructure.jpa.associations.StudyLikeRepository; +import kr.spot.study.infrastructure.jpa.associations.StudyMemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Order(1) +@Component +@RequiredArgsConstructor +public class CleanupStudyMembershipOnMemberWithdrawn { + + private final StudyMemberRepository studyMemberRepository; + private final StudyLikeRepository studyLikeRepository; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handle(MemberWithdrawnEvent event) { + long memberId = event.memberId(); + studyMemberRepository.deleteByMemberId(memberId); + studyLikeRepository.deleteAllByMemberId(memberId); + } +} diff --git a/modules/study/src/main/java/kr/spot/study/application/ports/HasActiveStudyAsLeaderService.java b/modules/study/src/main/java/kr/spot/study/application/ports/HasActiveStudyAsLeaderService.java new file mode 100644 index 0000000..132929f --- /dev/null +++ b/modules/study/src/main/java/kr/spot/study/application/ports/HasActiveStudyAsLeaderService.java @@ -0,0 +1,20 @@ +package kr.spot.study.application.ports; + +import kr.spot.ports.HasActiveStudyAsLeaderPort; +import kr.spot.study.infrastructure.jpa.StudyRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class HasActiveStudyAsLeaderService implements HasActiveStudyAsLeaderPort { + + private final StudyRepository studyRepository; + + @Override + public boolean hasActiveStudyAsLeader(long memberId) { + return studyRepository.existsByLeaderId(memberId); + } +} diff --git a/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/StudyRepository.java b/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/StudyRepository.java index 8e449bf..71a499e 100644 --- a/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/StudyRepository.java +++ b/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/StudyRepository.java @@ -14,4 +14,6 @@ default Study getStudyById(Long studyId) { } long countByLeaderIdAndRecruitingStatus(long leaderId, RecruitingStatus recruitingStatus); + + boolean existsByLeaderId(long leaderId); } diff --git a/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/associations/StudyLikeRepository.java b/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/associations/StudyLikeRepository.java index bf097e2..cbb7bab 100644 --- a/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/associations/StudyLikeRepository.java +++ b/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/associations/StudyLikeRepository.java @@ -26,4 +26,8 @@ int saveStudyLike(@Param("id") long id, """, nativeQuery = true) int hardDelete(@Param("studyId") long studyId, @Param("memberId") long memberId); + + @Modifying + @Query(value = "DELETE FROM study_like WHERE member_id = :memberId", nativeQuery = true) + void deleteAllByMemberId(@Param("memberId") long memberId); } diff --git a/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/associations/StudyMemberRepository.java b/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/associations/StudyMemberRepository.java index df1985f..e33c985 100644 --- a/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/associations/StudyMemberRepository.java +++ b/modules/study/src/main/java/kr/spot/study/infrastructure/jpa/associations/StudyMemberRepository.java @@ -25,4 +25,5 @@ long countByMemberIdAndStudyMemberStatusIn(long memberId, long countByMemberIdAndStudyMemberStatus(long memberId, StudyMemberStatus studyMemberStatus); + void deleteByMemberId(long memberId); } diff --git a/modules/study/src/main/java/kr/spot/todo/application/event/CleanupTodoOnMemberWithdrawn.java b/modules/study/src/main/java/kr/spot/todo/application/event/CleanupTodoOnMemberWithdrawn.java new file mode 100644 index 0000000..36c1336 --- /dev/null +++ b/modules/study/src/main/java/kr/spot/todo/application/event/CleanupTodoOnMemberWithdrawn.java @@ -0,0 +1,22 @@ +package kr.spot.todo.application.event; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.todo.infrastructure.jpa.TodoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Order(3) +@Component +@RequiredArgsConstructor +public class CleanupTodoOnMemberWithdrawn { + + private final TodoRepository todoRepository; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void handle(MemberWithdrawnEvent event) { + todoRepository.deleteByMemberId(event.memberId()); + } +} diff --git a/modules/study/src/main/java/kr/spot/todo/infrastructure/jpa/TodoRepository.java b/modules/study/src/main/java/kr/spot/todo/infrastructure/jpa/TodoRepository.java index 623fe3c..49dbab6 100644 --- a/modules/study/src/main/java/kr/spot/todo/infrastructure/jpa/TodoRepository.java +++ b/modules/study/src/main/java/kr/spot/todo/infrastructure/jpa/TodoRepository.java @@ -11,4 +11,6 @@ default Todo getById(long todoId) { return findById(todoId).orElseThrow( () -> new GeneralException(ErrorStatus._TODO_NOT_FOUND)); } + + void deleteByMemberId(long memberId); } From 50c0acce77d8080c6d0c6c456d5c0837b5c88135 Mon Sep 17 00:00:00 2001 From: msk226 Date: Wed, 31 Dec 2025 23:20:29 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[Feature]=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=EB=84=88=20=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RevokeTokenOnMemberWithdrawnTest.java | 40 ++++++++++++++ ...anupNotificationOnMemberWithdrawnTest.java | 41 ++++++++++++++ .../CleanupPostOnMemberWithdrawnTest.java | 53 +++++++++++++++++++ ...CleanupStudyPostOnMemberWithdrawnTest.java | 53 +++++++++++++++++++ .../CleanupReviewOnMemberWithdrawnTest.java | 48 +++++++++++++++++ ...leanupAttendanceOnMemberWithdrawnTest.java | 41 ++++++++++++++ ...pStudyMembershipOnMemberWithdrawnTest.java | 46 ++++++++++++++++ .../CleanupTodoOnMemberWithdrawnTest.java | 41 ++++++++++++++ 8 files changed, 363 insertions(+) create mode 100644 modules/auth/src/test/java/kr/spot/application/event/RevokeTokenOnMemberWithdrawnTest.java create mode 100644 modules/notification/src/test/java/kr/spot/application/event/CleanupNotificationOnMemberWithdrawnTest.java create mode 100644 modules/post/src/test/java/kr/spot/application/event/CleanupPostOnMemberWithdrawnTest.java create mode 100644 modules/study/src/test/java/kr/spot/post/application/event/CleanupStudyPostOnMemberWithdrawnTest.java create mode 100644 modules/study/src/test/java/kr/spot/review/application/event/CleanupReviewOnMemberWithdrawnTest.java create mode 100644 modules/study/src/test/java/kr/spot/schedule/application/event/CleanupAttendanceOnMemberWithdrawnTest.java create mode 100644 modules/study/src/test/java/kr/spot/study/application/event/CleanupStudyMembershipOnMemberWithdrawnTest.java create mode 100644 modules/study/src/test/java/kr/spot/todo/application/event/CleanupTodoOnMemberWithdrawnTest.java diff --git a/modules/auth/src/test/java/kr/spot/application/event/RevokeTokenOnMemberWithdrawnTest.java b/modules/auth/src/test/java/kr/spot/application/event/RevokeTokenOnMemberWithdrawnTest.java new file mode 100644 index 0000000..672ef9e --- /dev/null +++ b/modules/auth/src/test/java/kr/spot/application/event/RevokeTokenOnMemberWithdrawnTest.java @@ -0,0 +1,40 @@ +package kr.spot.application.event; + +import static kr.spot.common.AuthFixture.MEMBER_ID; +import static org.mockito.Mockito.verify; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.infrastructure.jpa.RefreshTokenRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class RevokeTokenOnMemberWithdrawnTest { + + @Mock + RefreshTokenRepository refreshTokenRepository; + + RevokeTokenOnMemberWithdrawn handler; + + @BeforeEach + void setUp() { + handler = new RevokeTokenOnMemberWithdrawn(refreshTokenRepository); + } + + @Test + @DisplayName("회원 탈퇴 시 해당 회원의 리프레시 토큰을 삭제한다") + void should_deleteRefreshToken_when_memberWithdrawn() { + // given + MemberWithdrawnEvent event = new MemberWithdrawnEvent(MEMBER_ID); + + // when + handler.handle(event); + + // then + verify(refreshTokenRepository).deleteByMemberId(MEMBER_ID); + } +} diff --git a/modules/notification/src/test/java/kr/spot/application/event/CleanupNotificationOnMemberWithdrawnTest.java b/modules/notification/src/test/java/kr/spot/application/event/CleanupNotificationOnMemberWithdrawnTest.java new file mode 100644 index 0000000..5a9b7bc --- /dev/null +++ b/modules/notification/src/test/java/kr/spot/application/event/CleanupNotificationOnMemberWithdrawnTest.java @@ -0,0 +1,41 @@ +package kr.spot.application.event; + +import static org.mockito.Mockito.verify; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.infrastructure.jpa.NotificationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CleanupNotificationOnMemberWithdrawnTest { + + private static final long MEMBER_ID = 1L; + + @Mock + NotificationRepository notificationRepository; + + CleanupNotificationOnMemberWithdrawn handler; + + @BeforeEach + void setUp() { + handler = new CleanupNotificationOnMemberWithdrawn(notificationRepository); + } + + @Test + @DisplayName("회원 탈퇴 시 알림을 삭제한다") + void should_deleteNotifications_when_memberWithdrawn() { + // given + MemberWithdrawnEvent event = new MemberWithdrawnEvent(MEMBER_ID); + + // when + handler.handle(event); + + // then + verify(notificationRepository).deleteByTargetTargetMemberId(MEMBER_ID); + } +} diff --git a/modules/post/src/test/java/kr/spot/application/event/CleanupPostOnMemberWithdrawnTest.java b/modules/post/src/test/java/kr/spot/application/event/CleanupPostOnMemberWithdrawnTest.java new file mode 100644 index 0000000..650bf0d --- /dev/null +++ b/modules/post/src/test/java/kr/spot/application/event/CleanupPostOnMemberWithdrawnTest.java @@ -0,0 +1,53 @@ +package kr.spot.application.event; + +import static org.mockito.Mockito.inOrder; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.infrastructure.jpa.CommentRepository; +import kr.spot.infrastructure.jpa.PostLikeRepository; +import kr.spot.infrastructure.jpa.PostRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CleanupPostOnMemberWithdrawnTest { + + private static final long MEMBER_ID = 1L; + + @Mock + PostLikeRepository postLikeRepository; + + @Mock + CommentRepository commentRepository; + + @Mock + PostRepository postRepository; + + CleanupPostOnMemberWithdrawn handler; + + @BeforeEach + void setUp() { + handler = new CleanupPostOnMemberWithdrawn(postLikeRepository, commentRepository, postRepository); + } + + @Test + @DisplayName("회원 탈퇴 시 좋아요, 댓글, 게시글 순서로 삭제한다") + void should_deleteLikesThenCommentsThenPosts_when_memberWithdrawn() { + // given + MemberWithdrawnEvent event = new MemberWithdrawnEvent(MEMBER_ID); + + // when + handler.handle(event); + + // then + InOrder inOrder = inOrder(postLikeRepository, commentRepository, postRepository); + inOrder.verify(postLikeRepository).deleteAllByMemberId(MEMBER_ID); + inOrder.verify(commentRepository).deleteByWriterInfoWriterId(MEMBER_ID); + inOrder.verify(postRepository).deleteByWriterInfoWriterId(MEMBER_ID); + } +} diff --git a/modules/study/src/test/java/kr/spot/post/application/event/CleanupStudyPostOnMemberWithdrawnTest.java b/modules/study/src/test/java/kr/spot/post/application/event/CleanupStudyPostOnMemberWithdrawnTest.java new file mode 100644 index 0000000..c0dcd68 --- /dev/null +++ b/modules/study/src/test/java/kr/spot/post/application/event/CleanupStudyPostOnMemberWithdrawnTest.java @@ -0,0 +1,53 @@ +package kr.spot.post.application.event; + +import static org.mockito.Mockito.inOrder; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.post.infrastructure.jpa.CommentRepository; +import kr.spot.post.infrastructure.jpa.PostLikeRepository; +import kr.spot.post.infrastructure.jpa.PostRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CleanupStudyPostOnMemberWithdrawnTest { + + private static final long MEMBER_ID = 1L; + + @Mock + PostLikeRepository postLikeRepository; + + @Mock + CommentRepository commentRepository; + + @Mock + PostRepository postRepository; + + CleanupStudyPostOnMemberWithdrawn handler; + + @BeforeEach + void setUp() { + handler = new CleanupStudyPostOnMemberWithdrawn(postLikeRepository, commentRepository, postRepository); + } + + @Test + @DisplayName("회원 탈퇴 시 좋아요, 댓글, 게시글 순서로 삭제한다") + void should_deleteLikesThenCommentsThenPosts_when_memberWithdrawn() { + // given + MemberWithdrawnEvent event = new MemberWithdrawnEvent(MEMBER_ID); + + // when + handler.handle(event); + + // then + InOrder inOrder = inOrder(postLikeRepository, commentRepository, postRepository); + inOrder.verify(postLikeRepository).deleteAllByMemberId(MEMBER_ID); + inOrder.verify(commentRepository).deleteByWriterInfoWriterId(MEMBER_ID); + inOrder.verify(postRepository).deleteByWriterInfoWriterId(MEMBER_ID); + } +} diff --git a/modules/study/src/test/java/kr/spot/review/application/event/CleanupReviewOnMemberWithdrawnTest.java b/modules/study/src/test/java/kr/spot/review/application/event/CleanupReviewOnMemberWithdrawnTest.java new file mode 100644 index 0000000..2ba8294 --- /dev/null +++ b/modules/study/src/test/java/kr/spot/review/application/event/CleanupReviewOnMemberWithdrawnTest.java @@ -0,0 +1,48 @@ +package kr.spot.review.application.event; + +import static org.mockito.Mockito.inOrder; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.review.infrastructure.jpa.ReviewReactionRepository; +import kr.spot.review.infrastructure.jpa.ReviewRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CleanupReviewOnMemberWithdrawnTest { + + private static final long MEMBER_ID = 1L; + + @Mock + ReviewReactionRepository reviewReactionRepository; + + @Mock + ReviewRepository reviewRepository; + + CleanupReviewOnMemberWithdrawn handler; + + @BeforeEach + void setUp() { + handler = new CleanupReviewOnMemberWithdrawn(reviewReactionRepository, reviewRepository); + } + + @Test + @DisplayName("회원 탈퇴 시 리뷰 반응을 먼저 삭제하고 리뷰를 삭제한다") + void should_deleteReactionsThenReviews_when_memberWithdrawn() { + // given + MemberWithdrawnEvent event = new MemberWithdrawnEvent(MEMBER_ID); + + // when + handler.handle(event); + + // then + InOrder inOrder = inOrder(reviewReactionRepository, reviewRepository); + inOrder.verify(reviewReactionRepository).deleteAllByMemberId(MEMBER_ID); + inOrder.verify(reviewRepository).deleteByWriterInfoWriterId(MEMBER_ID); + } +} diff --git a/modules/study/src/test/java/kr/spot/schedule/application/event/CleanupAttendanceOnMemberWithdrawnTest.java b/modules/study/src/test/java/kr/spot/schedule/application/event/CleanupAttendanceOnMemberWithdrawnTest.java new file mode 100644 index 0000000..c9fb825 --- /dev/null +++ b/modules/study/src/test/java/kr/spot/schedule/application/event/CleanupAttendanceOnMemberWithdrawnTest.java @@ -0,0 +1,41 @@ +package kr.spot.schedule.application.event; + +import static org.mockito.Mockito.verify; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.schedule.infrastructure.jpa.AttendanceRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CleanupAttendanceOnMemberWithdrawnTest { + + private static final long MEMBER_ID = 1L; + + @Mock + AttendanceRepository attendanceRepository; + + CleanupAttendanceOnMemberWithdrawn handler; + + @BeforeEach + void setUp() { + handler = new CleanupAttendanceOnMemberWithdrawn(attendanceRepository); + } + + @Test + @DisplayName("회원 탈퇴 시 출석 기록을 삭제한다") + void should_deleteAttendance_when_memberWithdrawn() { + // given + MemberWithdrawnEvent event = new MemberWithdrawnEvent(MEMBER_ID); + + // when + handler.handle(event); + + // then + verify(attendanceRepository).deleteByMemberInfoMemberId(MEMBER_ID); + } +} diff --git a/modules/study/src/test/java/kr/spot/study/application/event/CleanupStudyMembershipOnMemberWithdrawnTest.java b/modules/study/src/test/java/kr/spot/study/application/event/CleanupStudyMembershipOnMemberWithdrawnTest.java new file mode 100644 index 0000000..c1dd259 --- /dev/null +++ b/modules/study/src/test/java/kr/spot/study/application/event/CleanupStudyMembershipOnMemberWithdrawnTest.java @@ -0,0 +1,46 @@ +package kr.spot.study.application.event; + +import static org.mockito.Mockito.verify; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.study.infrastructure.jpa.associations.StudyLikeRepository; +import kr.spot.study.infrastructure.jpa.associations.StudyMemberRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CleanupStudyMembershipOnMemberWithdrawnTest { + + private static final long MEMBER_ID = 1L; + + @Mock + StudyMemberRepository studyMemberRepository; + + @Mock + StudyLikeRepository studyLikeRepository; + + CleanupStudyMembershipOnMemberWithdrawn handler; + + @BeforeEach + void setUp() { + handler = new CleanupStudyMembershipOnMemberWithdrawn(studyMemberRepository, studyLikeRepository); + } + + @Test + @DisplayName("회원 탈퇴 시 스터디 멤버십과 좋아요를 삭제한다") + void should_deleteStudyMembershipAndLikes_when_memberWithdrawn() { + // given + MemberWithdrawnEvent event = new MemberWithdrawnEvent(MEMBER_ID); + + // when + handler.handle(event); + + // then + verify(studyMemberRepository).deleteByMemberId(MEMBER_ID); + verify(studyLikeRepository).deleteAllByMemberId(MEMBER_ID); + } +} diff --git a/modules/study/src/test/java/kr/spot/todo/application/event/CleanupTodoOnMemberWithdrawnTest.java b/modules/study/src/test/java/kr/spot/todo/application/event/CleanupTodoOnMemberWithdrawnTest.java new file mode 100644 index 0000000..8216e01 --- /dev/null +++ b/modules/study/src/test/java/kr/spot/todo/application/event/CleanupTodoOnMemberWithdrawnTest.java @@ -0,0 +1,41 @@ +package kr.spot.todo.application.event; + +import static org.mockito.Mockito.verify; + +import kr.spot.domain.events.MemberWithdrawnEvent; +import kr.spot.todo.infrastructure.jpa.TodoRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CleanupTodoOnMemberWithdrawnTest { + + private static final long MEMBER_ID = 1L; + + @Mock + TodoRepository todoRepository; + + CleanupTodoOnMemberWithdrawn handler; + + @BeforeEach + void setUp() { + handler = new CleanupTodoOnMemberWithdrawn(todoRepository); + } + + @Test + @DisplayName("회원 탈퇴 시 투두를 삭제한다") + void should_deleteTodo_when_memberWithdrawn() { + // given + MemberWithdrawnEvent event = new MemberWithdrawnEvent(MEMBER_ID); + + // when + handler.handle(event); + + // then + verify(todoRepository).deleteByMemberId(MEMBER_ID); + } +}