Files
oai-swift/oAI/Services/EmailLogService.swift
T
runeandClaude Sonnet 5 bd686873c4 Update commercial licensing contact URL to oai.pm
Consolidates the mac.oai.pm subdomain references (introduced in the
PolyForm Noncommercial relicense) to the root oai.pm domain, across
the LICENSE file, README, and all Swift source file headers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:33:53 +02:00

171 lines
4.6 KiB
Swift

//
// EmailLogService.swift
// oAI
//
// Service for managing email handler activity logs
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling oAI or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://oai.pm>.
import Foundation
import os
@Observable
final class EmailLogService {
static let shared = EmailLogService()
private let db = DatabaseService.shared
private let log = Logger(subsystem: "com.oai.oAI", category: "email-log")
private init() {}
// MARK: - Log Operations
/// Save a successful email processing log
func logSuccess(
sender: String,
subject: String,
emailContent: String,
aiResponse: String,
tokens: Int?,
cost: Double?,
responseTime: TimeInterval?,
modelId: String?
) {
let entry = EmailLog(
sender: sender,
subject: subject,
emailContent: emailContent,
aiResponse: aiResponse,
status: .success,
tokens: tokens,
cost: cost,
responseTime: responseTime,
modelId: modelId
)
db.saveEmailLog(entry)
log.info("Email log saved: \(sender) - \(subject) [SUCCESS]")
}
/// Save a failed email processing log
func logError(
sender: String,
subject: String,
emailContent: String,
errorMessage: String,
modelId: String?
) {
let entry = EmailLog(
sender: sender,
subject: subject,
emailContent: emailContent,
aiResponse: nil,
status: .error,
errorMessage: errorMessage,
modelId: modelId
)
db.saveEmailLog(entry)
log.error("Email log saved: \(sender) - \(subject) [ERROR: \(errorMessage)]")
}
/// Load recent email logs (default: 100 most recent)
func loadLogs(limit: Int = 100) -> [EmailLog] {
do {
return try db.loadEmailLogs(limit: limit)
} catch {
log.error("Failed to load email logs: \(error.localizedDescription)")
return []
}
}
/// Delete a specific log entry
func deleteLog(id: UUID) {
db.deleteEmailLog(id: id)
log.info("Email log deleted: \(id.uuidString)")
}
/// Clear all email logs
func clearAllLogs() {
db.clearEmailLogs()
log.info("All email logs cleared")
}
/// Get total count of email logs
func getLogCount() -> Int {
do {
return try db.getEmailLogCount()
} catch {
log.error("Failed to get email log count: \(error.localizedDescription)")
return 0
}
}
// MARK: - Statistics
/// Get email processing statistics
func getStatistics() -> EmailStatistics {
let logs = loadLogs(limit: 1000) // Last 1000 for stats
let total = logs.count
let successful = logs.filter { $0.status == .success }.count
let errors = logs.filter { $0.status == .error }.count
let totalTokens = logs.compactMap { $0.tokens }.reduce(0, +)
let totalCost = logs.compactMap { $0.cost }.reduce(0.0, +)
let avgResponseTime: TimeInterval?
let responseTimes = logs.compactMap { $0.responseTime }
if !responseTimes.isEmpty {
avgResponseTime = responseTimes.reduce(0, +) / Double(responseTimes.count)
} else {
avgResponseTime = nil
}
return EmailStatistics(
total: total,
successful: successful,
errors: errors,
totalTokens: totalTokens,
totalCost: totalCost,
averageResponseTime: avgResponseTime
)
}
}
// MARK: - Statistics Model
struct EmailStatistics {
let total: Int
let successful: Int
let errors: Int
let totalTokens: Int
let totalCost: Double
let averageResponseTime: TimeInterval?
var successRate: Double {
guard total > 0 else { return 0.0 }
return Double(successful) / Double(total)
}
var errorRate: Double {
guard total > 0 else { return 0.0 }
return Double(errors) / Double(total)
}
}