diff --git a/Packages/MagnumOpusCore/Sources/MailStore/DatabaseSetup.swift b/Packages/MagnumOpusCore/Sources/MailStore/DatabaseSetup.swift new file mode 100644 index 0000000..5df4e3d --- /dev/null +++ b/Packages/MagnumOpusCore/Sources/MailStore/DatabaseSetup.swift @@ -0,0 +1,101 @@ +import GRDB + +public enum DatabaseSetup { + public static func migrator() -> DatabaseMigrator { + var migrator = DatabaseMigrator() + + migrator.registerMigration("v1_initial") { db in + try db.create(table: "account") { t in + t.primaryKey("id", .text) + t.column("name", .text).notNull() + t.column("email", .text).notNull() + t.column("imapHost", .text).notNull() + t.column("imapPort", .integer).notNull() + } + + try db.create(table: "mailbox") { t in + t.primaryKey("id", .text) + t.belongsTo("account", onDelete: .cascade).notNull() + t.column("name", .text).notNull() + t.column("uidValidity", .integer).notNull() + t.column("uidNext", .integer).notNull() + } + + try db.create(table: "message") { t in + t.primaryKey("id", .text) + t.belongsTo("account", onDelete: .cascade).notNull() + t.belongsTo("mailbox", onDelete: .cascade).notNull() + t.column("uid", .integer).notNull() + t.column("messageId", .text) + t.column("inReplyTo", .text) + t.column("refs", .text) + t.column("subject", .text) + t.column("fromAddress", .text) + t.column("fromName", .text) + t.column("toAddresses", .text) + t.column("ccAddresses", .text) + t.column("date", .text).notNull() + t.column("snippet", .text) + t.column("bodyText", .text) + t.column("bodyHtml", .text) + t.column("isRead", .boolean).notNull().defaults(to: false) + t.column("isFlagged", .boolean).notNull().defaults(to: false) + t.column("size", .integer).notNull().defaults(to: 0) + t.uniqueKey(["mailboxId", "uid"]) + } + + try db.create(table: "thread") { t in + t.primaryKey("id", .text) + t.belongsTo("account", onDelete: .cascade).notNull() + t.column("subject", .text) + t.column("lastDate", .text).notNull() + t.column("messageCount", .integer).notNull().defaults(to: 0) + } + + try db.create(table: "threadMessage") { t in + t.belongsTo("thread", onDelete: .cascade).notNull() + t.belongsTo("message", onDelete: .cascade).notNull() + t.primaryKey(["threadId", "messageId"]) + } + + try db.create(table: "attachment") { t in + t.primaryKey("id", .text) + t.belongsTo("message", onDelete: .cascade).notNull() + t.column("filename", .text) + t.column("mimeType", .text).notNull() + t.column("size", .integer).notNull().defaults(to: 0) + t.column("contentId", .text) + t.column("cachePath", .text) + } + + try db.create(index: "idx_message_mailbox_uid", on: "message", columns: ["mailboxId", "uid"]) + try db.create(index: "idx_message_messageId", on: "message", columns: ["messageId"]) + try db.create(index: "idx_thread_lastDate", on: "thread", columns: ["lastDate"]) + } + + migrator.registerMigration("v1_fts5") { db in + try db.create(virtualTable: "messageFts", using: FTS5()) { t in + t.synchronize(withTable: "message") + t.tokenizer = .porter(wrapping: .unicode61()) + t.column("subject") + t.column("fromName") + t.column("fromAddress") + t.column("bodyText") + } + } + + return migrator + } + + public static func openDatabase(atPath path: String) throws -> DatabasePool { + let pool = try DatabasePool(path: path) + try migrator().migrate(pool) + return pool + } + + public static func openInMemoryDatabase() throws -> DatabaseQueue { + let queue = try DatabaseQueue() + try migrator().migrate(queue) + return queue + } +} diff --git a/Packages/MagnumOpusCore/Sources/MailStore/Placeholder.swift b/Packages/MagnumOpusCore/Sources/MailStore/Placeholder.swift deleted file mode 100644 index 69d38bd..0000000 --- a/Packages/MagnumOpusCore/Sources/MailStore/Placeholder.swift +++ /dev/null @@ -1 +0,0 @@ -enum MailStorePlaceholder {} diff --git a/Packages/MagnumOpusCore/Sources/MailStore/Records/AccountRecord.swift b/Packages/MagnumOpusCore/Sources/MailStore/Records/AccountRecord.swift new file mode 100644 index 0000000..e5b0b9e --- /dev/null +++ b/Packages/MagnumOpusCore/Sources/MailStore/Records/AccountRecord.swift @@ -0,0 +1,19 @@ +import GRDB + +public struct AccountRecord: Codable, FetchableRecord, PersistableRecord, Sendable { + public static let databaseTableName = "account" + + public var id: String + public var name: String + public var email: String + public var imapHost: String + public var imapPort: Int + + public init(id: String, name: String, email: String, imapHost: String, imapPort: Int) { + self.id = id + self.name = name + self.email = email + self.imapHost = imapHost + self.imapPort = imapPort + } +} diff --git a/Packages/MagnumOpusCore/Sources/MailStore/Records/AttachmentRecord.swift b/Packages/MagnumOpusCore/Sources/MailStore/Records/AttachmentRecord.swift new file mode 100644 index 0000000..80a7ad7 --- /dev/null +++ b/Packages/MagnumOpusCore/Sources/MailStore/Records/AttachmentRecord.swift @@ -0,0 +1,26 @@ +import GRDB + +public struct AttachmentRecord: Codable, FetchableRecord, PersistableRecord, Sendable { + public static let databaseTableName = "attachment" + + public var id: String + public var messageId: String + public var filename: String? + public var mimeType: String + public var size: Int + public var contentId: String? + public var cachePath: String? + + public init( + id: String, messageId: String, filename: String?, mimeType: String, + size: Int, contentId: String?, cachePath: String? + ) { + self.id = id + self.messageId = messageId + self.filename = filename + self.mimeType = mimeType + self.size = size + self.contentId = contentId + self.cachePath = cachePath + } +} diff --git a/Packages/MagnumOpusCore/Sources/MailStore/Records/MailboxRecord.swift b/Packages/MagnumOpusCore/Sources/MailStore/Records/MailboxRecord.swift new file mode 100644 index 0000000..dc579dd --- /dev/null +++ b/Packages/MagnumOpusCore/Sources/MailStore/Records/MailboxRecord.swift @@ -0,0 +1,19 @@ +import GRDB + +public struct MailboxRecord: Codable, FetchableRecord, PersistableRecord, Sendable { + public static let databaseTableName = "mailbox" + + public var id: String + public var accountId: String + public var name: String + public var uidValidity: Int + public var uidNext: Int + + public init(id: String, accountId: String, name: String, uidValidity: Int, uidNext: Int) { + self.id = id + self.accountId = accountId + self.name = name + self.uidValidity = uidValidity + self.uidNext = uidNext + } +} diff --git a/Packages/MagnumOpusCore/Sources/MailStore/Records/MessageRecord.swift b/Packages/MagnumOpusCore/Sources/MailStore/Records/MessageRecord.swift new file mode 100644 index 0000000..b30fefc --- /dev/null +++ b/Packages/MagnumOpusCore/Sources/MailStore/Records/MessageRecord.swift @@ -0,0 +1,54 @@ +import GRDB + +public struct MessageRecord: Codable, FetchableRecord, PersistableRecord, Sendable { + public static let databaseTableName = "message" + + public var id: String + public var accountId: String + public var mailboxId: String + public var uid: Int + public var messageId: String? + public var inReplyTo: String? + public var refs: String? + public var subject: String? + public var fromAddress: String? + public var fromName: String? + public var toAddresses: String? + public var ccAddresses: String? + public var date: String + public var snippet: String? + public var bodyText: String? + public var bodyHtml: String? + public var isRead: Bool + public var isFlagged: Bool + public var size: Int + + public init( + id: String, accountId: String, mailboxId: String, uid: Int, + messageId: String?, inReplyTo: String?, refs: String?, + subject: String?, fromAddress: String?, fromName: String?, + toAddresses: String?, ccAddresses: String?, + date: String, snippet: String?, bodyText: String?, bodyHtml: String?, + isRead: Bool, isFlagged: Bool, size: Int + ) { + self.id = id + self.accountId = accountId + self.mailboxId = mailboxId + self.uid = uid + self.messageId = messageId + self.inReplyTo = inReplyTo + self.refs = refs + self.subject = subject + self.fromAddress = fromAddress + self.fromName = fromName + self.toAddresses = toAddresses + self.ccAddresses = ccAddresses + self.date = date + self.snippet = snippet + self.bodyText = bodyText + self.bodyHtml = bodyHtml + self.isRead = isRead + self.isFlagged = isFlagged + self.size = size + } +} diff --git a/Packages/MagnumOpusCore/Sources/MailStore/Records/ThreadMessageRecord.swift b/Packages/MagnumOpusCore/Sources/MailStore/Records/ThreadMessageRecord.swift new file mode 100644 index 0000000..0d199c6 --- /dev/null +++ b/Packages/MagnumOpusCore/Sources/MailStore/Records/ThreadMessageRecord.swift @@ -0,0 +1,13 @@ +import GRDB + +public struct ThreadMessageRecord: Codable, FetchableRecord, PersistableRecord, Sendable { + public static let databaseTableName = "threadMessage" + + public var threadId: String + public var messageId: String + + public init(threadId: String, messageId: String) { + self.threadId = threadId + self.messageId = messageId + } +} diff --git a/Packages/MagnumOpusCore/Sources/MailStore/Records/ThreadRecord.swift b/Packages/MagnumOpusCore/Sources/MailStore/Records/ThreadRecord.swift new file mode 100644 index 0000000..f385ada --- /dev/null +++ b/Packages/MagnumOpusCore/Sources/MailStore/Records/ThreadRecord.swift @@ -0,0 +1,19 @@ +import GRDB + +public struct ThreadRecord: Codable, FetchableRecord, PersistableRecord, Sendable { + public static let databaseTableName = "thread" + + public var id: String + public var accountId: String + public var subject: String? + public var lastDate: String + public var messageCount: Int + + public init(id: String, accountId: String, subject: String?, lastDate: String, messageCount: Int) { + self.id = id + self.accountId = accountId + self.subject = subject + self.lastDate = lastDate + self.messageCount = messageCount + } +}