diff --git a/Sources/Database/DB+Contact.swift b/Sources/Database/DB+Contact.swift deleted file mode 100644 index 4207fea75d83ea0ef6e56ae361cb5a172c8773c7..0000000000000000000000000000000000000000 --- a/Sources/Database/DB+Contact.swift +++ /dev/null @@ -1,57 +0,0 @@ -import GRDB -import Models - -extension Contact: Persistable { - public enum Column: String, ColumnExpression { - case id - case photo - case email - case phone - case userId - case status - case username - case isRecent - case nickname - case marshaled - case createdAt - } - - public mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } - - public static func query(_ request: Request) -> QueryInterfaceRequest<Contact> { - switch request { - case .all: - return Contact.all() - case .isRecent: - return Contact - .filter(Column.isRecent == true) - .order(Column.createdAt.desc) - case .verificationInProgress: - return Contact.filter(Column.status == Contact.Status.verificationInProgress.rawValue) - case .failed: - return Contact.filter( - Column.status == Contact.Status.requestFailed.rawValue || - Column.status == Contact.Status.confirmationFailed.rawValue - ) - case .requested: - return Contact.filter( - Column.status == Contact.Status.requested.rawValue || - Column.status == Contact.Status.requesting.rawValue - ) - case .received: - return Contact.filter( - Column.status == Contact.Status.hidden.rawValue || - Column.status == Contact.Status.verified.rawValue || - Column.status == Contact.Status.verificationFailed.rawValue || - Column.status == Contact.Status.verificationInProgress.rawValue - ) - - case .friends: return Contact.filter(Column.status == Contact.Status.friend.rawValue) - case let .withUserId(data): return Contact.filter(Column.userId == data) - case let .withUserIds(ids): return Contact.filter(ids.contains(Contact.Column.userId)) - case let .withUsername(username): return Contact.filter(Column.username.like("\(username)%")) - } - } -} diff --git a/Sources/Database/DB+FileTransfer.swift b/Sources/Database/DB+FileTransfer.swift deleted file mode 100644 index 617d8ce34c0b60f3a54c4e9f18e68cfa0ba1dc67..0000000000000000000000000000000000000000 --- a/Sources/Database/DB+FileTransfer.swift +++ /dev/null @@ -1,26 +0,0 @@ -import GRDB -import Models - -extension FileTransfer: Persistable { - public enum Column: String, ColumnExpression { - case id - case tid - case contact - case fileName - case fileType - case isIncoming - } - - public mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } - - public static func query(_ request: Request) -> QueryInterfaceRequest<FileTransfer> { - switch request { - case .withTID(let transferId): - return FileTransfer.filter(Column.tid == transferId) - case .withContactId(let contactId): - return FileTransfer.filter(Column.contact == contactId) - } - } -} diff --git a/Sources/Database/DB+Group.swift b/Sources/Database/DB+Group.swift deleted file mode 100644 index dc78a33efae1c30fd484d7fd2c99e3e996134ec2..0000000000000000000000000000000000000000 --- a/Sources/Database/DB+Group.swift +++ /dev/null @@ -1,35 +0,0 @@ -import GRDB -import Models - -extension Group: Persistable { - static let members = hasMany(GroupMember.self) - - public enum Column: String, ColumnExpression { - case id - case name - case leader - case groupId - case status - case serialize - case createdAt - case accepted // Deprecated - } - - public mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } - - public static func query(_ request: Request) -> QueryInterfaceRequest<Group> { - switch request { - case .withGroupId(let id): - return Group.filter(Column.groupId == id) - case .accepted: - return Group.filter(Column.status == Group.Status.participating.rawValue) - case .pending: - return Group.filter( - Column.status == Group.Status.pending.rawValue || - Column.status == Group.Status.hidden.rawValue - ) - } - } -} diff --git a/Sources/Database/DB+GroupChatInfo.swift b/Sources/Database/DB+GroupChatInfo.swift deleted file mode 100644 index 4be4dfad3fccda964b68873b5ddcf3cc4253aeed..0000000000000000000000000000000000000000 --- a/Sources/Database/DB+GroupChatInfo.swift +++ /dev/null @@ -1,38 +0,0 @@ -import GRDB -import Models - -extension GroupChatInfo: Requestable { - public static func query(_ request: Request) -> QueryInterfaceRequest<GroupChatInfo> { - let lastMessageRequest = GroupMessage - .annotated(with: max(GroupMessage.Column.timestamp)) - .group(GroupMessage.Column.groupId) - - let lastMessageCTE = CommonTableExpression<GroupMessage>( - named: "lastMessage", - request: lastMessageRequest - ) - - let lastMessage = Group.association(to: lastMessageCTE) { group, lastMessage in - lastMessage[GroupMessage.Column.groupId] == group[Group.Column.groupId] - }.order(GroupMessage.Column.timestamp.desc) - - switch request { - case .fromGroup(let groupId): - return Group - .filter(Group.Column.status == Group.Status.participating.rawValue) - .filter(Group.Column.groupId == groupId) - .with(lastMessageCTE) - .including(optional: lastMessage) - .including(all: Group.members.forKey("members")) - .asRequest(of: Self.self) - - case .accepted: - return Group - .filter(Group.Column.status == Group.Status.participating.rawValue) - .with(lastMessageCTE) - .including(optional: lastMessage) - .including(all: Group.members.forKey("members")) - .asRequest(of: Self.self) - } - } -} diff --git a/Sources/Database/DB+GroupMember.swift b/Sources/Database/DB+GroupMember.swift deleted file mode 100644 index 2153704e4d14c37b9a4e122ca77299b76bd655a6..0000000000000000000000000000000000000000 --- a/Sources/Database/DB+GroupMember.swift +++ /dev/null @@ -1,32 +0,0 @@ -import GRDB -import Models - -extension GroupMember: Persistable { - public enum Column: String, ColumnExpression { - case id - case photo - case status - case userId - case groupId - case username - } - - public mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } - - public static func query(_ request: Request) -> QueryInterfaceRequest<GroupMember> { - switch request { - case .all: - return GroupMember.all() - case let .withUserId(userId): - return GroupMember.filter(Column.userId == userId) - case .fromGroup(let groupId): - return GroupMember.filter(Column.groupId == groupId) - case .strangers: - return GroupMember.filter( - Column.status == GroupMember.Status.pendingUsername.rawValue - ) - } - } -} diff --git a/Sources/Database/DB+GroupMessage.swift b/Sources/Database/DB+GroupMessage.swift deleted file mode 100644 index 7ec6754d6fa0073ba6995b0a44145d1e4a16099e..0000000000000000000000000000000000000000 --- a/Sources/Database/DB+GroupMessage.swift +++ /dev/null @@ -1,36 +0,0 @@ -import GRDB -import Models - -extension GroupMessage: Persistable { - public enum Column: String, ColumnExpression { - case id - case sender - case status - case unread - case payload - case groupId - case uniqueId - case roundURL - case timestamp - case roundId - } - - public mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } - - public static func query(_ request: Request) -> QueryInterfaceRequest<GroupMessage> { - switch request { - case let .withUniqueId(id): - return GroupMessage.filter(Column.uniqueId == id) - case let .id(id): - return GroupMessage.filter(Column.id == id) - case let .fromGroup(id): - return GroupMessage.filter(Column.groupId == id).order(Column.timestamp.asc) - case let .unreadsFromGroup(id): - return GroupMessage.filter(Column.groupId == id).filter(Column.unread == true) - case .sending: - return GroupMessage.filter(Column.status == GroupMessage.Status.sending.rawValue) - } - } -} diff --git a/Sources/Database/DB+Message.swift b/Sources/Database/DB+Message.swift deleted file mode 100644 index b5057ea99d870f7861df21f46dac9b47c92d245d..0000000000000000000000000000000000000000 --- a/Sources/Database/DB+Message.swift +++ /dev/null @@ -1,46 +0,0 @@ -import GRDB -import Models - -extension Message: Persistable { - public enum Column: String, ColumnExpression { - case id - case report - case sender - case unread - case status - case payload - case roundURL - case receiver - case uniqueId - case timestamp - } - - public mutating func didInsert(with rowID: Int64, for column: String?) { - id = rowID - } - - public static func query(_ request: Request) -> QueryInterfaceRequest<Message> { - switch request { - case let .withUniqueId(id): - return Message.filter(Column.uniqueId == id) - case let .unreadsFromContactId(id): - return Message - .filter(Column.sender == id || Column.receiver == id) - .filter(Column.unread == true) - - case let .latestOnesFromContactIds(ids): - return Message - .annotated(with: Column.timestamp) - .filter(ids.contains(Column.sender) || ids.contains(Column.receiver)) - - case let .withId(id): - return Message.filter(Column.id == id) - case let .withContact(id): - return Message.filter(Column.sender == id || Column.receiver == id) - case .sending: - return Message.filter(Column.status == Message.Status.sending.rawValue) - case .sendingAttachment: - return Message.filter(Column.status == Message.Status.sendingAttachment.rawValue) - } - } -} diff --git a/Sources/Database/DB+SingleChatInfo.swift b/Sources/Database/DB+SingleChatInfo.swift deleted file mode 100644 index 16fbc4ff9299a8decc603559c09beebf1aa16f9b..0000000000000000000000000000000000000000 --- a/Sources/Database/DB+SingleChatInfo.swift +++ /dev/null @@ -1,27 +0,0 @@ -import GRDB -import Models - -extension SingleChatInfo: Requestable { - public static func query(_ request: Request) -> QueryInterfaceRequest<SingleChatInfo> { - let lastMessageRequest = Message - .annotated(with: max(Message.Column.timestamp)) - .group(Message.Column.sender || Message.Column.receiver) - - let lastMessageCTE = CommonTableExpression<Message>( - named: "lastMessage", - request: lastMessageRequest - ) - - let lastMessage = Contact.association(to: lastMessageCTE) { contact, lastMessage in - lastMessage[Message.Column.sender] == contact[Contact.Column.userId] || - lastMessage[Message.Column.receiver] == contact[Contact.Column.userId] - }.order(Message.Column.timestamp.desc) - - switch request { - case .all: - return Contact.with(lastMessageCTE) - .including(required: lastMessage) - .asRequest(of: Self.self) - } - } -} diff --git a/Sources/Database/DatabaseManager.swift b/Sources/Database/DatabaseManager.swift deleted file mode 100644 index 86271c2ebfcc146182d867aedf7dd622168807df..0000000000000000000000000000000000000000 --- a/Sources/Database/DatabaseManager.swift +++ /dev/null @@ -1,261 +0,0 @@ -import GRDB -import Models -import Combine -import Foundation - -public protocol Requestable: FetchableRecord { - associatedtype Request - static func query(_ request: Request) -> QueryInterfaceRequest<Self> -} - -public protocol Persistable: Requestable & MutablePersistableRecord & Identifiable { - var id: Int64? { get } -} - -public protocol DatabaseManager { - func drop() - func setup() throws - - func updateAll<T>(_ type: T.Type, - _ request: T.Request, - with: [ColumnAssignment]) throws where T: Persistable - - @discardableResult func save<T>(_ model: T) throws -> T where T: Persistable - func update<T>(_ model: T) throws where T: Persistable - func delete<T>(_ model: T) throws where T: Persistable - func fetch<T>(_ request: T.Request) throws -> [T] where T: Requestable - func fetch<T>(withId id: Int64) throws -> T? where T: Persistable - func publisher<T>(fetch request: T.Request) -> AnyPublisher<[T], Error> where T: Requestable - func delete<T>(_ type: T.Type, _ request: T.Request) throws where T: Persistable -} - -public extension DatabaseManager { - func publisher<T: Requestable>( - fetch type: T.Type, - _ request: T.Request - ) -> AnyPublisher<[T], Error> { - publisher(fetch: request) - } -} - -public final class GRDBDatabaseManager { - var databaseQueue: DatabaseQueue! - - public init() {} -} - -extension GRDBDatabaseManager: DatabaseManager { - public func drop() { - try? databaseQueue.write { db in - try db.drop(table: Contact.databaseTableName) - try db.drop(table: Message.databaseTableName) - try db.drop(table: Group.databaseTableName) - try db.drop(table: GroupMember.databaseTableName) - try db.drop(table: GroupMessage.databaseTableName) - } - } - - public func updateAll<T>(_ type: T.Type, - _ request: T.Request, - with assignments: [ColumnAssignment]) throws where T : Persistable { - _ = try databaseQueue.write { - try T.query(request).updateAll($0, assignments) - } - } - - public func save<T: Persistable>(_ model: T) throws -> T { - try databaseQueue.write { db in - var model = model - - if model.id == nil { - try model.insert(db) - } else { - try model.update(db) - } - - return model - } - } - - public func update<T>(_ model: T) throws where T: Persistable { - try databaseQueue.write { try model.update($0) } - } - - public func fetch<T>(withId id: Int64) throws -> T? where T: Persistable { - try databaseQueue.read { db in - try T.fetchOne(db, key: id) - } - } - - public func fetch<T>(_ request: T.Request) throws -> [T] where T: Requestable { - try databaseQueue.read { db in - try T.query(request).fetchAll(db) - } - } - - public func publisher<T>(fetch request: T.Request) -> AnyPublisher<[T], Error> where T: Requestable { - ValueObservation.tracking { - try T.query(request).fetchAll($0) - }.publisher(in: databaseQueue, scheduling: .immediate) - .eraseToAnyPublisher() - } - - public func delete<T>(_ model: T) throws where T: Persistable { - _ = try databaseQueue.write { - try model.delete($0) - } - } - - public func delete<T>(_ type: T.Type, _ request: T.Request) throws where T: Persistable { - _ = try databaseQueue.write { - try T.query(request).deleteAll($0) - } - } - - public func setup() throws { - var migrator = DatabaseMigrator() - - let oldPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] - .appending("/xxmessenger.sqlite") - - let url = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")! - .appendingPathComponent("database") - .appendingPathExtension("sqlite") - - if FileManager.default.fileExists(atPath: oldPath) && !FileManager.default.fileExists(atPath: url.path) { - do { - try FileManager.default.moveItem(atPath: oldPath, toPath: url.path) - } catch { - fatalError("Couldn't migrate database from old path to new one: \(error.localizedDescription)") - } - } - - databaseQueue = try DatabaseQueue(path: url.path) - try FileManager.default.setAttributes([ - .protectionKey : FileProtectionType.completeUntilFirstUserAuthentication - ], ofItemAtPath: url.path) - - migrator.registerMigration("v1") { db in - try db.create(table: Contact.databaseTableName, ifNotExists: true) { table in - table.autoIncrementedPrimaryKey(Contact.Column.id.rawValue, onConflict: .replace) - table.column(Contact.Column.photo.rawValue, .blob) - table.column(Contact.Column.email.rawValue, .text) - table.column(Contact.Column.phone.rawValue, .text) - table.column(Contact.Column.nickname.rawValue, .text) - table.column(Contact.Column.createdAt.rawValue, .datetime) - table.column(Contact.Column.userId.rawValue, .blob).unique() - table.column(Contact.Column.username.rawValue, .text).notNull() - table.column(Contact.Column.status.rawValue, .integer).notNull() - table.column(Contact.Column.marshaled.rawValue, .blob).notNull() - } - - try db.create(table: Message.databaseTableName, ifNotExists: true) { table in - table.autoIncrementedPrimaryKey(Message.Column.id.rawValue, onConflict: .replace) - table.column(Message.Column.report.rawValue, .blob) - table.column(Message.Column.uniqueId.rawValue, .blob) - table.column(Message.Column.sender.rawValue, .blob).notNull() - table.column(Message.Column.payload.rawValue, .text).notNull() - table.column(Message.Column.receiver.rawValue, .blob).notNull() - table.column(Message.Column.roundURL.rawValue, .text) - table.column(Message.Column.status.rawValue, .integer).notNull() - table.column(Message.Column.unread.rawValue, .boolean).notNull() - table.column(Message.Column.timestamp.rawValue, .integer).notNull() - } - - try db.create(table: Group.databaseTableName, ifNotExists: true) { table in - table.autoIncrementedPrimaryKey(Group.Column.id.rawValue, onConflict: .replace) - table.column(Group.Column.groupId.rawValue, .blob).unique() - table.column(Group.Column.name.rawValue, .text).notNull() - table.column(Group.Column.leader.rawValue, .blob).notNull() - table.column(Group.Column.serialize.rawValue, .blob).notNull() - table.column(Group.Column.accepted.rawValue, .boolean).notNull() - } - - try db.create(table: GroupMember.databaseTableName, ifNotExists: true) { table in - table.autoIncrementedPrimaryKey(GroupMember.Column.id.rawValue, onConflict: .replace) - table.column(GroupMember.Column.userId.rawValue, .blob).notNull() - table.column(GroupMember.Column.username.rawValue, .text).notNull() - table.column(GroupMember.Column.photo.rawValue, .blob) - table.column(GroupMember.Column.status.rawValue, .integer).notNull() - table.column(GroupMember.Column.groupId.rawValue, .blob).notNull() - .references( - Group.databaseTableName, - column: Group.Column.groupId.rawValue, - onDelete: .cascade, - deferred: true - ) - } - - try db.create(table: GroupMessage.databaseTableName, ifNotExists: true) { table in - table.autoIncrementedPrimaryKey(GroupMessage.Column.id.rawValue, onConflict: .replace) - table.column(GroupMessage.Column.uniqueId.rawValue, .blob) - table.column(GroupMessage.Column.roundId.rawValue, .integer) - table.column(GroupMessage.Column.groupId.rawValue, .blob).notNull() - table.column(GroupMessage.Column.sender.rawValue, .blob).notNull() - table.column(GroupMessage.Column.roundURL.rawValue, .text) - table.column(GroupMessage.Column.payload.rawValue, .text).notNull() - table.column(GroupMessage.Column.status.rawValue, .integer).notNull() - table.column(GroupMessage.Column.unread.rawValue, .boolean).notNull() - table.column(GroupMessage.Column.timestamp.rawValue, .integer).notNull() - } - - try db.create(table: FileTransfer.databaseTableName, ifNotExists: true) { table in - table.autoIncrementedPrimaryKey(FileTransfer.Column.id.rawValue, onConflict: .replace) - table.column(FileTransfer.Column.tid.rawValue, .blob).notNull() - table.column(FileTransfer.Column.contact.rawValue, .blob).notNull() - table.column(FileTransfer.Column.fileName.rawValue, .text).notNull() - table.column(FileTransfer.Column.fileType.rawValue, .text).notNull() - table.column(FileTransfer.Column.isIncoming.rawValue, .boolean).notNull() - } - } - - migrator.registerMigration("v1: Updating contact/group requests UI") { db in - try db.create(table: "temp_\(Group.databaseTableName)") { table in - table.autoIncrementedPrimaryKey(Group.Column.id.rawValue, onConflict: .replace) - table.column(Group.Column.groupId.rawValue, .blob).unique() - table.column(Group.Column.name.rawValue, .text).notNull() - table.column(Group.Column.leader.rawValue, .blob).notNull() - table.column(Group.Column.serialize.rawValue, .blob).notNull() - table.column(Group.Column.status.rawValue, .integer).notNull() - table.column(Group.Column.createdAt.rawValue, .datetime).notNull() - } - - let oldRows = try Row.fetchCursor(db, sql: "SELECT * FROM \(Group.databaseTableName)") - while let row = try oldRows.next() { - let status: Group.Status - - if row["accepted"] == true { - status = .participating - } else { - status = .pending - } - - try db.execute( - sql: "INSERT INTO temp_\(Group.databaseTableName) (id, groupId, name, leader, serialize, status, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?)", - arguments: - [row["id"], - row["groupId"], - row["name"], - row["leader"], - row["serialize"], - status.rawValue, - Date() - ]) - } - - try db.drop(table: Group.databaseTableName) - try db.rename(table: "temp_\(Group.databaseTableName)", to: Group.databaseTableName) - } - - migrator.registerMigration("v2") { db in - try db.alter(table: Contact.databaseTableName) { table in - table.add(column: Contact.Column.isRecent.rawValue, .boolean) - } - - try Contact.updateAll(db, Contact.Column.isRecent.set(to: false)) - } - - try migrator.migrate(databaseQueue) - } -}