-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWebPageService.swift
More file actions
154 lines (133 loc) · 5.3 KB
/
WebPageService.swift
File metadata and controls
154 lines (133 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//
// WebPageService.swift
// DevLog
//
// Created by opfic on 6/3/25.
//
import FirebaseAuth
import FirebaseFirestore
import FirebaseFunctions
final class WebPageService {
private enum FunctionName: String {
case requestWebPageDeletion
case undoWebPageDeletion
}
private let store = Firestore.firestore()
private let functions = Functions.functions(region: "asia-northeast3")
private let encoder = Firestore.Encoder()
private let logger = Logger(category: "WebPageService")
/// 저장한 웹페이지를 모두 불러옴
func fetchWebPages(_ query: String) async throws -> [WebPageResponse] {
logger.info("Fetching web pages")
guard let uid = Auth.auth().currentUser?.uid else {
logger.error("User not authenticated")
throw AuthError.notAuthenticated
}
do {
let collectionRef = store.collection(FirestorePath.webPages(uid))
let snapshot = try await collectionRef.getDocuments()
let items: [WebPageResponse] = snapshot.documents.compactMap { makeResponse(from: $0) }
let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmedQuery.isEmpty else {
logger.info("Successfully fetched \(items.count) web pages")
return items
}
let filtered = items.filter {
$0.title.localizedCaseInsensitiveContains(trimmedQuery) ||
$0.displayURL.localizedCaseInsensitiveContains(trimmedQuery)
}
logger.info("Successfully fetched \(filtered.count) web pages with query")
return filtered
} catch {
logger.error("Failed to fetch web pages", error: error)
throw error
}
}
/// 웹페이지를 추가 또는 업데이트
func upsertWebPage(_ request: WebPageRequest) async throws {
logger.info("Upserting web page: \(request.url)")
guard let uid = Auth.auth().currentUser?.uid else {
logger.error("User not authenticated")
throw AuthError.notAuthenticated
}
do {
let documentID = documentID(for: request.url)
let docRef = store.document(FirestorePath.webPage(uid, documentId: documentID))
let data = try encoder.encode(request)
try await docRef.setData(data, merge: true)
logger.info("Successfully upserted web page")
} catch {
logger.error("Failed to upsert web page", error: error)
throw error
}
}
func deleteWebPage(_ urlString: String) async throws {
logger.info("Requesting web page deletion: \(urlString)")
guard Auth.auth().currentUser?.uid != nil else {
logger.error("User not authenticated")
throw AuthError.notAuthenticated
}
do {
let function = functions.httpsCallable(FunctionName.requestWebPageDeletion)
_ = try await function.call(["urlString": urlString])
logger.info("Successfully requested web page deletion")
} catch {
logger.error("Failed to request web page deletion", error: error)
throw error
}
}
func undoDeleteWebPage(_ urlString: String) async throws {
logger.info("Undoing web page deletion: \(urlString)")
guard Auth.auth().currentUser?.uid != nil else {
logger.error("User not authenticated")
throw AuthError.notAuthenticated
}
do {
let function = functions.httpsCallable(FunctionName.undoWebPageDeletion)
_ = try await function.call(["urlString": urlString])
logger.info("Successfully undone web page deletion")
} catch {
logger.error("Failed to undo web page deletion", error: error)
throw error
}
}
private func documentID(for url: String) -> String {
if let encoded = url.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
return encoded
}
let base64 = Data(url.utf8).base64EncodedString()
return base64
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "=", with: "")
}
}
private extension WebPageService {
func makeResponse(from snapshot: QueryDocumentSnapshot) -> WebPageResponse? {
let data = snapshot.data()
if data[WebPageFieldKey.deletingAt.rawValue] is Timestamp {
return nil
}
guard
let title = data[WebPageFieldKey.title.rawValue] as? String,
let url = data[WebPageFieldKey.url.rawValue] as? String,
let displayURL = data[WebPageFieldKey.displayURL.rawValue] as? String,
let imageURL = data[WebPageFieldKey.imageURL.rawValue] as? String else {
return nil
}
return WebPageResponse(
id: snapshot.documentID,
title: title,
url: url,
displayURL: displayURL,
imageURL: imageURL
)
}
enum WebPageFieldKey: String {
case title
case url
case displayURL
case imageURL
case deletingAt // 삭제 요청은 되었지만, 5초 유예 후 최종 삭제되기 전 상태
}
}