From a9bea25b7f466df7ddb5a7c232c23e9a99bdf8c6 Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Thu, 4 Jun 2026 15:51:51 +0000 Subject: [PATCH] process badges --- .../src/Directory/Store.hs | 61 ++++--- bots/api/COMMANDS.md | 2 +- bots/src/API/Docs/Types.hs | 9 + bots/src/API/TypeInfo.hs | 2 +- simplex-chat.cabal | 3 + src/Simplex/Chat/Library/Internal.hs | 4 +- src/Simplex/Chat/Store/Connections.hs | 28 ++-- src/Simplex/Chat/Store/ContactRequest.hs | 31 +++- src/Simplex/Chat/Store/Direct.hs | 56 ++++--- src/Simplex/Chat/Store/Groups.hs | 155 ++++++++++-------- src/Simplex/Chat/Store/Messages.hs | 23 ++- src/Simplex/Chat/Store/Postgres/Migrations.hs | 4 +- .../Migrations/M20260516_supporter_badges.hs | 26 +++ src/Simplex/Chat/Store/SQLite/Migrations.hs | 4 +- .../Migrations/M20260516_supporter_badges.hs | 26 +++ src/Simplex/Chat/Store/Shared.hs | 56 ++++--- 16 files changed, 310 insertions(+), 180 deletions(-) create mode 100644 src/Simplex/Chat/Store/Postgres/Migrations/M20260516_supporter_badges.hs create mode 100644 src/Simplex/Chat/Store/SQLite/Migrations/M20260516_supporter_badges.hs diff --git a/apps/simplex-directory-service/src/Directory/Store.hs b/apps/simplex-directory-service/src/Directory/Store.hs index b5f7220724..b1c872781b 100644 --- a/apps/simplex-directory-service/src/Directory/Store.hs +++ b/apps/simplex-directory-service/src/Directory/Store.hs @@ -314,43 +314,48 @@ getGroupReg_ db gId = getGroupAndReg :: ChatController -> User -> GroupId -> IO (Either String (GroupInfo, GroupReg)) getGroupAndReg cc user@User {userId, userContactId} gId = - withDB "getGroupAndReg" cc $ \db -> - ExceptT $ firstRow (toGroupInfoReg (vr cc) user) ("group " ++ show gId ++ " not found") $ - DB.query db (groupReqQuery <> " AND g.group_id = ?") (userId, userContactId, gId) + withDB "getGroupAndReg" cc $ \db -> do + currentTs <- liftIO getCurrentTime + ExceptT $ firstRow (toGroupInfoReg currentTs (vr cc) user) ("group " ++ show gId ++ " not found") $ + DB.query db (groupReqQuery <> " AND g.group_id = ?") (userId, userContactId, gId) getUserGroupReg :: ChatController -> User -> ContactId -> UserGroupRegId -> IO (Either String (GroupInfo, GroupReg)) getUserGroupReg cc user@User {userId, userContactId} ctId ugrId = - withDB "getUserGroupReg" cc $ \db -> - ExceptT $ firstRow (toGroupInfoReg (vr cc) user) ("group " ++ show ugrId ++ " not found") $ + withDB "getUserGroupReg" cc $ \db -> do + currentTs <- liftIO getCurrentTime + ExceptT $ firstRow (toGroupInfoReg currentTs (vr cc) user) ("group " ++ show ugrId ++ " not found") $ DB.query db (groupReqQuery <> " AND r.contact_id = ? AND r.user_group_reg_id = ?") (userId, userContactId, ctId, ugrId) getUserGroupRegs :: ChatController -> User -> ContactId -> IO (Either String [(GroupInfo, GroupReg)]) getUserGroupRegs cc user@User {userId, userContactId} ctId = - withDB' "getUserGroupRegs" cc $ \db -> - map (toGroupInfoReg (vr cc) user) + withDB' "getUserGroupRegs" cc $ \db -> do + currentTs <- getCurrentTime + map (toGroupInfoReg currentTs (vr cc) user) <$> DB.query db (groupReqQuery <> " AND r.contact_id = ? ORDER BY r.user_group_reg_id") (userId, userContactId, ctId) getAllListedGroups :: ChatController -> User -> IO (Either String [(GroupInfo, GroupReg, Maybe GroupLink)]) getAllListedGroups cc user = withDB' "getAllListedGroups" cc $ \db -> getAllListedGroups_ db (vr cc) user getAllListedGroups_ :: DB.Connection -> VersionRangeChat -> User -> IO [(GroupInfo, GroupReg, Maybe GroupLink)] -getAllListedGroups_ db vr' user@User {userId, userContactId} = +getAllListedGroups_ db vr' user@User {userId, userContactId} = do + currentTs <- getCurrentTime DB.query db (groupReqQuery <> " AND r.group_reg_status = ?") (userId, userContactId, GRSActive) - >>= mapM (withGroupLink . toGroupInfoReg vr' user) + >>= mapM (withGroupLink . toGroupInfoReg currentTs vr' user) where withGroupLink (g, gr) = (g,gr,) . eitherToMaybe <$> runExceptT (getGroupLink db user g) searchListedGroups :: ChatController -> User -> SearchType -> Maybe GroupId -> Int -> IO (Either String ([(GroupInfo, GroupReg)], Int)) searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pageSize = - withDB' "searchListedGroups" cc $ \db -> + withDB' "searchListedGroups" cc $ \db -> do + currentTs <- getCurrentTime case searchType of STAll -> case lastGroup_ of Nothing -> do - gs <- groups $ DB.query db (listedGroupQuery <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, pageSize) + gs <- groups currentTs $ DB.query db (listedGroupQuery <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, pageSize) n <- count $ DB.query db countQuery' (Only GRSActive) pure (gs, n) Just gId -> do - gs <- groups $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, pageSize) + gs <- groups currentTs $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, pageSize) n <- count $ DB.query db (countQuery' <> " AND r.group_id > ?") (GRSActive, gId) pure (gs, n) where @@ -358,11 +363,11 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa orderBy = " ORDER BY g.summary_current_members_count DESC, r.group_reg_id ASC " STRecent -> case lastGroup_ of Nothing -> do - gs <- groups $ DB.query db (listedGroupQuery <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, pageSize) + gs <- groups currentTs $ DB.query db (listedGroupQuery <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, pageSize) n <- count $ DB.query db countQuery' (Only GRSActive) pure (gs, n) Just gId -> do - gs <- groups $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, pageSize) + gs <- groups currentTs $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, pageSize) n <- count $ DB.query db (countQuery' <> " AND r.group_id > ?") (GRSActive, gId) pure (gs, n) where @@ -370,11 +375,11 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa orderBy = " ORDER BY r.created_at DESC, r.group_reg_id ASC " STSearch search -> case lastGroup_ of Nothing -> do - gs <- groups $ DB.query db (listedGroupQuery <> searchCond <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, s, s, s, s, pageSize) + gs <- groups currentTs $ DB.query db (listedGroupQuery <> searchCond <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, s, s, s, s, pageSize) n <- count $ DB.query db (countQuery' <> searchCond) (GRSActive, s, s, s, s) pure (gs, n) Just gId -> do - gs <- groups $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> searchCond <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, s, s, s, s, pageSize) + gs <- groups currentTs $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> searchCond <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, s, s, s, s, pageSize) n <- count $ DB.query db (countQuery' <> " AND r.group_id > ? " <> searchCond) (GRSActive, gId, s, s, s, s) pure (gs, n) where @@ -382,7 +387,7 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa countQuery' = countQuery <> " JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id WHERE r.group_reg_status = ? " orderBy = " ORDER BY g.summary_current_members_count DESC, r.group_reg_id ASC " where - groups = (map (toGroupInfoReg (vr cc) user) <$>) + groups currentTs = (map (toGroupInfoReg currentTs (vr cc) user) <$>) count = maybeFirstRow' 0 fromOnly listedGroupQuery = groupReqQuery <> " AND r.group_reg_status = ? " countQuery = "SELECT COUNT(1) FROM groups g JOIN sx_directory_group_regs r ON g.group_id = r.group_id " @@ -396,21 +401,24 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa |] getAllGroupRegs_ :: DB.Connection -> User -> IO [(GroupInfo, GroupReg)] -getAllGroupRegs_ db user@User {userId, userContactId} = - map (toGroupInfoReg supportedChatVRange user) +getAllGroupRegs_ db user@User {userId, userContactId} = do + currentTs <- getCurrentTime + map (toGroupInfoReg currentTs supportedChatVRange user) <$> DB.query db groupReqQuery (userId, userContactId) getDuplicateGroupRegs :: ChatController -> User -> Text -> IO (Either String [(GroupInfo, GroupReg)]) getDuplicateGroupRegs cc user@User {userId, userContactId} displayName = - withDB' "getDuplicateGroupRegs" cc $ \db -> - map (toGroupInfoReg (vr cc) user) + withDB' "getDuplicateGroupRegs" cc $ \db -> do + currentTs <- getCurrentTime + map (toGroupInfoReg currentTs (vr cc) user) <$> DB.query db (groupReqQuery <> " AND gp.display_name = ?") (userId, userContactId, displayName) listLastGroups :: ChatController -> User -> Int -> IO (Either String ([(GroupInfo, GroupReg)], Int)) listLastGroups cc user@User {userId, userContactId} count = withDB' "getUserGroupRegs" cc $ \db -> do + currentTs <- getCurrentTime gs <- - map (toGroupInfoReg (vr cc) user) + map (toGroupInfoReg currentTs (vr cc) user) <$> DB.query db (groupReqQuery <> " ORDER BY group_reg_id DESC LIMIT ?") (userId, userContactId, count) n <- maybeFirstRow' 0 fromOnly $ DB.query_ db "SELECT COUNT(1) FROM sx_directory_group_regs" pure (gs, n) @@ -418,15 +426,16 @@ listLastGroups cc user@User {userId, userContactId} count = listPendingGroups :: ChatController -> User -> Int -> IO (Either String ([(GroupInfo, GroupReg)], Int)) listPendingGroups cc user@User {userId, userContactId} count = withDB' "getUserGroupRegs" cc $ \db -> do + currentTs <- getCurrentTime gs <- - map (toGroupInfoReg (vr cc) user) + map (toGroupInfoReg currentTs (vr cc) user) <$> DB.query db (groupReqQuery <> " AND r.group_reg_status LIKE 'pending_approval%' ORDER BY group_reg_id DESC LIMIT ?") (userId, userContactId, count) n <- maybeFirstRow' 0 fromOnly $ DB.query_ db "SELECT COUNT(1) FROM sx_directory_group_regs WHERE group_reg_status LIKE 'pending_approval%'" pure (gs, n) -toGroupInfoReg :: VersionRangeChat -> User -> (GroupInfoRow :. GroupRegRow) -> (GroupInfo, GroupReg) -toGroupInfoReg vr' User {userContactId} (groupRow :. grRow) = - (toGroupInfo vr' userContactId [] groupRow, rowToGroupReg grRow) +toGroupInfoReg :: UTCTime -> VersionRangeChat -> User -> (GroupInfoRow :. GroupRegRow) -> (GroupInfo, GroupReg) +toGroupInfoReg currentTs vr' User {userContactId} (groupRow :. grRow) = + (toGroupInfo currentTs vr' userContactId [] groupRow, rowToGroupReg grRow) type GroupRegRow = (GroupId, UserGroupRegId, ContactId, Maybe GroupMemberId, GroupRegStatus, BoolInt, UTCTime) diff --git a/bots/api/COMMANDS.md b/bots/api/COMMANDS.md index d14435cabd..19c96f9811 100644 --- a/bots/api/COMMANDS.md +++ b/bots/api/COMMANDS.md @@ -2,7 +2,7 @@ This file is generated automatically. -[Address commands](#address-commands) +[Address commands](# - [APICreateMyAddress](#apicreatemyaddress) - [APIDeleteMyAddress](#apideletemyaddress) - [APIShowMyAddress](#apishowmyaddress) diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index 8397503bbe..946eaf7b6b 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -34,6 +34,7 @@ import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Shared import Simplex.Chat.Operators import Simplex.Messaging.Agent.Store.Entity (DBStored (..)) +import Simplex.Chat.Badges (LocalBadge (..), BadgeStatus (..), SupporterBadge (..), BadgeType (..)) import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared @@ -216,6 +217,8 @@ chatTypesDocsData = (sti @ChatDeleteMode, STUnion, "CDM", [], Param "type" <> Choice "self" [("messages", "")] (OnOffParam "notify" "notify" (Just True)), ""), (sti @ChatError, STUnion, "Chat", ["ChatErrorDatabase", "ChatErrorRemoteHost", "ChatErrorRemoteCtrl"], "", ""), (sti @ChatErrorType, STUnion, "CE", ["CEContactNotFound", "CEServerProtocol", "CECallState", "CEInvalidChatMessage"], "", ""), + (sti @BadgeStatus, STEnum, "BS", [], "", ""), + (sti @BadgeType, STEnum, "BT", ["BTUnknown"], "", ""), (sti @ChatFeature, STEnum, "CF", [], "", ""), (sti @ChatItemDeletion, STRecord, "", [], "", "Message deletion result."), (sti @ChatPeerType, STEnum, "CPT", [], "", ""), @@ -303,6 +306,7 @@ chatTypesDocsData = (sti @LinkContent, STUnion, "LC", [], "", ""), (sti @LinkOwnerSig, STRecord, "", [], "", ""), (sti @LinkPreview, STRecord, "", [], "", ""), + (sti @LocalBadge, STRecord, "", [], "", ""), (sti @LocalProfile, STRecord, "", [], "", ""), (sti @MemberCriteria, STEnum1, "MC", [], "", ""), (sti @MsgChatLink, STUnion, "MCL", [], "", "Connection link sent in a message - only short links are allowed."), @@ -360,6 +364,7 @@ chatTypesDocsData = (sti @SrvError, STUnion, "SrvErr", [], "", ""), (sti @StoreError, STUnion, "SE", [], "", ""), (sti @SubscriptionStatus, STUnion, "SS", [], "", ""), + (sti @SupporterBadge, STRecord, "", [], "", ""), (sti @SupportGroupPreference, STRecord, "", [], "", ""), (sti @SwitchPhase, STEnum, "SP", [], "", ""), (sti @TimedMessagesGroupPreference, STRecord, "", [], "", ""), @@ -427,6 +432,8 @@ deriving instance Generic BlockingReason deriving instance Generic BrokerErrorType deriving instance Generic BusinessChatInfo deriving instance Generic BusinessChatType +deriving instance Generic BadgeStatus +deriving instance Generic BadgeType deriving instance Generic ChatBotCommand deriving instance Generic ChatDeleteMode deriving instance Generic ChatError @@ -524,6 +531,7 @@ deriving instance Generic JSONCIStatus deriving instance Generic LinkContent deriving instance Generic LinkOwnerSig deriving instance Generic LinkPreview +deriving instance Generic LocalBadge deriving instance Generic LocalProfile deriving instance Generic MemberCriteria deriving instance Generic MsgChatLink @@ -550,6 +558,7 @@ deriving instance Generic ProxyClientError deriving instance Generic ProxyError deriving instance Generic PublicGroupAccess deriving instance Generic PublicGroupData +deriving instance Generic SupporterBadge deriving instance Generic PublicGroupProfile deriving instance Generic RatchetSyncState deriving instance Generic RCErrorType diff --git a/bots/src/API/TypeInfo.hs b/bots/src/API/TypeInfo.hs index 36e87db62d..dc48131ce7 100644 --- a/bots/src/API/TypeInfo.hs +++ b/bots/src/API/TypeInfo.hs @@ -67,7 +67,7 @@ pattern TUTCTime :: String pattern TUTCTime = "UTCTime" primitiveTypes :: [ConsName] -primitiveTypes = [TBool, TString, TInt, TInt64, TWord32, TDouble, TJSONObject, TUTCTime] +primitiveTypes = [TBool, TString, TInt, TInt64, TWord32, TDouble, TJSONObject, TUTCTime, "BBSProof", "BBSPresHeader", "ByteString"] data SumTypeInfo = STI {typeName :: String, recordTypes :: [RecordTypeInfo]} deriving (Show) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 8d06427d63..7346fb0246 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -135,6 +135,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20260507_relay_inactive_at Simplex.Chat.Store.Postgres.Migrations.M20260514_relay_request_group_link_index Simplex.Chat.Store.Postgres.Migrations.M20260515_public_group_access + Simplex.Chat.Store.Postgres.Migrations.M20260516_supporter_badges else exposed-modules: Simplex.Chat.Archive @@ -291,6 +292,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20260507_relay_inactive_at Simplex.Chat.Store.SQLite.Migrations.M20260514_relay_request_group_link_index Simplex.Chat.Store.SQLite.Migrations.M20260515_public_group_access + Simplex.Chat.Store.SQLite.Migrations.M20260516_supporter_badges other-modules: Paths_simplex_chat hs-source-dirs: @@ -551,6 +553,7 @@ test-suite simplex-chat-test main-is: Test.hs other-modules: APIDocs + BadgeTests Bots.BroadcastTests Bots.DirectoryTests ChatClient diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 263cd7d4e3..46b734c747 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -1197,8 +1197,8 @@ memberInfo g m@GroupMember {memberId, memberRole, memberProfile, memberPubKey, a allowSimplexLinks = groupFeatureMemberAllowed SGFSimplexLinks m g redactedMemberProfile :: Bool -> Profile -> Profile -redactedMemberProfile allowSimplexLinks Profile {displayName, fullName, shortDescr, image, peerType} = - Profile {displayName, fullName, shortDescr = removeSimplexLink =<< shortDescr, image, contactLink = Nothing, preferences = Nothing, peerType, badge = Nothing} +redactedMemberProfile allowSimplexLinks Profile {displayName, fullName, shortDescr, image, peerType, badge} = + Profile {displayName, fullName, shortDescr = removeSimplexLink =<< shortDescr, image, contactLink = Nothing, preferences = Nothing, peerType, badge} where removeSimplexLink s | allowSimplexLinks = Just s diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 0e4ad40a83..13b8bd2dec 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -29,7 +29,8 @@ import Control.Monad.IO.Class import Data.Bitraversable (bitraverse) import Data.Int (Int64) import Data.Maybe (fromMaybe) -import Data.Time.Clock (getCurrentTime) +import Data.Time.Clock (UTCTime, getCurrentTime) +import Simplex.Chat.Badges (rowToBadge) import Simplex.Chat.Protocol import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Groups @@ -104,8 +105,9 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do (userId, agentConnId, ConnDeleted) getContactRec_ :: Int64 -> Connection -> ExceptT StoreError IO Contact getContactRec_ contactId c = ExceptT $ do + currentTs <- getCurrentTime chatTags <- getDirectChatTags db contactId - firstRow (toContact' contactId c chatTags) (SEInternalError "referenced contact not found") $ + firstRow (toContact' currentTs contactId c chatTags) (SEInternalError "referenced contact not found") $ DB.query db [sql| @@ -113,15 +115,16 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, 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.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, c.contact_group_member_id, c.contact_grp_inv_sent, c.grp_direct_inv_link, c.grp_direct_inv_from_group_id, c.grp_direct_inv_from_group_member_id, c.grp_direct_inv_from_member_conn_id, c.grp_direct_inv_started_connection, - c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl + c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl, + p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified 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.contact_status = ? AND c.deleted = 0 |] (userId, contactId, CSActive) - toContact' :: Int64 -> Connection -> [ChatTagId] -> ContactRow' -> Contact - toContact' contactId conn chatTags ((profileId, localDisplayName, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL)) = - let profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, peerType, badge = Nothing, preferences, localAlias} + toContact' :: UTCTime -> Int64 -> Connection -> [ChatTagId] -> ContactRow' -> Contact + toContact' currentTs contactId conn chatTags ((profileId, localDisplayName, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL) :. badgeRow) = + let profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, peerType, badge = rowToBadge currentTs badgeRow, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn activeConn = Just conn @@ -130,9 +133,10 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do in Contact {contactId, localDisplayName, profile, activeConn, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, preparedContact, contactRequestId, contactGroupMemberId, contactGrpInvSent, groupDirectInv, chatTags, chatItemTTL, uiThemes, chatDeleted, customData} getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember) getGroupAndMember_ groupMemberId c = do + currentTs <- liftIO getCurrentTime gm <- ExceptT $ - firstRow (toGroupAndMember c) (SEInternalError "referenced group member not found") $ + firstRow (toGroupAndMember currentTs c) (SEInternalError "referenced group member not found") $ DB.query db [sql| @@ -152,11 +156,13 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do 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, -- GroupInfo {membership = GroupMember {memberProfile}} pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, + pu.badge_proof, pu.badge_pres_header, pu.badge_expiry, pu.badge_type, pu.badge_verified, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts, mu.member_pub_key, mu.relay_link, -- from GroupMember m.group_member_id, m.group_id, m.index_in_group, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, m.member_pub_key, m.relay_link FROM group_members m @@ -170,10 +176,10 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do |] (groupMemberId, userId, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted) liftIO $ bitraverse (addGroupChatTags db) pure gm - toGroupAndMember :: Connection -> GroupInfoRow :. GroupMemberRow -> (GroupInfo, GroupMember) - toGroupAndMember c (groupInfoRow :. memberRow) = - let groupInfo = toGroupInfo vr userContactId [] groupInfoRow - member = toGroupMember userContactId memberRow + toGroupAndMember :: UTCTime -> Connection -> GroupInfoRow :. GroupMemberRow -> (GroupInfo, GroupMember) + toGroupAndMember currentTs c (groupInfoRow :. memberRow) = + let groupInfo = toGroupInfo currentTs vr userContactId [] groupInfoRow + member = toGroupMember currentTs userContactId memberRow in (groupInfo, (member :: GroupMember) {activeConn = Just c}) getUserContact_ :: Int64 -> ExceptT StoreError IO UserContact getUserContact_ userContactLinkId = ExceptT $ do diff --git a/src/Simplex/Chat/Store/ContactRequest.hs b/src/Simplex/Chat/Store/ContactRequest.hs index 1e0ca8bdc5..b4f522fa19 100644 --- a/src/Simplex/Chat/Store/ContactRequest.hs +++ b/src/Simplex/Chat/Store/ContactRequest.hs @@ -24,6 +24,7 @@ import Control.Monad.IO.Class import Crypto.Random (ChaChaDRG) import Data.Int (Int64) import Data.Time.Clock (getCurrentTime) +import Simplex.Chat.Badges (badgeToRow, srvBadgePublicKey, verifyBadge) import Simplex.Chat.Protocol (MsgContent, businessChatsVersion) import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Groups @@ -72,7 +73,7 @@ createOrUpdateContactRequest isSimplexTeam invId cReqChatVRange@(VersionRange minV maxV) - profile@Profile {displayName, fullName, shortDescr, image, contactLink, preferences} + profile@Profile {displayName, fullName, shortDescr, image, contactLink, badge, preferences} xContactId_ welcomeMsgId_ requestMsg_ @@ -103,8 +104,9 @@ createOrUpdateContactRequest where getAcceptedContact :: XContactId -> IO (Maybe Contact) getAcceptedContact xContactId = do + currentTs <- getCurrentTime ct_ <- - maybeFirstRow (toContact vr user []) $ + maybeFirstRow (toContact currentTs vr user []) $ DB.query db [sql| @@ -114,6 +116,7 @@ createOrUpdateContactRequest cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, + cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, -- 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.xcontact_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.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, @@ -127,8 +130,9 @@ createOrUpdateContactRequest mapM (addDirectChatTags db) ct_ getAcceptedBusinessChat :: XContactId -> IO (Maybe GroupInfo) getAcceptedBusinessChat xContactId = do + currentTs <- getCurrentTime g_ <- - maybeFirstRow (toGroupInfo vr userContactId []) $ + maybeFirstRow (toGroupInfo currentTs vr userContactId []) $ DB.query db (groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?") @@ -157,12 +161,14 @@ createOrUpdateContactRequest createContactRequest :: ExceptT StoreError IO RequestStage createContactRequest = do currentTs <- liftIO $ getCurrentTime + badgeVerified <- liftIO $ forM badge $ verifyBadge srvBadgePublicKey + let (bProof, bPresHeader, bExpiry, bType, bVerified) = badgeToRow badge badgeVerified ExceptT $ withLocalDisplayName db userId displayName $ \ldn -> runExceptT $ do liftIO $ DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" - (displayName, fullName, shortDescr, image, contactLink, userId, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, image, contactLink, userId, preferences, currentTs, currentTs) :. (bProof, bPresHeader, bExpiry, bType, bVerified)) profileId <- liftIO $ insertedRowId db liftIO $ DB.execute @@ -216,13 +222,15 @@ createOrUpdateContactRequest updateContactRequest :: UserContactRequest -> ExceptT StoreError IO RequestStage updateContactRequest ucr@UserContactRequest {contactRequestId, contactId_, localDisplayName = oldLdn, profile = Profile {displayName = oldDisplayName}} = do currentTs <- liftIO getCurrentTime - liftIO $ updateProfile currentTs + badgeVerified <- liftIO $ forM badge $ verifyBadge srvBadgePublicKey + let badgeRow@(bProof, bPresHeader, bExpiry, bType, bVerified) = badgeToRow badge badgeVerified + liftIO $ updateProfile currentTs badgeRow updateRequest currentTs ucr' <- getContactRequest db user contactRequestId re_ <- getRequestEntity ucr' pure $ RSCurrentRequest (Just ucr) ucr' re_ where - updateProfile currentTs = + updateProfile currentTs (bProof, bPresHeader, bExpiry, bType, bVerified) = DB.execute db [sql| @@ -232,7 +240,12 @@ createOrUpdateContactRequest short_descr = ?, image = ?, contact_link = ?, - updated_at = ? + updated_at = ?, + badge_proof = ?, + badge_pres_header = ?, + badge_expiry = ?, + badge_type = ?, + badge_verified = ? WHERE contact_profile_id IN ( SELECT contact_profile_id FROM contact_requests @@ -240,7 +253,7 @@ createOrUpdateContactRequest AND contact_request_id = ? ) |] - (displayName, fullName, shortDescr, image, contactLink, currentTs, userId, contactRequestId) + ((displayName, fullName, shortDescr, image, contactLink, currentTs) :. (bProof, bPresHeader, bExpiry, bType, bVerified) :. (userId, contactRequestId)) updateRequest currentTs = if displayName == oldDisplayName then diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index c19e68a79f..00734548d9 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -308,8 +308,9 @@ getConnReqContactXContactId db vr user@User {userId} cReqHash1 cReqHash2 = getContactByConnReqHash :: DB.Connection -> VersionRangeChat -> User -> ConnReqUriHash -> ConnReqUriHash -> IO (Maybe Contact) getContactByConnReqHash db vr user@User {userId} cReqHash1 cReqHash2 = do + currentTs <- getCurrentTime ct <- - maybeFirstRow (toContact vr user []) $ + maybeFirstRow (toContact currentTs vr user []) $ DB.query db [sql| @@ -319,6 +320,7 @@ getContactByConnReqHash db vr user@User {userId} cReqHash1 cReqHash2 = do cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, + cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, -- 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.xcontact_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.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, @@ -554,23 +556,24 @@ deleteUnusedProfile_ db userId profileId = ) updateContactProfile :: DB.Connection -> User -> Contact -> Profile -> ExceptT StoreError IO Contact -updateContactProfile db user@User {userId} c p' - | displayName == newName = do - currentTs <- liftIO getCurrentTime - badgeVerified <- liftIO $ profileBadgeVerified lp p' - liftIO $ updateContactProfile_' db userId profileId p' badgeVerified currentTs - pure c {profile = toLocalProfile profileId p' localAlias currentTs badgeVerified, mergedPreferences} - | otherwise = - ExceptT . withLocalDisplayName db userId newName $ \ldn -> do - currentTs <- getCurrentTime - badgeVerified <- profileBadgeVerified lp p' - updateContactProfile_' db userId profileId p' badgeVerified currentTs - updateContactLDN_ db user contactId localDisplayName ldn currentTs - pure $ Right c {localDisplayName = ldn, profile = toLocalProfile profileId p' localAlias currentTs badgeVerified, mergedPreferences} +updateContactProfile db user@User {userId} c p' = do + currentTs <- liftIO getCurrentTime + badgeVerified <- liftIO $ profileBadgeVerified lp p' + let profile = toLocalProfile profileId p' localAlias currentTs badgeVerified + updateContactProfile' currentTs badgeVerified profile where Contact {contactId, localDisplayName, profile = lp@LocalProfile {profileId, displayName, localAlias}, userPreferences} = c Profile {displayName = newName, preferences} = p' mergedPreferences = contactUserPreferences user userPreferences preferences $ contactConnIncognito c + updateContactProfile' currentTs badgeVerified profile + | displayName == newName = do + liftIO $ updateContactProfile_' db userId profileId p' badgeVerified currentTs + pure c {profile, mergedPreferences} + | otherwise = + ExceptT . withLocalDisplayName db userId newName $ \ldn -> do + updateContactProfile_' db userId profileId p' badgeVerified currentTs + updateContactLDN_ db user contactId localDisplayName ldn currentTs + pure $ Right c {localDisplayName = ldn, profile, mergedPreferences} updateContactUserPreferences :: DB.Connection -> User -> Contact -> Preferences -> IO Contact updateContactUserPreferences db user@User {userId} c@Contact {contactId} userPreferences = do @@ -709,12 +712,11 @@ updateContactProfile_' db userId profileId Profile {displayName, fullName, short db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = ?, preferences = ?, chat_peer_type = ?, - badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ?, - updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = ?, preferences = ?, chat_peer_type = ?, updated_at = ?, + badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ? WHERE user_id = ? AND contact_profile_id = ? |] - ((displayName, fullName, shortDescr, image, contactLink, preferences, peerType) :. (bProof, bPresHeader, bExpiry, bType, bVerified, updatedAt, userId, profileId)) + ((displayName, fullName, shortDescr, image, contactLink, preferences, peerType, updatedAt) :. (bProof, bPresHeader, bExpiry, bType, bVerified) :. (userId, profileId)) -- update only member profile fields (when member doesn't have associated contact - we can reset contactLink and prefs) updateMemberContactProfileReset_ :: DB.Connection -> UserId -> ProfileId -> Profile -> Maybe Bool -> IO () @@ -729,12 +731,11 @@ updateMemberContactProfileReset_' db userId profileId Profile {displayName, full db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = NULL, preferences = NULL, - badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ?, - updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = NULL, preferences = NULL, updated_at = ?, + badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ? WHERE user_id = ? AND contact_profile_id = ? |] - ((displayName, fullName, shortDescr, image) :. (bProof, bPresHeader, bExpiry, bType, bVerified, updatedAt, userId, profileId)) + ((displayName, fullName, shortDescr, image, updatedAt) :. (bProof, bPresHeader, bExpiry, bType, bVerified) :. (userId, profileId)) -- update only member profile fields (when member has associated contact - we keep contactLink and prefs) updateMemberContactProfile_ :: DB.Connection -> UserId -> ProfileId -> Profile -> Maybe Bool -> IO () @@ -749,12 +750,11 @@ updateMemberContactProfile_' db userId profileId Profile {displayName, fullName, db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, image = ?, - badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ?, - updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, updated_at = ?, + badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ? WHERE user_id = ? AND contact_profile_id = ? |] - ((displayName, fullName, shortDescr, image) :. (bProof, bPresHeader, bExpiry, bType, bVerified, updatedAt, userId, profileId)) + ((displayName, fullName, shortDescr, image, updatedAt) :. (bProof, bPresHeader, bExpiry, bType, bVerified) :. (userId, profileId)) updateContactLDN_ :: DB.Connection -> User -> Int64 -> ContactName -> ContactName -> UTCTime -> IO () updateContactLDN_ db user@User {userId} contactId displayName newName updatedAt = do @@ -916,8 +916,9 @@ getContact db vr user contactId = getContact_ db vr user contactId False getContact_ :: DB.Connection -> VersionRangeChat -> User -> Int64 -> Bool -> ExceptT StoreError IO Contact getContact_ db vr user@User {userId} contactId deleted = do + currentTs <- liftIO getCurrentTime chatTags <- liftIO $ getDirectChatTags db contactId - ExceptT . firstRow (toContact vr user chatTags) (SEContactNotFound contactId) $ + ExceptT . firstRow (toContact currentTs vr user chatTags) (SEContactNotFound contactId) $ DB.query db [sql| @@ -927,6 +928,7 @@ getContact_ db vr user@User {userId} contactId deleted = do cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, + cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, -- 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.xcontact_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.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, diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 824a111193..5037eedc42 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -196,6 +196,7 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Time.Clock (NominalDiffTime, UTCTime (..), addUTCTime, getCurrentTime) import Data.Text.Encoding (encodeUtf8) +import Simplex.Chat.Badges (BadgeRow, badgeToRow, srvBadgePublicKey, verifyBadge) import Simplex.Chat.Messages import Simplex.Chat.Operators import Simplex.Chat.Protocol hiding (Binary) @@ -225,12 +226,12 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..)) import Database.SQLite.Simple.QQ (sql) #endif -type MaybeGroupMemberRow = (Maybe GroupMemberId, Maybe GroupId, 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 Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact) +type MaybeGroupMemberRow = (Maybe GroupMemberId, Maybe GroupId, 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 Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. BadgeRow) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact) -toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember -toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just indexInGroup, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId) :. (Just profileId, Just displayName, Just fullName, shortDescr, image, contactLink, peerType, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink)) = - Just $ toGroupMember userContactId ((groupMemberId, groupId, indexInGroup, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. (profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink)) -toMaybeGroupMember _ _ = Nothing +toMaybeGroupMember :: UTCTime -> Int64 -> MaybeGroupMemberRow -> Maybe GroupMember +toMaybeGroupMember now userContactId ((Just groupMemberId, Just groupId, Just indexInGroup, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId) :. ((Just profileId, Just displayName, Just fullName, shortDescr, image, contactLink, peerType, Just localAlias, contactPreferences) :. badgeRow) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink)) = + Just $ toGroupMember now userContactId ((groupMemberId, groupId, indexInGroup, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. ((profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, contactPreferences) :. badgeRow) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink)) +toMaybeGroupMember _ _ _ = Nothing createGroupLink :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO GroupLink createGroupLink db gVar user@User {userId} groupInfo@GroupInfo {groupId, localDisplayName} agentConnId (CCLink cReq shortLink) groupLinkId memberRole subMode = do @@ -1005,7 +1006,8 @@ getInProgressGroups db vr user@User {userId} createdAtCutoff = do getBaseGroupDetails :: DB.Connection -> VersionRangeChat -> User -> Maybe ContactId -> Maybe Text -> IO [GroupInfo] getBaseGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do - map (toGroupInfo vr userContactId []) + currentTs <- getCurrentTime + map (toGroupInfo currentTs vr userContactId []) <$> DB.query db (groupInfoQuery <> " " <> condition) (userId, userContactId, search, search, search, search) where condition = @@ -1039,16 +1041,18 @@ getGroupInfoByName db vr user gName = do getGroupInfo db vr user gId getGroupMember :: DB.Connection -> VersionRangeChat -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO GroupMember -getGroupMember db vr user@User {userId} groupId groupMemberId = - ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFound groupMemberId) $ +getGroupMember db vr user@User {userId} groupId groupMemberId = do + currentTs <- liftIO getCurrentTime + ExceptT . firstRow (toContactMember currentTs vr user) (SEGroupMemberNotFound groupMemberId) $ DB.query db (groupMemberQuery <> " WHERE m.group_id = ? AND m.group_member_id = ? AND m.user_id = ?") (groupId, groupMemberId, userId) getHostMember :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO GroupMember -getHostMember db vr user groupId = - ExceptT . firstRow (toContactMember vr user) (SEGroupHostMemberNotFound groupId) $ +getHostMember db vr user groupId = do + currentTs <- liftIO getCurrentTime + ExceptT . firstRow (toContactMember currentTs vr user) (SEGroupHostMemberNotFound groupId) $ DB.query db (groupMemberQuery <> " WHERE m.group_id = ? AND m.member_category = ?") @@ -1088,32 +1092,36 @@ toMentionedMember (groupMemberId, memberId, memberRole, displayName, localAlias) in CIMention {memberId, memberRef} getGroupMemberById :: DB.Connection -> VersionRangeChat -> User -> GroupMemberId -> ExceptT StoreError IO GroupMember -getGroupMemberById db vr user@User {userId} groupMemberId = - ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFound groupMemberId) $ +getGroupMemberById db vr user@User {userId} groupMemberId = do + currentTs <- liftIO getCurrentTime + ExceptT . firstRow (toContactMember currentTs vr user) (SEGroupMemberNotFound groupMemberId) $ DB.query db (groupMemberQuery <> " WHERE m.group_member_id = ? AND m.user_id = ?") (groupMemberId, userId) getGroupMemberByIndex :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Int64 -> ExceptT StoreError IO GroupMember -getGroupMemberByIndex db vr user GroupInfo {groupId} indexInGroup = - ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFoundByIndex indexInGroup) $ +getGroupMemberByIndex db vr user GroupInfo {groupId} indexInGroup = do + currentTs <- liftIO getCurrentTime + ExceptT . firstRow (toContactMember currentTs vr user) (SEGroupMemberNotFoundByIndex indexInGroup) $ DB.query db (groupMemberQuery <> " WHERE m.group_id = ? AND m.index_in_group = ?") (groupId, indexInGroup) getSupportScopeMemberByIndex :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMemberId -> Int64 -> ExceptT StoreError IO GroupMember -getSupportScopeMemberByIndex db vr user GroupInfo {groupId} scopeGMId indexInGroup = - ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFoundByIndex indexInGroup) $ +getSupportScopeMemberByIndex db vr user GroupInfo {groupId} scopeGMId indexInGroup = do + currentTs <- liftIO getCurrentTime + ExceptT . firstRow (toContactMember currentTs vr user) (SEGroupMemberNotFoundByIndex indexInGroup) $ DB.query db (groupMemberQuery <> " WHERE m.group_id = ? AND m.index_in_group = ? AND (m.member_role IN (?,?,?) OR m.group_member_id = ?)") (groupId, indexInGroup, GRModerator, GRAdmin, GROwner, scopeGMId) getGroupMemberByMemberId :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> ExceptT StoreError IO GroupMember -getGroupMemberByMemberId db vr user GroupInfo {groupId} memberId = - ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFoundByMemberId memberId) $ +getGroupMemberByMemberId db vr user GroupInfo {groupId} memberId = do + currentTs <- liftIO getCurrentTime + ExceptT . firstRow (toContactMember currentTs vr user) (SEGroupMemberNotFoundByMemberId memberId) $ DB.query db (groupMemberQuery <> " WHERE m.group_id = ? AND m.member_id = ?") @@ -1146,8 +1154,9 @@ getGroupMemberIdViaMemberId db User {userId} GroupInfo {groupId} memberId = (userId, groupId, memberId) getGroupMembers :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember] -getGroupMembers db vr user@User {userId, userContactId} GroupInfo {groupId} = - map (toContactMember vr user) +getGroupMembers db vr user@User {userId, userContactId} GroupInfo {groupId} = do + currentTs <- getCurrentTime + map (toContactMember currentTs vr user) <$> DB.query db (groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?)") @@ -1181,7 +1190,8 @@ getSupportScopeMembersByIndexes db vr user gInfo scopeGMId indexesInGroup = do getGroupModerators :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember] getGroupModerators db vr user@User {userId, userContactId} GroupInfo {groupId} = do - map (toContactMember vr user) + currentTs <- getCurrentTime + map (toContactMember currentTs vr user) <$> DB.query db (groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND m.member_role IN (?,?,?)") @@ -1189,7 +1199,8 @@ getGroupModerators db vr user@User {userId, userContactId} GroupInfo {groupId} = getGroupRelayMembers :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember] getGroupRelayMembers db vr user@User {userId, userContactId} GroupInfo {groupId} = do - map (toContactMember vr user) + currentTs <- getCurrentTime + map (toContactMember currentTs vr user) <$> DB.query db (groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND m.contact_id IS DISTINCT FROM ? AND m.member_role = ?") @@ -1197,7 +1208,8 @@ getGroupRelayMembers db vr user@User {userId, userContactId} GroupInfo {groupId} getGroupMembersForExpiration :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember] getGroupMembersForExpiration db vr user@User {userId, userContactId} GroupInfo {groupId} = do - map (toContactMember vr user) + currentTs <- getCurrentTime + map (toContactMember currentTs vr user) <$> DB.query db ( groupMemberQuery @@ -1380,11 +1392,12 @@ createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {grou getGroupMemberById db vr user groupMemberId getCreateRelayForMember :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> ShortLinkContact -> ExceptT StoreError IO GroupMember -getCreateRelayForMember db vr gVar user@User {userId, userContactId} GroupInfo {groupId, localDisplayName = groupLDN} relayLink = - liftIO getGroupMemberByRelayLink >>= maybe createRelayMember pure +getCreateRelayForMember db vr gVar user@User {userId, userContactId} GroupInfo {groupId, localDisplayName = groupLDN} relayLink = do + currentTs <- liftIO getCurrentTime + liftIO (getGroupMemberByRelayLink currentTs) >>= maybe createRelayMember pure where - getGroupMemberByRelayLink = - maybeFirstRow (toContactMember vr user) $ + getGroupMemberByRelayLink currentTs = + maybeFirstRow (toContactMember currentTs vr user) $ DB.query db #if defined(dbPostgres) @@ -1651,7 +1664,8 @@ isRelayGroupRejected db User {userId} groupLink = getRelayServedGroups :: DB.Connection -> VersionRangeChat -> User -> IO [GroupInfo] getRelayServedGroups db vr User {userId, userContactId} = do - map (toGroupInfo vr userContactId []) + currentTs <- getCurrentTime + map (toGroupInfo currentTs vr userContactId []) <$> DB.query db ( groupInfoQuery @@ -1661,8 +1675,9 @@ getRelayServedGroups db vr User {userId, userContactId} = do getRelayInactiveGroups :: DB.Connection -> VersionRangeChat -> User -> NominalDiffTime -> IO [GroupInfo] getRelayInactiveGroups db vr User {userId, userContactId} ttl = do - cutoffTs <- addUTCTime (- ttl) <$> getCurrentTime - map (toGroupInfo vr userContactId []) + currentTs <- getCurrentTime + let cutoffTs = addUTCTime (- ttl) currentTs + map (toGroupInfo currentTs vr userContactId []) <$> DB.query db ( groupInfoQuery @@ -1704,7 +1719,7 @@ createJoiningMember User {userId, userContactId} GroupInfo {groupId, membership} cReqChatVRange - Profile {displayName, fullName, shortDescr, image, contactLink, preferences} + Profile {displayName, fullName, shortDescr, image, contactLink, badge, preferences} cReqXContactId_ cReqMemberId_ welcomeMsgId_ @@ -1712,12 +1727,14 @@ createJoiningMember memberStatus memberKey_ = do currentTs <- liftIO getCurrentTime + badgeVerified <- liftIO $ forM badge $ verifyBadge srvBadgePublicKey + let (bProof, bPresHeader, bExpiry, bType, bVerified) = badgeToRow badge badgeVerified ExceptT . withLocalDisplayName db userId displayName $ \ldn -> runExceptT $ do liftIO $ DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" - (displayName, fullName, shortDescr, image, contactLink, userId, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, image, contactLink, userId, preferences, currentTs, currentTs) :. (bProof, bPresHeader, bExpiry, bType, bVerified)) profileId <- liftIO $ insertedRowId db case cReqMemberId_ of Just memberId -> do @@ -2072,12 +2089,14 @@ createNewGroupMember db user gInfo invitingMember memInfo@MemberInfo {profile} m createNewMember_ db user gInfo newMember currentTs createNewMemberProfile_ :: DB.Connection -> User -> Profile -> UTCTime -> ExceptT StoreError IO (Text, ProfileId) -createNewMemberProfile_ db User {userId} Profile {displayName, fullName, shortDescr, image, contactLink, preferences} createdAt = +createNewMemberProfile_ db User {userId} Profile {displayName, fullName, shortDescr, image, contactLink, badge, preferences} createdAt = ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do + badgeVerified <- forM badge $ verifyBadge srvBadgePublicKey + let (bProof, bPresHeader, bExpiry, bType, bVerified) = badgeToRow badge badgeVerified DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" - (displayName, fullName, shortDescr, image, contactLink, userId, preferences, createdAt, createdAt) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, image, contactLink, userId, preferences, createdAt, createdAt) :. (bProof, bPresHeader, bExpiry, bType, bVerified)) profileId <- insertedRowId db pure $ Right (ldn, profileId) @@ -2984,46 +3003,46 @@ setMemberContactStartedConnection db Contact {contactId} = do (BI True, currentTs, contactId) updateMemberProfile :: DB.Connection -> User -> GroupMember -> Profile -> ExceptT StoreError IO GroupMember -updateMemberProfile db user@User {userId} m p' - | displayName == newName = do - currentTs <- liftIO getCurrentTime - badgeVerified <- liftIO $ profileBadgeVerified (memberProfile m) p' - liftIO $ updateMemberContactProfileReset_' db userId profileId p' badgeVerified currentTs - pure m {memberProfile = toLocalProfile profileId p' localAlias currentTs badgeVerified} - | otherwise = - ExceptT . withLocalDisplayName db userId newName $ \ldn -> do - currentTs <- getCurrentTime - badgeVerified <- profileBadgeVerified (memberProfile m) p' - updateMemberContactProfileReset_' db userId profileId p' badgeVerified currentTs - DB.execute - db - "UPDATE group_members SET local_display_name = ?, updated_at = ? WHERE user_id = ? AND group_member_id = ?" - (ldn, currentTs, userId, groupMemberId) - safeDeleteLDN db user localDisplayName - pure $ Right m {localDisplayName = ldn, memberProfile = toLocalProfile profileId p' localAlias currentTs badgeVerified} +updateMemberProfile db user@User {userId} m p' = do + currentTs <- liftIO getCurrentTime + badgeVerified <- liftIO $ profileBadgeVerified (memberProfile m) p' + let memberProfile = toLocalProfile profileId p' localAlias currentTs badgeVerified + updateMemberProfile' currentTs badgeVerified memberProfile where GroupMember {groupMemberId, localDisplayName, memberProfile = LocalProfile {profileId, displayName, localAlias}} = m Profile {displayName = newName} = p' + updateMemberProfile' currentTs badgeVerified memberProfile + | displayName == newName = do + liftIO $ updateMemberContactProfileReset_' db userId profileId p' badgeVerified currentTs + pure m {memberProfile} + | otherwise = + ExceptT . withLocalDisplayName db userId newName $ \ldn -> do + updateMemberContactProfileReset_' db userId profileId p' badgeVerified currentTs + DB.execute + db + "UPDATE group_members SET local_display_name = ?, updated_at = ? WHERE user_id = ? AND group_member_id = ?" + (ldn, currentTs, userId, groupMemberId) + safeDeleteLDN db user localDisplayName + pure $ Right m {localDisplayName = ldn, memberProfile} updateContactMemberProfile :: DB.Connection -> User -> GroupMember -> Contact -> Profile -> ExceptT StoreError IO (GroupMember, Contact) -updateContactMemberProfile db user@User {userId} m ct@Contact {contactId} p' - | displayName == newName = do - currentTs <- liftIO getCurrentTime - badgeVerified <- liftIO $ profileBadgeVerified (memberProfile m) p' - liftIO $ updateMemberContactProfile_' db userId profileId p' badgeVerified currentTs - let profile = toLocalProfile profileId p' localAlias currentTs badgeVerified - pure (m {memberProfile = profile}, ct {profile} :: Contact) - | otherwise = - ExceptT . withLocalDisplayName db userId newName $ \ldn -> do - currentTs <- getCurrentTime - badgeVerified <- profileBadgeVerified (memberProfile m) p' - updateMemberContactProfile_' db userId profileId p' badgeVerified currentTs - updateContactLDN_ db user contactId localDisplayName ldn currentTs - let profile = toLocalProfile profileId p' localAlias currentTs badgeVerified - pure $ Right (m {localDisplayName = ldn, memberProfile = profile}, ct {localDisplayName = ldn, profile} :: Contact) +updateContactMemberProfile db user@User {userId} m ct@Contact {contactId} p' = do + currentTs <- liftIO getCurrentTime + badgeVerified <- liftIO $ profileBadgeVerified (memberProfile m) p' + let profile = toLocalProfile profileId p' localAlias currentTs badgeVerified + updateContactMemberProfile' currentTs badgeVerified profile where GroupMember {localDisplayName, memberProfile = LocalProfile {profileId, displayName, localAlias}} = m Profile {displayName = newName} = p' + updateContactMemberProfile' currentTs badgeVerified profile + | displayName == newName = do + liftIO $ updateMemberContactProfile_' db userId profileId p' badgeVerified currentTs + pure (m {memberProfile = profile}, ct {profile} :: Contact) + | otherwise = + ExceptT . withLocalDisplayName db userId newName $ \ldn -> do + updateMemberContactProfile_' db userId profileId p' badgeVerified currentTs + updateContactLDN_ db user contactId localDisplayName ldn currentTs + pure $ Right (m {localDisplayName = ldn, memberProfile = profile}, ct {localDisplayName = ldn, profile} :: Contact) getXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> ExceptT StoreError IO Bool getXGrpLinkMemReceived db mId = diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 5d433088a4..add6ab0976 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -652,7 +652,8 @@ insertChatItemMessage_ :: DB.Connection -> ChatItemId -> MessageId -> UTCTime -> insertChatItemMessage_ db ciId msgId ts = DB.execute db "INSERT INTO chat_item_messages (chat_item_id, message_id, created_at, updated_at) VALUES (?,?,?,?)" (ciId, msgId, ts, ts) getChatItemQuote_ :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> QuotedMsg -> IO (CIQuote c) -getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRef = MsgRef {msgId, sentAt, sent, memberId}, content} = +getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRef = MsgRef {msgId, sentAt, sent, memberId}, content} = do + currentTs <- getCurrentTime case chatDirection of CDDirectRcv Contact {contactId} -> getDirectChatItemQuote_ contactId (not sent) CDGroupRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} _s sender@GroupMember {groupMemberId = senderGMId, memberId = senderMemberId} -> @@ -660,13 +661,13 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe Just mId | mId == userMemberId -> (`ciQuote` CIQGroupSnd) <$> getUserGroupChatItemId_ groupId | mId == senderMemberId -> (`ciQuote` CIQGroupRcv (Just sender)) <$> getGroupChatItemId_ groupId senderGMId - | otherwise -> getGroupChatItemQuote_ groupId mId + | otherwise -> getGroupChatItemQuote_ currentTs groupId mId _ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing CDChannelRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} _s -> case memberId of Just mId | mId == userMemberId -> (`ciQuote` CIQGroupSnd) <$> getUserGroupChatItemId_ groupId - | otherwise -> getGroupChatItemQuote_ groupId mId + | otherwise -> getGroupChatItemQuote_ currentTs groupId mId _ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing where ciQuote :: Maybe ChatItemId -> CIQDirection c -> CIQuote c @@ -695,8 +696,8 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe db "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id = ?" (userId, groupId, msgId, MDRcv, groupMemberId) - getGroupChatItemQuote_ :: Int64 -> MemberId -> IO (CIQuote 'CTGroup) - getGroupChatItemQuote_ groupId mId = do + getGroupChatItemQuote_ :: UTCTime -> Int64 -> MemberId -> IO (CIQuote 'CTGroup) + getGroupChatItemQuote_ currentTs groupId mId = do ciQuoteGroup <$> DB.query db @@ -706,6 +707,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe m.group_member_id, m.group_id, m.index_in_group, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, m.member_pub_key, m.relay_link FROM group_members m @@ -721,7 +723,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe where ciQuoteGroup :: [Only (Maybe ChatItemId) :. GroupMemberRow] -> CIQuote 'CTGroup ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing - ciQuoteGroup ((Only itemId :. memberRow) : _) = ciQuote itemId . CIQGroupRcv . Just $ toGroupMember userContactId memberRow + ciQuoteGroup ((Only itemId :. memberRow) : _) = ciQuote itemId . CIQGroupRcv . Just $ toGroupMember currentTs userContactId memberRow getChatPreviews :: DB.Connection -> VersionRangeChat -> User -> Bool -> PaginationByTime -> ChatListQuery -> IO [Either StoreError AChat] getChatPreviews db vr user withPCC pagination query = do @@ -2358,9 +2360,9 @@ toGroupChatItem ) = do chatItem $ fromRight invalid $ dbParseACIContent itemContentText where - member_ = toMaybeGroupMember userContactId memberRow_ - quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_ - deletedByGroupMember_ = toMaybeGroupMember userContactId deletedByGroupMemberRow_ + member_ = toMaybeGroupMember currentTs userContactId memberRow_ + quotedMember_ = toMaybeGroupMember currentTs userContactId quotedMemberRow_ + deletedByGroupMember_ = toMaybeGroupMember currentTs userContactId deletedByGroupMemberRow_ invalid = ACIContent msgDir $ CIInvalidJSON itemContentText chatItem itemContent = case (itemContent, itemStatus, member_, fileStatus_) of (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Just (AFS SMDSnd fileStatus)) -> @@ -3036,6 +3038,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do m.group_member_id, m.group_id, m.index_in_group, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, m.member_pub_key, m.relay_link, -- quoted ChatItem @@ -3044,12 +3047,14 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do rm.group_member_id, rm.group_id, rm.index_in_group, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category, rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, rp.display_name, rp.full_name, rp.short_descr, rp.image, rp.contact_link, rp.chat_peer_type, rp.local_alias, rp.preferences, + rp.badge_proof, rp.badge_pres_header, rp.badge_expiry, rp.badge_type, rp.badge_verified, rm.created_at, rm.updated_at, rm.support_chat_ts, rm.support_chat_items_unread, rm.support_chat_items_member_attention, rm.support_chat_items_mentions, rm.support_chat_last_msg_from_member_ts, rm.member_pub_key, rm.relay_link, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.index_in_group, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category, dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, dbp.display_name, dbp.full_name, dbp.short_descr, dbp.image, dbp.contact_link, dbp.chat_peer_type, dbp.local_alias, dbp.preferences, + dbp.badge_proof, dbp.badge_pres_header, dbp.badge_expiry, dbp.badge_type, dbp.badge_verified, dbm.created_at, dbm.updated_at, dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts, dbm.member_pub_key, dbm.relay_link FROM chat_items i diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index a8bb0da945..4c9a1b1c91 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -32,6 +32,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260429_relay_request_retries import Simplex.Chat.Store.Postgres.Migrations.M20260507_relay_inactive_at import Simplex.Chat.Store.Postgres.Migrations.M20260514_relay_request_group_link_index import Simplex.Chat.Store.Postgres.Migrations.M20260515_public_group_access +import Simplex.Chat.Store.Postgres.Migrations.M20260516_supporter_badges import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -63,7 +64,8 @@ schemaMigrations = ("20260429_relay_request_retries", m20260429_relay_request_retries, Just down_m20260429_relay_request_retries), ("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at), ("20260514_relay_request_group_link_index", m20260514_relay_request_group_link_index, Just down_m20260514_relay_request_group_link_index), - ("20260515_public_group_access", m20260515_public_group_access, Just down_m20260515_public_group_access) + ("20260515_public_group_access", m20260515_public_group_access, Just down_m20260515_public_group_access), + ("20260516_supporter_badges", m20260516_supporter_badges, Just down_m20260516_supporter_badges) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260516_supporter_badges.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260516_supporter_badges.hs new file mode 100644 index 0000000000..e196e49062 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260516_supporter_badges.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20260516_supporter_badges where + +import Database.PostgreSQL.Simple (Query) +import Database.PostgreSQL.Simple.SqlQQ (sql) + +m20260516_supporter_badges :: Query +m20260516_supporter_badges = + [sql| +ALTER TABLE contact_profiles ADD COLUMN badge_proof BYTEA; +ALTER TABLE contact_profiles ADD COLUMN badge_pres_header BYTEA; +ALTER TABLE contact_profiles ADD COLUMN badge_expiry TEXT; +ALTER TABLE contact_profiles ADD COLUMN badge_type TEXT; +ALTER TABLE contact_profiles ADD COLUMN badge_verified BOOLEAN; +|] + +down_m20260516_supporter_badges :: Query +down_m20260516_supporter_badges = + [sql| +ALTER TABLE contact_profiles DROP COLUMN badge_verified; +ALTER TABLE contact_profiles DROP COLUMN badge_type; +ALTER TABLE contact_profiles DROP COLUMN badge_proof; +ALTER TABLE contact_profiles DROP COLUMN badge_pres_header; +ALTER TABLE contact_profiles DROP COLUMN badge_expiry; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index 89ef373af8..5bf628b062 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -155,6 +155,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260429_relay_request_retries import Simplex.Chat.Store.SQLite.Migrations.M20260507_relay_inactive_at import Simplex.Chat.Store.SQLite.Migrations.M20260514_relay_request_group_link_index import Simplex.Chat.Store.SQLite.Migrations.M20260515_public_group_access +import Simplex.Chat.Store.SQLite.Migrations.M20260516_supporter_badges import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -309,7 +310,8 @@ schemaMigrations = ("20260429_relay_request_retries", m20260429_relay_request_retries, Just down_m20260429_relay_request_retries), ("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at), ("20260514_relay_request_group_link_index", m20260514_relay_request_group_link_index, Just down_m20260514_relay_request_group_link_index), - ("20260515_public_group_access", m20260515_public_group_access, Just down_m20260515_public_group_access) + ("20260515_public_group_access", m20260515_public_group_access, Just down_m20260515_public_group_access), + ("20260516_supporter_badges", m20260516_supporter_badges, Just down_m20260516_supporter_badges) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260516_supporter_badges.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260516_supporter_badges.hs new file mode 100644 index 0000000000..907dad968e --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260516_supporter_badges.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20260516_supporter_badges where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20260516_supporter_badges :: Query +m20260516_supporter_badges = + [sql| +ALTER TABLE contact_profiles ADD COLUMN badge_proof BLOB; +ALTER TABLE contact_profiles ADD COLUMN badge_pres_header BLOB; +ALTER TABLE contact_profiles ADD COLUMN badge_expiry TEXT; +ALTER TABLE contact_profiles ADD COLUMN badge_type TEXT; +ALTER TABLE contact_profiles ADD COLUMN badge_verified INTEGER; +|] + +down_m20260516_supporter_badges :: Query +down_m20260516_supporter_badges = + [sql| +ALTER TABLE contact_profiles DROP COLUMN badge_verified; +ALTER TABLE contact_profiles DROP COLUMN badge_type; +ALTER TABLE contact_profiles DROP COLUMN badge_expiry; +ALTER TABLE contact_profiles DROP COLUMN badge_proof; +ALTER TABLE contact_profiles DROP COLUMN badge_pres_header; +|] diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 680c823230..5a4f6d8818 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -32,6 +32,7 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Time.Clock (UTCTime (..), getCurrentTime) import Data.Type.Equality +import Simplex.Chat.Badges (BadgeRow, badgeToRow, rowToBadge, srvBadgePublicKey, verifyBadge) import Simplex.Chat.Messages import Simplex.Chat.Remote.Types import Simplex.Chat.Types @@ -412,12 +413,14 @@ createContact db user profile = do void $ createContact_ db user profile emptyChatPrefs Nothing "" currentTs createContact_ :: DB.Connection -> User -> Profile -> Preferences -> Maybe (ACreatedConnLink, Maybe SharedMsgId) -> LocalAlias -> UTCTime -> ExceptT StoreError IO ContactId -createContact_ db User {userId} Profile {displayName, fullName, shortDescr, image, contactLink, peerType, preferences} ctUserPreferences prepared localAlias currentTs = +createContact_ db User {userId} p@Profile {displayName, fullName, shortDescr, image, contactLink, peerType, badge, preferences} ctUserPreferences prepared localAlias currentTs = ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do + badgeVerified <- forM badge $ verifyBadge srvBadgePublicKey + let (bProof, bPresHeader, bExpiry, bType, bVerified) = badgeToRow badge badgeVerified DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, chat_peer_type, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)" - ((displayName, fullName, shortDescr, image, contactLink, peerType) :. (userId, localAlias, preferences, currentTs, currentTs)) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, chat_peer_type, user_id, local_alias, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, image, contactLink, peerType) :. (userId, localAlias, preferences, currentTs, currentTs) :. (bProof, bPresHeader, bExpiry, bType, bVerified)) profileId <- insertedRowId db DB.execute db @@ -484,13 +487,13 @@ type PreparedContactRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink, Ma type GroupDirectInvitationRow = (Maybe ConnReqInvitation, Maybe GroupId, Maybe GroupMemberId, Maybe Int64, BoolInt) -type ContactRow' = (ProfileId, ContactName, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe GroupMemberId, BoolInt) :. GroupDirectInvitationRow :. (Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) +type ContactRow' = (ProfileId, ContactName, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe GroupMemberId, BoolInt) :. GroupDirectInvitationRow :. (Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) :. BadgeRow type ContactRow = Only ContactId :. ContactRow' -toContact :: VersionRangeChat -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact -toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL)) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, peerType, badge = Nothing, preferences, localAlias} +toContact :: UTCTime -> VersionRangeChat -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact +toContact now vr user chatTags ((Only contactId :. (profileId, localDisplayName, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL) :. badgeRow) :. connRow) = + let profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, peerType, badge = rowToBadge now badgeRow, preferences, localAlias} activeConn = toMaybeConnection vr connRow chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} incognito = maybe False connIncognito activeConn @@ -516,12 +519,14 @@ toGroupDirectInvitation (Just groupDirectInvLink, fromGroupId_, fromGroupMemberI Just $ GroupDirectInvitation {groupDirectInvLink, fromGroupId_, fromGroupMemberId_, fromGroupMemberConnId_, groupDirectInvStartedConnection} getProfileById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO LocalProfile -getProfileById db userId profileId = - ExceptT . firstRow rowToLocalProfile (SEProfileNotFound profileId) $ +getProfileById db userId profileId = do + currentTs <- liftIO getCurrentTime + ExceptT . firstRow (rowToLocalProfile currentTs) (SEProfileNotFound profileId) $ DB.query db [sql| - SELECT cp.contact_profile_id, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, cp.preferences -- , ct.user_preferences + SELECT cp.contact_profile_id, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, cp.preferences, + cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified FROM contact_profiles cp WHERE cp.user_id = ? AND cp.contact_profile_id = ? |] @@ -671,11 +676,11 @@ type PublicGroupAccessRow = (Maybe Text, Maybe Text, Maybe BoolInt, Maybe BoolIn type GroupMemberRow = (GroupMemberId, GroupId, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId) :. ProfileRow :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact) -type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences) +type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences) :. BadgeRow -toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. groupKeysRow :. userMemberRow) = - let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} +toGroupInfo :: UTCTime -> VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo +toGroupInfo now vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. groupKeysRow :. userMemberRow) = + let membership = (toGroupMember now userContactId userMemberRow) {memberChatVRange = vr} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_ (toPublicGroupAccess accessRow) @@ -718,9 +723,9 @@ toGroupKeys (Just publicGroupId) (rootPrivKey_, rootPubKey_, Just memberPrivKey) <$> (GRKPrivate <$> rootPrivKey_ <|> GRKPublic <$> rootPubKey_) toGroupKeys _ _ = Nothing -toGroupMember :: Int64 -> GroupMemberRow -> GroupMember -toGroupMember userContactId ((groupMemberId, groupId, indexInGroup, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. profileRow :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink)) = - let memberProfile = rowToLocalProfile profileRow +toGroupMember :: UTCTime -> Int64 -> GroupMemberRow -> GroupMember +toGroupMember now userContactId ((groupMemberId, groupId, indexInGroup, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. profileRow :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink)) = + let memberProfile = rowToLocalProfile now profileRow memberSettings = GroupMemberSettings {showMessages} blockedByAdmin = maybe False mrsBlocked memberRestriction_ invitedBy = toInvitedBy userContactId invitedById @@ -745,6 +750,7 @@ groupMemberQuery = SELECT m.group_member_id, m.group_id, m.index_in_group, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, m.member_pub_key, m.relay_link, 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.xcontact_id, c.custom_user_profile_id, @@ -756,13 +762,13 @@ groupMemberQuery = LEFT JOIN connections c ON c.group_member_id = m.group_member_id |] -toContactMember :: VersionRangeChat -> User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember -toContactMember vr User {userContactId} (memberRow :. connRow) = - (toGroupMember userContactId memberRow) {activeConn = toMaybeConnection vr connRow} +toContactMember :: UTCTime -> VersionRangeChat -> User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember +toContactMember now vr User {userContactId} (memberRow :. connRow) = + (toGroupMember now userContactId memberRow) {activeConn = toMaybeConnection vr connRow} -rowToLocalProfile :: ProfileRow -> LocalProfile -rowToLocalProfile (profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, preferences) = - LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, peerType, badge = Nothing, localAlias, preferences} +rowToLocalProfile :: UTCTime -> ProfileRow -> LocalProfile +rowToLocalProfile now ((profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, preferences) :. badgeRow) = + LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, peerType, badge = rowToBadge now badgeRow, localAlias, preferences} toBusinessChatInfo :: BusinessChatInfoRow -> Maybe BusinessChatInfo toBusinessChatInfo (Just chatType, Just businessId, Just customerId) = Just BusinessChatInfo {chatType, businessId, customerId} @@ -789,6 +795,7 @@ groupInfoQueryFields = mu.group_member_id, mu.group_id, mu.index_in_group, 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.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, + pu.badge_proof, pu.badge_pres_header, pu.badge_expiry, pu.badge_type, pu.badge_verified, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts, mu.member_pub_key, mu.relay_link |] @@ -877,8 +884,9 @@ addGroupChatTags db g@GroupInfo {groupId} = do getGroupInfo :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO GroupInfo getGroupInfo db vr User {userId, userContactId} groupId = ExceptT $ do + currentTs <- getCurrentTime chatTags <- getGroupChatTags db groupId - firstRow (toGroupInfo vr userContactId chatTags) (SEGroupNotFound groupId) $ + firstRow (toGroupInfo currentTs vr userContactId chatTags) (SEGroupNotFound groupId) $ DB.query db (groupInfoQuery <> " WHERE g.group_id = ? AND g.user_id = ? AND mu.contact_id = ?")