From d5df54e2341e27432880c715bb465065f8dc85ab Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Tue, 3 Jun 2025 14:19:47 +0900 Subject: [PATCH 01/13] =?UTF-8?q?[#50]=20Link,=20Folder=20Repository=20Del?= =?UTF-8?q?ete=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift | 3 +-- Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift | 4 ++-- Mark-In/Sources/Domain/Interfaces/FolderRepository.swift | 2 +- Mark-In/Sources/Domain/Interfaces/LinkRepository.swift | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift index a5db1b4..cffbc34 100644 --- a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift @@ -83,9 +83,8 @@ struct FolderRepositoryImpl: FolderRepository { } } - func delete(userID: String, folder: Folder) async throws { + func delete(userID: String, folderID: String) async throws { /// 1. Folder 문서 참조 생성 - guard let folderID = folder.id else { return } let path = FirebaseEndpoint.FirestoreDB.folder(userID: userID, folderID: folderID).path let folderDocRef = db.document(path) diff --git a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift index f752726..332a2b2 100644 --- a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift @@ -114,9 +114,9 @@ struct LinkRepositoryImpl: LinkRepository { } } - func delete(userID: String, link: WebLink) async throws { + func delete(userID: String, linkID: String) async throws { /// 1. Link 문서 참조 생성 - let path = FirebaseEndpoint.FirestoreDB.link(userID: userID, linkID: link.id).path + let path = FirebaseEndpoint.FirestoreDB.link(userID: userID, linkID: linkID).path let linkDocRef = db.document(path) /// 2. 이미지 데이터 삭제 diff --git a/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift b/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift index c2152cd..2d90bcd 100644 --- a/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift +++ b/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift @@ -11,6 +11,6 @@ protocol FolderRepository { func create(userID: String, folder: WriteFolder) async throws -> Folder func fetchAll(userID: String) async throws -> [Folder] func update(userID: String, folder: Folder) async throws - func delete(userID: String, folder: Folder) async throws + func delete(userID: String, folderID: String) async throws func deleteAll(userID: String) async throws } diff --git a/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift b/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift index 8238596..9ed6a8e 100644 --- a/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift +++ b/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift @@ -11,6 +11,6 @@ protocol LinkRepository { func create(userID: String, link: WriteLink) async throws -> WebLink func fetchAll(userID: String) async throws -> [WebLink] func update(userID: String, link: WebLink) async throws - func delete(userID: String, link: WebLink) async throws + func delete(userID: String, linkID: String) async throws func deleteAll(userID: String) async throws } From da87898a46a33366a5dcc707c83e196dd3df1831 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Tue, 3 Jun 2025 14:20:39 +0900 Subject: [PATCH 02/13] =?UTF-8?q?[#50]=20DeleteLinkUseCase=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Implements/DeleteLinkUseCaseImpl.swift | 22 +++++++++++++++++++ .../Interfaces/DeleteLinkUseCase.swift | 12 ++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift create mode 100644 Mark-In/Sources/Domain/UseCases/Interfaces/DeleteLinkUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift new file mode 100644 index 0000000..5d48d8e --- /dev/null +++ b/Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift @@ -0,0 +1,22 @@ +// +// DeleteLinkUseCaseImpl.swift +// Mark-In +// +// Created by 이정동 on 6/3/25. +// + +import Foundation + +struct DeleteLinkUseCaseImpl: DeleteLinkUseCase { + + private let authUserManager: AuthUserManager + private let linkRepository: LinkRepository + + func execute(linkID: String) async throws { + guard let userID = authUserManager.user?.id else { + throw AuthError.unauthenticated + } + + try await linkRepository.delete(userID: userID, linkID: linkID) + } +} diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/DeleteLinkUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/DeleteLinkUseCase.swift new file mode 100644 index 0000000..2dfd783 --- /dev/null +++ b/Mark-In/Sources/Domain/UseCases/Interfaces/DeleteLinkUseCase.swift @@ -0,0 +1,12 @@ +// +// DeleteLinkUseCase.swift +// Mark-In +// +// Created by 이정동 on 6/3/25. +// + +import Foundation + +protocol DeleteLinkUseCase { + func execute(linkID: String) async throws +} From 6378b43338c4c5b8125befecdaaec6991668a68b Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Tue, 3 Jun 2025 16:46:08 +0900 Subject: [PATCH 03/13] =?UTF-8?q?[#50]=20Link,=20Folder=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/FolderRepositoryImpl.swift | 29 ++++++-------- .../Repositories/LinkRepositoryImpl.swift | 40 ++++++++++--------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift index cffbc34..1914de2 100644 --- a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift @@ -20,27 +20,24 @@ struct FolderRepositoryImpl: FolderRepository { let path = FirebaseEndpoint.FirestoreDB.folders(userID: userID).path let folderDocRef = db.collection(path).document() - /// 2. Firestore에 저장할 DTO 객체 생성 - let folderDTO = FolderDTO( + /// 2. 필드 값 생성 + let createdBy = Date() + + /// 3. Firestore에 추가 + try await folderDocRef.setData([ + "id": folderDocRef.documentID, + "name": folder.name, + "createdBy": createdBy + ]) + + /// 4. 생성된 데이터 반환 + let folderEntity = Folder( id: folderDocRef.documentID, name: folder.name, createdBy: .now ) - /// 3. Firestore에 추가 - try await withCheckedThrowingContinuation { (continuation: VoidCheckedContinuation) in - do { - try folderDocRef.setData(from: folderDTO) { error in - if let error { continuation.resume(throwing: error) } - else { continuation.resume(returning: ()) } - } - } catch { - continuation.resume(throwing: error) - } - } - - /// 4. DocumentID가 업데이트 된 Folder Entity로 리턴 - return folderDTO.toEntity() + return folderEntity } func fetchAll(userID: String) async throws -> [Folder] { diff --git a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift index 332a2b2..0269a49 100644 --- a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift @@ -40,33 +40,37 @@ struct LinkRepositoryImpl: LinkRepository { metadata: metadata ) - /// 4. Firestore에 저장할 DTO 객체 생성 - let linkDTO = WebLinkDTO( + /// 4. 필드 값 설정 + let title = link.title ?? metadata.title + let createdBy = Date() + + /// 5. Firestore에 추가 + try await linkDocRef.setData([ + "id": linkDocRef.documentID, + "url": link.url, + "title": title ?? NSNull(), + "thumbnailUrl": imageUrls.thumbnail ?? NSNull(), + "faviconUrl": imageUrls.favicon ?? NSNull(), + "isPinned": false, + "createdBy": createdBy, + "lastAccessedAt": NSNull(), + "folderID": link.folderID ?? NSNull() + ]) + + /// 6. 생성된 데이터 반환 + let linkEntity = WebLink( id: linkDocRef.documentID, url: link.url, - title: link.title ?? metadata.title, + title: title, thumbnailUrl: imageUrls.thumbnail, faviconUrl: imageUrls.favicon, isPinned: false, - createdBy: .now, + createdBy: createdBy, lastAccessedAt: nil, folderID: link.folderID ) - /// 5. Firestore에 추가 - try await withCheckedThrowingContinuation { (continuation: VoidCheckedContinuation) in - do { - try linkDocRef.setData(from: linkDTO) { error in - if let error { continuation.resume(throwing: error) } - else { continuation.resume(returning: ()) } - } - } catch { - continuation.resume(throwing: error) - } - } - - /// 6. 저장된 DTO 객체를 Entity로 변환 후 리턴 - return linkDTO.toEntity() + return linkEntity } func fetchAll(userID: String) async throws -> [WebLink] { From 47f91a6556470d95da1b05c08f90a5aa5d52f361 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 13:10:38 +0900 Subject: [PATCH 04/13] =?UTF-8?q?[#50]=20LinkRepository=20=EB=82=B4=20fold?= =?UTF-8?q?erID=20=EB=B3=80=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/LinkRepositoryImpl.swift | 82 ++++++++++++++----- .../Domain/Interfaces/LinkRepository.swift | 17 +++- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift index 0269a49..c248781 100644 --- a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift @@ -87,34 +87,47 @@ struct LinkRepositoryImpl: LinkRepository { } } - func update(userID: String, link: WebLink) async throws { + + func moveLinkInFolder( + userID: String, + target linkID: String, + to folderID: String? + ) async throws { /// 1. Link 문서 참조 생성 - let path = FirebaseEndpoint.FirestoreDB.link(userID: userID, linkID: link.id).path + let path = FirebaseEndpoint.FirestoreDB.link(userID: userID, linkID: linkID).path let linkDocRef = db.document(path) - /// 2. Entity를 DTO로 변환 - let linkDTO = WebLinkDTO( - id: link.id, - url: link.url, - title: link.title, - thumbnailUrl: link.thumbnailUrl, - faviconUrl: link.faviconUrl, - isPinned: link.isPinned, - createdBy: link.createdBy, - lastAccessedAt: link.lastAccessedAt, - folderID: link.folderID - ) + /// 2. 문서 업데이트 + try await linkDocRef.updateData([ + "folderID": folderID ?? NSNull() + ]) + } + + func moveLinksInFolder( + userID: String, + fromFolderID: String?, + toFolderID: String? + ) async throws { + /// 1. Link 컬렉션 참조 생성 + let path = FirebaseEndpoint.FirestoreDB.links(userID: userID).path + let linkColRef = db.collection(path) + + /// 2. 조건에 해당하는 모든 문서 가져오기 + let querySnapshot = try await linkColRef + .whereField("folderID", isEqualTo: fromFolderID ?? NSNull()) + .getDocuments() - /// 3. 업데이트 - try await withCheckedThrowingContinuation { (continuation: VoidCheckedContinuation) in - do { - try linkDocRef.setData(from: linkDTO) { error in - if let error { continuation.resume(throwing: error) } - else { continuation.resume(returning: ()) } + /// 3. 병렬 작업으로 문서 업데이트 + try await withThrowingTaskGroup(of: Void.self) { group in + querySnapshot.documents.forEach { document in + group.addTask { + try await document.reference.updateData([ + "folderID": toFolderID ?? NSNull() + ]) } - } catch { - continuation.resume(throwing: error) } + + try await group.waitForAll() } } @@ -130,6 +143,31 @@ struct LinkRepositoryImpl: LinkRepository { try await linkDocRef.delete() } + func deleteAllInFolder(userID: String, folderID: String?) async throws { + let path = FirebaseEndpoint.FirestoreDB.links(userID: userID).path + let querySnapshot = try await db.collection(path) + .whereField("folderID", isEqualTo: folderID ?? NSNull()) + .getDocuments() + + /// 3. 병렬 작업으로 데이터 삭제 + try await withThrowingTaskGroup(of: Void.self) { group in + /// 모둔 문서에 순차 접근 + querySnapshot.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() + } + } + func deleteAll(userID: String) async throws { /// 1. Links 컬렉션 참조 생성 let path = FirebaseEndpoint.FirestoreDB.links(userID: userID).path diff --git a/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift b/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift index 9ed6a8e..2e3ae77 100644 --- a/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift +++ b/Mark-In/Sources/Domain/Interfaces/LinkRepository.swift @@ -9,8 +9,23 @@ import Foundation protocol LinkRepository { func create(userID: String, link: WriteLink) async throws -> WebLink + func fetchAll(userID: String) async throws -> [WebLink] - func update(userID: String, link: WebLink) async throws + + func moveLinkInFolder( + userID: String, + target linkID: String, + to folderID: String? + ) async throws + + func moveLinksInFolder( + userID: String, + fromFolderID: String?, + toFolderID: String? + ) async throws + + func delete(userID: String, linkID: String) async throws + func deleteAllInFolder(userID: String, folderID: String?) async throws func deleteAll(userID: String) async throws } From e581509db7d3739d671148e62dcbf4132dfb2d4d Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 13:11:20 +0900 Subject: [PATCH 05/13] =?UTF-8?q?[#50]=20DeleteFolderUseCase=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Implements/DeleteFolderUseCaseImpl.swift | 40 +++++++++++++++++++ .../Interfaces/DeleteFolderUseCase.swift | 12 ++++++ 2 files changed, 52 insertions(+) create mode 100644 Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift create mode 100644 Mark-In/Sources/Domain/UseCases/Interfaces/DeleteFolderUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift new file mode 100644 index 0000000..1ebe965 --- /dev/null +++ b/Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift @@ -0,0 +1,40 @@ +// +// DeleteFolderUseCaseImpl.swift +// Mark-In +// +// Created by 이정동 on 6/3/25. +// + +import Foundation + +struct DeleteFolderUseCaseImpl: DeleteFolderUseCase { + + private let authUserManager: AuthUserManager + + private let linkRepository: LinkRepository + private let folderRepository: FolderRepository + + func execute(folderID: String?, includingChildren: Bool) async throws { + guard let userID = authUserManager.user?.id else { + throw AuthError.unauthenticated + } + + /// 폴더 하위 데이터까지 삭제 + if includingChildren { + try await linkRepository.deleteAllInFolder(userID: userID, folderID: folderID) + + /// 폴더 하위 데이터는 삭제하지 않음 -> 기본 폴더로 위치 변경시키기 + } else { + try await linkRepository.moveLinksInFolder( + userID: userID, + fromFolderID: folderID, + toFolderID: nil + ) + } + + /// 폴더ID가 존재할 경우 (= 기본 폴더가 아닌 경우) 폴더 삭제 + if let folderID { + try await folderRepository.delete(userID: userID, folderID: folderID) + } + } +} diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/DeleteFolderUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/DeleteFolderUseCase.swift new file mode 100644 index 0000000..497c786 --- /dev/null +++ b/Mark-In/Sources/Domain/UseCases/Interfaces/DeleteFolderUseCase.swift @@ -0,0 +1,12 @@ +// +// DeleteFolderUseCase.swift +// Mark-In +// +// Created by 이정동 on 6/3/25. +// + +import Foundation + +protocol DeleteFolderUseCase { + func execute(folderID: String?, includingChildren: Bool) async throws +} From 47f8b7e15204467f578586e69a5c9d0d43854e77 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 13:21:21 +0900 Subject: [PATCH 06/13] =?UTF-8?q?[#50]=20DIContainer=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=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 | 11 +++++++++++ .../UseCases/Implements/DeleteFolderUseCaseImpl.swift | 10 ++++++++++ .../UseCases/Implements/DeleteLinkUseCaseImpl.swift | 5 +++++ 3 files changed, 26 insertions(+) diff --git a/Mark-In/Sources/App/DIContainer+.swift b/Mark-In/Sources/App/DIContainer+.swift index f73f900..d5a40e7 100644 --- a/Mark-In/Sources/App/DIContainer+.swift +++ b/Mark-In/Sources/App/DIContainer+.swift @@ -79,6 +79,15 @@ private extension DIContainer { linkRepository: resolve(), folderRepsoitory: resolve() ) + let deleteLinkUseCase: DeleteLinkUseCase = DeleteLinkUseCaseImpl( + authUserManager: resolve(), + linkRepository: resolve() + ) + let deleteFolderUseCase: DeleteFolderUseCase = DeleteFolderUseCaseImpl( + authUserManager: resolve(), + linkRepository: resolve(), + folderRepository: resolve() + ) register(fetchLinkListUseCase) register(fetchFolderListUseCase) @@ -87,5 +96,7 @@ private extension DIContainer { register(signInUseCase) register(signOutUseCase) register(withdrawalUseCase) + register(deleteLinkUseCase) + register(deleteFolderUseCase) } } diff --git a/Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift index 1ebe965..9e5c402 100644 --- a/Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift +++ b/Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift @@ -14,6 +14,16 @@ struct DeleteFolderUseCaseImpl: DeleteFolderUseCase { private let linkRepository: LinkRepository private let folderRepository: FolderRepository + init( + authUserManager: AuthUserManager, + linkRepository: LinkRepository, + folderRepository: FolderRepository + ) { + self.authUserManager = authUserManager + self.linkRepository = linkRepository + self.folderRepository = folderRepository + } + func execute(folderID: String?, includingChildren: Bool) async throws { guard let userID = authUserManager.user?.id else { throw AuthError.unauthenticated diff --git a/Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift index 5d48d8e..1251cf6 100644 --- a/Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift +++ b/Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift @@ -12,6 +12,11 @@ struct DeleteLinkUseCaseImpl: DeleteLinkUseCase { private let authUserManager: AuthUserManager private let linkRepository: LinkRepository + init(authUserManager: AuthUserManager, linkRepository: LinkRepository) { + self.authUserManager = authUserManager + self.linkRepository = linkRepository + } + func execute(linkID: String) async throws { guard let userID = authUserManager.user?.id else { throw AuthError.unauthenticated From 23f1ff61d44a64a732e6bbcb3e43611a5fa7b36e Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 15:02:06 +0900 Subject: [PATCH 07/13] =?UTF-8?q?[#50]=20Reducer=EC=97=90=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94,=20=EB=A7=81=ED=81=AC=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Feature/Main/MainReducer.swift | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Mark-In/Sources/Feature/Main/MainReducer.swift b/Mark-In/Sources/Feature/Main/MainReducer.swift index 57b4901..4c6b97f 100644 --- a/Mark-In/Sources/Feature/Main/MainReducer.swift +++ b/Mark-In/Sources/Feature/Main/MainReducer.swift @@ -33,6 +33,9 @@ struct MainReducer: Reducer { case didCreateLink(WebLink) case didCreateFolder(Folder) + case deleteLinkButtonTapped(link: WebLink) + case deleteFolderButtonTapped(folder: Folder, includingChildren: Bool) + case occuredError case empty @@ -40,6 +43,8 @@ struct MainReducer: Reducer { @Dependency private var fetchLinkListUseCase: FetchLinkListUseCase @Dependency private var fetchFolderListUseCase: FetchFolderListUseCase + @Dependency private var deleteLinkUseCase: DeleteLinkUseCase + @Dependency private var deleteFolderUseCase: DeleteFolderUseCase func reduce(into state: inout State, action: Action) -> Effect { switch action { @@ -59,7 +64,7 @@ struct MainReducer: Reducer { state.links = links - state.folderTabs.append(.folder(.init(id: nil, name: "기본폴더", createdBy: .now))) + state.folderTabs = [.folder(.init(id: nil, name: "기본폴더", createdBy: .now))] folders.forEach { state.folderTabs.append( .folder(.init(id: $0.id, name: $0.name, createdBy: $0.createdBy)) @@ -85,6 +90,28 @@ struct MainReducer: Reducer { state.folderTabs.append(.folder(folder)) return .none + case .deleteLinkButtonTapped(let link): + state.isLoading = true + return .run { + do { + try await self.deleteLinkUseCase.execute(linkID: link.id) + return .onAppear + } catch { + return .occuredError + } + } + + case let .deleteFolderButtonTapped(folder, includingChildren): + state.isLoading = true + return .run { + do { + try await self.deleteFolderUseCase.execute(folderID: folder.id, includingChildren: includingChildren) + return .onAppear + } catch { + return .occuredError + } + } + // TODO: 에러 처리 로직 추가 case .occuredError: return .none From d97464a533673ea04132bcaae401cb9b0af051a5 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 15:02:30 +0900 Subject: [PATCH 08/13] =?UTF-8?q?[#50]=20View=EC=97=90=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94,=20=EB=A7=81=ED=81=AC=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/AddFolder/AddFolderView.swift | 2 +- .../Sources/Feature/Main/LinkListView.swift | 28 ++++++++++-- Mark-In/Sources/Feature/Main/SideBar.swift | 44 +++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift b/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift index 458de0c..8ff6384 100644 --- a/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift +++ b/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift @@ -55,7 +55,7 @@ struct AddFolderView: View { .stroke(.markBlack10, lineWidth: 0.5) } } - .disabled(title.isEmpty || isSaving) + .disabled(isSaving) Button { store.send(.didTapAddFolderButton(name: title)) diff --git a/Mark-In/Sources/Feature/Main/LinkListView.swift b/Mark-In/Sources/Feature/Main/LinkListView.swift index e94c70b..5bc95df 100644 --- a/Mark-In/Sources/Feature/Main/LinkListView.swift +++ b/Mark-In/Sources/Feature/Main/LinkListView.swift @@ -47,7 +47,10 @@ struct LinkListView: View { spacing: ViewConstants.spacing ) { ForEach(links, id: \.self) { link in - LinkCell(link: link) + LinkCell( + store: store, + link: link + ) } } .padding(20) @@ -76,6 +79,7 @@ struct LinkListView: View { private struct LinkCell: View { + let store: Store let link: WebLink var body: some View { @@ -115,7 +119,13 @@ private struct LinkCell: View { RoundedRectangle(cornerRadius: 6) .stroke(.markBlack20, lineWidth: 0.5) }) - + .contextMenu { + Button { + store.send(.deleteLinkButtonTapped(link: link)) + } label: { + Text("삭제") + } + } } private var headerTitle: some View { @@ -158,7 +168,19 @@ private struct LinkCell: View { } #Preview { - LinkCell(link: .init(id: "", url: "www.naver.com", isPinned: true, createdBy: .now)) + let store: Store = .init( + initialState: MainReducer.State(), + reducer: MainReducer() + ) + LinkCell( + store: store, + link: .init( + id: "", + url: "www.naver.com", + isPinned: true, + createdBy: .now + ) + ) .frame(width: 210, height: 160) // LinkListView(viewModel: MainViewModel()) // .frame(width: 600, height: 600) diff --git a/Mark-In/Sources/Feature/Main/SideBar.swift b/Mark-In/Sources/Feature/Main/SideBar.swift index 90a4ad5..87f8ee2 100644 --- a/Mark-In/Sources/Feature/Main/SideBar.swift +++ b/Mark-In/Sources/Feature/Main/SideBar.swift @@ -13,6 +13,9 @@ import ReducerKit struct SideBar: View { let store: Store + @State private var isPresentedDialog: Bool = false + @State private var deleteFolder: Folder? + var body: some View { VStack(alignment: .leading) { @@ -39,6 +42,17 @@ struct SideBar: View { NavigationLink(value: tab) { Label(tab.title, systemImage: tab.icon) } + .contextMenu { + if case .folder(let folder) = tab { + Button { + isPresentedDialog = true + deleteFolder = folder + } label: { + Text("삭제") + } + .disabled(folder.id == nil) + } + } } } } @@ -60,6 +74,36 @@ struct SideBar: View { .padding([.bottom, .leading], 10) } .buttonStyle(.plain) + .confirmationDialog( + "이 폴더를 삭제하시겠습니까?", + isPresented: $isPresentedDialog + ) { + Button(role: .destructive) { + store.send(.deleteFolderButtonTapped( + folder: deleteFolder!, + includingChildren: false + )) + } label: { + Text("폴더만 삭제") + } + + Button(role: .destructive) { + store.send(.deleteFolderButtonTapped( + folder: deleteFolder!, + includingChildren: true + )) + } label: { + Text("폴더와 링크 삭제") + } + + Button(role: .cancel) { + deleteFolder = nil + } label: { + Text("취소") + } + } message: { + Text("이 폴더를 삭제하면 하위 링크도 함께 삭제하거나, 그대로 유지할 수 있습니다.") + } } } From 3bc33e3351146e15bbdcca2596b4a8b545005ae6 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 15:22:55 +0900 Subject: [PATCH 09/13] =?UTF-8?q?[#50]=20AddFolderReducer=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In/Sources/Feature/AddFolder/AddFolderReducer.swift | 5 ++--- Mark-In/Sources/Feature/AddFolder/AddFolderView.swift | 9 +++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Mark-In/Sources/Feature/AddFolder/AddFolderReducer.swift b/Mark-In/Sources/Feature/AddFolder/AddFolderReducer.swift index 2c90e9a..4ecdd43 100644 --- a/Mark-In/Sources/Feature/AddFolder/AddFolderReducer.swift +++ b/Mark-In/Sources/Feature/AddFolder/AddFolderReducer.swift @@ -18,7 +18,7 @@ struct AddFolderReducer: Reducer { } enum Action { - case didTapAddFolderButton(name: String) + case didTapAddFolderButton(WriteFolder) case didCompleteSave(Folder) case updateErrorState(Bool) } @@ -27,12 +27,11 @@ struct AddFolderReducer: Reducer { func reduce(into state: inout State, action: Action) -> Effect { switch action { - case .didTapAddFolderButton(let name): + case .didTapAddFolderButton(let writeFolder): state.isLoading = true return .run { do { - let writeFolder = WriteFolder(name: name) let result = try await self.generateFolderUseCase.execute(writeFolder: writeFolder) return .didCompleteSave(result) } catch { diff --git a/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift b/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift index 8ff6384..c3d8837 100644 --- a/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift +++ b/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift @@ -16,7 +16,7 @@ struct AddFolderView: View { initialState: AddFolderReducer.State(), reducer: AddFolderReducer() ) - @State private var title: String = "" + @State private var name: String = "" private var isSaving: Bool { store.state.isLoading @@ -29,7 +29,7 @@ struct AddFolderView: View { Text("폴더를 추가:") .frame(maxWidth: .infinity, alignment: .leading) - TextField("", text: $title, prompt: Text("제목")) + TextField("", text: $name, prompt: Text("제목")) .textFieldStyle(.roundedBorder) .padding(.top, 14) .disabled(isSaving) @@ -58,7 +58,8 @@ struct AddFolderView: View { .disabled(isSaving) Button { - store.send(.didTapAddFolderButton(name: title)) + let writeFolder = WriteFolder(name: name) + store.send(.didTapAddFolderButton(writeFolder)) } label: { Text("추가") .padding(.vertical, 4) @@ -67,7 +68,7 @@ struct AddFolderView: View { .background(.markPoint) .clipShape(RoundedRectangle(cornerRadius: 6)) } - .disabled(title.isEmpty || isSaving) + .disabled(name.isEmpty || isSaving) } .frame(maxWidth: .infinity, alignment: .trailing) .padding(.top, 18) From e5b3a9e322b5b5fccfc109d32e23c62ea138aa41 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 15:27:06 +0900 Subject: [PATCH 10/13] =?UTF-8?q?[#50]=20UseCase=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/UseCases/Implements/{ => Auth}/SignInUseCaseImpl.swift | 0 .../UseCases/Implements/{ => Auth}/SignOutUseCaseImpl.swift | 0 .../UseCases/Implements/{ => Auth}/WithdrawalUseCaseImpl.swift | 0 .../Implements/{ => Folder}/DeleteFolderUseCaseImpl.swift | 0 .../Implements/{ => Folder}/FetchFolderListUseCaseImpl.swift | 0 .../Implements/{ => Folder}/GenerateFolderUseCaseImpl.swift | 0 .../UseCases/Implements/{ => Link}/DeleteLinkUseCaseImpl.swift | 0 .../UseCases/Implements/{ => Link}/FetchLinkListUseCaseImpl.swift | 0 .../UseCases/Implements/{ => Link}/GenerateLinkUseCaseImpl.swift | 0 .../Domain/UseCases/Interfaces/{ => Auth}/SignInUseCase.swift | 0 .../Domain/UseCases/Interfaces/{ => Auth}/SignOutUseCase.swift | 0 .../Domain/UseCases/Interfaces/{ => Auth}/WithdrawalUseCase.swift | 0 .../UseCases/Interfaces/{ => Folder}/DeleteFolderUseCase.swift | 0 .../UseCases/Interfaces/{ => Folder}/FetchFolderListUseCase.swift | 0 .../UseCases/Interfaces/{ => Folder}/GenerateFolderUseCase.swift | 0 .../Domain/UseCases/Interfaces/{ => Link}/DeleteLinkUseCase.swift | 0 .../UseCases/Interfaces/{ => Link}/FetchLinkListUseCase.swift | 0 .../UseCases/Interfaces/{ => Link}/GenerateLinkUseCase.swift | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Auth}/SignInUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Auth}/SignOutUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Auth}/WithdrawalUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Folder}/DeleteFolderUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Folder}/FetchFolderListUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Folder}/GenerateFolderUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Link}/DeleteLinkUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Link}/FetchLinkListUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Implements/{ => Link}/GenerateLinkUseCaseImpl.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Auth}/SignInUseCase.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Auth}/SignOutUseCase.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Auth}/WithdrawalUseCase.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Folder}/DeleteFolderUseCase.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Folder}/FetchFolderListUseCase.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Folder}/GenerateFolderUseCase.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Link}/DeleteLinkUseCase.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Link}/FetchLinkListUseCase.swift (100%) rename Mark-In/Sources/Domain/UseCases/Interfaces/{ => Link}/GenerateLinkUseCase.swift (100%) diff --git a/Mark-In/Sources/Domain/UseCases/Implements/SignInUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Auth/SignInUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/SignInUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Auth/SignInUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/SignOutUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Auth/SignOutUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/SignOutUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Auth/SignOutUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Auth/WithdrawalUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/WithdrawalUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Auth/WithdrawalUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Folder/DeleteFolderUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/DeleteFolderUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Folder/DeleteFolderUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/FetchFolderListUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Folder/FetchFolderListUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/FetchFolderListUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Folder/FetchFolderListUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/GenerateFolderUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Folder/GenerateFolderUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/GenerateFolderUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Folder/GenerateFolderUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Link/DeleteLinkUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/DeleteLinkUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Link/DeleteLinkUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/FetchLinkListUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Link/FetchLinkListUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/FetchLinkListUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Link/FetchLinkListUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Implements/GenerateLinkUseCaseImpl.swift b/Mark-In/Sources/Domain/UseCases/Implements/Link/GenerateLinkUseCaseImpl.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Implements/GenerateLinkUseCaseImpl.swift rename to Mark-In/Sources/Domain/UseCases/Implements/Link/GenerateLinkUseCaseImpl.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/SignInUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Auth/SignInUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/SignInUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Auth/SignInUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/SignOutUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Auth/SignOutUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/SignOutUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Auth/SignOutUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/WithdrawalUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Auth/WithdrawalUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/WithdrawalUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Auth/WithdrawalUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/DeleteFolderUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Folder/DeleteFolderUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/DeleteFolderUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Folder/DeleteFolderUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/FetchFolderListUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Folder/FetchFolderListUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/FetchFolderListUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Folder/FetchFolderListUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/GenerateFolderUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Folder/GenerateFolderUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/GenerateFolderUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Folder/GenerateFolderUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/DeleteLinkUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Link/DeleteLinkUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/DeleteLinkUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Link/DeleteLinkUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/FetchLinkListUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Link/FetchLinkListUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/FetchLinkListUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Link/FetchLinkListUseCase.swift diff --git a/Mark-In/Sources/Domain/UseCases/Interfaces/GenerateLinkUseCase.swift b/Mark-In/Sources/Domain/UseCases/Interfaces/Link/GenerateLinkUseCase.swift similarity index 100% rename from Mark-In/Sources/Domain/UseCases/Interfaces/GenerateLinkUseCase.swift rename to Mark-In/Sources/Domain/UseCases/Interfaces/Link/GenerateLinkUseCase.swift From 351c65d5ad7af1e14fe441b475c4cb3cb25d4cc6 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 15:42:14 +0900 Subject: [PATCH 11/13] =?UTF-8?q?[#50]=20createdBy=20->=20createdAt=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In/Sources/Data/DTOs/FolderDTO.swift | 4 ++-- Mark-In/Sources/Data/DTOs/WebLinkDTO.swift | 4 ++-- .../Sources/Data/Repositories/FolderRepositoryImpl.swift | 8 ++++---- .../Sources/Data/Repositories/LinkRepositoryImpl.swift | 6 +++--- Mark-In/Sources/Domain/Entities/Folder.swift | 2 +- Mark-In/Sources/Domain/Entities/WebLink.swift | 2 +- Mark-In/Sources/Feature/AddLink/AddLinkView.swift | 4 ++-- Mark-In/Sources/Feature/Main/LinkListView.swift | 4 ++-- Mark-In/Sources/Feature/Main/MainReducer.swift | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Mark-In/Sources/Data/DTOs/FolderDTO.swift b/Mark-In/Sources/Data/DTOs/FolderDTO.swift index 9b2ce42..6a17210 100644 --- a/Mark-In/Sources/Data/DTOs/FolderDTO.swift +++ b/Mark-In/Sources/Data/DTOs/FolderDTO.swift @@ -10,13 +10,13 @@ import Foundation struct FolderDTO: Codable { var id: String var name: String - var createdBy: Date + var createdAt: Date func toEntity() -> Folder { return Folder( id: self.id, name: self.name, - createdBy: self.createdBy + createdAt: self.createdAt ) } } diff --git a/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift b/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift index 087d46e..9ad7d92 100644 --- a/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift +++ b/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift @@ -14,7 +14,7 @@ struct WebLinkDTO: Codable { var thumbnailUrl: String? var faviconUrl: String? var isPinned: Bool - var createdBy: Date + var createdAt: Date var lastAccessedAt: Date? var folderID: String? @@ -26,7 +26,7 @@ struct WebLinkDTO: Codable { thumbnailUrl: self.thumbnailUrl, faviconUrl: self.faviconUrl, isPinned: self.isPinned, - createdBy: self.createdBy, + createdAt: self.createdAt, lastAccessedAt: self.lastAccessedAt, folderID: self.folderID ) diff --git a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift index 1914de2..e39eebe 100644 --- a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift @@ -21,20 +21,20 @@ struct FolderRepositoryImpl: FolderRepository { let folderDocRef = db.collection(path).document() /// 2. 필드 값 생성 - let createdBy = Date() + let createdAt = Date() /// 3. Firestore에 추가 try await folderDocRef.setData([ "id": folderDocRef.documentID, "name": folder.name, - "createdBy": createdBy + "createdAt": createdAt ]) /// 4. 생성된 데이터 반환 let folderEntity = Folder( id: folderDocRef.documentID, name: folder.name, - createdBy: .now + createdAt: .now ) return folderEntity @@ -64,7 +64,7 @@ struct FolderRepositoryImpl: FolderRepository { let folderDTO = FolderDTO( id: folderDocRef.documentID, name: folder.name, - createdBy: folder.createdBy + createdAt: folder.createdAt ) /// 3. 업데이트 diff --git a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift index c248781..33c13a9 100644 --- a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift @@ -42,7 +42,7 @@ struct LinkRepositoryImpl: LinkRepository { /// 4. 필드 값 설정 let title = link.title ?? metadata.title - let createdBy = Date() + let createdAt = Date() /// 5. Firestore에 추가 try await linkDocRef.setData([ @@ -52,7 +52,7 @@ struct LinkRepositoryImpl: LinkRepository { "thumbnailUrl": imageUrls.thumbnail ?? NSNull(), "faviconUrl": imageUrls.favicon ?? NSNull(), "isPinned": false, - "createdBy": createdBy, + "createdAt": createdAt, "lastAccessedAt": NSNull(), "folderID": link.folderID ?? NSNull() ]) @@ -65,7 +65,7 @@ struct LinkRepositoryImpl: LinkRepository { thumbnailUrl: imageUrls.thumbnail, faviconUrl: imageUrls.favicon, isPinned: false, - createdBy: createdBy, + createdAt: createdAt, lastAccessedAt: nil, folderID: link.folderID ) diff --git a/Mark-In/Sources/Domain/Entities/Folder.swift b/Mark-In/Sources/Domain/Entities/Folder.swift index 01a4cf3..8abc10e 100644 --- a/Mark-In/Sources/Domain/Entities/Folder.swift +++ b/Mark-In/Sources/Domain/Entities/Folder.swift @@ -10,5 +10,5 @@ import Foundation struct Folder: Equatable, Hashable { var id: String? var name: String - var createdBy: Date + var createdAt: Date } diff --git a/Mark-In/Sources/Domain/Entities/WebLink.swift b/Mark-In/Sources/Domain/Entities/WebLink.swift index 531f6f9..fe992ac 100644 --- a/Mark-In/Sources/Domain/Entities/WebLink.swift +++ b/Mark-In/Sources/Domain/Entities/WebLink.swift @@ -14,7 +14,7 @@ struct WebLink: Hashable { var thumbnailUrl: String? var faviconUrl: String? var isPinned: Bool - var createdBy: Date + var createdAt: Date var lastAccessedAt: Date? var folderID: String? } diff --git a/Mark-In/Sources/Feature/AddLink/AddLinkView.swift b/Mark-In/Sources/Feature/AddLink/AddLinkView.swift index 80c5f8f..2a4b662 100644 --- a/Mark-In/Sources/Feature/AddLink/AddLinkView.swift +++ b/Mark-In/Sources/Feature/AddLink/AddLinkView.swift @@ -136,8 +136,8 @@ struct AddLinkView: View { #Preview { AddLinkView( folders: [ - .init(id: "", name: "기본폴더", createdBy: .now), - .init(id: "1", name: "폴더1", createdBy: .now), + .init(id: "", name: "기본폴더", createdAt: .now), + .init(id: "1", name: "폴더1", createdAt: .now), ] ) { print($0) diff --git a/Mark-In/Sources/Feature/Main/LinkListView.swift b/Mark-In/Sources/Feature/Main/LinkListView.swift index 5bc95df..47c29ff 100644 --- a/Mark-In/Sources/Feature/Main/LinkListView.swift +++ b/Mark-In/Sources/Feature/Main/LinkListView.swift @@ -159,7 +159,7 @@ private struct LinkCell: View { .frame(width: 5, height: 5) } - Text(link.createdBy.description) + Text(link.createdAt.description) .font(.pretendard(size: 10, weight: .regular)) .foregroundStyle(.markBlack40) .lineLimit(1) @@ -178,7 +178,7 @@ private struct LinkCell: View { id: "", url: "www.naver.com", isPinned: true, - createdBy: .now + createdAt: .now ) ) .frame(width: 210, height: 160) diff --git a/Mark-In/Sources/Feature/Main/MainReducer.swift b/Mark-In/Sources/Feature/Main/MainReducer.swift index 4c6b97f..d289de2 100644 --- a/Mark-In/Sources/Feature/Main/MainReducer.swift +++ b/Mark-In/Sources/Feature/Main/MainReducer.swift @@ -64,10 +64,10 @@ struct MainReducer: Reducer { state.links = links - state.folderTabs = [.folder(.init(id: nil, name: "기본폴더", createdBy: .now))] + state.folderTabs = [.folder(.init(id: nil, name: "기본폴더", createdAt: .now))] folders.forEach { state.folderTabs.append( - .folder(.init(id: $0.id, name: $0.name, createdBy: $0.createdBy)) + .folder(.init(id: $0.id, name: $0.name, createdAt: $0.createdAt)) ) } From 03672c1d202b7910fa70685bc63546e4900f4ae7 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 15:58:58 +0900 Subject: [PATCH 12/13] =?UTF-8?q?[#50]=20FolderRepository.update=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/FolderRepositoryImpl.swift | 26 ------------------- .../Domain/Interfaces/FolderRepository.swift | 1 - 2 files changed, 27 deletions(-) diff --git a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift index e39eebe..b515962 100644 --- a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift @@ -54,32 +54,6 @@ struct FolderRepositoryImpl: FolderRepository { } } - func update(userID: String, folder: Folder) async throws { - /// 1. Folder 문서 참조 생성 - guard let folderID = folder.id else { return } - let path = FirebaseEndpoint.FirestoreDB.folder(userID: userID, folderID: folderID).path - let folderDocRef = db.document(path) - - /// 2. Entity를 DTO로 변환 - let folderDTO = FolderDTO( - id: folderDocRef.documentID, - name: folder.name, - createdAt: folder.createdAt - ) - - /// 3. 업데이트 - try await withCheckedThrowingContinuation { (continuation: VoidCheckedContinuation) in - do { - try folderDocRef.setData(from: folderDTO) { error in - if let error { continuation.resume(throwing: error) } - else { continuation.resume(returning: ()) } - } - } catch { - continuation.resume(throwing: error) - } - } - } - func delete(userID: String, folderID: String) async throws { /// 1. Folder 문서 참조 생성 let path = FirebaseEndpoint.FirestoreDB.folder(userID: userID, folderID: folderID).path diff --git a/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift b/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift index 2d90bcd..2ec9b8a 100644 --- a/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift +++ b/Mark-In/Sources/Domain/Interfaces/FolderRepository.swift @@ -10,7 +10,6 @@ import Foundation protocol FolderRepository { func create(userID: String, folder: WriteFolder) async throws -> Folder func fetchAll(userID: String) async throws -> [Folder] - func update(userID: String, folder: Folder) async throws func delete(userID: String, folderID: String) async throws func deleteAll(userID: String) async throws } From 1a6e8bd3bf3dd9ec9706bd2ef307fd7550b34ebd Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Wed, 4 Jun 2025 16:15:14 +0900 Subject: [PATCH 13/13] =?UTF-8?q?[#50]=20FirestoreFieldKey=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=A0=95=EC=9D=98=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In/Sources/Data/DTOs/FolderDTO.swift | 6 ++++ Mark-In/Sources/Data/DTOs/WebLinkDTO.swift | 12 ++++++++ Mark-In/Sources/Data/FirestoreFieldKey.swift | 28 +++++++++++++++++++ .../Repositories/FolderRepositoryImpl.swift | 8 +++--- .../Repositories/LinkRepositoryImpl.swift | 28 +++++++++---------- 5 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 Mark-In/Sources/Data/FirestoreFieldKey.swift diff --git a/Mark-In/Sources/Data/DTOs/FolderDTO.swift b/Mark-In/Sources/Data/DTOs/FolderDTO.swift index 6a17210..5aadbde 100644 --- a/Mark-In/Sources/Data/DTOs/FolderDTO.swift +++ b/Mark-In/Sources/Data/DTOs/FolderDTO.swift @@ -12,6 +12,12 @@ struct FolderDTO: Codable { var name: String var createdAt: Date + enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case createdAt = "createdAt" + } + func toEntity() -> Folder { return Folder( id: self.id, diff --git a/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift b/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift index 9ad7d92..832d300 100644 --- a/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift +++ b/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift @@ -18,6 +18,18 @@ struct WebLinkDTO: Codable { var lastAccessedAt: Date? var folderID: String? + enum CodingKeys: String, CodingKey { + case id = "id" + case url = "url" + case title = "title" + case thumbnailUrl = "thumbnailUrl" + case faviconUrl = "faviconUrl" + case isPinned = "isPinned" + case createdAt = "createdAt" + case lastAccessedAt = "lastAccessedAt" + case folderID = "folderID" + } + func toEntity() -> WebLink { WebLink( id: self.id, diff --git a/Mark-In/Sources/Data/FirestoreFieldKey.swift b/Mark-In/Sources/Data/FirestoreFieldKey.swift new file mode 100644 index 0000000..4c11eb7 --- /dev/null +++ b/Mark-In/Sources/Data/FirestoreFieldKey.swift @@ -0,0 +1,28 @@ +// +// FirestoreFieldKey.swift +// Mark-In +// +// Created by 이정동 on 6/4/25. +// + +import Foundation + +enum FirestoreFieldKey { + enum Link { + static let id = "id" + static let url = "url" + static let title = "title" + static let thumbnailUrl = "thumbnailUrl" + static let faviconUrl = "faviconUrl" + static let isPinned = "isPinned" + static let createdAt = "createdAt" + static let lastAccessedAt = "lastAccessedAt" + static let folderID = "folderID" + } + + enum Folder { + static let id = "id" + static let name = "name" + static let createdAt = "createdAt" + } +} diff --git a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift index b515962..705b912 100644 --- a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift @@ -11,7 +11,7 @@ import FirebaseFirestore struct FolderRepositoryImpl: FolderRepository { - typealias VoidCheckedContinuation = CheckedContinuation + typealias FolderFieldKey = FirestoreFieldKey.Folder private let db = Firestore.firestore() @@ -25,9 +25,9 @@ struct FolderRepositoryImpl: FolderRepository { /// 3. Firestore에 추가 try await folderDocRef.setData([ - "id": folderDocRef.documentID, - "name": folder.name, - "createdAt": createdAt + FolderFieldKey.id: folderDocRef.documentID, + FolderFieldKey.name: folder.name, + FolderFieldKey.createdAt: createdAt ]) /// 4. 생성된 데이터 반환 diff --git a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift index 33c13a9..8da3e35 100644 --- a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift @@ -14,7 +14,7 @@ import LinkMetadataKitInterface struct LinkRepositoryImpl: LinkRepository { - typealias VoidCheckedContinuation = CheckedContinuation + typealias LinkFieldKey = FirestoreFieldKey.Link private let db = Firestore.firestore() private let storage = Storage.storage().reference() @@ -46,15 +46,15 @@ struct LinkRepositoryImpl: LinkRepository { /// 5. Firestore에 추가 try await linkDocRef.setData([ - "id": linkDocRef.documentID, - "url": link.url, - "title": title ?? NSNull(), - "thumbnailUrl": imageUrls.thumbnail ?? NSNull(), - "faviconUrl": imageUrls.favicon ?? NSNull(), - "isPinned": false, - "createdAt": createdAt, - "lastAccessedAt": NSNull(), - "folderID": link.folderID ?? NSNull() + LinkFieldKey.id: linkDocRef.documentID, + LinkFieldKey.url: link.url, + LinkFieldKey.title: title ?? NSNull(), + LinkFieldKey.thumbnailUrl: imageUrls.thumbnail ?? NSNull(), + LinkFieldKey.faviconUrl: imageUrls.favicon ?? NSNull(), + LinkFieldKey.isPinned: false, + LinkFieldKey.createdAt: createdAt, + LinkFieldKey.lastAccessedAt: NSNull(), + LinkFieldKey.folderID: link.folderID ?? NSNull() ]) /// 6. 생성된 데이터 반환 @@ -99,7 +99,7 @@ struct LinkRepositoryImpl: LinkRepository { /// 2. 문서 업데이트 try await linkDocRef.updateData([ - "folderID": folderID ?? NSNull() + LinkFieldKey.folderID: folderID ?? NSNull() ]) } @@ -114,7 +114,7 @@ struct LinkRepositoryImpl: LinkRepository { /// 2. 조건에 해당하는 모든 문서 가져오기 let querySnapshot = try await linkColRef - .whereField("folderID", isEqualTo: fromFolderID ?? NSNull()) + .whereField(LinkFieldKey.folderID, isEqualTo: fromFolderID ?? NSNull()) .getDocuments() /// 3. 병렬 작업으로 문서 업데이트 @@ -122,7 +122,7 @@ struct LinkRepositoryImpl: LinkRepository { querySnapshot.documents.forEach { document in group.addTask { try await document.reference.updateData([ - "folderID": toFolderID ?? NSNull() + LinkFieldKey.folderID: toFolderID ?? NSNull() ]) } } @@ -146,7 +146,7 @@ struct LinkRepositoryImpl: LinkRepository { func deleteAllInFolder(userID: String, folderID: String?) async throws { let path = FirebaseEndpoint.FirestoreDB.links(userID: userID).path let querySnapshot = try await db.collection(path) - .whereField("folderID", isEqualTo: folderID ?? NSNull()) + .whereField(LinkFieldKey.folderID, isEqualTo: folderID ?? NSNull()) .getDocuments() /// 3. 병렬 작업으로 데이터 삭제