diff --git a/Keychy/Keychy/Core/Firebase/UserManager.swift b/Keychy/Keychy/Core/Firebase/UserManager.swift index d8932f7a4..37c634337 100644 --- a/Keychy/Keychy/Core/Firebase/UserManager.swift +++ b/Keychy/Keychy/Core/Firebase/UserManager.swift @@ -213,25 +213,23 @@ class UserManager { let uid = user.uid - // 1. 먼저 Firebase Auth 계정 삭제 시도 (재인증 필요 여부 확인) - user.delete { [weak self] error in - guard let self = self else { return } - - if let error = error { - completion(.failure(error)) - } else { - // 2. Auth 삭제 성공 → Firestore 데이터 삭제 - self.deleteUserData(uid: uid) { result in - switch result { - case .success: - completion(.success(())) - - case .failure: - // Auth는 이미 삭제됐지만 Firestore는 남아있음 - // 어차피 로그인 불가능하므로 성공으로 처리 + // 1. 먼저 Firestore 데이터 삭제 (Auth 유저가 있어야 권한이 있음) + deleteUserData(uid: uid) { [weak self] result in + guard self != nil else { return } + + switch result { + case .success: + // 2. 데이터 삭제 성공 → Firebase Auth 계정 삭제 + user.delete { error in + if let error = error { + completion(.failure(error)) + } else { completion(.success(())) } } + + case .failure(let error): + completion(.failure(error)) } } } @@ -245,23 +243,23 @@ class UserManager { let uid = user.uid - // 1. Firebase Auth 계정 삭제 - user.delete { [weak self] error in - guard let self = self else { return } + // 1. 먼저 Firestore 데이터 삭제 (Auth 유저가 있어야 권한이 있음) + deleteUserData(uid: uid) { [weak self] result in + guard self != nil else { return } - if let error = error { - completion(.failure(error)) - } else { - // 2. Auth 삭제 성공 → Firestore 데이터 삭제 - self.deleteUserData(uid: uid) { result in - switch result { - case .success: - completion(.success(())) - - case .failure: + switch result { + case .success: + // 2. 데이터 삭제 성공 → Firebase Auth 계정 삭제 + user.delete { error in + if let error = error { + completion(.failure(error)) + } else { completion(.success(())) } } + + case .failure(let error): + completion(.failure(error)) } } } @@ -405,6 +403,78 @@ class UserManager { deletionGroup.leave() } + // KeyringBundle 컬렉션에서 사용자의 뭉치 삭제 + deletionGroup.enter() + self.db.collection("KeyringBundle") + .whereField("userId", isEqualTo: uid) + .getDocuments { querySnapshot, error in + if let error = error { + print("❌ KeyringBundle 조회 실패: \(error.localizedDescription)") + deletionError = error + deletionGroup.leave() + return + } + + guard let documents = querySnapshot?.documents, !documents.isEmpty else { + print("✅ 삭제할 KeyringBundle 없음") + deletionGroup.leave() + return + } + + let bundleGroup = DispatchGroup() + for document in documents { + bundleGroup.enter() + document.reference.delete { error in + if let error = error { + print("❌ KeyringBundle 삭제 실패: \(document.documentID) - \(error.localizedDescription)") + deletionError = error + } else { + print("✅ KeyringBundle 삭제 완료: \(document.documentID)") + } + bundleGroup.leave() + } + } + bundleGroup.notify(queue: .main) { + deletionGroup.leave() + } + } + + // Notifications 컬렉션에서 사용자의 알림 삭제 + deletionGroup.enter() + self.db.collection("Notifications") + .whereField("receiverId", isEqualTo: uid) + .getDocuments { querySnapshot, error in + if let error = error { + print("❌ Notifications 조회 실패: \(error.localizedDescription)") + deletionError = error + deletionGroup.leave() + return + } + + guard let documents = querySnapshot?.documents, !documents.isEmpty else { + print("✅ 삭제할 Notifications 없음") + deletionGroup.leave() + return + } + + let notificationGroup = DispatchGroup() + for document in documents { + notificationGroup.enter() + document.reference.delete { error in + if let error = error { + print("❌ Notification 삭제 실패: \(document.documentID) - \(error.localizedDescription)") + deletionError = error + } else { + print("✅ Notification 삭제 완료: \(document.documentID)") + } + notificationGroup.leave() + } + } + notificationGroup.notify(queue: .main) { + deletionGroup.leave() + } + } + // 4. 모든 삭제 완료 후 User 문서 삭제 deletionGroup.notify(queue: .main) { print("🎉 모든 데이터 삭제 완료") diff --git a/Keychy/Keychy/Presentation/Home/ViewModels/MyPageViewModel.swift b/Keychy/Keychy/Presentation/Home/ViewModels/MyPageViewModel.swift index 0219422c0..7a54d1158 100644 --- a/Keychy/Keychy/Presentation/Home/ViewModels/MyPageViewModel.swift +++ b/Keychy/Keychy/Presentation/Home/ViewModels/MyPageViewModel.swift @@ -191,7 +191,7 @@ class MyPageViewModel { // MARK: - Delete Account - /// 회원탈퇴 + /// 회원탈퇴 - 항상 재인증 먼저 진행 (데이터 보호) func deleteAccount(userManager: UserManager, introViewModel: IntroViewModel) { // 네트워크 체크 guard NetworkManager.shared.isConnected else { @@ -199,76 +199,49 @@ class MyPageViewModel { return } - guard let user = Auth.auth().currentUser else { + guard Auth.auth().currentUser != nil else { return } - let uid = user.uid - - // LoadingAlert 표시 - showLoadingAlert = true - withAnimation(.spring(response: 0.6, dampingFraction: 0.5)) { - loadingAlertScale = 1.0 - } - - // 1. 먼저 Firebase Auth 계정 삭제 시도 (재인증 필요 여부 확인) - user.delete { [weak self] error in - guard let self = self else { return } - - if let error = error { - // LoadingAlert 숨기기 - self.hideLoadingAlert() - - // 재인증 필요 에러 처리 - let nsError = error as NSError - if nsError.code == 17014 { // FIRAuthErrorCodeRequiresRecentLogin - self.showReauthAlert = true - } - } else { - // 2. Auth 삭제 성공 → Firestore 데이터 삭제 - userManager.deleteUserData(uid: uid) { [weak self] result in - guard let self = self else { return } - - // LoadingAlert 숨기기 - self.hideLoadingAlert() - - // 3. UserManager 초기화 및 로그인 화면으로 이동 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - userManager.clearUserInfo() // 로컬 캐시 정리 - introViewModel.isLoggedIn = false - introViewModel.needsProfileSetup = false - } - } - } - } + // 재인증 먼저 진행 (재인증 성공 후에만 데이터 삭제) + startReauthentication(userManager: userManager, introViewModel: introViewModel) } /// 재인증 후 회원탈퇴 진행 func deleteAccountAfterReauth(user: FirebaseAuth.User, userManager: UserManager, introViewModel: IntroViewModel) { let uid = user.uid - // 1. Firebase Auth 계정 삭제 - user.delete { [weak self] error in + // 1. 먼저 Firestore 데이터 삭제 (Auth 유저가 있어야 권한이 있음) + userManager.deleteUserData(uid: uid) { [weak self] result in guard let self = self else { return } - if error != nil { - // LoadingAlert 숨기기 - self.hideLoadingAlert() - } else { - // 2. Auth 삭제 성공 → Firestore 데이터 삭제 - userManager.deleteUserData(uid: uid) { [weak self] result in + switch result { + case .success: + // 2. 데이터 삭제 성공 → Firebase Auth 계정 삭제 + user.delete { [weak self] error in guard let self = self else { return } // LoadingAlert 숨기기 self.hideLoadingAlert() - // 3. UserManager 초기화 및 로그인 화면으로 이동 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - userManager.clearUserInfo() // 로컬 캐시 정리 - introViewModel.isLoggedIn = false - introViewModel.needsProfileSetup = false + if let error = error { + // Auth 삭제 실패 + print("회원탈퇴 Auth 삭제 실패: \(error.localizedDescription)") + ToastManager.shared.show() + } else { + // 3. UserManager 초기화 및 로그인 화면으로 이동 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + userManager.clearUserInfo() // 로컬 캐시 정리 + introViewModel.isLoggedIn = false + introViewModel.needsProfileSetup = false + } } } + + case .failure: + // LoadingAlert 숨기기 + self.hideLoadingAlert() + ToastManager.shared.show() } } } @@ -325,6 +298,7 @@ class MyPageViewModel { if error != nil { // LoadingAlert 숨기기 self.hideLoadingAlert() + ToastManager.shared.show() } else { // 재인증 성공 → 회원탈퇴 진행 self.deleteAccountAfterReauth(user: user, userManager: userManager, introViewModel: introViewModel) diff --git a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/collectionGuiding.imageset/collectionGuiding.pdf b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/collectionGuiding.imageset/collectionGuiding.pdf index bfd6b8f85..fd8ac1f34 100644 Binary files a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/collectionGuiding.imageset/collectionGuiding.pdf and b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/collectionGuiding.imageset/collectionGuiding.pdf differ diff --git a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/Contents.json b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/Contents.json index a2b896e2f..e4c148d47 100644 --- a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/Contents.json +++ b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "homeGuiding.pdf", + "filename" : "d.pdf", "idiom" : "universal" } ], diff --git a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/d.pdf b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/d.pdf new file mode 100644 index 000000000..7a3e3a6d5 Binary files /dev/null and b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/d.pdf differ diff --git a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/homeGuiding.pdf b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/homeGuiding.pdf deleted file mode 100644 index 6abafdebc..000000000 Binary files a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/homeGuiding.imageset/homeGuiding.pdf and /dev/null differ diff --git a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/workshopGuiding.imageset/workshopGuiding.pdf b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/workshopGuiding.imageset/workshopGuiding.pdf index 32b06209d..8fbf168eb 100644 Binary files a/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/workshopGuiding.imageset/workshopGuiding.pdf and b/Keychy/Keychy/Resources/Assets.xcassets/15. IntroGuiding/workshopGuiding.imageset/workshopGuiding.pdf differ