Skip to content

Commit 618868d

Browse files
committed
feat : 회원 탈퇴 api 추가(#107)
- scheduler : 사용자 관련 정보 hard delete 진행
1 parent 75f9924 commit 618868d

35 files changed

Lines changed: 647 additions & 26 deletions

src/docs/asciidoc/user-api.adoc

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,18 @@ include::{snippetsDir}/userInfoUpdate/1/response-fields.adoc[]
161161
실패1.
162162
include::{snippetsDir}/userInfoUpdate/2/http-response.adoc[]
163163
실패 2
164-
include::{snippetsDir}/userInfoUpdate/3/http-response.adoc[]
164+
include::{snippetsDir}/userInfoUpdate/3/http-response.adoc[]
165+
166+
167+
=== **9. 회원 탈퇴 api**
168+
169+
회원 탈퇴를 진행
170+
171+
==== Request
172+
include::{snippetsDir}/userExit/1/http-request.adoc[]
173+
174+
==== 성공 Response
175+
include::{snippetsDir}/userExit/1/http-response.adoc[]
176+
177+
==== Response Body Fields
178+
include::{snippetsDir}/userExit/1/response-fields.adoc[]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.ftm.server.adapter.in.web.user.controller;
2+
3+
import com.ftm.server.application.command.user.DeleteUserByIdCommand;
4+
import com.ftm.server.application.port.in.user.UserExitUseCase;
5+
import com.ftm.server.common.response.ApiResponse;
6+
import com.ftm.server.common.response.enums.SuccessResponseCode;
7+
import com.ftm.server.infrastructure.security.UserPrincipal;
8+
import jakarta.servlet.http.HttpServletRequest;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.http.HttpStatus;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
13+
import org.springframework.security.core.context.SecurityContextHolder;
14+
import org.springframework.web.bind.annotation.DeleteMapping;
15+
import org.springframework.web.bind.annotation.RestController;
16+
17+
@RestController
18+
@RequiredArgsConstructor
19+
public class UserExitController {
20+
21+
private final UserExitUseCase userExitUseCase;
22+
23+
@DeleteMapping("api/users")
24+
public ResponseEntity<ApiResponse> userExit(
25+
@AuthenticationPrincipal UserPrincipal user, HttpServletRequest request) {
26+
// 회원 탈퇴
27+
userExitUseCase.execute(DeleteUserByIdCommand.of(user.getId()));
28+
// 로그아웃 처리
29+
request.getSession().invalidate();
30+
SecurityContextHolder.clearContext();
31+
return ResponseEntity.status(HttpStatus.OK)
32+
.body(ApiResponse.success(SuccessResponseCode.OK));
33+
}
34+
}

src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
package com.ftm.server.adapter.out.persistence.adapter.user;
22

33
import com.ftm.server.adapter.out.persistence.mapper.EmailVerificationLogsMapper;
4+
import com.ftm.server.adapter.out.persistence.mapper.PostMapper;
45
import com.ftm.server.adapter.out.persistence.mapper.UserImageMapper;
56
import com.ftm.server.adapter.out.persistence.mapper.UserMapper;
6-
import com.ftm.server.adapter.out.persistence.model.EmailVerificationLogsJpaEntity;
7-
import com.ftm.server.adapter.out.persistence.model.GroomingLevelJpaEntity;
8-
import com.ftm.server.adapter.out.persistence.model.UserImageJpaEntity;
9-
import com.ftm.server.adapter.out.persistence.model.UserJpaEntity;
10-
import com.ftm.server.adapter.out.persistence.repository.EmailVerificationLogsRepository;
11-
import com.ftm.server.adapter.out.persistence.repository.GroomingLevelRepository;
12-
import com.ftm.server.adapter.out.persistence.repository.UserImageRepository;
13-
import com.ftm.server.adapter.out.persistence.repository.UserRepository;
14-
import com.ftm.server.application.command.user.EmailVerificationLogCreationCommand;
7+
import com.ftm.server.adapter.out.persistence.model.*;
8+
import com.ftm.server.adapter.out.persistence.repository.*;
9+
import com.ftm.server.application.command.user.*;
1510
import com.ftm.server.application.port.out.persistence.user.*;
1611
import com.ftm.server.application.query.*;
1712
import com.ftm.server.common.annotation.Adapter;
1813
import com.ftm.server.common.exception.CustomException;
19-
import com.ftm.server.domain.entity.EmailVerificationLogs;
20-
import com.ftm.server.domain.entity.User;
21-
import com.ftm.server.domain.entity.UserImage;
14+
import com.ftm.server.domain.entity.*;
15+
import java.util.HashMap;
16+
import java.util.List;
17+
import java.util.Map;
2218
import java.util.Optional;
2319
import lombok.RequiredArgsConstructor;
2420
import lombok.extern.slf4j.Slf4j;
@@ -35,19 +31,29 @@ public class UserDomainPersistenceAdapter
3531
SaveUserImagePort,
3632
LoadUserPort,
3733
LoadUserImagePort,
38-
UpdateUserInfoPort,
39-
UpdateUserImagePort {
34+
UpdateUserPort,
35+
UpdateUserImagePort,
36+
LoadPostUserDomainPort,
37+
UpdatePostUserDomainPort,
38+
DeleteUserImagePort,
39+
DeleteGroomingTestResultPort,
40+
DeleteUserPort,
41+
DeleteBookmarkPort {
4042

4143
// repository
4244
private final EmailVerificationLogsRepository emailVerificationLogsRepository;
4345
private final UserRepository userRepository;
4446
private final GroomingLevelRepository groomingLevelRepository;
4547
private final UserImageRepository userImageRepository;
48+
private final PostRepository postRepository;
49+
private final BookmarkRepository bookmarkRepository;
50+
private final GroomingTestResultRepository groomingTestResultRepository;
4651

4752
// mapper
4853
private final EmailVerificationLogsMapper emailVerificationLogsMapper;
4954
private final UserMapper userMapper;
5055
private final UserImageMapper userImageMapper;
56+
private final PostMapper postMapper;
5157

5258
@Override
5359
public Optional<EmailVerificationLogs> loadEmailVerificationLogByEmail(FindByEmailQuery query) {
@@ -115,11 +121,27 @@ public User saveSocialUser(User user) {
115121
public User loadUserById(FindByUserIdQuery query) {
116122
UserJpaEntity userJpaEntity =
117123
userRepository
118-
.findById(query.getUserId())
124+
.findByIdAndIsDeleted(query.getUserId(), false)
119125
.orElseThrow(() -> CustomException.USER_NOT_FOUND);
120126
return userMapper.toDomainEntity(userJpaEntity);
121127
}
122128

129+
@Override
130+
public User loadUserByRole(FindUserByRoleQuery query) {
131+
UserJpaEntity userJpaEntity = userRepository.findByRole(query.getUserRole()).get();
132+
return userMapper.toDomainEntity(userJpaEntity);
133+
}
134+
135+
@Override
136+
public List<User> loadUserByDeleteOption(FindUserByDeleteOptionQuery query) {
137+
return userRepository
138+
.findAllByDeletedBefore(
139+
query.getIsDeleted(), query.getDeletedAt().atTime(23, 59, 59))
140+
.stream()
141+
.map(userMapper::toDomainEntity)
142+
.toList();
143+
}
144+
123145
@Override
124146
public UserImage loadUserImageByUserId(FindByUserIdQuery query) {
125147
UserImageJpaEntity userImageJpaEntity =
@@ -131,7 +153,7 @@ public UserImage loadUserImageByUserId(FindByUserIdQuery query) {
131153
}
132154

133155
@Override
134-
public void updateUserInfo(User user) {
156+
public void updateUser(User user) {
135157
UserJpaEntity savedUser =
136158
userRepository
137159
.findById(user.getId())
@@ -156,4 +178,49 @@ public void updateUserImage(UserImage userImage) {
156178

157179
userImageJpaEntity.updateFromDomainEntity(userImage);
158180
}
181+
182+
@Override
183+
public List<Post> loadPostListByUser(FindByUserIdQuery query) {
184+
return postRepository.findByUserId(query.getUserId()).stream()
185+
.map(postMapper::toDomainEntity)
186+
.toList();
187+
}
188+
189+
@Override
190+
public void updatePostListBySystemUser(List<Post> postList) {
191+
UserJpaEntity systemUser = userRepository.findById(postList.get(0).getUserId()).get();
192+
193+
Map<Long, Post> map = new HashMap<>();
194+
postList.forEach(p -> map.put(p.getId(), p));
195+
196+
List<PostJpaEntity> postJpaEntityList =
197+
postRepository.findAllById(postList.stream().map(Post::getId).toList());
198+
199+
postJpaEntityList.forEach(
200+
pj -> pj.updatePostForDomainEntity(map.get(pj.getId()), systemUser));
201+
}
202+
203+
@Override
204+
public void deleteGroomingTestResultByUserList(
205+
DeleteGroomingTestResultByUserIdCommand command) {
206+
groomingTestResultRepository.deleteAllByUserIdList(command.getUserIdList());
207+
}
208+
209+
@Override
210+
public List<String> deleteUserImageByUserList(DeleteUserImageByUserIdCommand command) {
211+
List<String> imageKeyList =
212+
userImageRepository.findAllByUserIdList(command.getUserIdList());
213+
userImageRepository.deleteAllByUserIdList(command.getUserIdList());
214+
return imageKeyList;
215+
}
216+
217+
@Override
218+
public void deleteAllUserByIdList(DeleteAllUserByIdListCommand command) {
219+
userRepository.deleteAllByUserIdList(command.getUserIdList());
220+
}
221+
222+
@Override
223+
public void deleteBookmarkByUserList(DeleteBookmarkByUserIdCommand command) {
224+
bookmarkRepository.deleteAllByUserIdList(command.getUserIdList());
225+
}
159226
}

src/main/java/com/ftm/server/adapter/out/persistence/model/PostJpaEntity.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,16 @@ public void updatePostForDomainEntity(Post post) {
102102
this.isDeleted = post.getIsDeleted();
103103
this.deletedAt = post.getDeletedAt();
104104
}
105+
106+
public void updatePostForDomainEntity(Post post, UserJpaEntity user) {
107+
this.title = post.getTitle();
108+
this.user = user;
109+
this.content = post.getContent();
110+
this.groomingCategory = post.getGroomingCategory();
111+
this.hashtags = post.getHashtags();
112+
this.viewCount = post.getViewCount();
113+
this.likeCount = post.getLikeCount();
114+
this.isDeleted = post.getIsDeleted();
115+
this.deletedAt = post.getDeletedAt();
116+
}
105117
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
package com.ftm.server.adapter.out.persistence.repository;
22

33
import com.ftm.server.adapter.out.persistence.model.BookmarkJpaEntity;
4+
import java.util.List;
45
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Modifying;
7+
import org.springframework.data.jpa.repository.Query;
8+
import org.springframework.data.repository.query.Param;
59

6-
public interface BookmarkRepository extends JpaRepository<BookmarkJpaEntity, Long> {}
10+
public interface BookmarkRepository extends JpaRepository<BookmarkJpaEntity, Long> {
11+
@Modifying
12+
@Query("DELETE FROM BookmarkJpaEntity b WHERE b.user.id in (:userIds)")
13+
void deleteAllByUserIdList(@Param("userIds") List<Long> userIds);
14+
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
package com.ftm.server.adapter.out.persistence.repository;
22

33
import com.ftm.server.adapter.out.persistence.model.GroomingTestResultJpaEntity;
4+
import java.util.List;
45
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Modifying;
7+
import org.springframework.data.jpa.repository.Query;
8+
import org.springframework.data.repository.query.Param;
59

610
public interface GroomingTestResultRepository
711
extends JpaRepository<GroomingTestResultJpaEntity, Long>,
8-
GroomingTestResultCustomRepository {}
12+
GroomingTestResultCustomRepository {
13+
@Modifying
14+
@Query("DELETE FROM GroomingTestResultJpaEntity g WHERE g.user.id in (:userIds)")
15+
void deleteAllByUserIdList(@Param("userIds") List<Long> userIds);
16+
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.ftm.server.adapter.out.persistence.repository;
22

33
import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
4+
import java.util.List;
45
import org.springframework.data.jpa.repository.JpaRepository;
56

6-
public interface PostRepository extends JpaRepository<PostJpaEntity, Long> {}
7+
public interface PostRepository extends JpaRepository<PostJpaEntity, Long> {
8+
9+
List<PostJpaEntity> findByUserId(Long userId);
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
package com.ftm.server.adapter.out.persistence.repository;
22

33
import com.ftm.server.adapter.out.persistence.model.UserImageJpaEntity;
4+
import java.util.List;
45
import java.util.Optional;
56
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Modifying;
8+
import org.springframework.data.jpa.repository.Query;
9+
import org.springframework.data.repository.query.Param;
610

711
public interface UserImageRepository extends JpaRepository<UserImageJpaEntity, Long> {
812

913
Optional<UserImageJpaEntity> findByUserId(Long userId);
14+
15+
@Modifying
16+
@Query("DELETE FROM UserImageJpaEntity u WHERE u.user.id in (:userIds)")
17+
void deleteAllByUserIdList(@Param("userIds") List<Long> userIds);
18+
19+
@Query("SELECT ui.objectKey FROM UserImageJpaEntity ui WHERE ui.user.id in (:userIds)")
20+
List<String> findAllByUserIdList(@Param("userIds") List<Long> userIds);
1021
}

src/main/java/com/ftm/server/adapter/out/persistence/repository/UserRepository.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@
22

33
import com.ftm.server.adapter.out.persistence.model.UserJpaEntity;
44
import com.ftm.server.domain.enums.SocialProvider;
5+
import com.ftm.server.domain.enums.UserRole;
6+
import java.time.LocalDateTime;
7+
import java.util.List;
58
import java.util.Optional;
69
import org.springframework.data.jpa.repository.JpaRepository;
10+
import org.springframework.data.jpa.repository.Modifying;
11+
import org.springframework.data.jpa.repository.Query;
12+
import org.springframework.data.repository.query.Param;
713

814
public interface UserRepository extends JpaRepository<UserJpaEntity, Long> {
915

16+
Optional<UserJpaEntity> findByIdAndIsDeleted(Long userId, Boolean isDeleted);
17+
1018
Boolean existsByEmail(String email);
1119

1220
Optional<UserJpaEntity> findByEmail(String email);
@@ -15,4 +23,14 @@ Optional<UserJpaEntity> findBySocialProviderAndSocialId(
1523
SocialProvider socialProvider, String socialId);
1624

1725
Boolean existsBySocialIdAndSocialProvider(String socialId, SocialProvider socialProvider);
26+
27+
Optional<UserJpaEntity> findByRole(UserRole role);
28+
29+
@Modifying
30+
@Query("DELETE FROM UserJpaEntity u WHERE u.id in (:userIds)")
31+
void deleteAllByUserIdList(@Param("userIds") List<Long> userIds);
32+
33+
@Query("SELECT u FROM UserJpaEntity u WHERE u.isDeleted = :isDeleted And u.deletedAt <=:end")
34+
List<UserJpaEntity> findAllByDeletedBefore(
35+
@Param("isDeleted") Boolean isDeleted, @Param("end") LocalDateTime end);
1836
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.ftm.server.adapter.out.scheduler;
2+
3+
import com.ftm.server.application.port.in.user.UserHardDeleteUseCase;
4+
import com.ftm.server.common.annotation.Adapter;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.scheduling.annotation.Scheduled;
7+
8+
@Adapter
9+
@RequiredArgsConstructor
10+
public class UserHardDeleteScheduler {
11+
12+
private final UserHardDeleteUseCase userHardDeleteUseCase;
13+
14+
// 매일 새벽 3시 hard delete 진행
15+
@Scheduled(cron = "0 0 3 * * *")
16+
public void run() {
17+
userHardDeleteUseCase.execute();
18+
}
19+
}

0 commit comments

Comments
 (0)