diff --git a/Sources/XXDatabase/Models/GroupChatInfo+GRDB.swift b/Sources/XXDatabase/Models/GroupChatInfo+GRDB.swift index 20062ab25781e6b59a9d4cb8b4d1ab3480d72ee9..a44b98df23f271de5c26009ac75c0a7da2d9a1b2 100644 --- a/Sources/XXDatabase/Models/GroupChatInfo+GRDB.swift +++ b/Sources/XXDatabase/Models/GroupChatInfo+GRDB.swift @@ -18,17 +18,32 @@ extension GroupChatInfo: FetchableRecord { _ = sqlArguments.append(contentsOf: sqlArgumentsAuthStatus(authStatus)) } - if query.excludeBlockedContacts || query.excludeBannedContacts { + if query.isLeaderBlocked != nil || query.isLeaderBanned != nil { sqlJoins.append("INNER JOIN contacts l ON g.leaderId = l.id") + + if let isLeaderBlocked = query.isLeaderBlocked { + sqlWhere.append("l.isBlocked = :isLeaderBlocked") + _ = sqlArguments.append(contentsOf: StatementArguments([ + "isLeaderBlocked": isLeaderBlocked + ])) + } + + if let isLeaderBanned = query.isLeaderBanned { + sqlWhere.append("l.isBanned = :isLeaderBanned") + _ = sqlArguments.append(contentsOf: StatementArguments([ + "isLeaderBanned": isLeaderBanned + ])) + } + } + + if query.excludeBlockedContactsMessages || query.excludeBannedContactsMessages { sqlJoins.append("INNER JOIN contacts s ON m.senderId = s.id") - if query.excludeBlockedContacts { - sqlWhere.append("l.isBlocked != 1") + if query.excludeBlockedContactsMessages { sqlWhere.append("s.isBlocked != 1") } - if query.excludeBannedContacts { - sqlWhere.append("l.isBanned != 1") + if query.excludeBannedContactsMessages { sqlWhere.append("s.isBanned != 1") } } diff --git a/Sources/XXModels/Models/GroupChatInfo.swift b/Sources/XXModels/Models/GroupChatInfo.swift index 8e92eddeeb155550a8659bbd7c3ab0b7f3f01206..4e5f4c592f4176072817198ad4f4afefa16b5aeb 100644 --- a/Sources/XXModels/Models/GroupChatInfo.swift +++ b/Sources/XXModels/Models/GroupChatInfo.swift @@ -49,18 +49,30 @@ extension GroupChatInfo { /// - authStatus: Filter groups by auth status. /// If set, only groups with any of the provided auth statuses will be included. /// If `nil`, the filter is not used. - /// - excludeBlockedContacts: Exclude groups with blocked leaders and last messages from - /// blocked contacts (defaults to `false`). - /// - excludeBannedContacts: Exclude groups with banned leaders and last messages from - /// banned contacts (defaults to `false`). + /// - isLeaderBlocked: Filter by leader contact's `isBlocked` status. + /// If `true`, only groups with blocked leader contacts are included. + /// If `false`, only groups with non-blocked contacts are included. + /// If `nil` (default), the filter is not used. + /// - isLeaderBanned: Filter by leader contact's `isBlocked` status. + /// If `true`, only groups with blocked leader contacts are included. + /// If `false`, only groups with non-blocked contacts are included. + /// If `nil` (default), the filter is not used. + /// - excludeBlockedContactsMessages: Exclude messages from blocked contacts + /// (defaults to `false`). + /// - excludeBannedContactsMessages: Exclude messages from banned contacts + /// (defaults to `false`). public init( authStatus: Set<Group.AuthStatus>? = nil, - excludeBlockedContacts: Bool = false, - excludeBannedContacts: Bool = false + isLeaderBlocked: Bool? = nil, + isLeaderBanned: Bool? = nil, + excludeBlockedContactsMessages: Bool = false, + excludeBannedContactsMessages: Bool = false ) { self.authStatus = authStatus - self.excludeBlockedContacts = excludeBlockedContacts - self.excludeBannedContacts = excludeBannedContacts + self.isLeaderBlocked = isLeaderBlocked + self.isLeaderBanned = isLeaderBanned + self.excludeBlockedContactsMessages = excludeBlockedContactsMessages + self.excludeBannedContactsMessages = excludeBannedContactsMessages } /// Filter groups by auth status @@ -69,10 +81,24 @@ extension GroupChatInfo { /// If `nil`, the filter is not used. public var authStatus: Set<Group.AuthStatus>? - /// Exclude groups with blocked leaders and last messages from blocked contacts. - public var excludeBlockedContacts: Bool + /// Filter by leader contact's `isBlocked` status + /// + /// If `true`, only groups with blocked leader contacts are included. + /// If `false`, only groups with non-blocked contacts are included. + /// If `nil`, the filter is not used. + public var isLeaderBlocked: Bool? + + /// Filter by leader contact's `isBanned` status + /// + /// If `true`, only groups with banned leader contacts are included. + /// If `false`, only groups with non-banned leader contacts are included. + /// If `nil`, the filter is not used. + public var isLeaderBanned: Bool? + + /// Exclude messages from blocked contacts. + public var excludeBlockedContactsMessages: Bool - /// Exclude groups with banned leaders and last messages from banned contacts. - public var excludeBannedContacts: Bool + /// Exclude messages from banned contacts. + public var excludeBannedContactsMessages: Bool } } diff --git a/Tests/XXDatabaseTests/GroupChatInfo+GRDBTests.swift b/Tests/XXDatabaseTests/GroupChatInfo+GRDBTests.swift index fd67cc0c6ae9d01815f7ab8fe8a48f6269884c59..8618df81c6a9294548e754a1987f76c84dae876b 100644 --- a/Tests/XXDatabaseTests/GroupChatInfo+GRDBTests.swift +++ b/Tests/XXDatabaseTests/GroupChatInfo+GRDBTests.swift @@ -213,7 +213,7 @@ final class GroupChatInfoGRDBTests: XCTestCase { // Mock up messages in group C: - try db.saveMessage(.stub( + let groupC_message_fromA_at5 = try db.saveMessage(.stub( from: contactA, to: groupC, at: 5, @@ -229,7 +229,12 @@ final class GroupChatInfoGRDBTests: XCTestCase { // Fetch group chats excluding messages from blocked contacts: - XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBlockedContacts: true)), [ + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBlockedContactsMessages: true)), [ + GroupChatInfo( + group: groupC, + lastMessage: groupC_message_fromA_at5, + unreadCount: 1 + ), GroupChatInfo( group: groupA, lastMessage: groupA_message_fromB_at2, @@ -239,7 +244,7 @@ final class GroupChatInfoGRDBTests: XCTestCase { // Fetch group chats including messages from blocked contacts: - XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBlockedContacts: false)), [ + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBlockedContactsMessages: false)), [ GroupChatInfo( group: groupC, lastMessage: groupC_message_fromC_at6, @@ -331,7 +336,7 @@ final class GroupChatInfoGRDBTests: XCTestCase { // Mock up messages in group C: - try db.saveMessage(.stub( + let groupC_message_fromA_at5 = try db.saveMessage(.stub( from: contactA, to: groupC, at: 5, @@ -347,7 +352,12 @@ final class GroupChatInfoGRDBTests: XCTestCase { // Fetch group chats excluding messages from banned contacts: - XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBannedContacts: true)), [ + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBannedContactsMessages: true)), [ + GroupChatInfo( + group: groupC, + lastMessage: groupC_message_fromA_at5, + unreadCount: 1 + ), GroupChatInfo( group: groupA, lastMessage: groupA_message_fromB_at2, @@ -357,7 +367,7 @@ final class GroupChatInfoGRDBTests: XCTestCase { // Fetch group chats including messages from banned contacts: - XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBannedContacts: false)), [ + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBannedContactsMessages: false)), [ GroupChatInfo( group: groupC, lastMessage: groupC_message_fromC_at6, @@ -375,4 +385,190 @@ final class GroupChatInfoGRDBTests: XCTestCase { ), ]) } + + func testFilterGroupsWithBlockedLeaders() throws { + // Mock up contacts: + + let contactA = try db.saveContact(.stub("A")) + let contactB = try db.saveContact(.stub("B")) + let contactC = try db.saveContact(.stub("C").withBlocked(true)) + + // Mock up groups: + + let groupA = try db.saveGroup(.stub( + "A", + leaderId: contactA.id, + createdAt: .stub(1) + )) + + let groupB = try db.saveGroup(.stub( + "B", + leaderId: contactB.id, + createdAt: .stub(2) + )) + + let groupC = try db.saveGroup(.stub( + "C", + leaderId: contactC.id, + createdAt: .stub(3) + )) + + // Mock up messages: + + let groupA_message_fromA_at1 = try db.saveMessage(.stub( + from: contactA, + to: groupA, + at: 1 + )) + + let groupB_message_fromA_at2 = try db.saveMessage(.stub( + from: contactA, + to: groupB, + at: 2 + )) + + let groupC_message_fromA_at3 = try db.saveMessage(.stub( + from: contactA, + to: groupC, + at: 3 + )) + + // Fetch group chats with blocked leaders: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(isLeaderBlocked: true)), [ + GroupChatInfo( + group: groupC, + lastMessage: groupC_message_fromA_at3, + unreadCount: 0 + ), + ]) + + // Fetch group chats with non-blocked leaders: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(isLeaderBlocked: false)), [ + GroupChatInfo( + group: groupB, + lastMessage: groupB_message_fromA_at2, + unreadCount: 0 + ), + GroupChatInfo( + group: groupA, + lastMessage: groupA_message_fromA_at1, + unreadCount: 0 + ), + ]) + + // Fetch group chats with regardless leader's blocked status: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(isLeaderBlocked: nil)), [ + GroupChatInfo( + group: groupC, + lastMessage: groupC_message_fromA_at3, + unreadCount: 0 + ), + GroupChatInfo( + group: groupB, + lastMessage: groupB_message_fromA_at2, + unreadCount: 0 + ), + GroupChatInfo( + group: groupA, + lastMessage: groupA_message_fromA_at1, + unreadCount: 0 + ), + ]) + } + + func testFilterGroupsWithBannedLeaders() throws { + // Mock up contacts: + + let contactA = try db.saveContact(.stub("A")) + let contactB = try db.saveContact(.stub("B")) + let contactC = try db.saveContact(.stub("C").withBanned(true)) + + // Mock up groups: + + let groupA = try db.saveGroup(.stub( + "A", + leaderId: contactA.id, + createdAt: .stub(1) + )) + + let groupB = try db.saveGroup(.stub( + "B", + leaderId: contactB.id, + createdAt: .stub(2) + )) + + let groupC = try db.saveGroup(.stub( + "C", + leaderId: contactC.id, + createdAt: .stub(3) + )) + + // Mock up messages: + + let groupA_message_fromA_at1 = try db.saveMessage(.stub( + from: contactA, + to: groupA, + at: 1 + )) + + let groupB_message_fromA_at2 = try db.saveMessage(.stub( + from: contactA, + to: groupB, + at: 2 + )) + + let groupC_message_fromA_at3 = try db.saveMessage(.stub( + from: contactA, + to: groupC, + at: 3 + )) + + // Fetch group chats with banned leaders: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(isLeaderBanned: true)), [ + GroupChatInfo( + group: groupC, + lastMessage: groupC_message_fromA_at3, + unreadCount: 0 + ), + ]) + + // Fetch group chats with non-banned leaders: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(isLeaderBanned: false)), [ + GroupChatInfo( + group: groupB, + lastMessage: groupB_message_fromA_at2, + unreadCount: 0 + ), + GroupChatInfo( + group: groupA, + lastMessage: groupA_message_fromA_at1, + unreadCount: 0 + ), + ]) + + // Fetch group chats with regardless leader's banned status: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(isLeaderBanned: nil)), [ + GroupChatInfo( + group: groupC, + lastMessage: groupC_message_fromA_at3, + unreadCount: 0 + ), + GroupChatInfo( + group: groupB, + lastMessage: groupB_message_fromA_at2, + unreadCount: 0 + ), + GroupChatInfo( + group: groupA, + lastMessage: groupA_message_fromA_at1, + unreadCount: 0 + ), + ]) + } }