diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 10a2b667ca..26741740c6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -70,6 +70,7 @@ library Simplex.Chat.Migrations.M20221211_group_description Simplex.Chat.Migrations.M20221212_chat_items_timed Simplex.Chat.Migrations.M20221214_live_message + Simplex.Chat.Migrations.M20221222_chat_ts Simplex.Chat.Mobile Simplex.Chat.Options Simplex.Chat.ProfileGenerator diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 072fe2312b..d0634bf4d1 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -578,9 +578,11 @@ processChatCommand = \case CTDirect -> do ct <- withStore $ \db -> getContact db user chatId filesInfo <- withStore' $ \db -> getContactFileInfo db user ct + -- TODO delete maxItemTs_ <- withStore' $ \db -> getContactMaxItemTs db user ct forM_ filesInfo $ \fileInfo -> deleteFile user fileInfo withStore' $ \db -> deleteContactCIs db user ct + -- TODO delete ct' <- case maxItemTs_ of Just ts -> do withStore' $ \db -> updateContactTs db user ct ts @@ -590,11 +592,13 @@ processChatCommand = \case CTGroup -> do gInfo <- withStore $ \db -> getGroupInfo db user chatId filesInfo <- withStore' $ \db -> getGroupFileInfo db user gInfo + -- TODO delete maxItemTs_ <- withStore' $ \db -> getGroupMaxItemTs db user gInfo forM_ filesInfo $ \fileInfo -> deleteFile user fileInfo withStore' $ \db -> deleteGroupCIs db user gInfo membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db user gInfo forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m + -- TODO delete gInfo' <- case maxItemTs_ of Just ts -> do withStore' $ \db -> updateGroupTs db user gInfo ts @@ -1809,9 +1813,11 @@ expireChatItems user ttl sync = do processContact :: UTCTime -> Contact -> m () processContact expirationDate ct = do filesInfo <- withStore' $ \db -> getContactExpiredFileInfo db user ct expirationDate + -- TODO delete maxItemTs_ <- withStore' $ \db -> getContactMaxItemTs db user ct forM_ filesInfo $ \fileInfo -> deleteFile user fileInfo withStore' $ \db -> deleteContactExpiredCIs db user ct expirationDate + -- TODO delete withStore' $ \db -> do ciCount_ <- getContactCICount db user ct case (maxItemTs_, ciCount_) of @@ -1820,11 +1826,13 @@ expireChatItems user ttl sync = do processGroup :: UTCTime -> UTCTime -> GroupInfo -> m () processGroup expirationDate createdAtCutoff gInfo = do filesInfo <- withStore' $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff + -- TODO delete maxItemTs_ <- withStore' $ \db -> getGroupMaxItemTs db user gInfo forM_ filesInfo $ \fileInfo -> deleteFile user fileInfo withStore' $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db user gInfo forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m + -- TODO delete withStore' $ \db -> do ciCount_ <- getGroupCICount db user gInfo case (maxItemTs_, ciCount_) of @@ -3335,8 +3343,11 @@ saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothi saveSndChatItem' :: ChatMonad m => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIFile 'MDSnd) -> Maybe (CIQuote c) -> Maybe CITimed -> Bool -> m (ChatItem c 'MDSnd) saveSndChatItem' user cd msg@SndMessage {sharedMsgId} content ciFile quotedItem itemTimed live = do createdAt <- liftIO getCurrentTime - ciId <- withStore' $ \db -> createNewSndChatItem db user cd msg content quotedItem itemTimed live createdAt - forM_ ciFile $ \CIFile {fileId} -> withStore' $ \db -> updateFileTransferChatItemId db fileId ciId + ciId <- withStore' $ \db -> do + when (ciRequiresAttention content) $ updateChatTs db user cd createdAt + ciId <- createNewSndChatItem db user cd msg content quotedItem itemTimed live createdAt + forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt + pure ciId liftIO $ mkChatItem cd ciId content ciFile quotedItem (Just sharedMsgId) itemTimed live createdAt createdAt saveRcvChatItem :: ChatMonad m => User -> ChatDirection c 'MDRcv -> RcvMessage -> MsgMeta -> CIContent 'MDRcv -> m (ChatItem c 'MDRcv) @@ -3346,11 +3357,14 @@ saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} msgMeta content = saveRcvChatItem' :: ChatMonad m => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> MsgMeta -> CIContent 'MDRcv -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> m (ChatItem c 'MDRcv) saveRcvChatItem' user cd msg sharedMsgId_ MsgMeta {broker = (_, brokerTs)} content ciFile itemTimed live = do createdAt <- liftIO getCurrentTime - (ciId, quotedItem) <- withStore' $ \db -> createNewRcvChatItem db user cd msg sharedMsgId_ content itemTimed live brokerTs createdAt - forM_ ciFile $ \CIFile {fileId} -> withStore' $ \db -> updateFileTransferChatItemId db fileId ciId + (ciId, quotedItem) <- withStore' $ \db -> do + when (ciRequiresAttention content) $ updateChatTs db user cd createdAt + (ciId, quotedItem) <- createNewRcvChatItem db user cd msg sharedMsgId_ content itemTimed live brokerTs createdAt + forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt + pure (ciId, quotedItem) liftIO $ mkChatItem cd ciId content ciFile quotedItem sharedMsgId_ itemTimed live brokerTs createdAt -mkChatItem :: ChatDirection c d -> ChatItemId -> CIContent d -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CITimed -> Bool -> ChatItemTs -> UTCTime -> IO (ChatItem c d) +mkChatItem :: forall c d. MsgDirectionI d => ChatDirection c d -> ChatItemId -> CIContent d -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CITimed -> Bool -> ChatItemTs -> UTCTime -> IO (ChatItem c d) mkChatItem cd ciId content file quotedItem sharedMsgId itemTimed live itemTs currentTs = do tz <- getCurrentTimeZone let itemText = ciContentToText content @@ -3449,7 +3463,9 @@ createInternalChatItem :: forall c d m. (ChatTypeI c, MsgDirectionI d, ChatMonad createInternalChatItem user cd content itemTs_ = do createdAt <- liftIO getCurrentTime let itemTs = fromMaybe createdAt itemTs_ - ciId <- withStore' $ \db -> createNewChatItemNoMsg db user cd content itemTs createdAt + ciId <- withStore' $ \db -> do + when (ciRequiresAttention content) $ updateChatTs db user cd createdAt + createNewChatItemNoMsg db user cd content itemTs createdAt ci <- liftIO $ mkChatItem cd ciId content Nothing Nothing Nothing Nothing False itemTs createdAt toView $ CRNewChatItem $ AChatItem (chatTypeI @c) (msgDirection @d) (toChatInfo cd) ci diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index c0a9a92cad..9008c516c3 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -69,6 +69,12 @@ data ChatInfo (c :: ChatType) where deriving instance Show (ChatInfo c) +chatInfoChatTs :: ChatInfo c -> Maybe UTCTime +chatInfoChatTs = \case + DirectChat Contact {chatTs} -> chatTs + GroupChat GroupInfo {chatTs} -> chatTs + _ -> Nothing + chatInfoUpdatedAt :: ChatInfo c -> UTCTime chatInfoUpdatedAt = \case DirectChat Contact {updatedAt} -> updatedAt @@ -627,27 +633,36 @@ data CIContent (d :: MsgDirection) where deriving instance Show (CIContent d) -ciCreateStatus :: CIContent d -> CIStatus d -ciCreateStatus = \case - CISndMsgContent _ -> ciStatusNew - CIRcvMsgContent _ -> ciStatusNew - CISndDeleted _ -> ciStatusNew - CIRcvDeleted _ -> ciStatusNew - CISndCall {} -> ciStatusNew - CIRcvCall {} -> ciStatusNew - CIRcvIntegrityError _ -> ciStatusNew - CIRcvGroupInvitation {} -> ciStatusNew - CISndGroupInvitation {} -> ciStatusNew - CIRcvGroupEvent rge -> rgeCreateStatus rge - CISndGroupEvent _ -> ciStatusNew - CIRcvConnEvent _ -> ciStatusNew - CISndConnEvent _ -> ciStatusNew - CIRcvChatFeature {} -> CISRcvRead - CISndChatFeature {} -> ciStatusNew - CIRcvGroupFeature {} -> CISRcvRead - CISndGroupFeature {} -> ciStatusNew - CIRcvChatFeatureRejected _ -> ciStatusNew - CIRcvGroupFeatureRejected _ -> ciStatusNew +ciRequiresAttention :: forall d. MsgDirectionI d => CIContent d -> Bool +ciRequiresAttention content = case msgDirection @d of + SMDSnd -> True + SMDRcv -> case content of + CIRcvMsgContent _ -> True + CIRcvDeleted _ -> True + CIRcvCall {} -> True + CIRcvIntegrityError _ -> True + CIRcvGroupInvitation {} -> True + CIRcvGroupEvent rge -> case rge of + RGEMemberAdded {} -> False + RGEMemberConnected -> False + RGEMemberLeft -> False + RGEMemberRole {} -> False + RGEUserRole _ -> True + RGEMemberDeleted {} -> False + RGEUserDeleted -> True + RGEGroupDeleted -> True + RGEGroupUpdated _ -> False + RGEInvitedViaGroupLink -> False + CIRcvConnEvent _ -> True + CIRcvChatFeature {} -> False + CIRcvGroupFeature {} -> False + CIRcvChatFeatureRejected _ -> True + CIRcvGroupFeatureRejected _ -> True + +ciCreateStatus :: forall d. MsgDirectionI d => CIContent d -> CIStatus d +ciCreateStatus content = case msgDirection @d of + SMDSnd -> ciStatusNew + SMDRcv -> if ciRequiresAttention content then ciStatusNew else CISRcvRead data RcvGroupEvent = RGEMemberAdded {groupMemberId :: GroupMemberId, profile :: Profile} -- CRJoinedGroupMemberConnecting @@ -681,19 +696,6 @@ instance ToJSON DBRcvGroupEvent where toJSON (RGE v) = J.genericToJSON (singleFieldJSON $ dropPrefix "RGE") v toEncoding (RGE v) = J.genericToEncoding (singleFieldJSON $ dropPrefix "RGE") v -rgeCreateStatus :: RcvGroupEvent -> CIStatus 'MDRcv -rgeCreateStatus = \case - RGEMemberAdded {} -> CISRcvRead - RGEMemberConnected -> CISRcvRead - RGEMemberLeft -> CISRcvRead - RGEMemberRole {} -> CISRcvRead - RGEUserRole _ -> ciStatusNew - RGEMemberDeleted {} -> CISRcvRead - RGEUserDeleted -> ciStatusNew - RGEGroupDeleted -> ciStatusNew - RGEGroupUpdated _ -> CISRcvRead - RGEInvitedViaGroupLink -> CISRcvRead - data SndGroupEvent = SGEMemberRole {groupMemberId :: GroupMemberId, profile :: Profile, role :: GroupMemberRole} | SGEUserRole {role :: GroupMemberRole} diff --git a/src/Simplex/Chat/Migrations/M20221222_chat_ts.hs b/src/Simplex/Chat/Migrations/M20221222_chat_ts.hs new file mode 100644 index 0000000000..5cadd03fe3 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20221222_chat_ts.hs @@ -0,0 +1,14 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20221222_chat_ts where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20221222_chat_ts :: Query +m20221222_chat_ts = + [sql| +ALTER TABLE contacts ADD COLUMN chat_ts TEXT; + +ALTER TABLE groups ADD COLUMN chat_ts TEXT; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index effd79a668..feed1c6484 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -60,6 +60,7 @@ CREATE TABLE contacts( unread_chat INTEGER DEFAULT 0 CHECK(unread_chat NOT NULL), contact_used INTEGER DEFAULT 0 CHECK(contact_used NOT NULL), user_preferences TEXT DEFAULT '{}' CHECK(user_preferences NOT NULL), + chat_ts TEXT, FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -130,7 +131,8 @@ CREATE TABLE groups( chat_item_id INTEGER DEFAULT NULL REFERENCES chat_items ON DELETE SET NULL, enable_ntfs INTEGER, host_conn_custom_user_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL, - unread_chat INTEGER DEFAULT 0 CHECK(unread_chat NOT NULL), -- received + unread_chat INTEGER DEFAULT 0 CHECK(unread_chat NOT NULL), + chat_ts TEXT, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs index 7824cf3e87..95b7b942ea 100644 --- a/src/Simplex/Chat/Store.hs +++ b/src/Simplex/Chat/Store.hs @@ -167,13 +167,13 @@ module Simplex.Chat.Store getFileTransferMeta, getSndFileTransfer, getContactFileInfo, - getContactMaxItemTs, + getContactMaxItemTs, -- TODO delete deleteContactCIs, - updateContactTs, + updateContactTs, -- TODO delete getGroupFileInfo, - getGroupMaxItemTs, + getGroupMaxItemTs, -- TODO delete deleteGroupCIs, - updateGroupTs, + updateGroupTs, -- TODO delete createNewSndMessage, createSndMsgDelivery, createNewMessageAndRcvMsgDelivery, @@ -182,6 +182,7 @@ module Simplex.Chat.Store createPendingGroupMessage, getPendingGroupMessages, deletePendingGroupMessage, + updateChatTs, createNewSndChatItem, createNewRcvChatItem, createNewChatItemNoMsg, @@ -233,10 +234,10 @@ module Simplex.Chat.Store setChatItemTTL, getContactExpiredFileInfo, deleteContactExpiredCIs, - getContactCICount, + getContactCICount, -- TODO delete getGroupExpiredFileInfo, deleteGroupExpiredCIs, - getGroupCICount, + getGroupCICount, -- TODO delete getPendingContactConnection, deletePendingContactConnection, updateContactSettings, @@ -320,6 +321,7 @@ import Simplex.Chat.Migrations.M20221210_idxs import Simplex.Chat.Migrations.M20221211_group_description import Simplex.Chat.Migrations.M20221212_chat_items_timed import Simplex.Chat.Migrations.M20221214_live_message +import Simplex.Chat.Migrations.M20221222_chat_ts import Simplex.Chat.Protocol import Simplex.Chat.Types import Simplex.Messaging.Agent.Protocol (ACorrId, AgentMsgId, ConnId, InvitationId, MsgMeta (..)) @@ -375,7 +377,8 @@ schemaMigrations = ("20221210_idxs", m20221210_idxs), ("20221211_group_description", m20221211_group_description), ("20221212_chat_items_timed", m20221212_chat_items_timed), - ("20221214_live_message", m20221214_live_message) + ("20221214_live_message", m20221214_live_message), + ("20221222_chat_ts", m20221222_chat_ts) ] -- | The list of migrations in ascending order by date @@ -483,7 +486,8 @@ getConnReqContactXContactId db user@User {userId} cReqHash = do [sql| 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.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- 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.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 @@ -568,14 +572,14 @@ createConnection_ db userId connType entityId acId viaContact viaUserContactLink createDirectContact :: DB.Connection -> User -> Connection -> Profile -> ExceptT StoreError IO Contact createDirectContact db user@User {userId} activeConn@Connection {connId, localAlias} p@Profile {preferences} = do createdAt <- liftIO getCurrentTime - (localDisplayName, contactId, profileId) <- createContact_ db userId connId p localAlias Nothing createdAt + (localDisplayName, contactId, profileId) <- createContact_ db userId connId p localAlias Nothing createdAt (Just createdAt) let profile = toLocalProfile profileId p localAlias userPreferences = emptyChatPrefs mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn - pure $ Contact {contactId, localDisplayName, profile, activeConn, viaGroup = Nothing, contactUsed = False, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt, updatedAt = createdAt} + pure $ Contact {contactId, localDisplayName, profile, activeConn, viaGroup = Nothing, contactUsed = False, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt, updatedAt = createdAt, chatTs = Just createdAt} -createContact_ :: DB.Connection -> UserId -> Int64 -> Profile -> LocalAlias -> Maybe Int64 -> UTCTime -> ExceptT StoreError IO (Text, ContactId, ProfileId) -createContact_ db userId connId Profile {displayName, fullName, image, preferences} localAlias viaGroup currentTs = +createContact_ :: DB.Connection -> UserId -> Int64 -> Profile -> LocalAlias -> Maybe Int64 -> UTCTime -> Maybe UTCTime -> ExceptT StoreError IO (Text, ContactId, ProfileId) +createContact_ db userId connId Profile {displayName, fullName, image, preferences} localAlias viaGroup currentTs chatTs = ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do DB.execute db @@ -584,8 +588,8 @@ createContact_ db userId connId Profile {displayName, fullName, image, preferenc profileId <- insertedRowId db DB.execute db - "INSERT INTO contacts (contact_profile_id, local_display_name, user_id, via_group, created_at, updated_at) VALUES (?,?,?,?,?,?)" - (profileId, ldn, userId, viaGroup, currentTs, currentTs) + "INSERT INTO contacts (contact_profile_id, local_display_name, user_id, via_group, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?)" + (profileId, ldn, userId, viaGroup, currentTs, currentTs, chatTs) contactId <- insertedRowId db DB.execute db "UPDATE connections SET contact_id = ?, updated_at = ? WHERE connection_id = ?" (contactId, currentTs, connId) pure $ Right (ldn, contactId, profileId) @@ -783,24 +787,24 @@ updateContact_ db userId contactId displayName newName updatedAt = do (newName, updatedAt, userId, contactId) DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (displayName, userId) -type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, LocalAlias, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime) +type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, LocalAlias, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) toContact :: User -> ContactRow :. ConnectionRow -> Contact -toContact user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt)) :. connRow) = +toContact user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} activeConn = toConnection connRow chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn - in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt} + in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs} toContactOrError :: User -> ContactRow :. MaybeConnectionRow -> Either StoreError Contact -toContactOrError user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt)) :. connRow) = +toContactOrError user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} in case toMaybeConnection connRow of Just activeConn -> let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn - in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt} + in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs} _ -> Left $ SEContactNotReady localDisplayName getContactByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Contact @@ -1099,7 +1103,8 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi [sql| 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.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- 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.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 @@ -1221,12 +1226,12 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences} let userPreferences = fromMaybe emptyChatPrefs $ incognitoProfile >> preferences DB.execute db - "INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, xcontact_id) VALUES (?,?,?,?,?,?,?,?)" - (userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, xContactId) + "INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id) VALUES (?,?,?,?,?,?,?,?,?)" + (userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, createdAt, xContactId) contactId <- insertedRowId db activeConn <- createConnection_ db userId ConnContact (Just contactId) agentConnId Nothing (Just userContactLinkId) customUserProfileId 0 createdAt let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn - pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn, viaGroup = Nothing, contactUsed = False, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = createdAt, updatedAt = createdAt} + pure $ Contact {contactId, localDisplayName, profile = toLocalProfile profileId profile "", activeConn, viaGroup = Nothing, contactUsed = False, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = createdAt, updatedAt = createdAt, chatTs = Just createdAt} getLiveSndFileTransfers :: DB.Connection -> User -> IO [SndFileTransfer] getLiveSndFileTransfers db User {userId} = do @@ -1509,18 +1514,20 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do <$> DB.query db [sql| - SELECT c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, c.via_group, c.contact_used, c.enable_ntfs, p.preferences, c.user_preferences, c.created_at, c.updated_at + SELECT + c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, c.via_group, c.contact_used, c.enable_ntfs, + p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts FROM contacts c JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id WHERE c.user_id = ? AND c.contact_id = ? |] (userId, contactId) - toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime)] -> Either StoreError Contact - toContact' contactId activeConn [(profileId, localDisplayName, displayName, fullName, image, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt)] = + toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)] -> Either StoreError Contact + toContact' contactId activeConn [(profileId, localDisplayName, displayName, fullName, image, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)] = let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn - in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt} + in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs} toContact' _ _ _ = Left $ SEInternalError "referenced contact not found" getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember) getGroupAndMember_ groupMemberId c = ExceptT $ do @@ -1530,7 +1537,7 @@ getConnectionEntity db 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.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, g.chat_ts, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -1631,7 +1638,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId = [sql| SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, g.chat_ts, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -1683,13 +1690,13 @@ createNewGroup db gVar user@User {userId} groupProfile = ExceptT $ do profileId <- insertedRowId db DB.execute db - "INSERT INTO groups (local_display_name, user_id, group_profile_id, enable_ntfs, created_at, updated_at) VALUES (?,?,?,?,?,?)" - (ldn, userId, profileId, True, currentTs, currentTs) + "INSERT INTO groups (local_display_name, user_id, group_profile_id, enable_ntfs, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?)" + (ldn, userId, profileId, True, currentTs, currentTs, currentTs) insertedRowId db memberId <- liftIO $ encodedRandomBytes gVar 12 membership <- createContactMemberInv_ db user groupId user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs let chatSettings = ChatSettings {enableNtfs = True} - pure GroupInfo {groupId, localDisplayName = ldn, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = Nothing, chatSettings, createdAt = currentTs, updatedAt = currentTs} + pure GroupInfo {groupId, localDisplayName = ldn, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = Nothing, chatSettings, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs} -- | creates a new group record for the group the current user was invited to, or returns an existing one createGroupInvitation :: DB.Connection -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId) @@ -1728,13 +1735,13 @@ createGroupInvitation db user@User {userId} contact@Contact {contactId, activeCo profileId <- insertedRowId db DB.execute db - "INSERT INTO groups (group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs) + "INSERT INTO groups (group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?,?,?)" + (profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs) insertedRowId db GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs membership <- createContactMemberInv_ db user groupId user invitedMember GCUserMember GSMemInvited (IBContact contactId) incognitoProfileId currentTs let chatSettings = ChatSettings {enableNtfs = True} - pure (GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = customUserProfileId, chatSettings, createdAt = currentTs, updatedAt = currentTs}, groupMemberId) + pure (GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = customUserProfileId, chatSettings, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs}, groupMemberId) getHostMemberId_ :: DB.Connection -> User -> GroupId -> ExceptT StoreError IO GroupMemberId getHostMemberId_ db User {userId} groupId = @@ -1870,7 +1877,7 @@ getUserGroupDetails db User {userId, userContactId} = <$> DB.query db [sql| - SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, + SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, g.chat_ts, mu.group_member_id, g.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences FROM groups g @@ -1904,15 +1911,15 @@ getGroupInfoByName db user gName = do gId <- getGroupIdByName db user gName getGroupInfo db user gId -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe Bool, Maybe GroupPreferences, UTCTime, UTCTime) :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe Bool, Maybe GroupPreferences, UTCTime, UTCTime, Maybe UTCTime) :. GroupMemberRow toGroupInfo :: Int64 -> GroupInfoRow -> GroupInfo -toGroupInfo userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, groupPreferences, createdAt, updatedAt) :. userMemberRow) = +toGroupInfo userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, groupPreferences, createdAt, updatedAt, chatTs) :. userMemberRow) = let membership = toGroupMember userContactId userMemberRow chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} fullGroupPreferences = mergeGroupPreferences groupPreferences groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} - in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt} + in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs} groupMemberQuery :: Query groupMemberQuery = @@ -2077,7 +2084,8 @@ getContactViaMember db user@User {userId} GroupMember {groupMemberId} = [sql| 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.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- 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.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 @@ -2311,7 +2319,7 @@ createIntroReMember db user@User {userId} gInfo@GroupInfo {groupId} _host@GroupM currentTs <- liftIO getCurrentTime Connection {connId = directConnId} <- liftIO $ createConnection_ db userId ConnContact Nothing directAgentConnId memberContactId Nothing customUserProfileId cLevel currentTs liftIO $ setCommandConnId db user directCmdId directConnId - (localDisplayName, contactId, memProfileId) <- createContact_ db userId directConnId memberProfile "" (Just groupId) currentTs + (localDisplayName, contactId, memProfileId) <- createContact_ db userId directConnId memberProfile "" (Just groupId) currentTs Nothing liftIO $ do let newMember = NewGroupMember @@ -2375,7 +2383,7 @@ getViaGroupMember db User {userId, userContactId} Contact {contactId} = [sql| SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, g.chat_ts, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -2415,7 +2423,8 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = db [sql| SELECT - ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs, p.preferences, ct.user_preferences, ct.created_at, ct.updated_at, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs, + p.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, 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.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 FROM contacts ct @@ -2431,13 +2440,13 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = |] (userId, groupMemberId) where - toContact' :: ((ContactId, ProfileId, ContactName, Text, Text, Maybe ImageData, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime)) :. ConnectionRow -> Contact - toContact' (((contactId, profileId, localDisplayName, displayName, fullName, image, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt)) :. connRow) = + toContact' :: ((ContactId, ProfileId, ContactName, Text, Text, Maybe ImageData, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)) :. ConnectionRow -> Contact + toContact' (((contactId, profileId, localDisplayName, displayName, fullName, image, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} activeConn = toConnection connRow mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn - in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt} + in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs} createSndDirectFileTransfer :: DB.Connection -> UserId -> Contact -> FilePath -> FileInvitation -> Maybe ConnId -> Integer -> IO FileTransferMeta createSndDirectFileTransfer db userId Contact {contactId} filePath FileInvitation {fileName, fileSize, fileInline} acId_ chunkSize = do @@ -2853,9 +2862,8 @@ deleteRcvFileChunks :: DB.Connection -> RcvFileTransfer -> IO () deleteRcvFileChunks db RcvFileTransfer {fileId} = DB.execute db "DELETE FROM rcv_file_chunks WHERE file_id = ?" (Only fileId) -updateFileTransferChatItemId :: DB.Connection -> FileTransferId -> ChatItemId -> IO () -updateFileTransferChatItemId db fileId ciId = do - currentTs <- getCurrentTime +updateFileTransferChatItemId :: DB.Connection -> FileTransferId -> ChatItemId -> UTCTime -> IO () +updateFileTransferChatItemId db fileId ciId currentTs = DB.execute db "UPDATE files SET chat_item_id = ?, updated_at = ? WHERE file_id = ?" (ciId, currentTs, fileId) getFileTransferProgress :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO (FileTransfer, [Integer]) @@ -2950,6 +2958,7 @@ getContactFileInfo db User {userId} Contact {contactId} = toFileInfo :: (Int64, Maybe ACIFileStatus, Maybe FilePath) -> CIFileInfo toFileInfo (fileId, fileStatus, filePath) = CIFileInfo {fileId, fileStatus, filePath} +-- TODO delete getContactMaxItemTs :: DB.Connection -> User -> Contact -> IO (Maybe UTCTime) getContactMaxItemTs db User {userId} Contact {contactId} = fmap join . maybeFirstRow fromOnly $ @@ -2967,11 +2976,12 @@ getContactConnIds_ db User {userId} Contact {contactId} = map fromOnly <$> DB.query db "SELECT connection_id FROM connections WHERE user_id = ? AND contact_id = ?" (userId, contactId) +-- TODO delete updateContactTs :: DB.Connection -> User -> Contact -> UTCTime -> IO () updateContactTs db User {userId} Contact {contactId} updatedAt = DB.execute db - "UPDATE contacts SET updated_at = ? WHERE user_id = ? AND contact_id = ?" + "UPDATE contacts SET chat_ts = ? WHERE user_id = ? AND contact_id = ?" (updatedAt, userId, contactId) getGroupFileInfo :: DB.Connection -> User -> GroupInfo -> IO [CIFileInfo] @@ -2987,6 +2997,7 @@ getGroupFileInfo db User {userId} GroupInfo {groupId} = |] (userId, groupId) +-- TODO delete getGroupMaxItemTs :: DB.Connection -> User -> GroupInfo -> IO (Maybe UTCTime) getGroupMaxItemTs db User {userId} GroupInfo {groupId} = fmap join . maybeFirstRow fromOnly $ @@ -2997,11 +3008,12 @@ deleteGroupCIs db User {userId} GroupInfo {groupId} = do DB.execute db "DELETE FROM messages WHERE group_id = ?" (Only groupId) DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND group_id = ?" (userId, groupId) +-- TODO delete updateGroupTs :: DB.Connection -> User -> GroupInfo -> UTCTime -> IO () updateGroupTs db User {userId} GroupInfo {groupId} updatedAt = DB.execute db - "UPDATE groups SET updated_at = ? WHERE user_id = ? AND group_id = ?" + "UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ?" (updatedAt, userId, groupId) createNewSndMessage :: MsgEncodingI e => DB.Connection -> TVar ChaChaDRG -> ConnOrGroupId -> (SharedMsgId -> NewMessage e) -> ExceptT StoreError IO SndMessage @@ -3149,6 +3161,20 @@ deletePendingGroupMessage db groupMemberId messageId = type NewQuoteRow = (Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool, Maybe MemberId) +updateChatTs :: DB.Connection -> User -> ChatDirection c d -> UTCTime -> IO () +updateChatTs db User {userId} chatDirection chatTs = case toChatInfo chatDirection of + DirectChat Contact {contactId} -> + DB.execute + db + "UPDATE contacts SET chat_ts = ? WHERE user_id = ? AND contact_id = ?" + (chatTs, userId, contactId) + GroupChat GroupInfo {groupId} -> + DB.execute + db + "UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ?" + (chatTs, userId, groupId) + _ -> pure () + createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> Maybe CITimed -> Bool -> UTCTime -> IO ChatItemId createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciContent quotedItem timed live createdAt = createNewChatItem_ db user chatDirection createdByMsgId (Just sharedMsgId) ciContent quoteRow timed live createdAt createdAt @@ -3295,8 +3321,11 @@ getChatPreviews db user withPCC = do pure $ sortOn (Down . ts) (directChats <> groupChats <> cReqChats <> connChats) where ts :: AChat -> UTCTime - ts (AChat _ Chat {chatInfo, chatItems = ci : _}) = max (chatItemTs ci) (chatInfoUpdatedAt chatInfo) - ts (AChat _ Chat {chatInfo}) = chatInfoUpdatedAt chatInfo + ts (AChat _ Chat {chatInfo, chatItems}) = case chatInfoChatTs chatInfo of + Just chatTs -> chatTs + Nothing -> case chatItems of + ci : _ -> max (chatItemTs ci) (chatInfoUpdatedAt chatInfo) + _ -> chatInfoUpdatedAt chatInfo getDirectChatPreviews_ :: DB.Connection -> User -> IO [AChat] getDirectChatPreviews_ db user@User {userId} = do @@ -3308,7 +3337,8 @@ getDirectChatPreviews_ db user@User {userId} = do [sql| 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.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- 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.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, @@ -3372,7 +3402,7 @@ getGroupChatPreviews_ db User {userId, userContactId} = do [sql| SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, g.chat_ts, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -3630,7 +3660,8 @@ getContact db user@User {userId} contactId = [sql| 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.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- 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.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 @@ -3738,7 +3769,7 @@ getGroupInfo db User {userId, userContactId} groupId = [sql| SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, g.chat_ts, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -4670,6 +4701,7 @@ deleteContactExpiredCIs db user@User {userId} ct@Contact {contactId} expirationD DB.execute db "DELETE FROM messages WHERE connection_id = ? AND created_at <= ?" (connId, expirationDate) DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND contact_id = ? AND created_at <= ?" (userId, contactId, expirationDate) +-- TODO delete getContactCICount :: DB.Connection -> User -> Contact -> IO (Maybe Int64) getContactCICount db User {userId} Contact {contactId} = fmap join . maybeFirstRow fromOnly $ @@ -4693,6 +4725,7 @@ deleteGroupExpiredCIs db User {userId} GroupInfo {groupId} expirationDate create DB.execute db "DELETE FROM messages WHERE group_id = ? AND created_at <= ?" (groupId, min expirationDate createdAtCutoff) DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND group_id = ? AND item_ts <= ? AND created_at <= ?" (userId, groupId, expirationDate, createdAtCutoff) +-- TODO delete getGroupCICount :: DB.Connection -> User -> GroupInfo -> IO (Maybe Int64) getGroupCICount db User {userId} GroupInfo {groupId} = fmap join . maybeFirstRow fromOnly $ diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index b586c7e337..fb1bb5315d 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -109,7 +109,8 @@ data Contact = Contact userPreferences :: Preferences, mergedPreferences :: ContactUserPreferences, createdAt :: UTCTime, - updatedAt :: UTCTime + updatedAt :: UTCTime, + chatTs :: Maybe UTCTime } deriving (Eq, Show, Generic) @@ -240,7 +241,8 @@ data GroupInfo = GroupInfo hostConnCustomUserProfileId :: Maybe ProfileId, chatSettings :: ChatSettings, createdAt :: UTCTime, - updatedAt :: UTCTime + updatedAt :: UTCTime, + chatTs :: Maybe UTCTime } deriving (Eq, Show, Generic)