mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-12 05:07:56 +00:00
core, ui: full delete member items from DB when fullDelete is on
This commit is contained in:
1 parent
27b06dfb38
commit
7f4e90c5f5
5 files changed
+67
-67
No files matched your search
+37
-15
@@ -696,23 +696,45 @@ object ChatModel {
|
||||
}
|
||||
|
||||
suspend fun removeMemberItems(rhId: Long?, removedMember: GroupMember, byMember: GroupMember, groupInfo: GroupInfo) {
|
||||
fun removedUpdatedItem(item: ChatItem): ChatItem? {
|
||||
val newContent = when {
|
||||
item.chatDir is CIDirection.GroupSnd && removedMember.groupMemberId == groupInfo.membership.groupMemberId -> CIContent.SndModerated
|
||||
item.chatDir is CIDirection.GroupRcv && item.chatDir.groupMember.groupMemberId == removedMember.groupMemberId -> CIContent.RcvModerated
|
||||
else -> return null
|
||||
}
|
||||
val updatedItem = item.copy(
|
||||
meta = item.meta.copy(itemDeleted = CIDeleted.Moderated(Clock.System.now(), byGroupMember = byMember)),
|
||||
content = if (groupInfo.fullGroupPreferences.fullDelete.on) newContent else item.content
|
||||
)
|
||||
if (item.isActiveReport) {
|
||||
decreaseGroupReportsCounter(rhId, groupInfo.id)
|
||||
}
|
||||
return updatedItem
|
||||
}
|
||||
fun isMemberItem(item: ChatItem): Boolean =
|
||||
(item.chatDir is CIDirection.GroupSnd && removedMember.groupMemberId == groupInfo.membership.groupMemberId) ||
|
||||
(item.chatDir is CIDirection.GroupRcv && item.chatDir.groupMember.groupMemberId == removedMember.groupMemberId)
|
||||
|
||||
val cInfo = ChatInfo.Group(groupInfo, groupChatScope = null) // TODO [knocking] review
|
||||
// When fullDelete is on, items are fully deleted from DB by backend —
|
||||
// remove them from UI immediately so no "moderated" bubbles are shown.
|
||||
if (groupInfo.fullGroupPreferences.fullDelete.on) {
|
||||
// Update chat list preview
|
||||
val i = getChatIndex(rhId, groupInfo.id)
|
||||
if (i >= 0) {
|
||||
val chat = chats[i]
|
||||
if (chat.chatItems.isNotEmpty() && isMemberItem(chat.chatItems[0])) {
|
||||
chats.value[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy))
|
||||
}
|
||||
}
|
||||
// Remove from open chat view
|
||||
if (chatId.value == groupInfo.id) {
|
||||
chatItems.removeAllAndNotify { item ->
|
||||
val remove = isMemberItem(item)
|
||||
if (remove) {
|
||||
AudioPlayer.stop(item) // stop voice message if playing
|
||||
if (item.isRcvNew) decreaseCounterInPrimaryContext(rhId, cInfo.id)
|
||||
if (item.isActiveReport) decreaseGroupReportsCounter(rhId, groupInfo.id)
|
||||
}
|
||||
remove
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
// When fullDelete is off, mark items as moderated (original behavior)
|
||||
fun removedUpdatedItem(item: ChatItem): ChatItem? {
|
||||
if (!isMemberItem(item)) return null
|
||||
val updatedItem = item.copy(
|
||||
meta = item.meta.copy(itemDeleted = CIDeleted.Moderated(Clock.System.now(), byGroupMember = byMember)),
|
||||
)
|
||||
if (item.isActiveReport) decreaseGroupReportsCounter(rhId, groupInfo.id)
|
||||
return updatedItem
|
||||
}
|
||||
if (chatId.value == groupInfo.id) {
|
||||
for (i in 0 until chatItems.value.size) {
|
||||
val updatedItem = removedUpdatedItem(chatItems.value[i])
|
||||
|
||||
@@ -2890,7 +2890,7 @@ processChatCommand vr nm = \case
|
||||
void $ deleteOrUpdateMemberRecordIO db user gInfo m
|
||||
pure m {memberStatus = GSMemRemoved}
|
||||
deleteMessages user gInfo@GroupInfo {membership} ms
|
||||
| groupFeatureUserAllowed SGFFullDelete gInfo = deleteGroupMembersCIs user gInfo ms membership
|
||||
| groupFeatureUserAllowed SGFFullDelete gInfo = fullDeleteGroupMembersCIs user gInfo ms
|
||||
| otherwise = markGroupMembersCIsDeleted user gInfo ms membership
|
||||
APILeaveGroup groupId -> withUser $ \user@User {userId} -> do
|
||||
gInfo@GroupInfo {membership} <- withFastStore $ \db -> getGroupInfo db vr user groupId
|
||||
|
||||
@@ -510,23 +510,21 @@ updateACIGroupInfo gInfo' = \case
|
||||
AChatItem SCTGroup dir (GroupChat gInfo' chatScopeInfo) ci
|
||||
aci -> aci
|
||||
|
||||
deleteGroupMemberCIs :: MsgDirectionI d => User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> CM ()
|
||||
deleteGroupMemberCIs user gInfo member byGroupMember msgDir = do
|
||||
deletedTs <- liftIO getCurrentTime
|
||||
filesInfo <- withStore' $ \db -> deleteGroupMemberCIs_ db user gInfo member byGroupMember msgDir deletedTs
|
||||
fullDeleteGroupMemberCIs :: User -> GroupInfo -> GroupMember -> CM ()
|
||||
fullDeleteGroupMemberCIs user gInfo member = do
|
||||
filesInfo <- withStore' $ \db -> getGroupMemberFileInfo db user gInfo member
|
||||
deleteCIFiles user filesInfo
|
||||
withStore' $ \db -> do
|
||||
fullDeleteMemberCIs db user gInfo member
|
||||
deleteGroupMember db user member
|
||||
|
||||
deleteGroupMembersCIs :: User -> GroupInfo -> [GroupMember] -> GroupMember -> CM ()
|
||||
deleteGroupMembersCIs user gInfo members byGroupMember = do
|
||||
deletedTs <- liftIO getCurrentTime
|
||||
filesInfo <- withStore' $ \db -> fmap concat $ forM members $ \m -> deleteGroupMemberCIs_ db user gInfo m byGroupMember SMDRcv deletedTs
|
||||
fullDeleteGroupMembersCIs :: User -> GroupInfo -> [GroupMember] -> CM ()
|
||||
fullDeleteGroupMembersCIs user gInfo members = do
|
||||
filesInfo <- withStore' $ \db -> fmap concat $ forM members $ \m -> getGroupMemberFileInfo db user gInfo m
|
||||
deleteCIFiles user filesInfo
|
||||
|
||||
deleteGroupMemberCIs_ :: MsgDirectionI d => DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> UTCTime -> IO [CIFileInfo]
|
||||
deleteGroupMemberCIs_ db user gInfo member byGroupMember msgDir deletedTs = do
|
||||
fs <- getGroupMemberFileInfo db user gInfo member
|
||||
updateMemberCIsModerated db user gInfo member byGroupMember msgDir deletedTs
|
||||
pure fs
|
||||
withStore' $ \db -> forM_ members $ \m -> do
|
||||
fullDeleteMemberCIs db user gInfo m
|
||||
deleteGroupMember db user m
|
||||
|
||||
deleteLocalCIs :: User -> NoteFolder -> [CChatItem 'CTLocal] -> Bool -> Bool -> CM ChatResponse
|
||||
deleteLocalCIs user nf items byUser timed = do
|
||||
|
||||
@@ -3072,7 +3072,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
unless (isUserGrpFwdRelay gInfo) $ deleteGroupConnections user gInfo False
|
||||
withStore' $ \db -> updateGroupMemberStatus db userId membership GSMemRemoved
|
||||
let membership' = membership {memberStatus = GSMemRemoved}
|
||||
when withMessages $ deleteMessages gInfo membership' SMDSnd
|
||||
when withMessages $ deleteMessages gInfo membership'
|
||||
deleteMemberItem gInfo RGEUserDeleted
|
||||
toView $ CEvtDeletedMemberUser user gInfo {membership = membership'} m withMessages msgSigned
|
||||
pure $ Just DJSGroup {jobSpec = DJRelayRemoved}
|
||||
@@ -3100,7 +3100,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
gInfo'' <- updatePublicGroupData user gInfo'
|
||||
let wasDeleted = memberStatus == GSMemRemoved || memberStatus == GSMemLeft
|
||||
deletedMember' = deletedMember {memberStatus = GSMemRemoved}
|
||||
when withMessages $ deleteMessages gInfo'' deletedMember' SMDRcv
|
||||
when withMessages $ deleteMessages gInfo'' deletedMember'
|
||||
unless wasDeleted $ deleteMemberItem gInfo'' $ RGEMemberDeleted groupMemberId (fromLocalProfile memberProfile)
|
||||
toView $ CEvtDeletedMember user gInfo'' m deletedMember' withMessages msgSigned
|
||||
pure deliveryScope
|
||||
@@ -3113,9 +3113,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
(gi', m', scopeInfo) <- mkGroupChatScope gi m
|
||||
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gi' scopeInfo m') msg brokerTs (CIRcvGroupEvent gEvent)
|
||||
groupMsgToView cInfo ci
|
||||
deleteMessages :: MsgDirectionI d => GroupInfo -> GroupMember -> SMsgDirection d -> CM ()
|
||||
deleteMessages gInfo' delMem msgDir
|
||||
| groupFeatureMemberAllowed SGFFullDelete m gInfo' = deleteGroupMemberCIs user gInfo' delMem m msgDir
|
||||
deleteMessages :: GroupInfo -> GroupMember -> CM ()
|
||||
deleteMessages gInfo' delMem
|
||||
| groupFeatureMemberAllowed SGFFullDelete m gInfo' = fullDeleteGroupMemberCIs user gInfo' delMem
|
||||
| otherwise = markGroupMemberCIsDeleted user gInfo' delMem m
|
||||
forwardToMember :: GroupMember -> CM ()
|
||||
forwardToMember member =
|
||||
|
||||
@@ -65,7 +65,7 @@ module Simplex.Chat.Store.Messages
|
||||
updateGroupCIMentions,
|
||||
deleteGroupChatItem,
|
||||
updateGroupChatItemModerated,
|
||||
updateMemberCIsModerated,
|
||||
fullDeleteMemberCIs,
|
||||
updateGroupCIBlockedByAdmin,
|
||||
markGroupChatItemDeleted,
|
||||
markMemberCIsDeleted,
|
||||
@@ -2814,39 +2814,19 @@ updateGroupChatItemModerated db User {userId} GroupInfo {groupId} ci m@GroupMemb
|
||||
(deletedTs, groupMemberId, toContent, toText, currentTs, userId, groupId, itemId)
|
||||
pure ci {content = toContent, meta = (meta ci) {itemText = toText, itemDeleted = Just (CIModerated (Just deletedTs) m), editable = False, deletable = False}, formattedText = Nothing}
|
||||
|
||||
updateMemberCIsModerated :: MsgDirectionI d => DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> UTCTime -> IO ()
|
||||
updateMemberCIsModerated db User {userId} GroupInfo {groupId, membership} member byGroupMember md deletedTs = do
|
||||
itemIds <- updateCIs =<< getCurrentTime
|
||||
#if defined(dbPostgres)
|
||||
let inItemIds = Only $ In (map fromOnly itemIds)
|
||||
DB.execute db "DELETE FROM messages WHERE message_id IN (SELECT message_id FROM chat_item_messages WHERE chat_item_id IN ?)" inItemIds
|
||||
DB.execute db "DELETE FROM chat_item_versions WHERE chat_item_id IN ?" inItemIds
|
||||
#else
|
||||
DB.executeMany db deleteChatItemMessagesQuery itemIds
|
||||
DB.executeMany db "DELETE FROM chat_item_versions WHERE chat_item_id = ?" itemIds
|
||||
#endif
|
||||
fullDeleteMemberCIs :: DB.Connection -> User -> GroupInfo -> GroupMember -> IO ()
|
||||
fullDeleteMemberCIs db User {userId} GroupInfo {groupId, membership} member
|
||||
| memId == groupMemberId' membership = deleteWithCond " AND group_member_id IS NULL AND item_sent = 1 " (userId, groupId)
|
||||
| otherwise = deleteWithCond " AND group_member_id = ? " (userId, groupId, memId)
|
||||
where
|
||||
memId = groupMemberId' member
|
||||
updateQuery =
|
||||
[sql|
|
||||
UPDATE chat_items
|
||||
SET item_deleted = 1, item_deleted_ts = ?, item_deleted_by_group_member_id = ?, item_content = ?, item_text = ?, updated_at = ?
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|]
|
||||
updateCIs :: UTCTime -> IO [Only Int64]
|
||||
updateCIs currentTs
|
||||
| memId == groupMemberId' membership =
|
||||
DB.query
|
||||
db
|
||||
(updateQuery <> " AND group_member_id IS NULL AND item_sent = 1 RETURNING chat_item_id")
|
||||
(columns :. (userId, groupId))
|
||||
| otherwise =
|
||||
DB.query
|
||||
db
|
||||
(updateQuery <> " AND group_member_id = ? RETURNING chat_item_id")
|
||||
(columns :. (userId, groupId, memId))
|
||||
where
|
||||
columns = (deletedTs, groupMemberId' byGroupMember, msgDirToModeratedContent_ md, ciModeratedText, currentTs)
|
||||
deleteWithCond :: ToRow p => Query -> p -> IO ()
|
||||
deleteWithCond cond params = do
|
||||
let ciQuery = "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? " <> cond
|
||||
-- delete messages first (they don't cascade from chat_items), using chat_item_messages join
|
||||
DB.execute db ("DELETE FROM messages WHERE message_id IN (SELECT message_id FROM chat_item_messages WHERE chat_item_id IN (" <> ciQuery <> "))") params
|
||||
-- delete chat_items; chat_item_messages, chat_item_versions, chat_item_mentions, files, calls, group_snd_item_statuses all cascade
|
||||
DB.execute db ("DELETE FROM chat_items WHERE user_id = ? AND group_id = ? " <> cond) params
|
||||
|
||||
updateGroupCIBlockedByAdmin :: DB.Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> UTCTime -> IO (ChatItem 'CTGroup d)
|
||||
updateGroupCIBlockedByAdmin db User {userId} GroupInfo {groupId} ci deletedTs = do
|
||||
|
||||
Reference in new issue
Block a user