From 18a00511a7bd155a6d9b289a611e6a6a1906ffb4 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:23:52 +0000 Subject: [PATCH] core: set via_group_link_uri for groups (#6137) * core: set via_group_link_uri for groups * schema * plans * schema * add to type * plans, api --- bots/api/TYPES.md | 1 + simplex-chat.cabal | 2 + src/Simplex/Chat/Library/Commands.hs | 24 +++++----- src/Simplex/Chat/Library/Subscriber.hs | 2 +- src/Simplex/Chat/Store/Connections.hs | 2 +- src/Simplex/Chat/Store/Direct.hs | 18 ++++---- src/Simplex/Chat/Store/Groups.hs | 14 +++--- src/Simplex/Chat/Store/Postgres/Migrations.hs | 4 +- .../M20250801_via_group_link_uri.hs | 23 ++++++++++ .../Store/Postgres/Migrations/chat_schema.sql | 6 ++- src/Simplex/Chat/Store/SQLite/Migrations.hs | 4 +- .../M20250801_via_group_link_uri.hs | 20 ++++++++ .../SQLite/Migrations/chat_query_plans.txt | 46 +++++++++++-------- .../Store/SQLite/Migrations/chat_schema.sql | 4 +- src/Simplex/Chat/Store/Shared.hs | 34 ++++++++------ src/Simplex/Chat/Types.hs | 3 +- 16 files changed, 139 insertions(+), 68 deletions(-) create mode 100644 src/Simplex/Chat/Store/Postgres/Migrations/M20250801_via_group_link_uri.hs create mode 100644 src/Simplex/Chat/Store/SQLite/Migrations/M20250801_via_group_link_uri.hs diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index c4e7ac6397..5515181de9 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -2094,6 +2094,7 @@ MemberSupport: - uiThemes: [UIThemeEntityOverrides](#uithemeentityoverrides)? - customData: JSONObject? - membersRequireAttention: int +- viaGroupLinkUri: string? --- diff --git a/simplex-chat.cabal b/simplex-chat.cabal index b86c5ef3d2..37239fa022 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -113,6 +113,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20250709_profile_short_descr Simplex.Chat.Store.Postgres.Migrations.M20250721_indexes Simplex.Chat.Store.Postgres.Migrations.M20250729_member_contact_requests + Simplex.Chat.Store.Postgres.Migrations.M20250801_via_group_link_uri else exposed-modules: Simplex.Chat.Archive @@ -251,6 +252,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20250709_profile_short_descr Simplex.Chat.Store.SQLite.Migrations.M20250721_indexes Simplex.Chat.Store.SQLite.Migrations.M20250729_member_contact_requests + Simplex.Chat.Store.SQLite.Migrations.M20250801_via_group_link_uri other-modules: Paths_simplex_chat hs-source-dirs: diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 7c28844f48..90e79d845b 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -3031,22 +3031,19 @@ processChatCommand vr nm = \case connectViaContact :: User -> Maybe PreparedChatEntity -> IncognitoEnabled -> CreatedLinkContact -> Maybe SharedMsgId -> Maybe (SharedMsgId, MsgContent) -> CM ConnectViaContactResult connectViaContact user@User {userId} preparedEntity_ incognito (CCLink cReq@(CRContactUri crData@ConnReqUriData {crClientData}) sLnk) welcomeSharedMsgId msg_ = withInvitationLock "connectViaContact" (strEncode cReq) $ do let groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli - cReqHash = ConnReqUriHash . C.sha256Hash . strEncode - cReqHash1 = cReqHash $ CRContactUri crData {crScheme = SSSimplex} - cReqHash2 = cReqHash $ CRContactUri crData {crScheme = simplexChat} -- groupLinkId is Nothing for business chats when (isJust msg_ && isJust groupLinkId) $ throwChatError CEConnReqMessageProhibited case preparedEntity_ of Just (PCEContact ct@Contact {activeConn}) -> case activeConn of - Nothing -> connect' Nothing cReqHash1 Nothing + Nothing -> connect' Nothing Nothing Just conn@Connection {connStatus, xContactId} -> case connStatus of ConnPrepared -> joinPreparedConn' xContactId conn False _ -> pure $ CVRConnectedContact ct Just (PCEGroup _gInfo GroupMember {activeConn}) -> case activeConn of - Nothing -> connect' groupLinkId cReqHash1 Nothing + Nothing -> connect' groupLinkId Nothing Just conn@Connection {connStatus, xContactId} -> case connStatus of ConnPrepared -> joinPreparedConn' xContactId conn $ isJust groupLinkId - _ -> connect' groupLinkId cReqHash1 xContactId -- why not "already connected" for host member? + _ -> connect' groupLinkId xContactId -- why not "already connected" for host member? Nothing -> withFastStore' (\db -> getConnReqContactXContactId db vr user cReqHash1 cReqHash2) >>= \case Right ct@Contact {activeConn} -> case groupLinkId of @@ -3056,14 +3053,17 @@ processChatCommand vr nm = \case Just gLinkId -> -- allow repeat contact request -- TODO [short links] is this branch needed? it probably remained from the time we created host contact - connect' (Just gLinkId) cReqHash1 Nothing + connect' (Just gLinkId) Nothing Left conn_ -> case conn_ of Just conn@Connection {connStatus = ConnPrepared, xContactId} -> joinPreparedConn' xContactId conn $ isJust groupLinkId -- TODO [short links] this is executed on repeat request after success -- it probably should send the second message without creating the second connection? - Just Connection {xContactId} -> connect' groupLinkId cReqHash1 xContactId - Nothing -> connect' groupLinkId cReqHash1 Nothing + Just Connection {xContactId} -> connect' groupLinkId xContactId + Nothing -> connect' groupLinkId Nothing where + cReqHash = ConnReqUriHash . C.sha256Hash . strEncode + cReqHash1 = cReqHash $ CRContactUri crData {crScheme = SSSimplex} + cReqHash2 = cReqHash $ CRContactUri crData {crScheme = simplexChat} joinPreparedConn' xContactId_ conn@Connection {customUserProfileId} inGroup = do when (incognito /= isJust customUserProfileId) $ throwCmdError "incognito mode is different from prepared connection" xContactId <- mkXContactId xContactId_ @@ -3071,7 +3071,7 @@ processChatCommand vr nm = \case let incognitoProfile = fromLocalProfile <$> localIncognitoProfile conn' <- joinContact user conn cReq incognitoProfile xContactId welcomeSharedMsgId msg_ inGroup PQSupportOn pure $ CVRSentInvitation conn' incognitoProfile - connect' groupLinkId cReqHash xContactId_ = do + connect' groupLinkId xContactId_ = do let inGroup = isJust groupLinkId pqSup = if inGroup then PQSupportOff else PQSupportOn (connId, chatV) <- prepareContact user cReq pqSup @@ -3080,7 +3080,7 @@ processChatCommand vr nm = \case incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing subMode <- chatReadVar subscriptionMode let sLnk' = serverShortLink <$> sLnk - conn <- withFastStore' $ \db -> createConnReqConnection db userId connId preparedEntity_ cReqHash sLnk' xContactId incognitoProfile groupLinkId subMode chatV pqSup + conn <- withFastStore' $ \db -> createConnReqConnection db userId connId preparedEntity_ cReq cReqHash1 sLnk' xContactId incognitoProfile groupLinkId subMode chatV pqSup conn' <- joinContact user conn cReq incognitoProfile xContactId welcomeSharedMsgId msg_ inGroup pqSup pure $ CVRSentInvitation conn' incognitoProfile connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> CreatedLinkContact -> CM ChatResponse @@ -3095,7 +3095,7 @@ processChatCommand vr nm = \case incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing subMode <- chatReadVar subscriptionMode let cReqHash = ConnReqUriHash . C.sha256Hash $ strEncode cReq - conn <- withFastStore' $ \db -> createConnReqConnection db userId connId (Just $ PCEContact ct) cReqHash shortLink newXContactId incognitoProfile Nothing subMode chatV pqSup + conn <- withFastStore' $ \db -> createConnReqConnection db userId connId (Just $ PCEContact ct) cReq cReqHash shortLink newXContactId incognitoProfile Nothing subMode chatV pqSup void $ joinContact user conn cReq incognitoProfile newXContactId Nothing Nothing False pqSup ct' <- withStore $ \db -> getContact db vr user contactId pure $ CRSentInvitationToContact user ct' incognitoProfile diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 013ae2fb26..320f81e497 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -2269,7 +2269,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = dm <- encodeConnInfo $ XGrpAcpt membershipMemId connIds <- joinAgentConnectionAsync user True connRequest dm subMode withStore' $ \db -> do - setViaGroupLinkHash db groupId connId + setViaGroupLinkUri db groupId connId createMemberConnectionAsync db user hostId connIds connChatVersion peerChatVRange subMode updateGroupMemberStatusById db userId hostId GSMemAccepted updateGroupMemberStatus db userId membership GSMemAccepted diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index e1bb3ed6f3..634c4e7175 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -143,7 +143,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 321177459c..c9b76a0fd2 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -154,8 +154,8 @@ deletePendingContactConnection db userId connId = |] (userId, connId, ConnContact) -createConnReqConnection :: DB.Connection -> UserId -> ConnId -> Maybe PreparedChatEntity -> ConnReqUriHash -> Maybe ShortLinkContact -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> VersionChat -> PQSupport -> IO Connection -createConnReqConnection db userId acId preparedEntity_ cReqHash sLnk xContactId incognitoProfile groupLinkId subMode chatV pqSup = do +createConnReqConnection :: DB.Connection -> UserId -> ConnId -> Maybe PreparedChatEntity -> ConnReqContact -> ConnReqUriHash -> Maybe ShortLinkContact -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> VersionChat -> PQSupport -> IO Connection +createConnReqConnection db userId acId preparedEntity_ cReq cReqHash sLnk xContactId incognitoProfile groupLinkId subMode chatV pqSup = do currentTs <- getCurrentTime customUserProfileId <- mapM (createIncognitoProfile_ db userId currentTs) incognitoProfile let connStatus = ConnPrepared @@ -164,13 +164,13 @@ createConnReqConnection db userId acId preparedEntity_ cReqHash sLnk xContactId [sql| INSERT INTO connections ( user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated, - via_contact_uri_hash, via_short_link_contact, contact_id, group_member_id, + via_contact_uri, via_contact_uri_hash, via_short_link_contact, contact_id, group_member_id, xcontact_id, custom_user_profile_id, via_group_link, group_link_id, created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (userId, acId, connStatus, connType, BI True) - :. (cReqHash, sLnk, contactId_, groupMemberId_) + :. (cReq, cReqHash, sLnk, contactId_, groupMemberId_) :. (xContactId, customUserProfileId, BI (isJust groupLinkId), groupLinkId) :. (currentTs, currentTs, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup) ) @@ -217,8 +217,8 @@ createConnReqConnection db userId acId preparedEntity_ cReqHash sLnk xContactId updatePreparedGroup GroupInfo {groupId, membership} customUserProfileId currentTs = do DB.execute db - "UPDATE groups SET via_group_link_uri_hash = ?, conn_link_prepared_connection = ?, updated_at = ? WHERE group_id = ?" - (cReqHash, BI True, currentTs, groupId) + "UPDATE groups SET via_group_link_uri = ?, via_group_link_uri_hash = ?, conn_link_prepared_connection = ?, updated_at = ? WHERE group_id = ?" + (cReq, cReqHash, BI True, currentTs, groupId) when (isJust customUserProfileId) $ DB.execute db @@ -336,8 +336,8 @@ createDirectConnection_ db userId acId (CCLink cReq shortLinkInv) contactId_ pcc ( (userId, acId, cReq, shortLinkInv, pccConnStatus, ConnContact, contactId_, BI contactConnInitiated, customUserProfileId) :. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup) ) - dbConnId <- insertedRowId db - pure (dbConnId, customUserProfileId, contactConnInitiated) + connId <- insertedRowId db + pure (connId, customUserProfileId, contactConnInitiated) createIncognitoProfile :: DB.Connection -> User -> Profile -> IO Int64 createIncognitoProfile db User {userId} p = do diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index b4c3e1ace4..cceef17228 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -373,7 +373,8 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc chatItemTTL = Nothing, uiThemes = Nothing, customData = Nothing, - membersRequireAttention = 0 + membersRequireAttention = 0, + viaGroupLinkUri = Nothing } -- | creates a new group record for the group the current user was invited to, or returns an existing one @@ -445,7 +446,8 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ chatItemTTL = Nothing, uiThemes = Nothing, customData = Nothing, - membersRequireAttention = 0 + membersRequireAttention = 0, + viaGroupLinkUri = Nothing }, groupMemberId ) @@ -744,7 +746,7 @@ createGroupViaLink' liftIO $ DB.execute db "UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?" (ConnMember, hostMemberId, currentTs, connId) -- using IBUnknown since host is created without contact void $ createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember membershipStatus IBUnknown customUserProfileId currentTs vr - liftIO $ setViaGroupLinkHash db groupId connId + liftIO $ setViaGroupLinkUri db groupId connId (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user hostMemberId where insertHost_ currentTs groupId = do @@ -906,7 +908,7 @@ cleanupHostGroupLinkConn db user@User {userId} GroupInfo {groupId} = do DB.execute db [sql| - UPDATE connections SET via_contact_uri_hash = NULL, xcontact_id = NULL + UPDATE connections SET via_contact_uri = NULL, via_contact_uri_hash = NULL, xcontact_id = NULL WHERE user_id = ? AND via_group_link = 1 AND contact_id IN ( SELECT contact_id FROM group_members @@ -954,7 +956,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, @@ -1911,7 +1913,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index 8b89de6a5c..1fc8054702 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -14,6 +14,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20250704_groups_conn_link_prepare import Simplex.Chat.Store.Postgres.Migrations.M20250709_profile_short_descr import Simplex.Chat.Store.Postgres.Migrations.M20250721_indexes import Simplex.Chat.Store.Postgres.Migrations.M20250729_member_contact_requests +import Simplex.Chat.Store.Postgres.Migrations.M20250801_via_group_link_uri import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -27,7 +28,8 @@ schemaMigrations = ("20250704_groups_conn_link_prepared_connection", m20250704_groups_conn_link_prepared_connection, Just down_m20250704_groups_conn_link_prepared_connection), ("20250709_profile_short_descr", m20250709_profile_short_descr, Just down_m20250709_profile_short_descr), ("20250721_indexes", m20250721_indexes, Just down_m20250721_indexes), - ("20250729_member_contact_requests", m20250729_member_contact_requests, Just down_m20250729_member_contact_requests) + ("20250729_member_contact_requests", m20250729_member_contact_requests, Just down_m20250729_member_contact_requests), + ("20250801_via_group_link_uri", m20250801_via_group_link_uri, Just down_m20250801_via_group_link_uri) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20250801_via_group_link_uri.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20250801_via_group_link_uri.hs new file mode 100644 index 0000000000..db0ba00eff --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20250801_via_group_link_uri.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20250801_via_group_link_uri where + +import Data.Text (Text) +import qualified Data.Text as T +import Text.RawString.QQ (r) + +m20250801_via_group_link_uri :: Text +m20250801_via_group_link_uri = + T.pack + [r| +ALTER TABLE groups ADD COLUMN via_group_link_uri BYTEA; +ALTER TABLE connections ADD COLUMN via_contact_uri BYTEA; +|] + +down_m20250801_via_group_link_uri :: Text +down_m20250801_via_group_link_uri = + T.pack + [r| +ALTER TABLE groups DROP COLUMN via_group_link_uri; +ALTER TABLE connections DROP COLUMN via_contact_uri; +|] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index c2d26df4bb..e649c03c34 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -303,7 +303,8 @@ CREATE TABLE test_chat_schema.connections ( pq_rcv_enabled smallint, quota_err_counter bigint DEFAULT 0 NOT NULL, short_link_inv bytea, - via_short_link_contact bytea + via_short_link_contact bytea, + via_contact_uri bytea ); @@ -653,7 +654,8 @@ CREATE TABLE test_chat_schema.groups ( conn_link_started_connection smallint DEFAULT 0 NOT NULL, welcome_shared_msg_id bytea, request_shared_msg_id bytea, - conn_link_prepared_connection smallint DEFAULT 0 NOT NULL + conn_link_prepared_connection smallint DEFAULT 0 NOT NULL, + via_group_link_uri bytea ); diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index 58035642ec..7665d5155f 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -137,6 +137,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250704_groups_conn_link_prepared_ import Simplex.Chat.Store.SQLite.Migrations.M20250709_profile_short_descr import Simplex.Chat.Store.SQLite.Migrations.M20250721_indexes import Simplex.Chat.Store.SQLite.Migrations.M20250729_member_contact_requests +import Simplex.Chat.Store.SQLite.Migrations.M20250801_via_group_link_uri import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -273,7 +274,8 @@ schemaMigrations = ("20250704_groups_conn_link_prepared_connection", m20250704_groups_conn_link_prepared_connection, Just down_m20250704_groups_conn_link_prepared_connection), ("20250709_profile_short_descr", m20250709_profile_short_descr, Just down_m20250709_profile_short_descr), ("20250721_indexes", m20250721_indexes, Just down_m20250721_indexes), - ("20250729_member_contact_requests", m20250729_member_contact_requests, Just down_m20250729_member_contact_requests) + ("20250729_member_contact_requests", m20250729_member_contact_requests, Just down_m20250729_member_contact_requests), + ("20250801_via_group_link_uri", m20250801_via_group_link_uri, Just down_m20250801_via_group_link_uri) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250801_via_group_link_uri.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250801_via_group_link_uri.hs new file mode 100644 index 0000000000..cc47bad7af --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250801_via_group_link_uri.hs @@ -0,0 +1,20 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20250801_via_group_link_uri where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20250801_via_group_link_uri :: Query +m20250801_via_group_link_uri = + [sql| +ALTER TABLE groups ADD COLUMN via_group_link_uri BLOB; +ALTER TABLE connections ADD COLUMN via_contact_uri BLOB; +|] + +down_m20250801_via_group_link_uri :: Query +down_m20250801_via_group_link_uri = + [sql| +ALTER TABLE groups DROP COLUMN via_group_link_uri; +ALTER TABLE connections DROP COLUMN via_contact_uri; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index c20d8b7a33..c66716d56a 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -65,7 +65,7 @@ Query: g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -973,7 +973,7 @@ Query: g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -1023,7 +1023,7 @@ Query: g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, @@ -1431,7 +1431,7 @@ SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN SEARCH g USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN Query: - UPDATE connections SET via_contact_uri_hash = NULL, xcontact_id = NULL + UPDATE connections SET via_contact_uri = NULL, via_contact_uri_hash = NULL, xcontact_id = NULL WHERE user_id = ? AND via_group_link = 1 AND contact_id IN ( SELECT contact_id FROM group_members @@ -3660,6 +3660,14 @@ Query: Plan: SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) +Query: + UPDATE groups + SET via_group_link_uri = ?, via_group_link_uri_hash = ? + WHERE group_id = ? + +Plan: +SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) + Query: DELETE FROM chat_items WHERE group_scope_group_member_id = ? @@ -4190,10 +4198,10 @@ Plan: Query: INSERT INTO connections ( user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated, - via_contact_uri_hash, via_short_link_contact, contact_id, group_member_id, + via_contact_uri, via_contact_uri_hash, via_short_link_contact, contact_id, group_member_id, xcontact_id, custom_user_profile_id, via_group_link, group_link_id, created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: @@ -4620,16 +4628,6 @@ Query: Plan: SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) -Query: - UPDATE groups - SET via_group_link_uri_hash = (SELECT via_contact_uri_hash FROM connections WHERE connection_id = ?) - WHERE group_id = ? - -Plan: -SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) -SCALAR SUBQUERY 1 -SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) - Query: UPDATE msg_deliveries SET delivery_status = ?, updated_at = ? @@ -4751,7 +4749,7 @@ Query: g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -4777,7 +4775,7 @@ Query: g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -5328,6 +5326,9 @@ Error: SQLite3 returned ErrorError while attempting to perform prepare "explain Query: CREATE TABLE temp_delete_members (contact_profile_id INTEGER, member_profile_id INTEGER, local_display_name TEXT) Error: SQLite3 returned ErrorError while attempting to perform prepare "explain query plan CREATE TABLE temp_delete_members (contact_profile_id INTEGER, member_profile_id INTEGER, local_display_name TEXT)": table temp_delete_members already exists +Query: DELETE FROM app_settings +Plan: + Query: DELETE FROM calls WHERE user_id = ? AND contact_id = ? Plan: SEARCH calls USING INDEX idx_calls_contact_id (contact_id=?) @@ -5717,6 +5718,9 @@ Plan: Query: DROP TABLE temp_delete_members Plan: +Query: INSERT INTO app_settings (app_settings) VALUES (?) +Plan: + Query: INSERT INTO chat_item_mentions (chat_item_id, group_id, member_id, display_name) VALUES (?, ?, ?, ?) Plan: @@ -6068,6 +6072,10 @@ Query: SELECT user_id FROM users WHERE local_display_name = ? Plan: SEARCH users USING COVERING INDEX sqlite_autoindex_users_2 (local_display_name=?) +Query: SELECT via_contact_uri, via_contact_uri_hash FROM connections WHERE connection_id = ? +Plan: +SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) + Query: SELECT xgrplinkmem_received FROM group_members WHERE group_member_id = ? Plan: SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) @@ -6324,7 +6332,7 @@ Query: UPDATE groups SET user_member_profile_sent_at = ? WHERE user_id = ? AND g Plan: SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) -Query: UPDATE groups SET via_group_link_uri_hash = ?, conn_link_prepared_connection = ?, updated_at = ? WHERE group_id = ? +Query: UPDATE groups SET via_group_link_uri = ?, via_group_link_uri_hash = ?, conn_link_prepared_connection = ?, updated_at = ? WHERE group_id = ? Plan: SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 91effef7c1..371f3f09ec 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -154,7 +154,8 @@ CREATE TABLE groups( conn_link_started_connection INTEGER NOT NULL DEFAULT 0, welcome_shared_msg_id BLOB, request_shared_msg_id BLOB, - conn_link_prepared_connection INTEGER NOT NULL DEFAULT 0, -- received + conn_link_prepared_connection INTEGER NOT NULL DEFAULT 0, + via_group_link_uri BLOB, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -327,6 +328,7 @@ CREATE TABLE connections( quota_err_counter INTEGER NOT NULL DEFAULT 0, short_link_inv BLOB, via_short_link_contact BLOB, + via_contact_uri BLOB, FOREIGN KEY(snd_file_id, connection_id) REFERENCES snd_files(file_id, connection_id) ON DELETE CASCADE diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 3f39fdeedb..67c4eb8c1e 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -638,21 +638,21 @@ type PreparedGroupRow = (Maybe ConnReqContact, Maybe ShortLinkContact, BoolInt, type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId) -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64, Int) :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact) :. GroupMemberRow type GroupMemberRow = (Int64, 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) type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image) :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (uiThemes, customData, chatItemTTL, membersRequireAttention) :. userMemberRow) = +toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image) :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (uiThemes, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. userMemberRow) = let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences groupProfile = GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} businessChat = toBusinessChatInfo businessRow preparedGroup = toPreparedGroup preparedGroupRow - in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, preparedGroup, chatTags, chatItemTTL, uiThemes, customData, membersRequireAttention} + in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, preparedGroup, chatTags, chatItemTTL, uiThemes, customData, membersRequireAttention, viaGroupLinkUri} toPreparedGroup :: PreparedGroupRow -> Maybe PreparedGroup toPreparedGroup = \case @@ -698,7 +698,7 @@ groupInfoQuery = g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, - g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, + g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -784,16 +784,22 @@ addGroupChatTags db g@GroupInfo {groupId} = do chatTags <- getGroupChatTags db groupId pure (g :: GroupInfo) {chatTags} -setViaGroupLinkHash :: DB.Connection -> GroupId -> Int64 -> IO () -setViaGroupLinkHash db groupId connId = - DB.execute - db - [sql| - UPDATE groups - SET via_group_link_uri_hash = (SELECT via_contact_uri_hash FROM connections WHERE connection_id = ?) - WHERE group_id = ? - |] - (connId, groupId) +setViaGroupLinkUri :: DB.Connection -> GroupId -> Int64 -> IO () +setViaGroupLinkUri db groupId connId = do + r <- + DB.query + db + "SELECT via_contact_uri, via_contact_uri_hash FROM connections WHERE connection_id = ?" + (Only connId) :: IO [(Maybe ConnReqContact, Maybe ConnReqUriHash)] + forM_ (listToMaybe r) $ \(viaContactUri, viaContactUriHash) -> + DB.execute + db + [sql| + UPDATE groups + SET via_group_link_uri = ?, via_group_link_uri_hash = ? + WHERE group_id = ? + |] + (viaContactUri, viaContactUriHash, groupId) deleteConnectionRecord :: DB.Connection -> User -> Int64 -> IO () deleteConnectionRecord db User {userId} cId = do diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index fb36f11790..964d8f083b 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -486,7 +486,8 @@ data GroupInfo = GroupInfo chatItemTTL :: Maybe Int64, uiThemes :: Maybe UIThemeEntityOverrides, customData :: Maybe CustomData, - membersRequireAttention :: Int + membersRequireAttention :: Int, + viaGroupLinkUri :: Maybe ConnReqContact } deriving (Eq, Show)