core: archive reports when removing member with messages

Reports about a member's messages are authored by the reporter, so
deleting the removed member's own chat items never reached them - unlike
moderation, which archives them via markMessageReportsDeleted. Reports
stayed in the reports list pointing at deleted messages.
This commit is contained in:
Narasimha-sc committed 2026-09-07 14:54:10 +00:00
1 parent 5847fa7f31
commit 35d541a5a7
5 files changed
+62 -6

No files matched your search

+5 -3
View File
@@ -3171,9 +3171,11 @@ processChatCommand cxt nm = \case
then void $ fullyDeleteMemberRecordIO db user gInfo m
else void $ deleteOrUpdateMemberRecordIO db user gInfo m
pure m {memberStatus = GSMemRemoved}
deleteMessages user gInfo@GroupInfo {membership} ms
| groupFeatureUserAllowed SGFFullDelete gInfo = deleteGroupMembersCIs user gInfo ms
| otherwise = markGroupMembersCIsDeleted user gInfo ms membership
deleteMessages user gInfo@GroupInfo {membership} ms = do
archiveMembersReports user gInfo ms membership True
if groupFeatureUserAllowed SGFFullDelete gInfo
then deleteGroupMembersCIs user gInfo ms
else markGroupMembersCIsDeleted user gInfo ms membership
APILeaveGroup groupId -> withUser $ \user@User {userId} -> do
gInfo@GroupInfo {membership} <- withFastStore $ \db -> getGroupInfo db cxt user groupId
filesInfo <- withFastStore' $ \db -> getGroupFileInfo db user gInfo
+8
View File
@@ -647,6 +647,14 @@ markGroupMembersCIsDeleted user gInfo members byGroupMember = do
filesInfo <- withStore' $ \db -> fmap concat $ forM members $ \m -> markGroupMemberCIsDeleted_ db user gInfo m byGroupMember deletedTs
cancelFilesInProgress user filesInfo
-- Reports about a member's messages are authored by the reporter, so deleting the member's own
-- chat items never reaches them - they have to be archived separately, as moderation does.
archiveMembersReports :: User -> GroupInfo -> [GroupMember] -> GroupMember -> Bool -> CM ()
archiveMembersReports user gInfo members byGroupMember byUser = do
deletedTs <- liftIO getCurrentTime
ciIds <- concat <$> withStore' (\db -> forM members $ \m -> markMemberReportsDeleted db user gInfo m byGroupMember deletedTs)
unless (null ciIds) $ toView $ CEvtGroupChatItemsDeleted user gInfo ciIds byUser (Just byGroupMember)
markGroupMemberCIsDeleted_ :: DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> UTCTime -> IO [CIFileInfo]
markGroupMemberCIsDeleted_ db user gInfo member byGroupMember deletedTs = do
fs <- getGroupMemberFileInfo db user gInfo member
+5 -3
View File
@@ -3654,9 +3654,11 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gi' scopeInfo m') msg' brokerTs (CIRcvGroupEvent gEvent)
groupMsgToView cInfo ci
deleteMessages :: GroupInfo -> GroupMember -> CM ()
deleteMessages gInfo' delMem
| groupFeatureMemberAllowed SGFFullDelete m gInfo' = deleteGroupMemberCIs user gInfo' delMem
| otherwise = markGroupMemberCIsDeleted user gInfo' delMem m
deleteMessages gInfo' delMem = do
archiveMembersReports user gInfo' [delMem] m False
if groupFeatureMemberAllowed SGFFullDelete m gInfo'
then deleteGroupMemberCIs user gInfo' delMem
else markGroupMemberCIsDeleted user gInfo' delMem m
forwardToMember :: GroupMember -> CM ()
forwardToMember member =
let fwd = GrpMsgForward {fwdSender = FwdMember (memberId' m) (memberShortenedName m), fwdBrokerTs = brokerTs}
+15
View File
@@ -73,6 +73,7 @@ module Simplex.Chat.Store.Messages
markGroupChatItemBlocked,
markGroupCIBlockedByAdmin,
markMessageReportsDeleted,
markMemberReportsDeleted,
markReceivedGroupReportsDeleted,
deleteLocalChatItem,
updateDirectChatItemsRead,
@@ -3024,6 +3025,20 @@ markMessageReportsDeleted db User {userId} GroupInfo {groupId} ChatItem {meta =
|]
(DBCIDeleted, deletedTs, groupMemberId, currentTs, userId, groupId, MCReport_, itemSharedMsgId, DBCINotDeleted)
markMemberReportsDeleted :: DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> UTCTime -> IO [ChatItemId]
markMemberReportsDeleted db User {userId} GroupInfo {groupId} reportedMember byGroupMember deletedTs = do
currentTs <- liftIO getCurrentTime
map fromOnly
<$> DB.query
db
[sql|
UPDATE chat_items
SET item_deleted = ?, item_deleted_ts = ?, item_deleted_by_group_member_id = ?, updated_at = ?
WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? AND quoted_member_id = ? AND item_deleted = ?
RETURNING chat_item_id;
|]
(DBCIDeleted, deletedTs, groupMemberId' byGroupMember, currentTs, userId, groupId, MCReport_, memberId' reportedMember, DBCINotDeleted)
markReceivedGroupReportsDeleted :: DB.Connection -> User -> GroupInfo -> UTCTime -> IO [ChatItemId]
markReceivedGroupReportsDeleted db User {userId} GroupInfo {groupId, membership} deletedTs = do
currentTs <- liftIO getCurrentTime
+29
View File
@@ -219,6 +219,7 @@ chatGroupTests = do
it "mark member inactive on reaching quota" testGroupMemberInactive
describe "group member reports" $ do
it "should send report to group owner, admins and moderators, but not other users" testGroupMemberReports
it "should archive reports about the messages of the member removed with messages" testGroupMemberReportsRemoveMember
describe "group member mentions" $ do
it "should send and edit messages with member mentions" testMemberMention
it "should forward and quote message updating mentioned member name" testForwardQuoteMention
@@ -7390,6 +7391,34 @@ testGroupMemberInactive ps = do
}
}
testGroupMemberReportsRemoveMember :: HasCallStack => TestParams -> IO ()
testGroupMemberReportsRemoveMember =
testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do
createGroup3' "jokes" alice (bob, GRMember) (cath, GRMember)
threadDelay 1000000
bob #> "#jokes inappropriate joke"
concurrently_
(alice <# "#jokes bob> inappropriate joke")
(cath <# "#jokes bob> inappropriate joke")
cath ##> "/report #jokes content inappropriate joke"
cath <# "#jokes (support) > bob inappropriate joke"
cath <## " report content"
alice <# "#jokes (support: cath) cath> > bob inappropriate joke"
alice <## " report content"
alice #$> ("/_get chat #1 content=report count=100", chat, [(0, "report content")])
cath #$> ("/_get chat #1 content=report count=100", chat, [(1, "report content")])
threadDelay 1000000
alice ##> "/rm #jokes bob messages=on"
alice <## "#jokes: 1 messages deleted by user"
alice <## "#jokes: you removed bob from the group with all messages"
bob <## "#jokes: alice removed you from the group with all messages"
bob <## "use /d #jokes to delete the group"
cath <## "#jokes: 1 messages deleted by member alice"
cath <## "#jokes: alice removed bob from the group with all messages"
alice #$> ("/_get chat #1 content=report count=100", chat, [(0, "report content [marked deleted by you]")])
cath #$> ("/_get chat #1 content=report count=100", chat, [(1, "report content [marked deleted by alice]")])
testGroupMemberReports :: HasCallStack => TestParams -> IO ()
testGroupMemberReports =
testChat4 aliceProfile bobProfile cathProfile danProfile $