Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 99 additions & 29 deletions Keychy/Keychy/Core/Firebase/UserManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
}
Expand All @@ -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))
}
}
}
Expand Down Expand Up @@ -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("🎉 모든 데이터 삭제 완료")
Expand Down
80 changes: 27 additions & 53 deletions Keychy/Keychy/Presentation/Home/ViewModels/MyPageViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,84 +191,57 @@ class MyPageViewModel {

// MARK: - Delete Account

/// 회원탈퇴
/// 회원탈퇴 - 항상 재인증 먼저 진행 (데이터 보호)
func deleteAccount(userManager: UserManager, introViewModel: IntroViewModel) {
// 네트워크 체크
guard NetworkManager.shared.isConnected else {
ToastManager.shared.show()
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()
}
}
}
Expand Down Expand Up @@ -325,6 +298,7 @@ class MyPageViewModel {
if error != nil {
// LoadingAlert 숨기기
self.hideLoadingAlert()
ToastManager.shared.show()
} else {
// 재인증 성공 → 회원탈퇴 진행
self.deleteAccountAfterReauth(user: user, userManager: userManager, introViewModel: introViewModel)
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "homeGuiding.pdf",
"filename" : "d.pdf",
"idiom" : "universal"
}
],
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.