From ef6f42f3428c5b4ccb97c1857480d9b62dd93d65 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 28 May 2025 14:15:35 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[#48]=20WithdrawalUseCase=EC=97=90=20Reposi?= =?UTF-8?q?tory=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In/Sources/App/DIContainer+.swift | 4 +++- .../UseCases/Implements/WithdrawalUseCaseImpl.swift | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Mark-In/Sources/App/DIContainer+.swift b/Mark-In/Sources/App/DIContainer+.swift index b2f26d6..f73f900 100644 --- a/Mark-In/Sources/App/DIContainer+.swift +++ b/Mark-In/Sources/App/DIContainer+.swift @@ -75,7 +75,9 @@ private extension DIContainer { ) let withdrawalUseCase: WithdrawalUseCase = WithdrawalUseCaseImpl( keychainStore: resolve(), - authUserManager: resolve() + authUserManager: resolve(), + linkRepository: resolve(), + folderRepsoitory: resolve() ) register(fetchLinkListUseCase) diff --git a/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift index a98af7c..04d172c 100644 --- a/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift +++ b/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift @@ -15,9 +15,19 @@ struct WithdrawalUseCaseImpl: WithdrawalUseCase { private let keychainStore: KeychainStore private let authUserManager: AuthUserManager - init(keychainStore: KeychainStore, authUserManager: AuthUserManager) { + private let linkRepository: LinkRepository + private let folderRepsoitory: FolderRepository + + init( + keychainStore: KeychainStore, + authUserManager: AuthUserManager, + linkRepository: LinkRepository, + folderRepsoitory: FolderRepository + ) { self.keychainStore = keychainStore self.authUserManager = authUserManager + self.linkRepository = linkRepository + self.folderRepsoitory = folderRepsoitory } func execute() async throws { From 00c1778e5959283cc1a562ea05b65d067941cf39 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 28 May 2025 14:37:42 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[#48]=20LinkRepository=EC=97=90=20deleteAll?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/LinkRepositoryImpl.swift | 27 +++++++++++++++++++ .../Domain/Interfaces/LinkRepository.swift | 1 + 2 files changed, 28 insertions(+) diff --git a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift index ca8e109..f752726 100644 --- a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift @@ -125,6 +125,33 @@ struct LinkRepositoryImpl: LinkRepository { /// 3. Link 삭제 try await linkDocRef.delete() } + + func deleteAll(userID: String) async throws { + /// 1. Links 컬렉션 참조 생성 + let path = FirebaseEndpoint.FirestoreDB.links(userID: userID).path + let linkColRef = db.collection(path) + + /// 2. 컬렉션의 모든 문서 가져오기 + let snapshot = try await linkColRef.getDocuments() + + /// 3. 병렬 작업으로 데이터 삭제 + try await withThrowingTaskGroup(of: Void.self) { group in + /// 모둔 문서에 순차 접근 + snapshot.documents.forEach { document in + /// Link 데이터 삭제 + group.addTask { + try await document.reference.delete() + } + + /// 이미지 데이터 삭제 + group.addTask { + try await deleteImageData(userID: userID, fileID: document.documentID) + } + } + + try await group.waitForAll() + } + } } private extension LinkRepositoryImpl { diff --git a/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift b/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift index 32c665f..8238596 100644 --- a/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift +++ b/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift @@ -12,4 +12,5 @@ protocol LinkRepository { func fetchAll(userID: String) async throws -> [WebLink] func update(userID: String, link: WebLink) async throws func delete(userID: String, link: WebLink) async throws + func deleteAll(userID: String) async throws } From e88bf58eaf7d0bf4639fd2cf8ab3ff17bb559ac4 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 28 May 2025 14:40:21 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[#48]=20FolderRepository=EC=97=90=20deleteA?= =?UTF-8?q?ll=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/FolderRepositoryImpl.swift | 20 +++++++++++++++++++ .../Domain/Interfaces/FolderRepository.swift | 1 + 2 files changed, 21 insertions(+) diff --git a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift index 7c87bb8..a5db1b4 100644 --- a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift @@ -92,4 +92,24 @@ struct FolderRepositoryImpl: FolderRepository { /// 2. Folder 삭제 try await folderDocRef.delete() } + + func deleteAll(userID: String) async throws { + /// 1. Folders 컬렉션 참조 생성 + let path = FirebaseEndpoint.FirestoreDB.folders(userID: userID).path + let folderColRef = db.collection(path) + + /// 2. 컬렉션의 모든 문서 가져오기 + let snapshot = try await folderColRef.getDocuments() + + /// 3. 모든 데이터 병렬 작업으로 삭제 + try await withThrowingTaskGroup(of: Void.self) { group in + snapshot.documents.forEach { document in + group.addTask { + try await document.reference.delete() + } + } + + try await group.waitForAll() + } + } } diff --git a/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift b/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift index f0f472e..c2152cd 100644 --- a/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift +++ b/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift @@ -12,4 +12,5 @@ protocol FolderRepository { func fetchAll(userID: String) async throws -> [Folder] func update(userID: String, folder: Folder) async throws func delete(userID: String, folder: Folder) async throws + func deleteAll(userID: String) async throws } From 5866d389240bd3a6d0d230f4ad86773e960628c0 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 28 May 2025 14:49:37 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[#48]=20WithdrawalUseCaseImpl=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Implements/WithdrawalUseCaseImpl.swift | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift index 04d172c..362650e 100644 --- a/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift +++ b/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift @@ -41,24 +41,34 @@ struct WithdrawalUseCaseImpl: WithdrawalUseCase { switch provider { case .apple: - let token: String? = try? keychainStore.load(forKey: .refreshToken) - guard let token else { return } - - let url = URL(string: "https://\(Config.value(forKey: .revokeTokenURL))/revokeToken?refresh_token=\(token)" - .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)! - - _ = try await URLSession.shared.data(from: url) - - try keychainStore.delete(forKey: .refreshToken) + try await revokeAppleAuthorization() case .google: - try? await GIDSignIn.sharedInstance.disconnect() - GIDSignIn.sharedInstance.signOut() + try await revokeGoogleAuthorization() @unknown default: - fatalError("") + fatalError("Do not implement revoke function for provider: \(provider)") } authUserManager.clear() } } + +private extension WithdrawalUseCaseImpl { + func revokeAppleAuthorization() async throws { + let token: String? = try? keychainStore.load(forKey: .refreshToken) + guard let token else { return } + + let url = URL(string: "https://\(Config.value(forKey: .revokeTokenURL))/revokeToken?refresh_token=\(token)" + .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)! + + _ = try await URLSession.shared.data(from: url) + + try keychainStore.delete(forKey: .refreshToken) + } + + func revokeGoogleAuthorization() async throws { + try? await GIDSignIn.sharedInstance.disconnect() + GIDSignIn.sharedInstance.signOut() + } +} From 58515fc5c775aaf646344145465c62fee2ee879a Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 28 May 2025 14:59:46 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[#48]=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=8B=9C=20Firebase=EC=97=90=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EB=90=9C=20=EB=AA=A8=EB=93=A0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Implements/WithdrawalUseCaseImpl.swift | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift index 362650e..05335a4 100644 --- a/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift +++ b/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift @@ -31,15 +31,30 @@ struct WithdrawalUseCaseImpl: WithdrawalUseCase { } func execute() async throws { + /// 현재 로그인 된 유저 상태 확인 후 인증 정보 삭제 do { try await Auth.auth().currentUser?.delete() } catch { throw WithdrawalError.credentialTooOld } - guard let provider = authUserManager.user?.provider else { return } + guard let user = authUserManager.user else { return } - switch provider { + /// 사용자의 모든 데이터(링크, 폴더) 삭제 + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + try await linkRepository.deleteAll(userID: user.id) + } + + group.addTask { + try await folderRepsoitory.deleteAll(userID: user.id) + } + + try await group.waitForAll() + } + + /// OAuth 인증 해제 + switch user.provider { case .apple: try await revokeAppleAuthorization() @@ -47,7 +62,7 @@ struct WithdrawalUseCaseImpl: WithdrawalUseCase { try await revokeGoogleAuthorization() @unknown default: - fatalError("Do not implement revoke function for provider: \(provider)") + fatalError("Do not implement revoke function for provider: \(user.provider)") } authUserManager.clear()