diff --git a/Mark-In-Tests/Test/UseCase/DeleteFolderUseCaseTests.swift b/Mark-In-Tests/Test/UseCase/DeleteFolderUseCaseTests.swift new file mode 100644 index 0000000..d734ba0 --- /dev/null +++ b/Mark-In-Tests/Test/UseCase/DeleteFolderUseCaseTests.swift @@ -0,0 +1,289 @@ +// +// DeleteFolderUseCaseTests.swift +// Mark-In-Tests +// +// Created by 이정동 on 6/6/25. +// + +import Testing + +@testable import Mark_In + +struct DeleteFolderUseCaseTests { + + @Test + func test_인증된_유저_정보가_없을_때_unauthenticated_에러를_반환한다() async throws { + // Given: 준비 + let stubAuthUserManager = StubAuthUserManager(userID: nil) + let mockLinkRepository = MockLinkRepository() + let mockFolderRepository = MockFolderRepository() + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: mockLinkRepository, + folderRepository: mockFolderRepository + ) + + // When: 실행 + var thrownError: Error? + do { + try await sut.execute(folderID: "", includingChildren: false) + } catch { + thrownError = error + } + + // Then: 검증 + #expect(thrownError as? AuthError == AuthError.unauthenticated) + + // TearDown: 해제 + + } + + @Test + func test_인증된_유저_정보가_있다면_에러를_반환하지_않는다() async throws { + // Given: 준비 + let stubAuthUserManager = StubAuthUserManager(userID: "testUser") + let mockLinkRepository = MockLinkRepository() + let mockFolderRepository = MockFolderRepository() + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: mockLinkRepository, + folderRepository: mockFolderRepository + ) + + // When: 실행 + var thrownError: Error? + do { + try await sut.execute(folderID: "", includingChildren: false) + } catch { + thrownError = error + } + + // Then: 검증 + #expect(thrownError as? AuthError == nil) + + // TearDown: 해제 + + } + + @Test + func test_includeChildren이_true이면_linkRepository의_deleteAllInFolder를_호출한다() async throws { + // Given: 준비 + let stubAuthUserManager = StubAuthUserManager(userID: "testUser") + let mockLinkRepository = MockLinkRepository() + let mockFolderRepository = MockFolderRepository() + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: mockLinkRepository, + folderRepository: mockFolderRepository + ) + + // When: 실행 + try await sut.execute(folderID: "", includingChildren: true) + + // Then: 검증 + let deleteAllInFolderCallCount = mockLinkRepository.deleteAllInFolderCallCount + #expect(deleteAllInFolderCallCount == 1) + + // TearDown: 해제 + + } + + @Test + func test_includeChildren이_false이면_linkRepository의_moveLinksInFolder를_호출한다() async throws { + // Given: 준비 + let stubAuthUserManager = StubAuthUserManager(userID: "testUser") + let mockLinkRepository = MockLinkRepository() + let mockFolderRepository = MockFolderRepository() + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: mockLinkRepository, + folderRepository: mockFolderRepository + ) + + // When: 실행 + try await sut.execute(folderID: "", includingChildren: false) + + // Then: 검증 + let moveLinksInFolderCallCount = mockLinkRepository.moveLinksInFolderCallCount + #expect(moveLinksInFolderCallCount == 1) + + // TearDown: 해제 + + } + + @Test + func test_폴더ID가_존재할_경우_folderRepository의_delete를_호출한다() async throws { + // Given: 준비 + let stubAuthUserManager = StubAuthUserManager(userID: "testUser") + let mockLinkRepository = MockLinkRepository() + let mockFolderRepository = MockFolderRepository() + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: mockLinkRepository, + folderRepository: mockFolderRepository + ) + + // When: 실행 + try await sut.execute(folderID: "testFolder", includingChildren: false) + + // Then: 검증 + let deleteCallCount = mockFolderRepository.deleteCallCount + #expect(deleteCallCount == 1) + + // TearDown: 해제 + + } + + @Test + func test_폴더ID가_존재하지_않을_경우_folderRepository의_delete를_호출하지_않는다() async throws { + // Given: 준비 + let stubAuthUserManager = StubAuthUserManager(userID: "testUser") + let mockLinkRepository = MockLinkRepository() + let mockFolderRepository = MockFolderRepository() + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: mockLinkRepository, + folderRepository: mockFolderRepository + ) + + // When: 실행 + try await sut.execute(folderID: nil, includingChildren: false) + + // Then: 검증 + let deleteCallCount = mockFolderRepository.deleteCallCount + #expect(deleteCallCount == 0) + + // TearDown: 해제 + + } + + + @Test + func test_includingChildren이_true면_링크들을_삭제한다() async throws { + // Given: 준비 + let userID = "testUser" + let folderID = "testFolderID" + + let stubAuthUserManager = StubAuthUserManager(userID: userID) + let fakeLinkRepo = FakeLinkRepository() + .withTestLinks(userID: userID, folderID: folderID, count: 5) + let fakeFolderRepo = FakeFolderRepository() + .withTestFolder(userID: userID, folderID: folderID) + .withTestFolders(userID: userID, count: 2) + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: fakeLinkRepo, + folderRepository: fakeFolderRepo + ) + + // When: 실행 + _ = try await sut.execute(folderID: folderID, includingChildren: true) + + // Then: 검증 + let links = fakeLinkRepo.data[userID]! + + #expect(links.filter { $0.folderID == folderID }.isEmpty) + + // TearDown: 해제 + + } + + @Test + func test_includingChildren이_false면_링크들을_기본_폴더로_이동한다() async throws { + // Given: 준비 + let userID = "testUser" + let folderID = "testFolderID" + let defaultFolderID: String? = nil + + let stubAuthUserManager = StubAuthUserManager(userID: userID) + let fakeLinkRepo = FakeLinkRepository() + .withTestLinks(userID: userID, folderID: folderID, count: 5) + let fakeFolderRepo = FakeFolderRepository() + .withTestFolder(userID: userID, folderID: folderID) + .withTestFolder(userID: userID, folderID: defaultFolderID) + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: fakeLinkRepo, + folderRepository: fakeFolderRepo + ) + + // When: 실행 + _ = try await sut.execute(folderID: folderID, includingChildren: false) + + // Then: 검증 + let links = fakeLinkRepo.data[userID]! + + #expect(links.filter { $0.folderID == defaultFolderID }.count == 5) + + // TearDown: 해제 + + } + + @Test + func test_folderID가_nil이면_폴더는_삭제되지_않는다() async throws { + // Given: 준비 + let userID = "testUser" + let folderID: String? = nil + + let stubAuthUserManager = StubAuthUserManager(userID: userID) + let fakeLinkRepo = FakeLinkRepository() + .withTestLinks(userID: userID, folderID: folderID, count: 5) + let fakeFolderRepo = FakeFolderRepository() + .withTestFolder(userID: userID, folderID: folderID) + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: fakeLinkRepo, + folderRepository: fakeFolderRepo + ) + + // When: 실행 + _ = try await sut.execute(folderID: folderID, includingChildren: false) + + // Then: 검증 + let folders = fakeFolderRepo.data[userID]! + + #expect(folders.contains { $0.id == folderID }) + + // TearDown: 해제 + + } + + @Test + func test_folderID가_존재하면_폴더를_삭제한다() async throws { + // Given: 준비 + let userID = "testUser" + let folderID = "testFolder" + + let stubAuthUserManager = StubAuthUserManager(userID: userID) + let fakeLinkRepo = FakeLinkRepository() + .withTestLinks(userID: userID, folderID: folderID, count: 5) + let fakeFolderRepo = FakeFolderRepository() + .withTestFolder(userID: userID, folderID: folderID) + + let sut = DeleteFolderUseCaseImpl( + authUserManager: stubAuthUserManager, + linkRepository: fakeLinkRepo, + folderRepository: fakeFolderRepo + ) + + // When: 실행 + _ = try await sut.execute(folderID: folderID, includingChildren: false) + + // Then: 검증 + let folders = fakeFolderRepo.data[userID]! + + #expect(folders.contains { $0.id == folderID } == false) + + // TearDown: 해제 + + } +} diff --git a/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift b/Mark-In-Tests/Testing/Repository/FakeFolderRepository.swift similarity index 98% rename from Mark-In/Sources/Data/Tests/FakeFolderRepository.swift rename to Mark-In-Tests/Testing/Repository/FakeFolderRepository.swift index 38219d3..f4141ec 100644 --- a/Mark-In/Sources/Data/Tests/FakeFolderRepository.swift +++ b/Mark-In-Tests/Testing/Repository/FakeFolderRepository.swift @@ -5,6 +5,7 @@ // Created by 이정동 on 6/6/25. // +@testable import Mark_In import Foundation final class FakeFolderRepository: FolderRepository { diff --git a/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift b/Mark-In-Tests/Testing/Repository/FakeLinkRepository.swift similarity index 99% rename from Mark-In/Sources/Data/Tests/FakeLinkRepository.swift rename to Mark-In-Tests/Testing/Repository/FakeLinkRepository.swift index 1cbfb8a..1ccfd38 100644 --- a/Mark-In/Sources/Data/Tests/FakeLinkRepository.swift +++ b/Mark-In-Tests/Testing/Repository/FakeLinkRepository.swift @@ -5,6 +5,7 @@ // Created by 이정동 on 6/6/25. // +@testable import Mark_In import Foundation final class FakeLinkRepository: LinkRepository { diff --git a/Mark-In-Tests/Testing/Repository/MockFolderRepository.swift b/Mark-In-Tests/Testing/Repository/MockFolderRepository.swift new file mode 100644 index 0000000..9667563 --- /dev/null +++ b/Mark-In-Tests/Testing/Repository/MockFolderRepository.swift @@ -0,0 +1,30 @@ +// +// MockFolderRepository.swift +// Mark-In-Tests +// +// Created by 이정동 on 6/17/25. +// + +@testable import Mark_In +import Foundation + +final class MockFolderRepository: FolderRepository { + + var deleteCallCount: Int = 0 + + func create(userID: String, folder: WriteFolder) async throws -> Folder { + return Folder(name: "", createdAt: Date()) + } + + func fetchAll(userID: String) async throws -> [Folder] { + return [] + } + + func delete(userID: String, folderID: String) async throws { + deleteCallCount += 1 + } + + func deleteAll(userID: String) async throws { + + } +} diff --git a/Mark-In-Tests/Testing/Repository/MockLinkRepository.swift b/Mark-In-Tests/Testing/Repository/MockLinkRepository.swift new file mode 100644 index 0000000..83ae77f --- /dev/null +++ b/Mark-In-Tests/Testing/Repository/MockLinkRepository.swift @@ -0,0 +1,43 @@ +// +// MockLinkRepository.swift +// Mark-In-Tests +// +// Created by 이정동 on 6/17/25. +// + +@testable import Mark_In +import Foundation + +final class MockLinkRepository: LinkRepository { + + var moveLinksInFolderCallCount: Int = 0 + var deleteAllInFolderCallCount: Int = 0 + + func create(userID: String, link: WriteLink) async throws -> WebLink { + return WebLink(id: "", url: "", isPinned: false, createdAt: Date()) + } + + func fetchAll(userID: String) async throws -> [WebLink] { + return [] + } + + func moveLinkInFolder(userID: String, target linkID: String, to folderID: String?) async throws { + + } + + func moveLinksInFolder(userID: String, fromFolderID: String?, toFolderID: String?) async throws { + moveLinksInFolderCallCount += 1 + } + + func delete(userID: String, linkID: String) async throws { + + } + + func deleteAllInFolder(userID: String, folderID: String?) async throws { + deleteAllInFolderCallCount += 1 + } + + func deleteAll(userID: String) async throws { + + } +} diff --git a/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift b/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift deleted file mode 100644 index 1699bb0..0000000 --- a/Mark-In-Tests/UseCaseTests/DeleteFolderUseCaseTests.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// DeleteFolderUseCaseTests.swift -// Mark-In-Tests -// -// Created by 이정동 on 6/6/25. -// - -import Testing - -@testable import Mark_In - -struct DeleteFolderUseCaseTests { - - @Test - func test_includingChildren이_true면_링크들을_삭제한다() async throws { - // Given: 준비 - let userID = "testUser" - let folderID = "testFolderID" - - let stubAuthUserManager = StubAuthUserManager(userID: userID) - let fakeLinkRepo = FakeLinkRepository() - .withTestLinks(userID: userID, folderID: folderID, count: 5) - let fakeFolderRepo = FakeFolderRepository() - .withTestFolder(userID: userID, folderID: folderID) - .withTestFolders(userID: userID, count: 2) - - let sut = DeleteFolderUseCaseImpl( - authUserManager: stubAuthUserManager, - linkRepository: fakeLinkRepo, - folderRepository: fakeFolderRepo - ) - - // When: 실행 - _ = try await sut.execute(folderID: folderID, includingChildren: true) - - // Then: 검증 - let links = fakeLinkRepo.data[userID]! - - #expect(links.filter { $0.folderID == folderID }.isEmpty) - - // TearDown: 해제 - - } - - @Test - func test_includingChildren이_false면_링크들을_기본_폴더로_이동한다() async throws { - // Given: 준비 - let userID = "testUser" - let folderID = "testFolderID" - let defaultFolderID: String? = nil - - let stubAuthUserManager = StubAuthUserManager(userID: userID) - let fakeLinkRepo = FakeLinkRepository() - .withTestLinks(userID: userID, folderID: folderID, count: 5) - let fakeFolderRepo = FakeFolderRepository() - .withTestFolder(userID: userID, folderID: folderID) - .withTestFolder(userID: userID, folderID: defaultFolderID) - - let sut = DeleteFolderUseCaseImpl( - authUserManager: stubAuthUserManager, - linkRepository: fakeLinkRepo, - folderRepository: fakeFolderRepo - ) - - // When: 실행 - _ = try await sut.execute(folderID: folderID, includingChildren: false) - - // Then: 검증 - let links = fakeLinkRepo.data[userID]! - - #expect(links.filter { $0.folderID == defaultFolderID }.count == 5) - - // TearDown: 해제 - - } - - @Test - func test_folderID가_nil이면_폴더는_삭제되지_않는다() async throws { - // Given: 준비 - let userID = "testUser" - let folderID: String? = nil - - let stubAuthUserManager = StubAuthUserManager(userID: userID) - let fakeLinkRepo = FakeLinkRepository() - .withTestLinks(userID: userID, folderID: folderID, count: 5) - let fakeFolderRepo = FakeFolderRepository() - .withTestFolder(userID: userID, folderID: folderID) - - let sut = DeleteFolderUseCaseImpl( - authUserManager: stubAuthUserManager, - linkRepository: fakeLinkRepo, - folderRepository: fakeFolderRepo - ) - - // When: 실행 - _ = try await sut.execute(folderID: folderID, includingChildren: false) - - // Then: 검증 - let folders = fakeFolderRepo.data[userID]! - - #expect(folders.contains { $0.id == folderID }) - - // TearDown: 해제 - - } - - @Test - func test_folderID가_존재하면_폴더를_삭제한다() async throws { - // Given: 준비 - let userID = "testUser" - let folderID = "testFolder" - - let stubAuthUserManager = StubAuthUserManager(userID: userID) - let fakeLinkRepo = FakeLinkRepository() - .withTestLinks(userID: userID, folderID: folderID, count: 5) - let fakeFolderRepo = FakeFolderRepository() - .withTestFolder(userID: userID, folderID: folderID) - - let sut = DeleteFolderUseCaseImpl( - authUserManager: stubAuthUserManager, - linkRepository: fakeLinkRepo, - folderRepository: fakeFolderRepo - ) - - // When: 실행 - _ = try await sut.execute(folderID: folderID, includingChildren: false) - - // Then: 검증 - let folders = fakeFolderRepo.data[userID]! - - #expect(folders.contains { $0.id == folderID } == false) - - // TearDown: 해제 - - } -} diff --git a/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift b/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift deleted file mode 100644 index 0c7a8b1..0000000 --- a/Mark-In-Tests/ValidationsTests/DTOFieldKeyMappingTests.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Mark_In_Tests.swift -// Mark-In-Tests -// -// Created by 이정동 on 6/4/25. -// - -import Testing -@testable import Mark_In - -struct DTOFieldKeyMappingTests { - - @Test - func test_FolderDTO의_CodingKey와_FirestoreFieldKey가_일치해야_한다() async throws { - // Given: 준비 - - // When: 실행 - - // Then: 검증 - #expect(FolderDTO.CodingKeys.id.rawValue == FirestoreFieldKey.Folder.id) - #expect(FolderDTO.CodingKeys.name.rawValue == FirestoreFieldKey.Folder.name) - #expect(FolderDTO.CodingKeys.createdAt.rawValue == FirestoreFieldKey.Folder.createdAt) - - // TearDown: 해제 - - } - - @Test - func test_WebLinkDTO의_CodingKey와_FirestoreFieldKey가_일치해야_한다() async throws { - // Given: 준비 - - // When: 실행 - - // Then: 검증 - #expect(WebLinkDTO.CodingKeys.id.rawValue == FirestoreFieldKey.Link.id) - #expect(WebLinkDTO.CodingKeys.url.rawValue == FirestoreFieldKey.Link.url) - #expect(WebLinkDTO.CodingKeys.title.rawValue == FirestoreFieldKey.Link.title) - #expect(WebLinkDTO.CodingKeys.thumbnailUrl.rawValue == FirestoreFieldKey.Link.thumbnailUrl) - #expect(WebLinkDTO.CodingKeys.faviconUrl.rawValue == FirestoreFieldKey.Link.faviconUrl) - #expect(WebLinkDTO.CodingKeys.isPinned.rawValue == FirestoreFieldKey.Link.isPinned) - #expect(WebLinkDTO.CodingKeys.createdAt.rawValue == FirestoreFieldKey.Link.createdAt) - #expect(WebLinkDTO.CodingKeys.lastAccessedAt.rawValue == FirestoreFieldKey.Link.lastAccessedAt) - #expect(WebLinkDTO.CodingKeys.folderID.rawValue == FirestoreFieldKey.Link.folderID) - - // TearDown: 해제 - - } -} diff --git a/Mark-In/Sources/App/AuthUserManager.swift b/Mark-In/Sources/App/AuthUserManager.swift index 0bd820c..7803da6 100644 --- a/Mark-In/Sources/App/AuthUserManager.swift +++ b/Mark-In/Sources/App/AuthUserManager.swift @@ -61,13 +61,17 @@ final class AuthUserManagerImpl: AuthUserManager { final class StubAuthUserManager: AuthUserManager { var user: AuthUser? - init(userID: String) { - self.user = .init( - id: userID, - name: userID, - email: "\(userID)@test.com", - provider: .apple - ) + init(userID: String?) { + if let userID { + self.user = .init( + id: userID, + name: userID, + email: "\(userID)@test.com", + provider: .apple + ) + } else { + self.user = nil + } } func save(_ user: AuthUser) { } diff --git a/Mark-In/Sources/Data/DTOs/FolderDTO.swift b/Mark-In/Sources/Data/DTOs/FolderDTO.swift index 5aadbde..edd9af5 100644 --- a/Mark-In/Sources/Data/DTOs/FolderDTO.swift +++ b/Mark-In/Sources/Data/DTOs/FolderDTO.swift @@ -12,10 +12,12 @@ struct FolderDTO: Codable { var name: String var createdAt: Date - enum CodingKeys: String, CodingKey { - case id = "id" - case name = "name" - case createdAt = "createdAt" + var documentData: [String: Any] { + [ + "id": self.id, + "name": self.name, + "createdAt": self.createdAt + ] } func toEntity() -> Folder { diff --git a/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift b/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift index 832d300..b09b135 100644 --- a/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift +++ b/Mark-In/Sources/Data/DTOs/WebLinkDTO.swift @@ -18,18 +18,20 @@ 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" + var documentData: [String: Any] { + [ + "id": self.id, + "url": self.url, + "title": self.title ?? NSNull(), + "thumbnailUrl": self.thumbnailUrl ?? NSNull(), + "faviconUrl": self.faviconUrl ?? NSNull(), + "isPinned": self.isPinned, + "createdAt": self.createdAt, + "lastAccessedAt": self.lastAccessedAt ?? NSNull(), + "folderID": self.folderID ?? NSNull(), + ] } - + func toEntity() -> WebLink { WebLink( id: self.id, @@ -42,5 +44,5 @@ struct WebLinkDTO: Codable { lastAccessedAt: self.lastAccessedAt, folderID: self.folderID ) - } + } } diff --git a/Mark-In/Sources/Data/FirestoreFieldKey.swift b/Mark-In/Sources/Data/FirestoreFieldKey.swift deleted file mode 100644 index 4c11eb7..0000000 --- a/Mark-In/Sources/Data/FirestoreFieldKey.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// 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 705b912..1da6da9 100644 --- a/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift @@ -11,8 +11,6 @@ import FirebaseFirestore struct FolderRepositoryImpl: FolderRepository { - typealias FolderFieldKey = FirestoreFieldKey.Folder - private let db = Firestore.firestore() func create(userID: String, folder: WriteFolder) async throws -> Folder { @@ -20,24 +18,18 @@ struct FolderRepositoryImpl: FolderRepository { let path = FirebaseEndpoint.FirestoreDB.folders(userID: userID).path let folderDocRef = db.collection(path).document() - /// 2. 필드 값 생성 - let createdAt = Date() - - /// 3. Firestore에 추가 - try await folderDocRef.setData([ - FolderFieldKey.id: folderDocRef.documentID, - FolderFieldKey.name: folder.name, - FolderFieldKey.createdAt: createdAt - ]) - - /// 4. 생성된 데이터 반환 - let folderEntity = Folder( + /// 2. DTO 객체 생성 + let folderDTO = FolderDTO( id: folderDocRef.documentID, name: folder.name, - createdAt: .now + createdAt: Date() ) - return folderEntity + /// 3. Firestore에 추가 + try await folderDocRef.setData(folderDTO.documentData) + + /// 4. 생성된 데이터 반환 + return folderDTO.toEntity() } 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 8da3e35..b3c73cf 100644 --- a/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift +++ b/Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift @@ -14,8 +14,6 @@ import LinkMetadataKitInterface struct LinkRepositoryImpl: LinkRepository { - typealias LinkFieldKey = FirestoreFieldKey.Link - private let db = Firestore.firestore() private let storage = Storage.storage().reference() @@ -40,37 +38,24 @@ struct LinkRepositoryImpl: LinkRepository { metadata: metadata ) - /// 4. 필드 값 설정 - let title = link.title ?? metadata.title - let createdAt = Date() - - /// 5. Firestore에 추가 - try await linkDocRef.setData([ - 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. 생성된 데이터 반환 - let linkEntity = WebLink( + /// 4. DTO 객체 생성 + let linkDTO = WebLinkDTO( id: linkDocRef.documentID, url: link.url, - title: title, + title: link.title ?? metadata.title, thumbnailUrl: imageUrls.thumbnail, faviconUrl: imageUrls.favicon, isPinned: false, - createdAt: createdAt, + createdAt: Date(), lastAccessedAt: nil, folderID: link.folderID ) - return linkEntity + /// 5. Firestore에 추가 + try await linkDocRef.setData(linkDTO.documentData) + + /// 6. 생성된 데이터 반환 + return linkDTO.toEntity() } func fetchAll(userID: String) async throws -> [WebLink] { @@ -99,7 +84,7 @@ struct LinkRepositoryImpl: LinkRepository { /// 2. 문서 업데이트 try await linkDocRef.updateData([ - LinkFieldKey.folderID: folderID ?? NSNull() + "folderID": folderID as Any ]) } @@ -114,7 +99,7 @@ struct LinkRepositoryImpl: LinkRepository { /// 2. 조건에 해당하는 모든 문서 가져오기 let querySnapshot = try await linkColRef - .whereField(LinkFieldKey.folderID, isEqualTo: fromFolderID ?? NSNull()) + .whereField("folderID", isEqualTo: fromFolderID as Any) .getDocuments() /// 3. 병렬 작업으로 문서 업데이트 @@ -122,7 +107,7 @@ struct LinkRepositoryImpl: LinkRepository { querySnapshot.documents.forEach { document in group.addTask { try await document.reference.updateData([ - LinkFieldKey.folderID: toFolderID ?? NSNull() + "folderID": toFolderID as Any ]) } } @@ -146,7 +131,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(LinkFieldKey.folderID, isEqualTo: folderID ?? NSNull()) + .whereField("folderID", isEqualTo: folderID as Any) .getDocuments() /// 3. 병렬 작업으로 데이터 삭제 diff --git a/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift b/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift index 1cf78de..da28e77 100644 --- a/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift +++ b/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift @@ -29,7 +29,7 @@ struct AddFolderView: View { Text("폴더를 추가:") .frame(maxWidth: .infinity, alignment: .leading) - MarkTextField(text: $title, placeholder: "제목") + MarkTextField(text: $name, placeholder: "제목") .padding(.top, 14) .disabled(isSaving)