diff --git a/Sources/XXDatabase/Models/GroupChatInfo+GRDB.swift b/Sources/XXDatabase/Models/GroupChatInfo+GRDB.swift index f8a549e06863143cc0829f0b7e2bc767d733ff4a..20062ab25781e6b59a9d4cb8b4d1ab3480d72ee9 100644 --- a/Sources/XXDatabase/Models/GroupChatInfo+GRDB.swift +++ b/Sources/XXDatabase/Models/GroupChatInfo+GRDB.swift @@ -9,6 +9,7 @@ extension GroupChatInfo: FetchableRecord { } static func request(_ query: Query) -> AdaptedFetchRequest<SQLRequest<GroupChatInfo>> { + var sqlJoins: [String] = ["INNER JOIN groups g ON g.id = m.groupId"] var sqlWhere: [String] = [] var sqlArguments: StatementArguments = [:] @@ -17,6 +18,21 @@ extension GroupChatInfo: FetchableRecord { _ = sqlArguments.append(contentsOf: sqlArgumentsAuthStatus(authStatus)) } + if query.excludeBlockedContacts || query.excludeBannedContacts { + sqlJoins.append("INNER JOIN contacts l ON g.leaderId = l.id") + sqlJoins.append("INNER JOIN contacts s ON m.senderId = s.id") + + if query.excludeBlockedContacts { + sqlWhere.append("l.isBlocked != 1") + sqlWhere.append("s.isBlocked != 1") + } + + if query.excludeBannedContacts { + sqlWhere.append("l.isBanned != 1") + sqlWhere.append("s.isBanned != 1") + } + } + let sql = """ SELECT -- All group columns: @@ -29,9 +45,8 @@ extension GroupChatInfo: FetchableRecord { MAX(m.date) AS date FROM messages m - INNER JOIN groups g - ON g.id = m.groupId - \(sqlWhere.isEmpty ? "" : "WHERE\n \(sqlWhere.joined(separator: "\n "))") + \(sqlJoins.joined(separator: "\n")) + \(sqlWhere.isEmpty ? "" : "WHERE\n \(sqlWhere.joined(separator: "\n AND "))") GROUP BY g.id ORDER BY diff --git a/Sources/XXModels/Models/GroupChatInfo.swift b/Sources/XXModels/Models/GroupChatInfo.swift index a9db76634871e70b6fa6ffd9e7ce84a4d52b39a2..8e92eddeeb155550a8659bbd7c3ab0b7f3f01206 100644 --- a/Sources/XXModels/Models/GroupChatInfo.swift +++ b/Sources/XXModels/Models/GroupChatInfo.swift @@ -49,10 +49,18 @@ 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`). public init( - authStatus: Set<Group.AuthStatus>? = nil + authStatus: Set<Group.AuthStatus>? = nil, + excludeBlockedContacts: Bool = false, + excludeBannedContacts: Bool = false ) { self.authStatus = authStatus + self.excludeBlockedContacts = excludeBlockedContacts + self.excludeBannedContacts = excludeBannedContacts } /// Filter groups by auth status @@ -60,5 +68,11 @@ extension GroupChatInfo { /// If set, only groups with any of the provided auth statuses will be included. /// 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 + + /// Exclude groups with banned leaders and last messages from banned contacts. + public var excludeBannedContacts: Bool } } diff --git a/Tests/XXDatabaseTests/GroupChatInfo+GRDBTests.swift b/Tests/XXDatabaseTests/GroupChatInfo+GRDBTests.swift index ffc5c4a82a27b43e3e0541c59acdbc3a8b38c3c5..fd67cc0c6ae9d01815f7ab8fe8a48f6269884c59 100644 --- a/Tests/XXDatabaseTests/GroupChatInfo+GRDBTests.swift +++ b/Tests/XXDatabaseTests/GroupChatInfo+GRDBTests.swift @@ -139,4 +139,240 @@ final class GroupChatInfoGRDBTests: XCTestCase { ] ) } + + func testExcludeMessagesFromBlockedContacts() 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) + )) + + try db.saveGroupMember(GroupMember(groupId: groupA.id, contactId: contactA.id)) + try db.saveGroupMember(GroupMember(groupId: groupA.id, contactId: contactB.id)) + try db.saveGroupMember(GroupMember(groupId: groupA.id, contactId: contactC.id)) + + let groupB = try db.saveGroup(.stub( + "B", + leaderId: contactB.id, + createdAt: .stub(2) + )) + + try db.saveGroupMember(GroupMember(groupId: groupB.id, contactId: contactA.id)) + try db.saveGroupMember(GroupMember(groupId: groupB.id, contactId: contactB.id)) + try db.saveGroupMember(GroupMember(groupId: groupB.id, contactId: contactC.id)) + + let groupC = try db.saveGroup(.stub( + "C", + leaderId: contactC.id, + createdAt: .stub(3) + )) + + try db.saveGroupMember(GroupMember(groupId: groupC.id, contactId: contactA.id)) + try db.saveGroupMember(GroupMember(groupId: groupC.id, contactId: contactB.id)) + try db.saveGroupMember(GroupMember(groupId: groupC.id, contactId: contactC.id)) + + // Mock up messages in group A: + + try db.saveMessage(.stub( + from: contactA, + to: groupA, + at: 1, + isUnread: true + )) + + let groupA_message_fromB_at2 = try db.saveMessage(.stub( + from: contactB, + to: groupA, + at: 2, + isUnread: true + )) + + let groupA_message_fromC_at3 = try db.saveMessage(.stub( + from: contactC, + to: groupA, + at: 3, + isUnread: true + )) + + // Mock up messages in group B: + + let groupB_message_fromC_at4 = try db.saveMessage(.stub( + from: contactC, + to: groupB, + at: 4, + isUnread: true + )) + + // Mock up messages in group C: + + try db.saveMessage(.stub( + from: contactA, + to: groupC, + at: 5, + isUnread: true + )) + + let groupC_message_fromC_at6 = try db.saveMessage(.stub( + from: contactC, + to: groupC, + at: 6, + isUnread: true + )) + + // Fetch group chats excluding messages from blocked contacts: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBlockedContacts: true)), [ + GroupChatInfo( + group: groupA, + lastMessage: groupA_message_fromB_at2, + unreadCount: 2 + ), + ]) + + // Fetch group chats including messages from blocked contacts: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBlockedContacts: false)), [ + GroupChatInfo( + group: groupC, + lastMessage: groupC_message_fromC_at6, + unreadCount: 2 + ), + GroupChatInfo( + group: groupB, + lastMessage: groupB_message_fromC_at4, + unreadCount: 1 + ), + GroupChatInfo( + group: groupA, + lastMessage: groupA_message_fromC_at3, + unreadCount: 3 + ), + ]) + } + + func testExcludeMessagesFromBannedContacts() 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) + )) + + try db.saveGroupMember(GroupMember(groupId: groupA.id, contactId: contactA.id)) + try db.saveGroupMember(GroupMember(groupId: groupA.id, contactId: contactB.id)) + try db.saveGroupMember(GroupMember(groupId: groupA.id, contactId: contactC.id)) + + let groupB = try db.saveGroup(.stub( + "B", + leaderId: contactB.id, + createdAt: .stub(2) + )) + + try db.saveGroupMember(GroupMember(groupId: groupB.id, contactId: contactA.id)) + try db.saveGroupMember(GroupMember(groupId: groupB.id, contactId: contactB.id)) + try db.saveGroupMember(GroupMember(groupId: groupB.id, contactId: contactC.id)) + + let groupC = try db.saveGroup(.stub( + "C", + leaderId: contactC.id, + createdAt: .stub(3) + )) + + try db.saveGroupMember(GroupMember(groupId: groupC.id, contactId: contactA.id)) + try db.saveGroupMember(GroupMember(groupId: groupC.id, contactId: contactB.id)) + try db.saveGroupMember(GroupMember(groupId: groupC.id, contactId: contactC.id)) + + // Mock up messages in group A: + + try db.saveMessage(.stub( + from: contactA, + to: groupA, + at: 1, + isUnread: true + )) + + let groupA_message_fromB_at2 = try db.saveMessage(.stub( + from: contactB, + to: groupA, + at: 2, + isUnread: true + )) + + let groupA_message_fromC_at3 = try db.saveMessage(.stub( + from: contactC, + to: groupA, + at: 3, + isUnread: true + )) + + // Mock up messages in group B: + + let groupB_message_fromC_at4 = try db.saveMessage(.stub( + from: contactC, + to: groupB, + at: 4, + isUnread: true + )) + + // Mock up messages in group C: + + try db.saveMessage(.stub( + from: contactA, + to: groupC, + at: 5, + isUnread: true + )) + + let groupC_message_fromC_at6 = try db.saveMessage(.stub( + from: contactC, + to: groupC, + at: 6, + isUnread: true + )) + + // Fetch group chats excluding messages from banned contacts: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBannedContacts: true)), [ + GroupChatInfo( + group: groupA, + lastMessage: groupA_message_fromB_at2, + unreadCount: 2 + ), + ]) + + // Fetch group chats including messages from banned contacts: + + XCTAssertNoDifference(try db.fetchGroupChatInfos(.init(excludeBannedContacts: false)), [ + GroupChatInfo( + group: groupC, + lastMessage: groupC_message_fromC_at6, + unreadCount: 2 + ), + GroupChatInfo( + group: groupB, + lastMessage: groupB_message_fromC_at4, + unreadCount: 1 + ), + GroupChatInfo( + group: groupA, + lastMessage: groupA_message_fromC_at3, + unreadCount: 3 + ), + ]) + } }