Added a lot of functionality. Bugfixes and changes
This commit is contained in:
@@ -18,6 +18,7 @@ struct ConversationRecord: Codable, FetchableRecord, PersistableRecord, Sendable
|
||||
var name: String
|
||||
var createdAt: String
|
||||
var updatedAt: String
|
||||
var primaryModel: String?
|
||||
}
|
||||
|
||||
struct MessageRecord: Codable, FetchableRecord, PersistableRecord, Sendable {
|
||||
@@ -31,6 +32,7 @@ struct MessageRecord: Codable, FetchableRecord, PersistableRecord, Sendable {
|
||||
var cost: Double?
|
||||
var timestamp: String
|
||||
var sortOrder: Int
|
||||
var modelId: String?
|
||||
}
|
||||
|
||||
struct SettingRecord: Codable, FetchableRecord, PersistableRecord, Sendable {
|
||||
@@ -48,6 +50,23 @@ struct HistoryRecord: Codable, FetchableRecord, PersistableRecord, Sendable {
|
||||
var timestamp: String
|
||||
}
|
||||
|
||||
struct EmailLogRecord: Codable, FetchableRecord, PersistableRecord, Sendable {
|
||||
static let databaseTableName = "email_logs"
|
||||
|
||||
var id: String
|
||||
var timestamp: String
|
||||
var sender: String
|
||||
var subject: String
|
||||
var emailContent: String
|
||||
var aiResponse: String?
|
||||
var status: String // "success" or "error"
|
||||
var errorMessage: String?
|
||||
var tokens: Int?
|
||||
var cost: Double?
|
||||
var responseTime: Double?
|
||||
var modelId: String?
|
||||
}
|
||||
|
||||
// MARK: - DatabaseService
|
||||
|
||||
final class DatabaseService: Sendable {
|
||||
@@ -127,6 +146,42 @@ final class DatabaseService: Sendable {
|
||||
)
|
||||
}
|
||||
|
||||
migrator.registerMigration("v4") { db in
|
||||
// Add modelId to messages table (nullable for existing messages)
|
||||
try db.alter(table: "messages") { t in
|
||||
t.add(column: "modelId", .text)
|
||||
}
|
||||
|
||||
// Add primaryModel to conversations table (nullable)
|
||||
try db.alter(table: "conversations") { t in
|
||||
t.add(column: "primaryModel", .text)
|
||||
}
|
||||
}
|
||||
|
||||
migrator.registerMigration("v5") { db in
|
||||
// Email handler logs
|
||||
try db.create(table: "email_logs") { t in
|
||||
t.primaryKey("id", .text)
|
||||
t.column("timestamp", .text).notNull()
|
||||
t.column("sender", .text).notNull()
|
||||
t.column("subject", .text).notNull()
|
||||
t.column("emailContent", .text).notNull()
|
||||
t.column("aiResponse", .text)
|
||||
t.column("status", .text).notNull() // "success" or "error"
|
||||
t.column("errorMessage", .text)
|
||||
t.column("tokens", .integer)
|
||||
t.column("cost", .double)
|
||||
t.column("responseTime", .double)
|
||||
t.column("modelId", .text)
|
||||
}
|
||||
|
||||
try db.create(
|
||||
index: "email_logs_on_timestamp",
|
||||
on: "email_logs",
|
||||
columns: ["timestamp"]
|
||||
)
|
||||
}
|
||||
|
||||
return migrator
|
||||
}
|
||||
|
||||
@@ -152,32 +207,68 @@ final class DatabaseService: Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Encrypted Settings Operations
|
||||
|
||||
/// Store an encrypted setting (for sensitive data like API keys)
|
||||
nonisolated func setEncryptedSetting(key: String, value: String) throws {
|
||||
let encryptedValue = try EncryptionService.shared.encrypt(value)
|
||||
try dbQueue.write { db in
|
||||
let record = SettingRecord(key: "encrypted_\(key)", value: encryptedValue)
|
||||
try record.save(db)
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve and decrypt an encrypted setting
|
||||
nonisolated func getEncryptedSetting(key: String) throws -> String? {
|
||||
let encryptedValue = try dbQueue.read { db in
|
||||
try SettingRecord.fetchOne(db, key: "encrypted_\(key)")?.value
|
||||
}
|
||||
|
||||
guard let encryptedValue = encryptedValue else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try EncryptionService.shared.decrypt(encryptedValue)
|
||||
}
|
||||
|
||||
/// Delete an encrypted setting
|
||||
nonisolated func deleteEncryptedSetting(key: String) {
|
||||
try? dbQueue.write { db in
|
||||
_ = try SettingRecord.deleteOne(db, key: "encrypted_\(key)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Conversation Operations
|
||||
|
||||
nonisolated func saveConversation(name: String, messages: [Message]) throws -> Conversation {
|
||||
Log.db.info("Saving conversation '\(name)' with \(messages.count) messages")
|
||||
let convId = UUID()
|
||||
return try saveConversation(id: UUID(), name: name, messages: messages, primaryModel: nil)
|
||||
}
|
||||
|
||||
nonisolated func saveConversation(id: UUID, name: String, messages: [Message], primaryModel: String?) throws -> Conversation {
|
||||
Log.db.info("Saving conversation '\(name)' with \(messages.count) messages (primaryModel: \(primaryModel ?? "none"))")
|
||||
let now = Date()
|
||||
let nowString = isoFormatter.string(from: now)
|
||||
|
||||
let convRecord = ConversationRecord(
|
||||
id: convId.uuidString,
|
||||
id: id.uuidString,
|
||||
name: name,
|
||||
createdAt: nowString,
|
||||
updatedAt: nowString
|
||||
updatedAt: nowString,
|
||||
primaryModel: primaryModel
|
||||
)
|
||||
|
||||
let messageRecords = messages.enumerated().compactMap { index, msg -> MessageRecord? in
|
||||
guard msg.role != .system else { return nil }
|
||||
return MessageRecord(
|
||||
id: UUID().uuidString,
|
||||
conversationId: convId.uuidString,
|
||||
conversationId: id.uuidString,
|
||||
role: msg.role.rawValue,
|
||||
content: msg.content,
|
||||
tokens: msg.tokens,
|
||||
cost: msg.cost,
|
||||
timestamp: isoFormatter.string(from: msg.timestamp),
|
||||
sortOrder: index
|
||||
sortOrder: index,
|
||||
modelId: msg.modelId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -190,11 +281,12 @@ final class DatabaseService: Sendable {
|
||||
|
||||
let savedMessages = messages.filter { $0.role != .system }
|
||||
return Conversation(
|
||||
id: convId,
|
||||
id: id,
|
||||
name: name,
|
||||
messages: savedMessages,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
updatedAt: now,
|
||||
primaryModel: primaryModel
|
||||
)
|
||||
}
|
||||
|
||||
@@ -221,7 +313,8 @@ final class DatabaseService: Sendable {
|
||||
content: record.content,
|
||||
tokens: record.tokens,
|
||||
cost: record.cost,
|
||||
timestamp: timestamp
|
||||
timestamp: timestamp,
|
||||
modelId: record.modelId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -235,7 +328,8 @@ final class DatabaseService: Sendable {
|
||||
name: convRecord.name,
|
||||
messages: messages,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt
|
||||
updatedAt: updatedAt,
|
||||
primaryModel: convRecord.primaryModel
|
||||
)
|
||||
|
||||
return (conversation, messages)
|
||||
@@ -404,4 +498,80 @@ final class DatabaseService: Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Email Log Operations
|
||||
|
||||
nonisolated func saveEmailLog(_ log: EmailLog) {
|
||||
let record = EmailLogRecord(
|
||||
id: log.id.uuidString,
|
||||
timestamp: isoFormatter.string(from: log.timestamp),
|
||||
sender: log.sender,
|
||||
subject: log.subject,
|
||||
emailContent: log.emailContent,
|
||||
aiResponse: log.aiResponse,
|
||||
status: log.status.rawValue,
|
||||
errorMessage: log.errorMessage,
|
||||
tokens: log.tokens,
|
||||
cost: log.cost,
|
||||
responseTime: log.responseTime,
|
||||
modelId: log.modelId
|
||||
)
|
||||
|
||||
try? dbQueue.write { db in
|
||||
try record.insert(db)
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func loadEmailLogs(limit: Int = 100) throws -> [EmailLog] {
|
||||
try dbQueue.read { db in
|
||||
let records = try EmailLogRecord
|
||||
.order(Column("timestamp").desc)
|
||||
.limit(limit)
|
||||
.fetchAll(db)
|
||||
|
||||
return records.compactMap { record in
|
||||
guard let timestamp = isoFormatter.date(from: record.timestamp),
|
||||
let status = EmailLogStatus(rawValue: record.status),
|
||||
let id = UUID(uuidString: record.id) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return EmailLog(
|
||||
id: id,
|
||||
timestamp: timestamp,
|
||||
sender: record.sender,
|
||||
subject: record.subject,
|
||||
emailContent: record.emailContent,
|
||||
aiResponse: record.aiResponse,
|
||||
status: status,
|
||||
errorMessage: record.errorMessage,
|
||||
tokens: record.tokens,
|
||||
cost: record.cost,
|
||||
responseTime: record.responseTime,
|
||||
modelId: record.modelId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func deleteEmailLog(id: UUID) {
|
||||
try? dbQueue.write { db in
|
||||
try db.execute(
|
||||
sql: "DELETE FROM email_logs WHERE id = ?",
|
||||
arguments: [id.uuidString]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func clearEmailLogs() {
|
||||
try? dbQueue.write { db in
|
||||
try db.execute(sql: "DELETE FROM email_logs")
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func getEmailLogCount() throws -> Int {
|
||||
try dbQueue.read { db in
|
||||
try EmailLogRecord.fetchCount(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user