From 4fb44217662235db4b6129730e4876c4c55170c2 Mon Sep 17 00:00:00 2001 From: myqewr Date: Thu, 1 May 2025 15:35:24 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20api=20=EC=B6=94=EA=B0=80(#107)=20-=20scheduler=20:?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EA=B4=80=EB=A0=A8=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20hard=20delete=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/user-api.adoc | 16 ++- .../user/controller/UserExitController.java | 34 +++++++ .../user/UserDomainPersistenceAdapter.java | 99 ++++++++++++++++--- .../out/persistence/model/PostJpaEntity.java | 12 +++ .../repository/BookmarkRepository.java | 10 +- .../GroomingTestResultRepository.java | 10 +- .../repository/PostRepository.java | 6 +- .../repository/UserImageRepository.java | 11 +++ .../repository/UserRepository.java | 18 ++++ .../scheduler/UserHardDeleteScheduler.java | 19 ++++ .../user/DeleteAllUserByIdListCommand.java | 13 +++ .../user/DeleteBookmarkByUserIdCommand.java | 13 +++ ...leteGroomingTestResultByUserIdCommand.java | 13 +++ .../command/user/DeleteUserByIdCommand.java | 12 +++ .../user/DeleteUserImageByUserIdCommand.java | 13 +++ .../port/in/user/UserExitUseCase.java | 9 ++ .../port/in/user/UserHardDeleteUseCase.java | 5 + .../persistence/user/DeleteBookmarkPort.java | 9 ++ .../user/DeleteGroomingTestResultPort.java | 9 ++ .../persistence/user/DeleteUserImagePort.java | 10 ++ .../out/persistence/user/DeleteUserPort.java | 9 ++ .../user/LoadPostUserDomainPort.java | 10 ++ .../out/persistence/user/LoadUserPort.java | 9 ++ .../user/UpdatePostUserDomainPort.java | 10 ++ ...eUserInfoPort.java => UpdateUserPort.java} | 4 +- .../query/FindUserByDeleteOptionQuery.java | 14 +++ .../query/FindUserByRoleQuery.java | 13 +++ .../service/user/UpdateUserInfoService.java | 6 +- .../service/user/UserExitService.java | 53 ++++++++++ .../service/user/UserHardDeleteService.java | 66 +++++++++++++ .../com/ftm/server/domain/entity/Post.java | 4 + .../com/ftm/server/domain/entity/User.java | 8 ++ .../com/ftm/server/domain/enums/UserRole.java | 3 +- .../com/ftm/server/user/UserExitTest.java | 70 +++++++++++++ .../ftm/server/user/UserHardDeleteTest.java | 53 ++++++++++ 35 files changed, 647 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/ftm/server/adapter/in/web/user/controller/UserExitController.java create mode 100644 src/main/java/com/ftm/server/adapter/out/scheduler/UserHardDeleteScheduler.java create mode 100644 src/main/java/com/ftm/server/application/command/user/DeleteAllUserByIdListCommand.java create mode 100644 src/main/java/com/ftm/server/application/command/user/DeleteBookmarkByUserIdCommand.java create mode 100644 src/main/java/com/ftm/server/application/command/user/DeleteGroomingTestResultByUserIdCommand.java create mode 100644 src/main/java/com/ftm/server/application/command/user/DeleteUserByIdCommand.java create mode 100644 src/main/java/com/ftm/server/application/command/user/DeleteUserImageByUserIdCommand.java create mode 100644 src/main/java/com/ftm/server/application/port/in/user/UserExitUseCase.java create mode 100644 src/main/java/com/ftm/server/application/port/in/user/UserHardDeleteUseCase.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteBookmarkPort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteGroomingTestResultPort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteUserImagePort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteUserPort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/user/LoadPostUserDomainPort.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/user/UpdatePostUserDomainPort.java rename src/main/java/com/ftm/server/application/port/out/persistence/user/{UpdateUserInfoPort.java => UpdateUserPort.java} (68%) create mode 100644 src/main/java/com/ftm/server/application/query/FindUserByDeleteOptionQuery.java create mode 100644 src/main/java/com/ftm/server/application/query/FindUserByRoleQuery.java create mode 100644 src/main/java/com/ftm/server/application/service/user/UserExitService.java create mode 100644 src/main/java/com/ftm/server/application/service/user/UserHardDeleteService.java create mode 100644 src/test/java/com/ftm/server/user/UserExitTest.java create mode 100644 src/test/java/com/ftm/server/user/UserHardDeleteTest.java diff --git a/src/docs/asciidoc/user-api.adoc b/src/docs/asciidoc/user-api.adoc index 14eca72..97283ed 100644 --- a/src/docs/asciidoc/user-api.adoc +++ b/src/docs/asciidoc/user-api.adoc @@ -161,4 +161,18 @@ include::{snippetsDir}/userInfoUpdate/1/response-fields.adoc[] 실패1. include::{snippetsDir}/userInfoUpdate/2/http-response.adoc[] 실패 2 -include::{snippetsDir}/userInfoUpdate/3/http-response.adoc[] \ No newline at end of file +include::{snippetsDir}/userInfoUpdate/3/http-response.adoc[] + + +=== **9. 회원 탈퇴 api** + +회원 탈퇴를 진행 + +==== Request +include::{snippetsDir}/userExit/1/http-request.adoc[] + +==== 성공 Response +include::{snippetsDir}/userExit/1/http-response.adoc[] + +==== Response Body Fields +include::{snippetsDir}/userExit/1/response-fields.adoc[] diff --git a/src/main/java/com/ftm/server/adapter/in/web/user/controller/UserExitController.java b/src/main/java/com/ftm/server/adapter/in/web/user/controller/UserExitController.java new file mode 100644 index 0000000..a7843b6 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/in/web/user/controller/UserExitController.java @@ -0,0 +1,34 @@ +package com.ftm.server.adapter.in.web.user.controller; + +import com.ftm.server.application.command.user.DeleteUserByIdCommand; +import com.ftm.server.application.port.in.user.UserExitUseCase; +import com.ftm.server.common.response.ApiResponse; +import com.ftm.server.common.response.enums.SuccessResponseCode; +import com.ftm.server.infrastructure.security.UserPrincipal; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class UserExitController { + + private final UserExitUseCase userExitUseCase; + + @DeleteMapping("api/users") + public ResponseEntity userExit( + @AuthenticationPrincipal UserPrincipal user, HttpServletRequest request) { + // 회원 탈퇴 + userExitUseCase.execute(DeleteUserByIdCommand.of(user.getId())); + // 로그아웃 처리 + request.getSession().invalidate(); + SecurityContextHolder.clearContext(); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(SuccessResponseCode.OK)); + } +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java b/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java index 2515435..b591311 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java @@ -1,24 +1,20 @@ package com.ftm.server.adapter.out.persistence.adapter.user; import com.ftm.server.adapter.out.persistence.mapper.EmailVerificationLogsMapper; +import com.ftm.server.adapter.out.persistence.mapper.PostMapper; import com.ftm.server.adapter.out.persistence.mapper.UserImageMapper; import com.ftm.server.adapter.out.persistence.mapper.UserMapper; -import com.ftm.server.adapter.out.persistence.model.EmailVerificationLogsJpaEntity; -import com.ftm.server.adapter.out.persistence.model.GroomingLevelJpaEntity; -import com.ftm.server.adapter.out.persistence.model.UserImageJpaEntity; -import com.ftm.server.adapter.out.persistence.model.UserJpaEntity; -import com.ftm.server.adapter.out.persistence.repository.EmailVerificationLogsRepository; -import com.ftm.server.adapter.out.persistence.repository.GroomingLevelRepository; -import com.ftm.server.adapter.out.persistence.repository.UserImageRepository; -import com.ftm.server.adapter.out.persistence.repository.UserRepository; -import com.ftm.server.application.command.user.EmailVerificationLogCreationCommand; +import com.ftm.server.adapter.out.persistence.model.*; +import com.ftm.server.adapter.out.persistence.repository.*; +import com.ftm.server.application.command.user.*; import com.ftm.server.application.port.out.persistence.user.*; import com.ftm.server.application.query.*; import com.ftm.server.common.annotation.Adapter; import com.ftm.server.common.exception.CustomException; -import com.ftm.server.domain.entity.EmailVerificationLogs; -import com.ftm.server.domain.entity.User; -import com.ftm.server.domain.entity.UserImage; +import com.ftm.server.domain.entity.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -35,19 +31,29 @@ public class UserDomainPersistenceAdapter SaveUserImagePort, LoadUserPort, LoadUserImagePort, - UpdateUserInfoPort, - UpdateUserImagePort { + UpdateUserPort, + UpdateUserImagePort, + LoadPostUserDomainPort, + UpdatePostUserDomainPort, + DeleteUserImagePort, + DeleteGroomingTestResultPort, + DeleteUserPort, + DeleteBookmarkPort { // repository private final EmailVerificationLogsRepository emailVerificationLogsRepository; private final UserRepository userRepository; private final GroomingLevelRepository groomingLevelRepository; private final UserImageRepository userImageRepository; + private final PostRepository postRepository; + private final BookmarkRepository bookmarkRepository; + private final GroomingTestResultRepository groomingTestResultRepository; // mapper private final EmailVerificationLogsMapper emailVerificationLogsMapper; private final UserMapper userMapper; private final UserImageMapper userImageMapper; + private final PostMapper postMapper; @Override public Optional loadEmailVerificationLogByEmail(FindByEmailQuery query) { @@ -115,11 +121,27 @@ public User saveSocialUser(User user) { public User loadUserById(FindByUserIdQuery query) { UserJpaEntity userJpaEntity = userRepository - .findById(query.getUserId()) + .findByIdAndIsDeleted(query.getUserId(), false) .orElseThrow(() -> CustomException.USER_NOT_FOUND); return userMapper.toDomainEntity(userJpaEntity); } + @Override + public User loadUserByRole(FindUserByRoleQuery query) { + UserJpaEntity userJpaEntity = userRepository.findByRole(query.getUserRole()).get(); + return userMapper.toDomainEntity(userJpaEntity); + } + + @Override + public List loadUserByDeleteOption(FindUserByDeleteOptionQuery query) { + return userRepository + .findAllByDeletedBefore( + query.getIsDeleted(), query.getDeletedAt().atTime(23, 59, 59)) + .stream() + .map(userMapper::toDomainEntity) + .toList(); + } + @Override public UserImage loadUserImageByUserId(FindByUserIdQuery query) { UserImageJpaEntity userImageJpaEntity = @@ -131,7 +153,7 @@ public UserImage loadUserImageByUserId(FindByUserIdQuery query) { } @Override - public void updateUserInfo(User user) { + public void updateUser(User user) { UserJpaEntity savedUser = userRepository .findById(user.getId()) @@ -156,4 +178,49 @@ public void updateUserImage(UserImage userImage) { userImageJpaEntity.updateFromDomainEntity(userImage); } + + @Override + public List loadPostListByUser(FindByUserIdQuery query) { + return postRepository.findByUserId(query.getUserId()).stream() + .map(postMapper::toDomainEntity) + .toList(); + } + + @Override + public void updatePostListBySystemUser(List postList) { + UserJpaEntity systemUser = userRepository.findById(postList.get(0).getUserId()).get(); + + Map map = new HashMap<>(); + postList.forEach(p -> map.put(p.getId(), p)); + + List postJpaEntityList = + postRepository.findAllById(postList.stream().map(Post::getId).toList()); + + postJpaEntityList.forEach( + pj -> pj.updatePostForDomainEntity(map.get(pj.getId()), systemUser)); + } + + @Override + public void deleteGroomingTestResultByUserList( + DeleteGroomingTestResultByUserIdCommand command) { + groomingTestResultRepository.deleteAllByUserIdList(command.getUserIdList()); + } + + @Override + public List deleteUserImageByUserList(DeleteUserImageByUserIdCommand command) { + List imageKeyList = + userImageRepository.findAllByUserIdList(command.getUserIdList()); + userImageRepository.deleteAllByUserIdList(command.getUserIdList()); + return imageKeyList; + } + + @Override + public void deleteAllUserByIdList(DeleteAllUserByIdListCommand command) { + userRepository.deleteAllByUserIdList(command.getUserIdList()); + } + + @Override + public void deleteBookmarkByUserList(DeleteBookmarkByUserIdCommand command) { + bookmarkRepository.deleteAllByUserIdList(command.getUserIdList()); + } } diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java b/src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java index 1361380..6b6fdcd 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java @@ -102,4 +102,16 @@ public void updatePostForDomainEntity(Post post) { this.isDeleted = post.getIsDeleted(); this.deletedAt = post.getDeletedAt(); } + + public void updatePostForDomainEntity(Post post, UserJpaEntity user) { + this.title = post.getTitle(); + this.user = user; + this.content = post.getContent(); + this.groomingCategory = post.getGroomingCategory(); + this.hashtags = post.getHashtags(); + this.viewCount = post.getViewCount(); + this.likeCount = post.getLikeCount(); + this.isDeleted = post.getIsDeleted(); + this.deletedAt = post.getDeletedAt(); + } } diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkRepository.java index 3168682..ce651d8 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkRepository.java @@ -1,6 +1,14 @@ package com.ftm.server.adapter.out.persistence.repository; import com.ftm.server.adapter.out.persistence.model.BookmarkJpaEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; -public interface BookmarkRepository extends JpaRepository {} +public interface BookmarkRepository extends JpaRepository { + @Modifying + @Query("DELETE FROM BookmarkJpaEntity b WHERE b.user.id in (:userIds)") + void deleteAllByUserIdList(@Param("userIds") List userIds); +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/GroomingTestResultRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/GroomingTestResultRepository.java index 9ff8fa7..7ae0545 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/GroomingTestResultRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/GroomingTestResultRepository.java @@ -1,8 +1,16 @@ package com.ftm.server.adapter.out.persistence.repository; import com.ftm.server.adapter.out.persistence.model.GroomingTestResultJpaEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface GroomingTestResultRepository extends JpaRepository, - GroomingTestResultCustomRepository {} + GroomingTestResultCustomRepository { + @Modifying + @Query("DELETE FROM GroomingTestResultJpaEntity g WHERE g.user.id in (:userIds)") + void deleteAllByUserIdList(@Param("userIds") List userIds); +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostRepository.java index cd44700..fa48214 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostRepository.java @@ -1,6 +1,10 @@ package com.ftm.server.adapter.out.persistence.repository; import com.ftm.server.adapter.out.persistence.model.PostJpaEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -public interface PostRepository extends JpaRepository {} +public interface PostRepository extends JpaRepository { + + List findByUserId(Long userId); +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserImageRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserImageRepository.java index 9163c3a..234a579 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserImageRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserImageRepository.java @@ -1,10 +1,21 @@ package com.ftm.server.adapter.out.persistence.repository; import com.ftm.server.adapter.out.persistence.model.UserImageJpaEntity; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface UserImageRepository extends JpaRepository { Optional findByUserId(Long userId); + + @Modifying + @Query("DELETE FROM UserImageJpaEntity u WHERE u.user.id in (:userIds)") + void deleteAllByUserIdList(@Param("userIds") List userIds); + + @Query("SELECT ui.objectKey FROM UserImageJpaEntity ui WHERE ui.user.id in (:userIds)") + List findAllByUserIdList(@Param("userIds") List userIds); } diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java index a6341e3..0597624 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java @@ -2,11 +2,19 @@ import com.ftm.server.adapter.out.persistence.model.UserJpaEntity; import com.ftm.server.domain.enums.SocialProvider; +import com.ftm.server.domain.enums.UserRole; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface UserRepository extends JpaRepository { + Optional findByIdAndIsDeleted(Long userId, Boolean isDeleted); + Boolean existsByEmail(String email); Optional findByEmail(String email); @@ -15,4 +23,14 @@ Optional findBySocialProviderAndSocialId( SocialProvider socialProvider, String socialId); Boolean existsBySocialIdAndSocialProvider(String socialId, SocialProvider socialProvider); + + Optional findByRole(UserRole role); + + @Modifying + @Query("DELETE FROM UserJpaEntity u WHERE u.id in (:userIds)") + void deleteAllByUserIdList(@Param("userIds") List userIds); + + @Query("SELECT u FROM UserJpaEntity u WHERE u.isDeleted = :isDeleted And u.deletedAt <=:end") + List findAllByDeletedBefore( + @Param("isDeleted") Boolean isDeleted, @Param("end") LocalDateTime end); } diff --git a/src/main/java/com/ftm/server/adapter/out/scheduler/UserHardDeleteScheduler.java b/src/main/java/com/ftm/server/adapter/out/scheduler/UserHardDeleteScheduler.java new file mode 100644 index 0000000..1014d1f --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/out/scheduler/UserHardDeleteScheduler.java @@ -0,0 +1,19 @@ +package com.ftm.server.adapter.out.scheduler; + +import com.ftm.server.application.port.in.user.UserHardDeleteUseCase; +import com.ftm.server.common.annotation.Adapter; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; + +@Adapter +@RequiredArgsConstructor +public class UserHardDeleteScheduler { + + private final UserHardDeleteUseCase userHardDeleteUseCase; + + // 매일 새벽 3시 hard delete 진행 + @Scheduled(cron = "0 0 3 * * *") + public void run() { + userHardDeleteUseCase.execute(); + } +} diff --git a/src/main/java/com/ftm/server/application/command/user/DeleteAllUserByIdListCommand.java b/src/main/java/com/ftm/server/application/command/user/DeleteAllUserByIdListCommand.java new file mode 100644 index 0000000..3551bc3 --- /dev/null +++ b/src/main/java/com/ftm/server/application/command/user/DeleteAllUserByIdListCommand.java @@ -0,0 +1,13 @@ +package com.ftm.server.application.command.user; + +import java.util.*; +import lombok.Data; + +@Data +public class DeleteAllUserByIdListCommand { + private final List userIdList; + + public static DeleteAllUserByIdListCommand of(List userIdList) { + return new DeleteAllUserByIdListCommand(userIdList); + } +} diff --git a/src/main/java/com/ftm/server/application/command/user/DeleteBookmarkByUserIdCommand.java b/src/main/java/com/ftm/server/application/command/user/DeleteBookmarkByUserIdCommand.java new file mode 100644 index 0000000..ebbb358 --- /dev/null +++ b/src/main/java/com/ftm/server/application/command/user/DeleteBookmarkByUserIdCommand.java @@ -0,0 +1,13 @@ +package com.ftm.server.application.command.user; + +import java.util.List; +import lombok.Data; + +@Data +public class DeleteBookmarkByUserIdCommand { + private final List userIdList; + + public static DeleteBookmarkByUserIdCommand of(List userIdList) { + return new DeleteBookmarkByUserIdCommand(userIdList); + } +} diff --git a/src/main/java/com/ftm/server/application/command/user/DeleteGroomingTestResultByUserIdCommand.java b/src/main/java/com/ftm/server/application/command/user/DeleteGroomingTestResultByUserIdCommand.java new file mode 100644 index 0000000..28c5506 --- /dev/null +++ b/src/main/java/com/ftm/server/application/command/user/DeleteGroomingTestResultByUserIdCommand.java @@ -0,0 +1,13 @@ +package com.ftm.server.application.command.user; + +import java.util.List; +import lombok.Data; + +@Data +public class DeleteGroomingTestResultByUserIdCommand { + private final List userIdList; + + public static DeleteGroomingTestResultByUserIdCommand of(List userIdList) { + return new DeleteGroomingTestResultByUserIdCommand(userIdList); + } +} diff --git a/src/main/java/com/ftm/server/application/command/user/DeleteUserByIdCommand.java b/src/main/java/com/ftm/server/application/command/user/DeleteUserByIdCommand.java new file mode 100644 index 0000000..fd4669d --- /dev/null +++ b/src/main/java/com/ftm/server/application/command/user/DeleteUserByIdCommand.java @@ -0,0 +1,12 @@ +package com.ftm.server.application.command.user; + +import lombok.Data; + +@Data +public class DeleteUserByIdCommand { + private final Long userId; + + public static DeleteUserByIdCommand of(Long userId) { + return new DeleteUserByIdCommand(userId); + } +} diff --git a/src/main/java/com/ftm/server/application/command/user/DeleteUserImageByUserIdCommand.java b/src/main/java/com/ftm/server/application/command/user/DeleteUserImageByUserIdCommand.java new file mode 100644 index 0000000..38be055 --- /dev/null +++ b/src/main/java/com/ftm/server/application/command/user/DeleteUserImageByUserIdCommand.java @@ -0,0 +1,13 @@ +package com.ftm.server.application.command.user; + +import java.util.List; +import lombok.Data; + +@Data +public class DeleteUserImageByUserIdCommand { + private final List userIdList; + + public static DeleteUserImageByUserIdCommand of(List userIdList) { + return new DeleteUserImageByUserIdCommand(userIdList); + } +} diff --git a/src/main/java/com/ftm/server/application/port/in/user/UserExitUseCase.java b/src/main/java/com/ftm/server/application/port/in/user/UserExitUseCase.java new file mode 100644 index 0000000..9ccf4c0 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/in/user/UserExitUseCase.java @@ -0,0 +1,9 @@ +package com.ftm.server.application.port.in.user; + +import com.ftm.server.application.command.user.DeleteUserByIdCommand; +import com.ftm.server.common.annotation.Port; + +@Port +public interface UserExitUseCase { + void execute(DeleteUserByIdCommand query); +} diff --git a/src/main/java/com/ftm/server/application/port/in/user/UserHardDeleteUseCase.java b/src/main/java/com/ftm/server/application/port/in/user/UserHardDeleteUseCase.java new file mode 100644 index 0000000..4da7da1 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/in/user/UserHardDeleteUseCase.java @@ -0,0 +1,5 @@ +package com.ftm.server.application.port.in.user; + +public interface UserHardDeleteUseCase { + void execute(); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteBookmarkPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteBookmarkPort.java new file mode 100644 index 0000000..f73eaaa --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteBookmarkPort.java @@ -0,0 +1,9 @@ +package com.ftm.server.application.port.out.persistence.user; + +import com.ftm.server.application.command.user.DeleteBookmarkByUserIdCommand; +import com.ftm.server.common.annotation.Port; + +@Port +public interface DeleteBookmarkPort { + void deleteBookmarkByUserList(DeleteBookmarkByUserIdCommand command); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteGroomingTestResultPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteGroomingTestResultPort.java new file mode 100644 index 0000000..1ab39e7 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteGroomingTestResultPort.java @@ -0,0 +1,9 @@ +package com.ftm.server.application.port.out.persistence.user; + +import com.ftm.server.application.command.user.DeleteGroomingTestResultByUserIdCommand; +import com.ftm.server.common.annotation.Port; + +@Port +public interface DeleteGroomingTestResultPort { + void deleteGroomingTestResultByUserList(DeleteGroomingTestResultByUserIdCommand command); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteUserImagePort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteUserImagePort.java new file mode 100644 index 0000000..efee4f7 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteUserImagePort.java @@ -0,0 +1,10 @@ +package com.ftm.server.application.port.out.persistence.user; + +import com.ftm.server.application.command.user.DeleteUserImageByUserIdCommand; +import com.ftm.server.common.annotation.Port; +import java.util.List; + +@Port +public interface DeleteUserImagePort { + List deleteUserImageByUserList(DeleteUserImageByUserIdCommand command); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteUserPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteUserPort.java new file mode 100644 index 0000000..10cab7d --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/DeleteUserPort.java @@ -0,0 +1,9 @@ +package com.ftm.server.application.port.out.persistence.user; + +import com.ftm.server.application.command.user.DeleteAllUserByIdListCommand; +import com.ftm.server.common.annotation.Port; + +@Port +public interface DeleteUserPort { + void deleteAllUserByIdList(DeleteAllUserByIdListCommand command); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadPostUserDomainPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadPostUserDomainPort.java new file mode 100644 index 0000000..0cfda54 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadPostUserDomainPort.java @@ -0,0 +1,10 @@ +package com.ftm.server.application.port.out.persistence.user; + +import com.ftm.server.application.query.FindByUserIdQuery; +import com.ftm.server.domain.entity.Post; +import java.util.List; + +public interface LoadPostUserDomainPort { + + List loadPostListByUser(FindByUserIdQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadUserPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadUserPort.java index 4a43236..012f4de 100644 --- a/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadUserPort.java +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadUserPort.java @@ -1,8 +1,17 @@ package com.ftm.server.application.port.out.persistence.user; import com.ftm.server.application.query.FindByUserIdQuery; +import com.ftm.server.application.query.FindUserByDeleteOptionQuery; +import com.ftm.server.application.query.FindUserByRoleQuery; +import com.ftm.server.common.annotation.Port; import com.ftm.server.domain.entity.User; +import java.util.*; +@Port public interface LoadUserPort { User loadUserById(FindByUserIdQuery query); + + User loadUserByRole(FindUserByRoleQuery query); + + List loadUserByDeleteOption(FindUserByDeleteOptionQuery query); } diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/UpdatePostUserDomainPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/UpdatePostUserDomainPort.java new file mode 100644 index 0000000..578f7c9 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/UpdatePostUserDomainPort.java @@ -0,0 +1,10 @@ +package com.ftm.server.application.port.out.persistence.user; + +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.Post; +import java.util.List; + +@Port +public interface UpdatePostUserDomainPort { + void updatePostListBySystemUser(List postList); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/UpdateUserInfoPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/UpdateUserPort.java similarity index 68% rename from src/main/java/com/ftm/server/application/port/out/persistence/user/UpdateUserInfoPort.java rename to src/main/java/com/ftm/server/application/port/out/persistence/user/UpdateUserPort.java index bf4da1b..f12cdc4 100644 --- a/src/main/java/com/ftm/server/application/port/out/persistence/user/UpdateUserInfoPort.java +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/UpdateUserPort.java @@ -4,6 +4,6 @@ import com.ftm.server.domain.entity.User; @Port -public interface UpdateUserInfoPort { - void updateUserInfo(User user); +public interface UpdateUserPort { + void updateUser(User user); } diff --git a/src/main/java/com/ftm/server/application/query/FindUserByDeleteOptionQuery.java b/src/main/java/com/ftm/server/application/query/FindUserByDeleteOptionQuery.java new file mode 100644 index 0000000..04e2c79 --- /dev/null +++ b/src/main/java/com/ftm/server/application/query/FindUserByDeleteOptionQuery.java @@ -0,0 +1,14 @@ +package com.ftm.server.application.query; + +import java.time.LocalDate; +import lombok.Data; + +@Data +public class FindUserByDeleteOptionQuery { + private final Boolean isDeleted; + private final LocalDate deletedAt; + + public static FindUserByDeleteOptionQuery of(Boolean isDeleted, LocalDate deletedAt) { + return new FindUserByDeleteOptionQuery(isDeleted, deletedAt); + } +} diff --git a/src/main/java/com/ftm/server/application/query/FindUserByRoleQuery.java b/src/main/java/com/ftm/server/application/query/FindUserByRoleQuery.java new file mode 100644 index 0000000..c3da45d --- /dev/null +++ b/src/main/java/com/ftm/server/application/query/FindUserByRoleQuery.java @@ -0,0 +1,13 @@ +package com.ftm.server.application.query; + +import com.ftm.server.domain.enums.UserRole; +import lombok.Data; + +@Data +public class FindUserByRoleQuery { + private final UserRole userRole; + + public static FindUserByRoleQuery of(UserRole userRole) { + return new FindUserByRoleQuery(userRole); + } +} diff --git a/src/main/java/com/ftm/server/application/service/user/UpdateUserInfoService.java b/src/main/java/com/ftm/server/application/service/user/UpdateUserInfoService.java index c60d4ad..da4a7ea 100644 --- a/src/main/java/com/ftm/server/application/service/user/UpdateUserInfoService.java +++ b/src/main/java/com/ftm/server/application/service/user/UpdateUserInfoService.java @@ -5,7 +5,7 @@ import com.ftm.server.application.port.out.persistence.user.LoadUserImagePort; import com.ftm.server.application.port.out.persistence.user.LoadUserPort; import com.ftm.server.application.port.out.persistence.user.UpdateUserImagePort; -import com.ftm.server.application.port.out.persistence.user.UpdateUserInfoPort; +import com.ftm.server.application.port.out.persistence.user.UpdateUserPort; import com.ftm.server.application.port.out.s3.S3ImageDeletePort; import com.ftm.server.application.port.out.s3.S3UserImageUploadPort; import com.ftm.server.application.port.out.transcation.AfterCommitExecutorPort; @@ -28,7 +28,7 @@ public class UpdateUserInfoService implements UpdateUserInfoUseCase { private final LoadUserImagePort loadUserImagePort; private final LoadUserPort loadUserPort; - private final UpdateUserInfoPort updateUserInfoPort; + private final UpdateUserPort updateUserPort; private final UpdateUserImagePort updateUserImagePort; private final S3UserImageUploadPort s3UserImageUploadPort; @@ -87,7 +87,7 @@ public UserWithImageVo execute(UpdateUserCommand updateUserCommand) { } } - updateUserInfoPort.updateUserInfo(user); + updateUserPort.updateUser(user); updateUserImagePort.updateUserImage(userImage); return UserWithImageVo.of(user, userImage); diff --git a/src/main/java/com/ftm/server/application/service/user/UserExitService.java b/src/main/java/com/ftm/server/application/service/user/UserExitService.java new file mode 100644 index 0000000..fe5d94f --- /dev/null +++ b/src/main/java/com/ftm/server/application/service/user/UserExitService.java @@ -0,0 +1,53 @@ +package com.ftm.server.application.service.user; + +import com.ftm.server.application.command.user.DeleteUserByIdCommand; +import com.ftm.server.application.port.in.user.UserExitUseCase; +import com.ftm.server.application.port.out.persistence.user.LoadPostUserDomainPort; +import com.ftm.server.application.port.out.persistence.user.LoadUserPort; +import com.ftm.server.application.port.out.persistence.user.UpdatePostUserDomainPort; +import com.ftm.server.application.port.out.persistence.user.UpdateUserPort; +import com.ftm.server.application.query.FindByUserIdQuery; +import com.ftm.server.application.query.FindUserByRoleQuery; +import com.ftm.server.domain.entity.Post; +import com.ftm.server.domain.entity.User; +import com.ftm.server.domain.enums.UserRole; +import jakarta.transaction.Transactional; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class UserExitService implements UserExitUseCase { + + private final LoadUserPort loadUserPort; + private final LoadPostUserDomainPort loadPostPort; + + private final UpdateUserPort updateUserPort; + private final UpdatePostUserDomainPort updatePostPort; + + @Transactional + @Override + public void execute(DeleteUserByIdCommand query) { + + // user soft delete + User user = loadUserPort.loadUserById(FindByUserIdQuery.of(query.getUserId())); + user.updateIsDeleted(true); + user.updateDeletedAt(LocalDateTime.now()); + updateUserPort.updateUser(user); + + // user가 쓴 게시글의 작성자를 익명 사용자로 변경 + List postList = loadPostPort.loadPostListByUser(FindByUserIdQuery.of(user.getId())); + if (!postList.isEmpty()) { + // 익명 사용자 조회 + User systemUser = loadUserPort.loadUserByRole(FindUserByRoleQuery.of(UserRole.SYSTEM)); + Long systemUserId = systemUser.getId(); + // post update + postList.forEach(p -> p.updateUserId(systemUserId)); + updatePostPort.updatePostListBySystemUser(postList); + } + } +} diff --git a/src/main/java/com/ftm/server/application/service/user/UserHardDeleteService.java b/src/main/java/com/ftm/server/application/service/user/UserHardDeleteService.java new file mode 100644 index 0000000..98ca818 --- /dev/null +++ b/src/main/java/com/ftm/server/application/service/user/UserHardDeleteService.java @@ -0,0 +1,66 @@ +package com.ftm.server.application.service.user; + +import com.ftm.server.application.command.user.DeleteAllUserByIdListCommand; +import com.ftm.server.application.command.user.DeleteBookmarkByUserIdCommand; +import com.ftm.server.application.command.user.DeleteGroomingTestResultByUserIdCommand; +import com.ftm.server.application.command.user.DeleteUserImageByUserIdCommand; +import com.ftm.server.application.port.in.user.UserHardDeleteUseCase; +import com.ftm.server.application.port.out.persistence.user.*; +import com.ftm.server.application.port.out.s3.S3ImageDeletePort; +import com.ftm.server.application.port.out.transcation.AfterCommitExecutorPort; +import com.ftm.server.application.query.FindUserByDeleteOptionQuery; +import com.ftm.server.domain.entity.User; +import jakarta.transaction.Transactional; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +@Slf4j +public class UserHardDeleteService implements UserHardDeleteUseCase { + + private final LoadUserPort loadUserPort; + + private final DeleteBookmarkPort deleteBookmarkPort; + private final DeleteGroomingTestResultPort deleteGroomingTestResultPort; + private final DeleteUserImagePort deleteUserImagePort; + private final DeleteUserPort deleteUserPort; + + private final AfterCommitExecutorPort afterCommitExecutorPort; + private final S3ImageDeletePort s3ImageDeletePort; + + @Override + @Transactional + public void execute() { + + // 삭제 대상 user 조회 : 이미 삭제가 된 회원 중 30일이 지난 경우 hard delete 대상 + List deletedUserIdList = + loadUserPort + .loadUserByDeleteOption( + FindUserByDeleteOptionQuery.of(true, LocalDate.now().minusDays(30))) + .stream() + .map(User::getId) + .toList(); + + // user 관련 엔티티 모두 삭제 (batch 삭제 진행) + // 1. 북마크 삭제 + deleteBookmarkPort.deleteBookmarkByUserList( + DeleteBookmarkByUserIdCommand.of(deletedUserIdList)); + // 2. 그루밍 결과 삭제 + deleteGroomingTestResultPort.deleteGroomingTestResultByUserList( + DeleteGroomingTestResultByUserIdCommand.of(deletedUserIdList)); + // 3. user 이미지 삭제 + List imageKeyList = + deleteUserImagePort.deleteUserImageByUserList( + DeleteUserImageByUserIdCommand.of(deletedUserIdList)); + afterCommitExecutorPort.doAfterCommit( + () -> + s3ImageDeletePort.deleteImages( + imageKeyList)); // transaction commit 이후에 s3에 이미지 삭제 요청 + // 4. user 삭제 + deleteUserPort.deleteAllUserByIdList(DeleteAllUserByIdListCommand.of(deletedUserIdList)); + } +} diff --git a/src/main/java/com/ftm/server/domain/entity/Post.java b/src/main/java/com/ftm/server/domain/entity/Post.java index 344d468..c659ed3 100644 --- a/src/main/java/com/ftm/server/domain/entity/Post.java +++ b/src/main/java/com/ftm/server/domain/entity/Post.java @@ -121,4 +121,8 @@ public void validateWriter(Long userId) { throw new CustomException(ErrorResponseCode.UNAUTHORIZED_POST_ACCESS); } } + + public void updateUserId(Long userId) { + this.userId = userId; + } } diff --git a/src/main/java/com/ftm/server/domain/entity/User.java b/src/main/java/com/ftm/server/domain/entity/User.java index 20e2b80..5b46a00 100644 --- a/src/main/java/com/ftm/server/domain/entity/User.java +++ b/src/main/java/com/ftm/server/domain/entity/User.java @@ -165,4 +165,12 @@ public void updateHashtag(HashTag[] hashTags) { public void updateAge(AgeGroup ageGroup) { this.ageGroup = ageGroup; } + + public void updateIsDeleted(Boolean isDeleted) { + this.isDeleted = isDeleted; + } + + public void updateDeletedAt(LocalDateTime deletedAt) { + this.deletedAt = deletedAt; + } } diff --git a/src/main/java/com/ftm/server/domain/enums/UserRole.java b/src/main/java/com/ftm/server/domain/enums/UserRole.java index 774a03e..44cabb8 100644 --- a/src/main/java/com/ftm/server/domain/enums/UserRole.java +++ b/src/main/java/com/ftm/server/domain/enums/UserRole.java @@ -6,7 +6,8 @@ public enum UserRole { ADMIN("admin"), USER("일반 user"), - GUEST("방문자"); + GUEST("방문자"), + SYSTEM("시스템 용 사용자"); private final String value; diff --git a/src/test/java/com/ftm/server/user/UserExitTest.java b/src/test/java/com/ftm/server/user/UserExitTest.java new file mode 100644 index 0000000..6656ba1 --- /dev/null +++ b/src/test/java/com/ftm/server/user/UserExitTest.java @@ -0,0 +1,70 @@ +package com.ftm.server.user; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.ftm.server.BaseTest; +import jakarta.transaction.Transactional; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +public class UserExitTest extends BaseTest { + private final List responseFieldDescriptors = + List.of( + fieldWithPath("status").type(JsonFieldType.NUMBER).description("응답 상태"), + fieldWithPath("code").type(JsonFieldType.STRING).description("상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("메시지"), + fieldWithPath("data") + .type(JsonFieldType.OBJECT) + .optional() + .description("data")); + + private ResultActions getResultActions(MockHttpSession session) throws Exception { + return mockMvc.perform( // api 실행 + RestDocumentationRequestBuilders.delete("/api/users").session(session)); + } + + // 문서화 반환 함수 + private RestDocumentationResultHandler getDocument(Integer identifier) { + return document( + "userExit/" + identifier, + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint(), getModifiedHeader()), + responseFields(responseFieldDescriptors), + resource( + ResourceSnippetParameters.builder() + .tag("회원") + .summary("회원 탈퇴 api") + .description("회원 탈퇴를 진행합니다.") + .responseFields(responseFieldDescriptors) + .build())); + } + + @Test + @Transactional + void 회원탈퇴_성공() throws Exception { + // given + MockHttpSession session = createUserAndLogin(); + + // when + ResultActions resultActions = getResultActions(session); + + // then + resultActions.andExpect(status().isOk()); + + // documentation + resultActions.andDo(getDocument(1)); + } +} diff --git a/src/test/java/com/ftm/server/user/UserHardDeleteTest.java b/src/test/java/com/ftm/server/user/UserHardDeleteTest.java new file mode 100644 index 0000000..0691ad4 --- /dev/null +++ b/src/test/java/com/ftm/server/user/UserHardDeleteTest.java @@ -0,0 +1,53 @@ +package com.ftm.server.user; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import com.ftm.server.BaseTest; +import com.ftm.server.adapter.out.persistence.repository.UserRepository; +import com.ftm.server.application.port.in.user.UserHardDeleteUseCase; +import com.ftm.server.application.port.out.persistence.user.UpdateUserPort; +import com.ftm.server.application.port.out.s3.S3ImageDeletePort; +import com.ftm.server.domain.entity.User; +import java.time.LocalDateTime; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; + +@SpringBootTest +public class UserHardDeleteTest extends BaseTest { + + @Autowired private UserHardDeleteUseCase userHardDeleteUseCase; + + @Autowired private UserRepository userRepository; // 실제 JPA 리포지토리 + + @Autowired private UpdateUserPort updateUserPort; + + @MockitoSpyBean private S3ImageDeletePort s3ImageDeletePort; + + @Test + void hard_delete_성공() { + // given + User user1 = createTestUser("test1", "qwe123@"); + User user2 = createTestUser("test2", "qwe123@"); + + user1.updateIsDeleted(true); + user1.updateDeletedAt(LocalDateTime.now().minusDays(30)); + user2.updateIsDeleted(true); + user2.updateDeletedAt(LocalDateTime.now().minusDays(30)); + + updateUserPort.updateUser(user1); + updateUserPort.updateUser(user2); + userRepository.flush(); + + doNothing().when(s3ImageDeletePort); // stub 생성, s3 직접 호출 x + + // when + userHardDeleteUseCase.execute(); + + // then + assertThat(userRepository.findById(user1.getId())).isEmpty(); + assertThat(userRepository.findById(user2.getId())).isEmpty(); + } +}