- Replace multiple boolean properties with single OfflineBookmarkSyncState enum - Add Use Case pattern for OfflineSyncManager with dependency injection - Simplify LocalBookmarksSyncView using state-driven UI with external bindings - Convert OfflineBookmarksViewModel to use @Observable instead of ObservableObject - Move credentials from Core Data to Keychain for better persistence - Implement comprehensive database migration for App Group containers - Add structured logging throughout sync operations and API calls Architecture improvements follow MVVM principles with clean separation of concerns.
106 lines
4.9 KiB
Swift
106 lines
4.9 KiB
Swift
import Foundation
|
|
|
|
class SimpleAPI {
|
|
private static let logger = Logger.network
|
|
|
|
// MARK: - API Methods
|
|
static func addBookmark(title: String, url: String, labels: [String]? = nil, showStatus: @escaping (String, Bool) -> Void) async {
|
|
logger.info("Adding bookmark: \(url)")
|
|
guard let token = KeychainHelper.shared.loadToken() else {
|
|
showStatus("No token found. Please log in via the main app.", true)
|
|
return
|
|
}
|
|
guard let endpoint = KeychainHelper.shared.loadEndpoint(), !endpoint.isEmpty else {
|
|
showStatus("No server endpoint found.", true)
|
|
return
|
|
}
|
|
let requestDto = CreateBookmarkRequestDto(url: url, title: title, labels: labels)
|
|
guard let requestData = try? JSONEncoder().encode(requestDto) else {
|
|
showStatus("Failed to encode request.", true)
|
|
return
|
|
}
|
|
guard let apiUrl = URL(string: endpoint + "/api/bookmarks") else {
|
|
showStatus("Invalid server endpoint.", true)
|
|
return
|
|
}
|
|
var request = URLRequest(url: apiUrl)
|
|
request.httpMethod = "POST"
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
request.httpBody = requestData
|
|
do {
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
guard let httpResponse = response as? HTTPURLResponse else {
|
|
logger.error("Invalid server response for bookmark creation")
|
|
showStatus("Invalid server response.", true)
|
|
return
|
|
}
|
|
|
|
logger.logNetworkRequest(method: "POST", url: "/api/bookmarks", statusCode: httpResponse.statusCode)
|
|
|
|
guard 200...299 ~= httpResponse.statusCode else {
|
|
let msg = String(data: data, encoding: .utf8) ?? "Unknown error"
|
|
logger.error("Server error \(httpResponse.statusCode): \(msg)")
|
|
showStatus("Server error: \(httpResponse.statusCode)\n\(msg)", true)
|
|
return
|
|
}
|
|
|
|
if let resp = try? JSONDecoder().decode(CreateBookmarkResponseDto.self, from: data) {
|
|
logger.info("Bookmark created successfully: \(resp.message)")
|
|
showStatus("Saved: \(resp.message)", false)
|
|
} else {
|
|
logger.info("Bookmark created successfully")
|
|
showStatus("Bookmark saved!", false)
|
|
}
|
|
} catch {
|
|
logger.logNetworkError(method: "POST", url: "/api/bookmarks", error: error)
|
|
showStatus("Network error: \(error.localizedDescription)", true)
|
|
}
|
|
}
|
|
|
|
static func getBookmarkLabels(showStatus: @escaping (String, Bool) -> Void) async -> [BookmarkLabelDto]? {
|
|
logger.info("Fetching bookmark labels")
|
|
guard let token = KeychainHelper.shared.loadToken() else {
|
|
showStatus("No token found. Please log in via the main app.", true)
|
|
return nil
|
|
}
|
|
guard let endpoint = KeychainHelper.shared.loadEndpoint(), !endpoint.isEmpty else {
|
|
showStatus("No server endpoint found.", true)
|
|
return nil
|
|
}
|
|
guard let apiUrl = URL(string: endpoint + "/api/bookmarks/labels") else {
|
|
showStatus("Invalid server endpoint.", true)
|
|
return nil
|
|
}
|
|
var request = URLRequest(url: apiUrl)
|
|
request.httpMethod = "GET"
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
do {
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
guard let httpResponse = response as? HTTPURLResponse else {
|
|
logger.error("Invalid server response for labels request")
|
|
showStatus("Invalid server response.", true)
|
|
return nil
|
|
}
|
|
|
|
logger.logNetworkRequest(method: "GET", url: "/api/bookmarks/labels", statusCode: httpResponse.statusCode)
|
|
|
|
guard 200...299 ~= httpResponse.statusCode else {
|
|
let msg = String(data: data, encoding: .utf8) ?? "Unknown error"
|
|
logger.error("Server error \(httpResponse.statusCode): \(msg)")
|
|
showStatus("Server error: \(httpResponse.statusCode)\n\(msg)", true)
|
|
return nil
|
|
}
|
|
|
|
let labels = try JSONDecoder().decode([BookmarkLabelDto].self, from: data)
|
|
logger.info("Successfully fetched \(labels.count) bookmark labels")
|
|
return labels
|
|
} catch {
|
|
logger.logNetworkError(method: "GET", url: "/api/bookmarks/labels", error: error)
|
|
showStatus("Network error: \(error.localizedDescription)", true)
|
|
return nil
|
|
}
|
|
}
|
|
}
|