11package org .clokey .domain .auth .service ;
22
3+ import java .util .ArrayList ;
4+ import java .util .List ;
5+ import java .util .stream .Collectors ;
36import lombok .RequiredArgsConstructor ;
7+ import org .clokey .cloth .entity .Cloth ;
8+ import org .clokey .coordinate .entity .Coordinate ;
49import org .clokey .domain .auth .dto .AccessTokenDto ;
510import org .clokey .domain .auth .dto .RefreshTokenDto ;
611import org .clokey .domain .auth .dto .request .DeviceTokenRenewRequest ;
712import org .clokey .domain .auth .dto .request .TokenReissueRequest ;
13+ import org .clokey .domain .auth .dto .request .UserStatusUpdateRequest ;
814import org .clokey .domain .auth .dto .response .TokenResponse ;
915import org .clokey .domain .auth .dto .response .UserStatusResponse ;
1016import org .clokey .domain .auth .enums .RegisterStatus ;
1117import org .clokey .domain .auth .exception .AuthErrorCode ;
1218import org .clokey .domain .auth .repository .RefreshTokenRepository ;
19+ import org .clokey .domain .cloth .repository .ClothRepository ;
20+ import org .clokey .domain .comment .repository .CommentRepository ;
21+ import org .clokey .domain .coordinate .repository .CoordinateClothRepository ;
22+ import org .clokey .domain .coordinate .repository .CoordinateRepository ;
23+ import org .clokey .domain .folder .repository .ClothFolderRepository ;
24+ import org .clokey .domain .folder .repository .FolderRepository ;
25+ import org .clokey .domain .history .repository .HistoryClothTagRepository ;
26+ import org .clokey .domain .history .repository .HistoryHashtagRepository ;
27+ import org .clokey .domain .history .repository .HistoryImageRepository ;
28+ import org .clokey .domain .history .repository .HistoryRepository ;
29+ import org .clokey .domain .history .repository .HistoryStyleRepository ;
30+ import org .clokey .domain .image .event .ImagesDeleteEvent ;
31+ import org .clokey .domain .like .repository .MemberLikeRepository ;
32+ import org .clokey .domain .lookbook .repository .LookBookRepository ;
1333import org .clokey .domain .member .exception .MemberErrorCode ;
34+ import org .clokey .domain .member .repository .BlockRepository ;
35+ import org .clokey .domain .member .repository .FollowRepository ;
1436import org .clokey .domain .member .repository .MemberRepository ;
37+ import org .clokey .domain .member .repository .PendingFollowRepository ;
38+ import org .clokey .domain .notification .repository .CodiveNotificationRepository ;
39+ import org .clokey .domain .report .repository .ReportRepository ;
1540import org .clokey .domain .term .repository .MemberTermRepository ;
1641import org .clokey .exception .BaseCustomException ;
1742import org .clokey .global .util .MemberUtil ;
43+ import org .clokey .history .entity .History ;
44+ import org .clokey .history .entity .HistoryImage ;
1845import org .clokey .member .entity .Member ;
46+ import org .springframework .context .ApplicationEventPublisher ;
1947import org .springframework .stereotype .Service ;
2048import org .springframework .transaction .annotation .Transactional ;
2149
@@ -30,6 +58,27 @@ public class AuthServiceImpl implements AuthService {
3058 private final MemberRepository memberRepository ;
3159 private final JwtTokenService jwtTokenService ;
3260 private final RefreshTokenRepository refreshTokenRepository ;
61+ private final ApplicationEventPublisher eventPublisher ;
62+
63+ // 회원 탈퇴 관련 Repository
64+ private final HistoryRepository historyRepository ;
65+ private final HistoryImageRepository historyImageRepository ;
66+ private final HistoryClothTagRepository historyClothTagRepository ;
67+ private final HistoryStyleRepository historyStyleRepository ;
68+ private final HistoryHashtagRepository historyHashtagRepository ;
69+ private final MemberLikeRepository memberLikeRepository ;
70+ private final CommentRepository commentRepository ;
71+ private final CoordinateRepository coordinateRepository ;
72+ private final CoordinateClothRepository coordinateClothRepository ;
73+ private final LookBookRepository lookBookRepository ;
74+ private final ClothRepository clothRepository ;
75+ private final ClothFolderRepository clothFolderRepository ;
76+ private final FolderRepository folderRepository ;
77+ private final CodiveNotificationRepository codiveNotificationRepository ;
78+ private final FollowRepository followRepository ;
79+ private final PendingFollowRepository pendingFollowRepository ;
80+ private final BlockRepository blockRepository ;
81+ private final ReportRepository reportRepository ;
3382
3483 @ Override
3584 public UserStatusResponse getUserStatus () {
@@ -76,6 +125,217 @@ public void logoutUser() {
76125 .ifPresent (refreshTokenRepository ::delete );
77126 }
78127
128+ @ Override
129+ @ Transactional
130+ public void updateUserStatus (UserStatusUpdateRequest request ) {
131+ final Member currentMember = memberUtil .getCurrentMember ();
132+ if (request .active ()) {
133+ currentMember .activate ();
134+ } else {
135+ currentMember .deactivate ();
136+ }
137+ }
138+
139+ @ Override
140+ @ Transactional
141+ public void withdrawMember () {
142+ final Member currentMember = memberUtil .getCurrentMember ();
143+ withdrawMemberById (currentMember .getId ());
144+ }
145+
146+ // TODO: (중요 메모) 절대로 - 카테고리, 상황, 약관, 스타일은 삭제하면 안됩니다!! 미리 적재된 데이터입니다.
147+ @ Override
148+ @ Transactional
149+ public void withdrawMemberById (Long memberId ) {
150+ final Member currentMember =
151+ memberRepository
152+ .findById (memberId )
153+ .orElseThrow (
154+ () -> new BaseCustomException (MemberErrorCode .MEMBER_NOT_FOUND ));
155+
156+ List <String > imageUrlsToDelete = new ArrayList <>();
157+
158+ // 1. History 관련 데이터 삭제
159+ List <History > histories =
160+ historyRepository .findAll ().stream ()
161+ .filter (h -> h .getMember ().getId ().equals (memberId ))
162+ .collect (Collectors .toList ());
163+
164+ List <Long > historyIds = histories .stream ().map (History ::getId ).collect (Collectors .toList ());
165+
166+ if (!historyIds .isEmpty ()) {
167+ // HistoryClothTag 삭제
168+ List <Long > historyImageIds =
169+ historyImageRepository .findAll ().stream ()
170+ .filter (hi -> historyIds .contains (hi .getHistory ().getId ()))
171+ .map (HistoryImage ::getId )
172+ .collect (Collectors .toList ());
173+
174+ historyImageIds .forEach (
175+ historyImageId ->
176+ historyClothTagRepository
177+ .findByHistoryImageId (historyImageId )
178+ .forEach (historyClothTagRepository ::delete ));
179+
180+ // HistoryImage 삭제 및 이미지 URL 수집
181+ List <HistoryImage > historyImages =
182+ historyImageRepository .findAll ().stream ()
183+ .filter (hi -> historyIds .contains (hi .getHistory ().getId ()))
184+ .collect (Collectors .toList ());
185+
186+ historyImages .forEach (
187+ hi -> {
188+ if (hi .getImageUrl () != null ) {
189+ imageUrlsToDelete .add (hi .getImageUrl ());
190+ }
191+ });
192+ historyImageRepository .deleteAll (historyImages );
193+
194+ // HistoryHashtag 삭제
195+ historyIds .forEach (
196+ historyId ->
197+ historyHashtagRepository
198+ .findByHistoryId (historyId )
199+ .forEach (historyHashtagRepository ::delete ));
200+
201+ // HistoryStyle 삭제
202+ historyIds .forEach (
203+ historyId ->
204+ historyStyleRepository
205+ .findByHistoryId (historyId )
206+ .forEach (historyStyleRepository ::delete ));
207+
208+ // MemberLike 삭제 (해당 회원이 좋아요한 것)
209+ memberLikeRepository .findAll ().stream ()
210+ .filter (ml -> ml .getMember ().getId ().equals (memberId ))
211+ .forEach (memberLikeRepository ::delete );
212+
213+ // Comment 삭제
214+ commentRepository .findAll ().stream ()
215+ .filter (c -> c .getMember ().getId ().equals (memberId ))
216+ .forEach (commentRepository ::delete );
217+
218+ // History 삭제
219+ historyRepository .deleteAll (histories );
220+ }
221+
222+ // 2. Coordinate 관련 데이터 삭제
223+ List <Coordinate > coordinates =
224+ coordinateRepository .findAll ().stream ()
225+ .filter (c -> c .getMember ().getId ().equals (memberId ))
226+ .collect (Collectors .toList ());
227+
228+ List <Long > coordinateIds =
229+ coordinates .stream ().map (Coordinate ::getId ).collect (Collectors .toList ());
230+
231+ if (!coordinateIds .isEmpty ()) {
232+ // CoordinateCloth 삭제
233+ coordinateIds .forEach (coordinateClothRepository ::deleteAllByCoordinateId );
234+
235+ // Coordinate 이미지 URL 수집 및 삭제
236+ coordinates .forEach (
237+ c -> {
238+ if (c .getImageUrl () != null ) {
239+ imageUrlsToDelete .add (c .getImageUrl ());
240+ }
241+ });
242+ coordinateRepository .deleteAll (coordinates );
243+ }
244+
245+ // 3. LookBook 삭제
246+ lookBookRepository .findAll ().stream ()
247+ .filter (lb -> lb .getMember ().getId ().equals (memberId ))
248+ .forEach (lookBookRepository ::delete );
249+
250+ // 4. Cloth 관련 데이터 삭제
251+ List <Cloth > clothes =
252+ clothRepository .findAll ().stream ()
253+ .filter (c -> c .getMember ().getId ().equals (memberId ))
254+ .collect (Collectors .toList ());
255+
256+ List <Long > clothIds = clothes .stream ().map (Cloth ::getId ).collect (Collectors .toList ());
257+
258+ if (!clothIds .isEmpty ()) {
259+ // ClothFolder 삭제
260+ clothIds .forEach (clothFolderRepository ::deleteAllByClothId );
261+
262+ // Cloth 이미지 URL 수집 및 삭제
263+ clothes .forEach (
264+ c -> {
265+ if (c .getClothImageUrl () != null ) {
266+ imageUrlsToDelete .add (c .getClothImageUrl ());
267+ }
268+ });
269+ clothRepository .deleteAll (clothes );
270+ }
271+
272+ // 5. Folder 삭제
273+ folderRepository .findAll ().stream ()
274+ .filter (f -> f .getMember ().getId ().equals (memberId ))
275+ .forEach (folderRepository ::delete );
276+
277+ // 6. MemberTerm 삭제
278+ memberTermRepository .findAll ().stream ()
279+ .filter (mt -> mt .getMember ().getId ().equals (memberId ))
280+ .forEach (memberTermRepository ::delete );
281+
282+ // 7. CodiveNotification 삭제
283+ codiveNotificationRepository .findAll ().stream ()
284+ .filter (cn -> cn .getMember ().getId ().equals (memberId ))
285+ .forEach (codiveNotificationRepository ::delete );
286+
287+ // 8. Follow 삭제 (followFrom, followTo 모두)
288+ followRepository .findAll ().stream ()
289+ .filter (
290+ f ->
291+ f .getFollowFrom ().getId ().equals (memberId )
292+ || f .getFollowTo ().getId ().equals (memberId ))
293+ .forEach (followRepository ::delete );
294+
295+ // 9. PendingFollow 삭제
296+ pendingFollowRepository .findAll ().stream ()
297+ .filter (
298+ pf ->
299+ pf .getFollowFrom ().getId ().equals (memberId )
300+ || pf .getFollowTo ().getId ().equals (memberId ))
301+ .forEach (pendingFollowRepository ::delete );
302+
303+ // 10. Block 삭제 (blocker, blocked 모두)
304+ blockRepository .findAll ().stream ()
305+ .filter (
306+ b ->
307+ b .getBlocker ().getId ().equals (memberId )
308+ || b .getBlocked ().getId ().equals (memberId ))
309+ .forEach (blockRepository ::delete );
310+
311+ // 11. Report 삭제 (reporter, reported 모두)
312+ reportRepository .findAll ().stream ()
313+ .filter (
314+ r ->
315+ r .getReporter ().getId ().equals (memberId )
316+ || r .getReported ().getId ().equals (memberId ))
317+ .forEach (reportRepository ::delete );
318+
319+ // 12. RefreshToken 삭제
320+ refreshTokenRepository .findById (memberId ).ifPresent (refreshTokenRepository ::delete );
321+
322+ // 13. Member 프로필 이미지 URL 수집
323+ if (currentMember .getProfileImageUrl () != null ) {
324+ imageUrlsToDelete .add (currentMember .getProfileImageUrl ());
325+ }
326+ if (currentMember .getProfileBackImageUrl () != null ) {
327+ imageUrlsToDelete .add (currentMember .getProfileBackImageUrl ());
328+ }
329+
330+ // 14. 이미지 삭제 이벤트 발행 (비동기 처리)
331+ if (!imageUrlsToDelete .isEmpty ()) {
332+ eventPublisher .publishEvent (ImagesDeleteEvent .of (imageUrlsToDelete ));
333+ }
334+
335+ // 15. Member 삭제
336+ memberRepository .delete (currentMember );
337+ }
338+
79339 private Member getMember (RefreshTokenDto refreshTokenDto ) {
80340 return memberRepository
81341 .findById (refreshTokenDto .memberId ())
0 commit comments