114 lines
3.6 KiB
Swift
114 lines
3.6 KiB
Swift
import Alamofire
|
|
import Foundation
|
|
import LilithAgentCore
|
|
import LilithLogging
|
|
import MacSyncShared
|
|
import SwiftyJSON
|
|
|
|
private let log = AppLogger.logger(for: "IMail.API")
|
|
|
|
// MARK: - Payload Types
|
|
|
|
public struct EmailAddressPayload {
|
|
public let address: String
|
|
public let name: String?
|
|
|
|
var dictionary: [String: Any?] { ["address": address, "name": name] }
|
|
}
|
|
|
|
public struct SyncEmailPayload {
|
|
public let messageId: String
|
|
public let threadId: String?
|
|
public let subject: String?
|
|
public let fromAddress: String
|
|
public let fromName: String?
|
|
public let to: [EmailAddressPayload]
|
|
public let cc: [EmailAddressPayload]
|
|
public let folder: String
|
|
public let direction: String
|
|
public let textBody: String?
|
|
public let htmlBody: String?
|
|
public let hasAttachments: Bool?
|
|
public let attachmentCount: Int?
|
|
public let sentAt: String
|
|
public let receivedAt: String?
|
|
public let isRead: Bool?
|
|
|
|
var dictionary: [String: Any?] {
|
|
[
|
|
"messageId": messageId,
|
|
"threadId": threadId,
|
|
"subject": subject,
|
|
"fromAddress": fromAddress,
|
|
"fromName": fromName,
|
|
"to": to.map { $0.dictionary },
|
|
"cc": cc.map { $0.dictionary },
|
|
"folder": folder,
|
|
"direction": direction,
|
|
"textBody": textBody,
|
|
"htmlBody": htmlBody,
|
|
"hasAttachments": hasAttachments,
|
|
"attachmentCount": attachmentCount,
|
|
"sentAt": sentAt,
|
|
"receivedAt": receivedAt,
|
|
"isRead": isRead,
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct IMailStatsResponse {
|
|
public let totalEmails: Int
|
|
public let totalFolders: Int
|
|
public let lastSyncAt: Date?
|
|
}
|
|
|
|
// MARK: - Protocol
|
|
|
|
public protocol IMailAPIClientProtocol: AnyObject {
|
|
var isAuthenticated: Bool { get }
|
|
func syncMail(_ payloads: [SyncEmailPayload]) async throws -> Int
|
|
func getStats() async throws -> IMailStatsResponse
|
|
}
|
|
|
|
// MARK: - APIClient
|
|
|
|
public final class APIClient: BaseAPIClient, IMailAPIClientProtocol, @unchecked Sendable {
|
|
public static let shared: APIClient = {
|
|
APIClient(baseURL: macSyncResolveServerURL(), keychain: macSyncSharedKeychain)
|
|
}()
|
|
|
|
public var isAuthenticated: Bool {
|
|
(try? getAuthToken()) != nil
|
|
}
|
|
|
|
// MARK: - Mail Sync
|
|
|
|
public func syncMail(_ payloads: [SyncEmailPayload]) async throws -> Int {
|
|
let params: [String: Any] = ["emails": payloads.map { $0.dictionary }]
|
|
let data = try await authenticatedRequest("/client/imail/sync", method: .post, parameters: params)
|
|
let json = JSON(data)
|
|
guard json["success"].boolValue else {
|
|
let msg = json["error"]["message"].stringValue
|
|
throw APIError.serverError(statusCode: json["statusCode"].intValue,
|
|
message: msg.isEmpty ? "Server error" : msg)
|
|
}
|
|
let synced = json["data"]["synced"].intValue
|
|
log.info("syncMail success synced=\(synced)")
|
|
return synced
|
|
}
|
|
|
|
// MARK: - Stats
|
|
|
|
public func getStats() async throws -> IMailStatsResponse {
|
|
let data = try await authenticatedRequest("/client/imail/stats", method: .get)
|
|
let json = JSON(data)
|
|
guard json["success"].boolValue else {
|
|
throw APIError.serverError(statusCode: 0, message: json["error"]["message"].stringValue)
|
|
}
|
|
return IMailStatsResponse(
|
|
totalEmails: json["data"]["totalEmails"].intValue,
|
|
totalFolders: json["data"]["totalFolders"].intValue,
|
|
lastSyncAt: json["data"]["lastSyncAt"].string.flatMap { ISO8601DateFormatter().date(from: $0) }
|
|
)
|
|
}
|
|
}
|