core, ui: support chat item TTL per chat and group aliases (#5415)

* core: support chat item TTL per chat

* ios: UI mockup

* core: chat time to live and group local alias support (#5533)

* functions and type placeholders

* simplify

* queries to make tests pass

* set chat queries

* fetch queries

* get local aliases for groups

* local alias support for groups

* simplify

* fix tests

* fix

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* migration

* add test for expiration

* expireChatItems

* refactor queries, read objects inside the loop

* add groupId to query

* fix updateGroupAlias

* ios group alias

* ttl

* changes

* fixes and test

* new types for ttl

* chat and groups ttl in ios

* accurate alert

* label

* progress indicator, disable interactions while api running

* just call expire chat items

* android, desktop: add local alias to groups (#5544)

* android, desktop: add local alias to groups

* different placeholder for chats vs contacts

* improvements and fixes

* only expire chat items, not all items, when chat ttl changes

* refactor, fix conditions

* refactor

* refactor ChatTTLOption

* text

* fix

* make ttl state

* fix crash/remove warnings

* fix for current?

---------

Co-authored-by: Diogo <diogofncunha@gmail.com>
This commit is contained in:
Evgeny
2025-01-20 18:06:00 +00:00
committed by GitHub
co-authored by Diogo
parent 20fa30eacc
commit 7e864f9178
27 changed files with 869 additions and 326 deletions
+7 -2
View File
@@ -342,6 +342,7 @@ data ChatCommand
| APIUpdateProfile UserId Profile
| APISetContactPrefs ContactId Preferences
| APISetContactAlias ContactId LocalAlias
| APISetGroupAlias GroupId LocalAlias
| APISetConnectionAlias Int64 LocalAlias
| APISetUserUIThemes UserId (Maybe UIThemeEntityOverrides)
| APISetChatUIThemes ChatRef (Maybe UIThemeEntityOverrides)
@@ -379,10 +380,13 @@ data ChatCommand
| APIGetUsageConditions
| APISetConditionsNotified Int64
| APIAcceptConditions Int64 (NonEmpty Int64)
| APISetChatItemTTL UserId (Maybe Int64)
| SetChatItemTTL (Maybe Int64)
| APISetChatItemTTL UserId Int64
| SetChatItemTTL Int64
| APIGetChatItemTTL UserId
| GetChatItemTTL
| APISetChatTTL UserId ChatRef (Maybe Int64)
| SetChatTTL ChatName (Maybe Int64)
| GetChatTTL ChatName
| APISetNetworkConfig NetworkConfig
| APIGetNetworkConfig
| SetNetworkConfig SimpleNetCfg
@@ -720,6 +724,7 @@ data ChatResponse
| CRUserProfileUpdated {user :: User, fromProfile :: Profile, toProfile :: Profile, updateSummary :: UserProfileUpdateSummary}
| CRUserProfileImage {user :: User, profile :: Profile}
| CRContactAliasUpdated {user :: User, toContact :: Contact}
| CRGroupAliasUpdated {user :: User, toGroup :: GroupInfo}
| CRConnectionAliasUpdated {user :: User, toConnection :: PendingContactConnection}
| CRContactPrefsUpdated {user :: User, fromContact :: Contact, toContact :: Contact}
| CRContactConnecting {user :: User, contact :: Contact}
+119 -54
View File
@@ -176,7 +176,7 @@ startChatController mainApp enableSndFiles = do
startXFTP xftpStartWorkers
void $ forkIO $ startFilesToReceive users
startCleanupManager
void $ forkIO $ startExpireCIs users
void $ forkIO $ mapM_ startExpireCIs users
else when enableSndFiles $ startXFTP xftpStartSndWorkers
pure a1
startXFTP startWorkers = do
@@ -191,12 +191,15 @@ startChatController mainApp enableSndFiles = do
a <- Just <$> async (void $ runExceptT cleanupManager)
atomically $ writeTVar cleanupAsync a
_ -> pure ()
startExpireCIs users =
forM_ users $ \user -> do
ttl <- fromRight Nothing <$> runExceptT (withStore' (`getChatItemTTL` user))
forM_ ttl $ \_ -> do
startExpireCIThread user
setExpireCIFlag user True
startExpireCIs user = whenM shouldExpireChats $ do
startExpireCIThread user
setExpireCIFlag user True
where
shouldExpireChats =
fmap (fromRight False) $ runExceptT $ withStore' $ \db -> do
ttl <- getChatItemTTL db user
ttlCount <- getChatTTLCount db user
pure $ ttl > 0 || ttlCount > 0
subscribeUsers :: Bool -> [User] -> CM' ()
subscribeUsers onlyNeeded users = do
@@ -1256,6 +1259,11 @@ processChatCommand' vr = \case
ct <- getContact db vr user contactId
liftIO $ updateContactAlias db userId ct localAlias
pure $ CRContactAliasUpdated user ct'
APISetGroupAlias gId localAlias -> withUser $ \user@User {userId} -> do
gInfo' <- withFastStore $ \db -> do
gInfo <- getGroupInfo db vr user gId
liftIO $ updateGroupAlias db userId gInfo localAlias
pure $ CRGroupAliasUpdated user gInfo'
APISetConnectionAlias connId localAlias -> withUser $ \user@User {userId} -> do
conn' <- withFastStore $ \db -> do
conn <- getPendingContactConnection db userId connId
@@ -1401,27 +1409,55 @@ processChatCommand' vr = \case
currentTs <- liftIO getCurrentTime
acceptConditions db condId opIds currentTs
CRServerOperatorConditions <$> getServerOperators db
APISetChatItemTTL userId newTTL_ -> withUserId userId $ \user ->
APISetChatTTL userId (ChatRef cType chatId) newTTL_ ->
withUserId userId $ \user -> checkStoreNotChanged $ withChatLock "setChatTTL" $ do
(oldTTL_, globalTTL, ttlCount) <- withStore' $ \db ->
(,,) <$> getSetChatTTL db <*> getChatItemTTL db user <*> getChatTTLCount db user
let newTTL = fromMaybe globalTTL newTTL_
oldTTL = fromMaybe globalTTL oldTTL_
when (newTTL > 0 && (newTTL < oldTTL || oldTTL == 0)) $ do
lift $ setExpireCIFlag user False
expireChat user globalTTL `catchChatError` (toView . CRChatError (Just user))
lift $ setChatItemsExpiration user globalTTL ttlCount
ok user
where
getSetChatTTL db = case cType of
CTDirect -> getDirectChatTTL db chatId <* setDirectChatTTL db chatId newTTL_
CTGroup -> getGroupChatTTL db chatId <* setGroupChatTTL db chatId newTTL_
_ -> pure Nothing
expireChat user globalTTL = do
currentTs <- liftIO getCurrentTime
case cType of
CTDirect -> expireContactChatItems user vr globalTTL chatId
CTGroup ->
let createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs
in expireGroupChatItems user vr globalTTL createdAtCutoff chatId
_ -> throwChatError $ CECommandError "not supported"
SetChatTTL chatName newTTL -> withUser' $ \user@User {userId} -> do
chatRef <- getChatRef user chatName
processChatCommand $ APISetChatTTL userId chatRef newTTL
GetChatTTL chatName -> withUser' $ \user -> do
ChatRef cType chatId <- getChatRef user chatName
ttl <- case cType of
CTDirect -> withFastStore' (`getDirectChatTTL` chatId)
CTGroup -> withFastStore' (`getGroupChatTTL` chatId)
_ -> throwChatError $ CECommandError "not supported"
pure $ CRChatItemTTL user ttl
APISetChatItemTTL userId newTTL -> withUserId userId $ \user ->
checkStoreNotChanged $
withChatLock "setChatItemTTL" $ do
case newTTL_ of
Nothing -> do
withFastStore' $ \db -> setChatItemTTL db user newTTL_
lift $ setExpireCIFlag user False
Just newTTL -> do
oldTTL <- withFastStore' (`getChatItemTTL` user)
when (maybe True (newTTL <) oldTTL) $ do
lift $ setExpireCIFlag user False
expireChatItems user newTTL True
withFastStore' $ \db -> setChatItemTTL db user newTTL_
lift $ startExpireCIThread user
lift . whenM chatStarted $ setExpireCIFlag user True
(oldTTL, ttlCount) <- withFastStore' $ \db ->
(,) <$> getChatItemTTL db user <* setChatItemTTL db user newTTL <*> getChatTTLCount db user
when (newTTL > 0 && (newTTL < oldTTL || oldTTL == 0)) $ do
lift $ setExpireCIFlag user False
expireChatItems user newTTL True
lift $ setChatItemsExpiration user newTTL ttlCount
ok user
SetChatItemTTL newTTL_ -> withUser' $ \User {userId} -> do
processChatCommand $ APISetChatItemTTL userId newTTL_
APIGetChatItemTTL userId -> withUserId' userId $ \user -> do
ttl <- withFastStore' (`getChatItemTTL` user)
pure $ CRChatItemTTL user ttl
pure $ CRChatItemTTL user (Just ttl)
GetChatItemTTL -> withUser' $ \User {userId} -> do
processChatCommand $ APIGetChatItemTTL userId
APISetNetworkConfig cfg -> withUser' $ \_ -> lift (withAgent' (`setNetworkConfig` cfg)) >> ok_
@@ -3246,9 +3282,16 @@ startExpireCIThread user@User {userId} = do
atomically $ TM.lookup userId expireFlags >>= \b -> unless (b == Just True) retry
lift waitChatStartedAndActivated
ttl <- withStore' (`getChatItemTTL` user)
forM_ ttl $ \t -> expireChatItems user t False
expireChatItems user ttl False
liftIO $ threadDelay' interval
setChatItemsExpiration :: User -> Int64 -> Int -> CM' ()
setChatItemsExpiration user newTTL ttlCount
| newTTL > 0 || ttlCount > 0 = do
startExpireCIThread user
whenM chatStarted $ setExpireCIFlag user True
| otherwise = setExpireCIFlag user False
setExpireCIFlag :: User -> Bool -> CM' ()
setExpireCIFlag User {userId} b = do
expireFlags <- asks expireCIFlags
@@ -3496,20 +3539,19 @@ cleanupManager = do
withStore' (`deleteOldProbes` cutoffTs)
expireChatItems :: User -> Int64 -> Bool -> CM ()
expireChatItems user@User {userId} ttl sync = do
expireChatItems user@User {userId} globalTTL sync = do
currentTs <- liftIO getCurrentTime
vr <- chatVersionRange
let expirationDate = addUTCTime (-1 * fromIntegral ttl) currentTs
-- this is to keep group messages created during last 12 hours even if they're expired according to item_ts
createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs
-- this is to keep group messages created during last 12 hours even if they're expired according to item_ts
let createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs
lift waitChatStartedAndActivated
contacts <- withStore' $ \db -> getUserContacts db vr user
loop contacts $ processContact expirationDate
contactIds <- withStore' $ \db -> getUserContactsToExpire db user globalTTL
loop contactIds $ expireContactChatItems user vr globalTTL
lift waitChatStartedAndActivated
groups <- withStore' $ \db -> getUserGroupDetails db vr user Nothing Nothing
loop groups $ processGroup vr expirationDate createdAtCutoff
groupIds <- withStore' $ \db -> getUserGroupsToExpire db user globalTTL
loop groupIds $ expireGroupChatItems user vr globalTTL createdAtCutoff
where
loop :: [a] -> (a -> CM ()) -> CM ()
loop :: [Int64] -> (Int64 -> CM ()) -> CM ()
loop [] _ = pure ()
loop (a : as) process = continue $ do
process a `catchChatError` (toView . CRChatError (Just user))
@@ -3522,22 +3564,40 @@ expireChatItems user@User {userId} ttl sync = do
expireFlags <- asks expireCIFlags
expire <- atomically $ TM.lookup userId expireFlags
when (expire == Just True) $ threadDelay 100000 >> a
processContact :: UTCTime -> Contact -> CM ()
processContact expirationDate ct = do
lift waitChatStartedAndActivated
filesInfo <- withStore' $ \db -> getContactExpiredFileInfo db user ct expirationDate
cancelFilesInProgress user filesInfo
deleteFilesLocally filesInfo
withStore' $ \db -> deleteContactExpiredCIs db user ct expirationDate
processGroup :: VersionRangeChat -> UTCTime -> UTCTime -> GroupInfo -> CM ()
processGroup vr expirationDate createdAtCutoff gInfo = do
lift waitChatStartedAndActivated
filesInfo <- withStore' $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff
cancelFilesInProgress user filesInfo
deleteFilesLocally filesInfo
withStore' $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff
membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db vr user gInfo
forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m
expireContactChatItems :: User -> VersionRangeChat -> Int64 -> ContactId -> CM ()
expireContactChatItems user vr globalTTL ctId =
-- reading contacts and groups inside the loop,
-- to allow ttl changing while processing and to reduce memory usage
tryChatError (withStore $ \db -> getContact db vr user ctId) >>= mapM_ process
where
process ct@Contact {chatItemTTL} =
withExpirationDate globalTTL chatItemTTL $ \expirationDate -> do
lift waitChatStartedAndActivated
filesInfo <- withStore' $ \db -> getContactExpiredFileInfo db user ct expirationDate
cancelFilesInProgress user filesInfo
deleteFilesLocally filesInfo
withStore' $ \db -> deleteContactExpiredCIs db user ct expirationDate
expireGroupChatItems :: User -> VersionRangeChat -> Int64 -> UTCTime -> GroupId -> CM ()
expireGroupChatItems user vr globalTTL createdAtCutoff groupId =
tryChatError (withStore $ \db -> getGroupInfo db vr user groupId) >>= mapM_ process
where
process gInfo@GroupInfo {chatItemTTL} =
withExpirationDate globalTTL chatItemTTL $ \expirationDate -> do
lift waitChatStartedAndActivated
filesInfo <- withStore' $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff
cancelFilesInProgress user filesInfo
deleteFilesLocally filesInfo
withStore' $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff
membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db vr user gInfo
forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m
withExpirationDate :: Int64 -> Maybe Int64 -> (UTCTime -> CM ()) -> CM ()
withExpirationDate globalTTL chatItemTTL action = do
currentTs <- liftIO getCurrentTime
let ttl = fromMaybe globalTTL chatItemTTL
when (ttl > 0) $ action $ addUTCTime (-1 * fromIntegral ttl) currentTs
chatCommandP :: Parser ChatCommand
chatCommandP =
@@ -3653,6 +3713,7 @@ chatCommandP =
"/_network_statuses" $> APIGetNetworkStatuses,
"/_profile " *> (APIUpdateProfile <$> A.decimal <* A.space <*> jsonP),
"/_set alias @" *> (APISetContactAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
"/_set alias #" *> (APISetGroupAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
"/_set alias :" *> (APISetConnectionAlias <$> A.decimal <*> (A.space *> textP <|> pure "")),
"/_set prefs @" *> (APISetContactPrefs <$> A.decimal <* A.space <*> jsonP),
"/_set theme user " *> (APISetUserUIThemes <$> A.decimal <*> optional (A.space *> jsonP)),
@@ -3688,10 +3749,13 @@ chatCommandP =
"/_conditions" $> APIGetUsageConditions,
"/_conditions_notified " *> (APISetConditionsNotified <$> A.decimal),
"/_accept_conditions " *> (APIAcceptConditions <$> A.decimal <*> _strP),
"/_ttl " *> (APISetChatItemTTL <$> A.decimal <* A.space <*> ciTTLDecimal),
"/ttl " *> (SetChatItemTTL <$> ciTTL),
"/_ttl " *> (APISetChatItemTTL <$> A.decimal <* A.space <*> A.decimal),
"/_ttl " *> (APISetChatTTL <$> A.decimal <* A.space <*> chatRefP <* A.space <*> ciTTLDecimal),
"/_ttl " *> (APIGetChatItemTTL <$> A.decimal),
"/ttl " *> (SetChatItemTTL <$> ciTTL),
"/ttl" $> GetChatItemTTL,
"/ttl " *> (SetChatTTL <$> chatNameP <* A.space <*> (("default" $> Nothing) <|> (Just <$> ciTTL))),
"/ttl " *> (GetChatTTL <$> chatNameP),
"/_network info " *> (APISetNetworkInfo <$> jsonP),
"/_network " *> (APISetNetworkConfig <$> jsonP),
("/network " <|> "/net ") *> (SetNetworkConfig <$> netCfgP),
@@ -3982,12 +4046,13 @@ chatCommandP =
chatNameP' = ChatName <$> (chatTypeP <|> pure CTDirect) <*> displayNameP
chatRefP = ChatRef <$> chatTypeP <*> A.decimal
msgCountP = A.space *> A.decimal <|> pure 10
ciTTLDecimal = ("none" $> Nothing) <|> (Just <$> A.decimal)
ciTTLDecimal = ("default" $> Nothing) <|> (Just <$> A.decimal)
ciTTL =
("day" $> Just 86400)
<|> ("week" $> Just (7 * 86400))
<|> ("month" $> Just (30 * 86400))
<|> ("none" $> Nothing)
("day" $> 86400)
<|> ("week" $> (7 * 86400))
<|> ("month" $> (30 * 86400))
<|> ("year" $> (365 * 86400))
<|> ("none" $> 0)
timedTTLP =
("30s" $> 30)
<|> ("5min" $> 300)
+5 -5
View File
@@ -110,19 +110,19 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
[sql|
SELECT
c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite,
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.contact_group_member_id, c.contact_grp_inv_sent, c.ui_themes, c.chat_deleted, c.custom_data
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.contact_group_member_id, c.contact_grp_inv_sent, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl
FROM contacts c
JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id
WHERE c.user_id = ? AND c.contact_id = ? AND c.deleted = 0
|]
(userId, contactId)
toContact' :: Int64 -> Connection -> [ChatTagId] -> ContactRow' -> Contact
toContact' contactId conn chatTags ((profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData)) =
toContact' contactId conn chatTags ((profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) =
let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
activeConn = Just conn
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, chatTags, uiThemes, chatDeleted, customData}
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember)
getGroupAndMember_ groupMemberId c = do
gm <-
@@ -133,9 +133,9 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
[sql|
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+24 -3
View File
@@ -82,6 +82,9 @@ module Simplex.Chat.Store.Direct
setContactChatDeleted,
getDirectChatTags,
updateDirectChatTags,
setDirectChatTTL,
getDirectChatTTL,
getUserContactsToExpire
)
where
@@ -198,7 +201,7 @@ getContactByConnReqHash db vr user@User {userId} cReqHash = do
SELECT
-- Contact
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data,
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
-- Connection
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -263,6 +266,7 @@ createDirectContact db user@User {userId} conn@Connection {connId, localAlias} p
contactGroupMemberId = Nothing,
contactGrpInvSent = False,
chatTags = [],
chatItemTTL = Nothing,
uiThemes = Nothing,
chatDeleted = False,
customData = Nothing
@@ -659,7 +663,7 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact
SELECT
-- Contact
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data,
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
-- Connection
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -838,6 +842,7 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}
contactGroupMemberId = Nothing,
contactGrpInvSent = False,
chatTags = [],
chatItemTTL = Nothing,
uiThemes = Nothing,
chatDeleted = False,
customData = Nothing
@@ -873,7 +878,7 @@ getContact_ db vr user@User {userId} contactId deleted = do
SELECT
-- Contact
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data,
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
-- Connection
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -1078,3 +1083,19 @@ addDirectChatTags :: DB.Connection -> Contact -> IO Contact
addDirectChatTags db ct = do
chatTags <- getDirectChatTags db $ contactId' ct
pure (ct :: Contact) {chatTags}
setDirectChatTTL :: DB.Connection -> ContactId -> Maybe Int64 -> IO ()
setDirectChatTTL db ctId ttl = do
updatedAt <- getCurrentTime
DB.execute db "UPDATE contacts SET chat_item_ttl = ?, updated_at = ? WHERE contact_id = ?" (ttl, updatedAt, ctId)
getDirectChatTTL :: DB.Connection -> ContactId -> IO (Maybe Int64)
getDirectChatTTL db ctId =
fmap join . maybeFirstRow fromOnly $
DB.query db "SELECT chat_item_ttl FROM contacts WHERE contact_id = ? LIMIT 1" (Only ctId)
getUserContactsToExpire :: DB.Connection -> User -> Int64 -> IO [ContactId]
getUserContactsToExpire db User {userId} globalTTL =
map fromOnly <$> DB.query db ("SELECT contact_id FROM contacts WHERE user_id = ? AND chat_item_ttl > 0" <> cond) (Only userId)
where
cond = if globalTTL == 0 then "" else " OR chat_item_ttl IS NULL"
+42 -13
View File
@@ -126,6 +126,10 @@ module Simplex.Chat.Store.Groups
setGroupUIThemes,
updateGroupChatTags,
getGroupChatTags,
setGroupChatTTL,
getGroupChatTTL,
getUserGroupsToExpire,
updateGroupAlias,
)
where
@@ -160,13 +164,9 @@ import Simplex.Messaging.Protocol (SubscriptionMode (..))
import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>))
import Simplex.Messaging.Version
import UnliftIO.STM
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..), Query, (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (Only (..), Query, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
#endif
type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences))
@@ -268,9 +268,9 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = do
[sql|
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -337,6 +337,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
{ groupId,
localDisplayName = ldn,
groupProfile,
localAlias = "",
businessChat = Nothing,
fullGroupPreferences,
membership,
@@ -347,6 +348,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
chatTs = Just currentTs,
userMemberProfileSentAt = Just currentTs,
chatTags = [],
chatItemTTL = Nothing,
uiThemes = Nothing,
customData = Nothing
}
@@ -406,6 +408,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
{ groupId,
localDisplayName,
groupProfile,
localAlias = "",
businessChat = Nothing,
fullGroupPreferences,
membership,
@@ -416,6 +419,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
chatTs = Just currentTs,
userMemberProfileSentAt = Just currentTs,
chatTags = [],
chatItemTTL = Nothing,
uiThemes = Nothing,
customData = Nothing
},
@@ -646,9 +650,9 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do
db
[sql|
SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
FROM groups g
@@ -1388,9 +1392,9 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do
[sql|
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -2074,7 +2078,7 @@ createMemberContact
quotaErrCounter = 0
}
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, chatTags = [], uiThemes = Nothing, chatDeleted = False, customData = Nothing}
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
getMemberContact :: DB.Connection -> VersionRangeChat -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation)
getMemberContact db vr user contactId = do
@@ -2111,7 +2115,7 @@ createMemberContactInvited
contactId <- createContactUpdateMember currentTs userPreferences
ctConn <- createMemberContactConn_ db user connIds gInfo mConn contactId subMode
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Nothing, contactGrpInvSent = False, chatTags = [], uiThemes = Nothing, chatDeleted = False, customData = Nothing}
mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Nothing, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
m' = m {memberContactId = Just contactId}
pure (mCt', m')
where
@@ -2350,3 +2354,28 @@ untagGroupChat db groupId tId =
WHERE group_id = ? AND chat_tag_id = ?
|]
(groupId, tId)
setGroupChatTTL :: DB.Connection -> GroupId -> Maybe Int64 -> IO ()
setGroupChatTTL db gId ttl = do
updatedAt <- getCurrentTime
DB.execute
db
"UPDATE groups SET chat_item_ttl = ?, updated_at = ? WHERE group_id = ?"
(ttl, updatedAt, gId)
getGroupChatTTL :: DB.Connection -> GroupId -> IO (Maybe Int64)
getGroupChatTTL db gId =
fmap join . maybeFirstRow fromOnly $
DB.query db "SELECT chat_item_ttl FROM groups WHERE group_id = ? LIMIT 1" (Only gId)
getUserGroupsToExpire :: DB.Connection -> User -> Int64 -> IO [GroupId]
getUserGroupsToExpire db User {userId} globalTTL =
map fromOnly <$> DB.query db ("SELECT group_id FROM groups WHERE user_id = ? AND chat_item_ttl > 0" <> cond) (Only userId)
where
cond = if globalTTL == 0 then "" else " OR chat_item_ttl IS NULL"
updateGroupAlias :: DB.Connection -> UserId -> GroupInfo -> LocalAlias -> IO GroupInfo
updateGroupAlias db userId g@GroupInfo {groupId} localAlias = do
updatedAt <- getCurrentTime
DB.execute db "UPDATE groups SET local_alias = ?, updated_at = ? WHERE user_id = ? AND group_id = ?" (localAlias, updatedAt, userId, groupId)
pure (g :: GroupInfo) {localAlias = localAlias}
+13 -3
View File
@@ -107,6 +107,7 @@ module Simplex.Chat.Store.Messages
getTimedItems,
getChatItemTTL,
setChatItemTTL,
getChatTTLCount,
getContactExpiredFileInfo,
deleteContactExpiredCIs,
getGroupExpiredFileInfo,
@@ -2885,11 +2886,12 @@ getTimedItems db User {userId} startTimedThreadCutoff =
(itemId, Nothing, Just groupId, deleteAt) -> Just ((ChatRef CTGroup groupId, itemId), deleteAt)
_ -> Nothing
getChatItemTTL :: DB.Connection -> User -> IO (Maybe Int64)
getChatItemTTL :: DB.Connection -> User -> IO Int64
getChatItemTTL db User {userId} =
fmap join . maybeFirstRow fromOnly $ DB.query db "SELECT chat_item_ttl FROM settings WHERE user_id = ? LIMIT 1" (Only userId)
fmap (fromMaybe 0 . join) . maybeFirstRow fromOnly $
DB.query db "SELECT chat_item_ttl FROM settings WHERE user_id = ? LIMIT 1" (Only userId)
setChatItemTTL :: DB.Connection -> User -> Maybe Int64 -> IO ()
setChatItemTTL :: DB.Connection -> User -> Int64 -> IO ()
setChatItemTTL db User {userId} chatItemTTL = do
currentTs <- getCurrentTime
r :: (Maybe Int64) <- maybeFirstRow fromOnly $ DB.query db "SELECT 1 FROM settings WHERE user_id = ? LIMIT 1" (Only userId)
@@ -2905,6 +2907,14 @@ setChatItemTTL db User {userId} chatItemTTL = do
"INSERT INTO settings (user_id, chat_item_ttl, created_at, updated_at) VALUES (?,?,?,?)"
(userId, chatItemTTL, currentTs, currentTs)
getChatTTLCount :: DB.Connection -> User -> IO Int
getChatTTLCount db User {userId} = do
contactCount <- getCount "SELECT COUNT(1) FROM contacts WHERE user_id = ? AND chat_item_ttl > 0"
groupCount <- getCount "SELECT COUNT(1) FROM groups WHERE user_id = ? AND chat_item_ttl > 0"
pure $ contactCount + groupCount
where
getCount q = fromOnly . head <$> DB.query db q (Only userId)
getContactExpiredFileInfo :: DB.Connection -> User -> Contact -> UTCTime -> IO [CIFileInfo]
getContactExpiredFileInfo db User {userId} Contact {contactId} expirationDate =
map toFileInfo
@@ -82,6 +82,7 @@ CREATE TABLE contacts(
custom_data BYTEA,
ui_themes TEXT,
chat_deleted SMALLINT NOT NULL DEFAULT 0,
chat_item_ttl BIGINT,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -140,6 +141,8 @@ CREATE TABLE groups(
business_chat TEXT NULL,
business_xcontact_id BYTEA NULL,
customer_member_id BYTEA NULL,
chat_item_ttl BIGINT,
local_alias TEXT DEFAULT '',
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
+3 -1
View File
@@ -123,6 +123,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20241222_operator_conditions
import Simplex.Chat.Store.SQLite.Migrations.M20241223_chat_tags
import Simplex.Chat.Store.SQLite.Migrations.M20241230_reports
import Simplex.Chat.Store.SQLite.Migrations.M20250105_indexes
import Simplex.Chat.Store.SQLite.Migrations.M20250115_chat_ttl
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -245,7 +246,8 @@ schemaMigrations =
("20241222_operator_conditions", m20241222_operator_conditions, Just down_m20241222_operator_conditions),
("20241223_chat_tags", m20241223_chat_tags, Just down_m20241223_chat_tags),
("20241230_reports", m20241230_reports, Just down_m20241230_reports),
("20250105_indexes", m20250105_indexes, Just down_m20250105_indexes)
("20250105_indexes", m20250105_indexes, Just down_m20250105_indexes),
("20250115_chat_ttl", m20250115_chat_ttl, Just down_m20250115_chat_ttl)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,22 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.SQLite.Migrations.M20250115_chat_ttl where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20250115_chat_ttl :: Query
m20250115_chat_ttl =
[sql|
ALTER TABLE contacts ADD COLUMN chat_item_ttl INTEGER;
ALTER TABLE groups ADD COLUMN chat_item_ttl INTEGER;
ALTER TABLE groups ADD COLUMN local_alias TEXT DEFAULT '';
|]
down_m20250115_chat_ttl :: Query
down_m20250115_chat_ttl =
[sql|
ALTER TABLE contacts DROP COLUMN chat_item_ttl;
ALTER TABLE groups DROP COLUMN chat_item_ttl;
ALTER TABLE groups DROP COLUMN local_alias;
|]
@@ -78,6 +78,7 @@ CREATE TABLE contacts(
custom_data BLOB,
ui_themes TEXT,
chat_deleted INTEGER NOT NULL DEFAULT 0,
chat_item_ttl INTEGER,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -131,7 +132,9 @@ CREATE TABLE groups(
business_member_id BLOB NULL,
business_chat TEXT NULL,
business_xcontact_id BLOB NULL,
customer_member_id BLOB NULL, -- received
customer_member_id BLOB NULL,
chat_item_ttl INTEGER,
local_alias TEXT DEFAULT '', -- received
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
+8 -8
View File
@@ -414,18 +414,18 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId =
|]
(userId, profileId, userId, profileId, userId, profileId)
type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData)
type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64)
type ContactRow = Only ContactId :. ContactRow'
toContact :: VersionRangeChat -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact
toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData)) :. connRow) =
toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) :. connRow) =
let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
activeConn = toMaybeConnection vr connRow
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
incognito = maybe False connIncognito activeConn
mergedPreferences = contactUserPreferences user userPreferences preferences incognito
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, chatTags, uiThemes, chatDeleted, customData}
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
getProfileById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO LocalProfile
getProfileById db userId profileId =
@@ -575,18 +575,18 @@ safeDeleteLDN db User {userId} localDisplayName = do
type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId)
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow
type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, BI favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData) :. userMemberRow) =
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, BI favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) =
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
fullGroupPreferences = mergeGroupPreferences groupPreferences
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences}
businessChat = toBusinessChatInfo businessRow
in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, uiThemes, customData}
in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, chatItemTTL, uiThemes, customData}
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) =
@@ -607,9 +607,9 @@ groupInfoQuery =
[sql|
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+3
View File
@@ -188,6 +188,7 @@ data Contact = Contact
contactGroupMemberId :: Maybe GroupMemberId,
contactGrpInvSent :: Bool,
chatTags :: [ChatTagId],
chatItemTTL :: Maybe Int64,
uiThemes :: Maybe UIThemeEntityOverrides,
chatDeleted :: Bool,
customData :: Maybe CustomData
@@ -381,6 +382,7 @@ data GroupInfo = GroupInfo
{ groupId :: GroupId,
localDisplayName :: GroupName,
groupProfile :: GroupProfile,
localAlias :: Text,
businessChat :: Maybe BusinessChatInfo,
fullGroupPreferences :: FullGroupPreferences,
membership :: GroupMember,
@@ -391,6 +393,7 @@ data GroupInfo = GroupInfo
chatTs :: Maybe UTCTime,
userMemberProfileSentAt :: Maybe UTCTime,
chatTags :: [ChatTagId],
chatItemTTL :: Maybe Int64,
uiThemes :: Maybe UIThemeEntityOverrides,
customData :: Maybe CustomData
}
+13 -2
View File
@@ -237,6 +237,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
CRUserProfileImage u p -> ttyUser u $ viewUserProfileImage p
CRContactPrefsUpdated {user = u, fromContact, toContact} -> ttyUser u $ viewUserContactPrefsUpdated u fromContact toContact
CRContactAliasUpdated u c -> ttyUser u $ viewContactAliasUpdated c
CRGroupAliasUpdated u g -> ttyUser u $ viewGroupAliasUpdated g
CRConnectionAliasUpdated u c -> ttyUser u $ viewConnectionAliasUpdated c
CRContactUpdated {user = u, fromContact = c, toContact = c'} -> ttyUser u $ viewContactUpdated c c' <> viewContactPrefsUpdated u c c'
CRGroupMemberUpdated {} -> []
@@ -1182,7 +1183,7 @@ viewGroupsList gs = map groupSS $ sortOn (ldn_ . fst) gs
groupSS (g@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}}, GroupSummary {currentMembers}) =
case memberStatus membership of
GSMemInvited -> groupInvitation' g
s -> membershipIncognito g <> ttyFullGroup g <> viewMemberStatus s
s -> membershipIncognito g <> ttyFullGroup g <> viewMemberStatus s <> alias g
where
viewMemberStatus = \case
GSMemRemoved -> delete "you are removed"
@@ -1197,6 +1198,9 @@ viewGroupsList gs = map groupSS $ sortOn (ldn_ . fst) gs
unmute = "you can " <> highlight ("/unmute #" <> viewGroupName g)
delete reason = " (" <> reason <> ", delete local copy: " <> highlight ("/d #" <> viewGroupName g) <> ")"
memberCount = sShow currentMembers <> " member" <> if currentMembers == 1 then "" else "s"
alias GroupInfo {localAlias}
| localAlias == "" = ""
| otherwise = " (alias: " <> plain localAlias <> ")"
groupInvitation' :: GroupInfo -> StyledString
groupInvitation' g@GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}} =
@@ -1359,11 +1363,13 @@ viewUsageConditions current accepted_ =
viewChatItemTTL :: Maybe Int64 -> [StyledString]
viewChatItemTTL = \case
Nothing -> ["old messages are not being deleted"]
Nothing -> ["old messages are set to delete according to default user config"]
Just ttl
| ttl == 0 -> ["old messages are not being deleted"]
| ttl == 86400 -> deletedAfter "one day"
| ttl == 7 * 86400 -> deletedAfter "one week"
| ttl == 30 * 86400 -> deletedAfter "one month"
| ttl == 365 * 86400 -> deletedAfter "one year"
| otherwise -> deletedAfter $ sShow ttl <> " second(s)"
where
deletedAfter ttlStr = ["old messages are set to be deleted after: " <> ttlStr]
@@ -1626,6 +1632,11 @@ viewContactAliasUpdated ct@Contact {profile = LocalProfile {localAlias}}
| localAlias == "" = ["contact " <> ttyContact' ct <> " alias removed"]
| otherwise = ["contact " <> ttyContact' ct <> " alias updated: " <> plain localAlias]
viewGroupAliasUpdated :: GroupInfo -> [StyledString]
viewGroupAliasUpdated g@GroupInfo {localAlias}
| localAlias == "" = ["group " <> ttyGroup' g <> " alias removed"]
| otherwise = ["group " <> ttyGroup' g <> " alias updated: " <> plain localAlias]
viewConnectionAliasUpdated :: PendingContactConnection -> [StyledString]
viewConnectionAliasUpdated PendingContactConnection {pccConnId, localAlias}
| localAlias == "" = ["connection " <> sShow pccConnId <> " alias removed"]