import Foundation import GRDB import Martin struct RosterVersion: DBStorable { static let databaseTableName = "rosterVersions" var bareJid: String var version: String var id: String { bareJid } } struct Roster: DBStorable { static let databaseTableName = "rosters" var bareJid: String = "" var contactBareJid: String var name: String? var subscription: String var ask: Bool var data: DBRosterData var locallyDeleted: Bool = false var id: String { "\(bareJid)-\(contactBareJid)" } } struct DBRosterData: Codable, DatabaseValueConvertible { let groups: [String] let annotations: [RosterItemAnnotation] public var databaseValue: DatabaseValue { let encoder = JSONEncoder() // swiftlint:disable:next force_try let data = try! encoder.encode(self) return data.databaseValue } public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { guard let data = Data.fromDatabaseValue(dbValue) else { return nil } let decoder = JSONDecoder() // swiftlint:disable:next force_try return try! decoder.decode(Self.self, from: data) } static func == (lhs: DBRosterData, rhs: DBRosterData) -> Bool { lhs.groups == rhs.groups && lhs.annotations == rhs.annotations } } extension RosterItemAnnotation: Equatable { public static func == (lhs: RosterItemAnnotation, rhs: RosterItemAnnotation) -> Bool { lhs.type == rhs.type && lhs.values == rhs.values } } extension Roster: Equatable { static func == (lhs: Roster, rhs: Roster) -> Bool { lhs.bareJid == rhs.bareJid && lhs.contactBareJid == rhs.contactBareJid } } extension Roster { static func fetchAll(for jid: String) async throws -> [Roster] { let rosters = try await Database.shared.dbQueue.read { db in try Roster.filter(Column("bareJid") == jid).fetchAll(db) } return rosters } }