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
11 changes: 11 additions & 0 deletions Mark-In/Sources/App/DIContainer+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -87,5 +96,7 @@ private extension DIContainer {
register(signInUseCase)
register(signOutUseCase)
register(withdrawalUseCase)
register(deleteLinkUseCase)
register(deleteFolderUseCase)
}
}
10 changes: 8 additions & 2 deletions Mark-In/Sources/Data/DTOs/FolderDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ import Foundation
struct FolderDTO: Codable {
var id: String
var name: String
var createdBy: Date
var createdAt: Date

enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case createdAt = "createdAt"
}

func toEntity() -> Folder {
return Folder(
id: self.id,
name: self.name,
createdBy: self.createdBy
createdAt: self.createdAt
)
}
}
16 changes: 14 additions & 2 deletions Mark-In/Sources/Data/DTOs/WebLinkDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,22 @@ struct WebLinkDTO: Codable {
var thumbnailUrl: String?
var faviconUrl: String?
var isPinned: Bool
var createdBy: Date
var createdAt: Date
var lastAccessedAt: Date?
var folderID: String?

enum CodingKeys: String, CodingKey {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요한 코드면 제거되는게 좋아보여요

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"
}
Comment on lines +21 to +31
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firestore 문서에 저장되는 Field를 관리하기 위해 FirestoreFieldKey를 구현했습니다.
(현재 커밋에 구현됨)

Firestore 데이터를 DTO로 매핑하기 위해서는 Firestore의 Field와 DTO의 프로퍼티가 매핑이 되어야하고, 이를 위해 CodingKey를 사용했습니다.
그런데, case id = FirestoreFieldKey.Link.id 처럼 CodingKey 열겨형 케이스에 값을 지정하고 싶었지만, CodingKey의 특성 상 리터럴 값을 직접 입력해야만 하고 있습니다.
현재 구조는 FirestoreFieldKey와 CodingKey의 열거형 값이 불일치할 경우 데이터 매핑 과정에서 문제가 발생할 수 있다고 느껴졌습니다.
이러한 실수를 예방하기 위해 다음 작업에서 FieldKey와 CodingKey의 열거형 값이 일치하는지 확인하는 테스트 코드를 작성할 예정입니다.


func toEntity() -> WebLink {
WebLink(
id: self.id,
Expand All @@ -26,7 +38,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
)
Expand Down
28 changes: 28 additions & 0 deletions Mark-In/Sources/Data/FirestoreFieldKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// FirestoreFieldKey.swift
// Mark-In
//
// Created by 이정동 on 6/4/25.
//

import Foundation

enum FirestoreFieldKey {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공통화하기에 애매한 부분 같아요!

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"
}
}
Comment on lines +10 to +28
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firestore 문서에 저장되는 Field를 관리하기 위함입니다.

62 changes: 16 additions & 46 deletions Mark-In/Sources/Data/Repositories/FolderRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import FirebaseFirestore

struct FolderRepositoryImpl: FolderRepository {

typealias VoidCheckedContinuation = CheckedContinuation<Void, any Error>
typealias FolderFieldKey = FirestoreFieldKey.Folder

private let db = Firestore.firestore()

Expand All @@ -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 createdAt = Date()

/// 3. Firestore에 추가
try await folderDocRef.setData([
FolderFieldKey.id: folderDocRef.documentID,
FolderFieldKey.name: folder.name,
FolderFieldKey.createdAt: createdAt
])

/// 4. 생성된 데이터 반환
let folderEntity = Folder(
id: folderDocRef.documentID,
name: folder.name,
createdBy: .now
createdAt: .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] {
Expand All @@ -57,35 +54,8 @@ 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,
createdBy: folder.createdBy
)

/// 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, 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)

Expand Down
128 changes: 85 additions & 43 deletions Mark-In/Sources/Data/Repositories/LinkRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import LinkMetadataKitInterface

struct LinkRepositoryImpl: LinkRepository {

typealias VoidCheckedContinuation = CheckedContinuation<Void, any Error>
typealias LinkFieldKey = FirestoreFieldKey.Link

private let db = Firestore.firestore()
private let storage = Storage.storage().reference()
Expand All @@ -40,33 +40,37 @@ struct LinkRepositoryImpl: LinkRepository {
metadata: metadata
)

/// 4. Firestore에 저장할 DTO 객체 생성
let linkDTO = WebLinkDTO(
/// 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(
id: linkDocRef.documentID,
url: link.url,
title: link.title ?? metadata.title,
title: title,
thumbnailUrl: imageUrls.thumbnail,
faviconUrl: imageUrls.favicon,
isPinned: false,
createdBy: .now,
createdAt: createdAt,
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] {
Expand All @@ -83,40 +87,53 @@ 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([
LinkFieldKey.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(LinkFieldKey.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([
LinkFieldKey.folderID: toFolderID ?? NSNull()
])
}
} catch {
continuation.resume(throwing: error)
}

try await group.waitForAll()
}
}

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. 이미지 데이터 삭제
Expand All @@ -126,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(LinkFieldKey.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
Expand Down
2 changes: 1 addition & 1 deletion Mark-In/Sources/Domain/Entities/Folder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ import Foundation
struct Folder: Equatable, Hashable {
var id: String?
var name: String
var createdBy: Date
var createdAt: Date
}
2 changes: 1 addition & 1 deletion Mark-In/Sources/Domain/Entities/WebLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
}
3 changes: 1 addition & 2 deletions Mark-In/Sources/Domain/Interfaces/FolderRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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, folder: Folder) async throws
func delete(userID: String, folderID: String) async throws
func deleteAll(userID: String) async throws
}
Loading
Loading