From df0453992a4d2697a18c32fcc8283f98f33ff6a4 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Thu, 11 Aug 2022 02:05:55 +0100
Subject: [PATCH] Filter messages by sender's blocked and banned status

---
 Sources/XXDatabase/Models/Message+GRDB.swift  | 20 +++++
 Sources/XXModels/Models/Message.swift         | 26 ++++++
 Tests/XXDatabaseTests/Message+GRDBTests.swift | 88 +++++++++++++++++++
 3 files changed, 134 insertions(+)

diff --git a/Sources/XXDatabase/Models/Message+GRDB.swift b/Sources/XXDatabase/Models/Message+GRDB.swift
index bb0196b..d242a67 100644
--- a/Sources/XXDatabase/Models/Message+GRDB.swift
+++ b/Sources/XXDatabase/Models/Message+GRDB.swift
@@ -17,6 +17,13 @@ extension Message: FetchableRecord, MutablePersistableRecord {
     case fileTransferId
   }
 
+  enum Association {
+    static let sender = belongsTo(
+      Contact.self,
+      using: .init([Column.senderId], to: [Contact.Column.id])
+    )
+  }
+
   public static let databaseTableName = "messages"
 
   static func request(_ query: Query) -> QueryInterfaceRequest<Message> {
@@ -74,6 +81,19 @@ extension Message: FetchableRecord, MutablePersistableRecord {
       break
     }
 
+    if query.isSenderBlocked != nil || query.isSenderBanned != nil {
+      let sender = TableAlias(name: "sender")
+      request = request.joining(required: Association.sender.aliased(sender))
+
+      if let isSenderBlocked = query.isSenderBlocked {
+        request = request.filter(sender[Contact.Column.isBlocked] == isSenderBlocked)
+      }
+
+      if let isSenderBanned = query.isSenderBanned {
+        request = request.filter(sender[Contact.Column.isBanned] == isSenderBanned)
+      }
+    }
+
     switch query.sortBy {
     case .date(desc: false):
       request = request.order(Column.date)
diff --git a/Sources/XXModels/Models/Message.swift b/Sources/XXModels/Models/Message.swift
index 9e75dd7..cc5645d 100644
--- a/Sources/XXModels/Models/Message.swift
+++ b/Sources/XXModels/Models/Message.swift
@@ -176,6 +176,14 @@ extension Message {
     ///     If `true`, get only unread messages.
     ///     If `false`, get only read messages.
     ///     If `nil` (default), disable the filter.
+    ///   - isBlocked: Filter by sender's contact `isBlocked` status.
+    ///     If `true`, include only messages from blocked senders.
+    ///     If `false`, include only messages from non-blocked senders.
+    ///     If `nil` (default), disable the filter.
+    ///   - isBanned: Filter by sender's contact `isBanned` status.
+    ///     If `true`, include only messages from banned senders.
+    ///     If `false`, include only messages from non-banned senders.
+    ///     If `nil` (default), disable the filter.
     ///   - fileTransferId: Filter by file transfer id.
     ///     If `.some(.some(fileTransferId))`, get messages with provided `fileTransferId`.
     ///     If `.some(.none)`, get messages without `fileTransferId`.
@@ -187,6 +195,8 @@ extension Message {
       chat: Chat? = nil,
       status: Set<Status>? = nil,
       isUnread: Bool? = nil,
+      isSenderBlocked: Bool? = nil,
+      isSenderBanned: Bool? = nil,
       fileTransferId: FileTransfer.ID?? = nil,
       sortBy: SortOrder = .date()
     ) {
@@ -195,6 +205,8 @@ extension Message {
       self.chat = chat
       self.status = status
       self.isUnread = isUnread
+      self.isSenderBlocked = isSenderBlocked
+      self.isSenderBanned = isSenderBanned
       self.fileTransferId = fileTransferId
       self.sortBy = sortBy
     }
@@ -229,6 +241,20 @@ extension Message {
     /// If `nil`, disable the filter.
     public var isUnread: Bool?
 
+    /// Filter by sender's contact `isBlocked` status
+    ///
+    /// If `true`, include only messages from blocked senders.
+    /// If `false`, include only messages from non-blocked senders.
+    /// If `nil`, disable the filter.
+    public var isSenderBlocked: Bool?
+
+    /// Filter by sender's contact `isBanned` status
+    ///
+    /// If `true`, include only messages from banned senders.
+    /// If `false`, include only messages from non-banned senders.
+    /// If `nil`, disable the filter.
+    public var isSenderBanned: Bool?
+
     /// Filter by file transfer id
     ///
     /// If `.some(.some(fileTransferId))`, get messages with provided `fileTransferId`.
diff --git a/Tests/XXDatabaseTests/Message+GRDBTests.swift b/Tests/XXDatabaseTests/Message+GRDBTests.swift
index f77d31c..00a2c99 100644
--- a/Tests/XXDatabaseTests/Message+GRDBTests.swift
+++ b/Tests/XXDatabaseTests/Message+GRDBTests.swift
@@ -474,6 +474,94 @@ final class MessageGRDBTests: XCTestCase {
     )
   }
 
+  func testFetchingBySenderBlockedStatus() throws {
+    // Mock up contacts:
+
+    let contactA = try db.saveContact(.stub("A").withBlocked(false))
+    let contactB = try db.saveContact(.stub("B").withBlocked(true))
+    let contactC = try db.saveContact(.stub("C").withBlocked(false))
+
+    // Mock up messages:
+
+    let message_AtoB_at1 = try db.saveMessage(.stub(from: contactA, to: contactB, at: 1))
+    let message_AtoC_at2 = try db.saveMessage(.stub(from: contactA, to: contactC, at: 2))
+    let message_BtoA_at3 = try db.saveMessage(.stub(from: contactB, to: contactA, at: 3))
+    let message_BtoC_at4 = try db.saveMessage(.stub(from: contactB, to: contactC, at: 4))
+    let message_CtoA_at5 = try db.saveMessage(.stub(from: contactC, to: contactA, at: 5))
+    let message_CtoB_at6 = try db.saveMessage(.stub(from: contactC, to: contactB, at: 6))
+
+    // Fetch messages from blocked contacts:
+
+    XCTAssertNoDifference(try db.fetchMessages(.init(isSenderBlocked: true)), [
+      message_BtoA_at3,
+      message_BtoC_at4,
+    ])
+
+    // Fetch messages from non-blocked contacts:
+
+    XCTAssertNoDifference(try db.fetchMessages(.init(isSenderBlocked: false)), [
+      message_AtoB_at1,
+      message_AtoC_at2,
+      message_CtoA_at5,
+      message_CtoB_at6,
+    ])
+
+    // Fetch messages regardless sender contact's `isBlocked` status:
+
+    XCTAssertNoDifference(try db.fetchMessages(.init(isSenderBlocked: nil)), [
+      message_AtoB_at1,
+      message_AtoC_at2,
+      message_BtoA_at3,
+      message_BtoC_at4,
+      message_CtoA_at5,
+      message_CtoB_at6,
+    ])
+  }
+
+  func testFetchingBySenderBannedStatus() throws {
+    // Mock up contacts:
+
+    let contactA = try db.saveContact(.stub("A").withBanned(false))
+    let contactB = try db.saveContact(.stub("B").withBanned(true))
+    let contactC = try db.saveContact(.stub("C").withBanned(false))
+
+    // Mock up messages:
+
+    let message_AtoB_at1 = try db.saveMessage(.stub(from: contactA, to: contactB, at: 1))
+    let message_AtoC_at2 = try db.saveMessage(.stub(from: contactA, to: contactC, at: 2))
+    let message_BtoA_at3 = try db.saveMessage(.stub(from: contactB, to: contactA, at: 3))
+    let message_BtoC_at4 = try db.saveMessage(.stub(from: contactB, to: contactC, at: 4))
+    let message_CtoA_at5 = try db.saveMessage(.stub(from: contactC, to: contactA, at: 5))
+    let message_CtoB_at6 = try db.saveMessage(.stub(from: contactC, to: contactB, at: 6))
+
+    // Fetch messages from banned contacts:
+
+    XCTAssertNoDifference(try db.fetchMessages(.init(isSenderBanned: true)), [
+      message_BtoA_at3,
+      message_BtoC_at4,
+    ])
+
+    // Fetch messages from non-banned contacts:
+
+    XCTAssertNoDifference(try db.fetchMessages(.init(isSenderBanned: false)), [
+      message_AtoB_at1,
+      message_AtoC_at2,
+      message_CtoA_at5,
+      message_CtoB_at6,
+    ])
+
+    // Fetch messages regardless sender contact's `isBanned` status:
+
+    XCTAssertNoDifference(try db.fetchMessages(.init(isSenderBanned: nil)), [
+      message_AtoB_at1,
+      message_AtoC_at2,
+      message_BtoA_at3,
+      message_BtoC_at4,
+      message_CtoA_at5,
+      message_CtoB_at6,
+    ])
+  }
+
   func testDeletingMany() throws {
     // Mock up contacts
 
-- 
GitLab