core: allow to accept contact requests after address is deleted (#6032)

* core: allow to accept contact requests after address is deleted

* update

* update

* plans

* ios, postgres migration

* schema

* request lock, refactor

* update simplexmq

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
spaced4ndy
2025-07-03 10:34:11 +00:00
committed by GitHub
co-authored by Evgeny Poberezkin
parent 4f227518f1
commit 79041390f1
21 changed files with 243 additions and 107 deletions
+56 -38
View File
@@ -1150,45 +1150,62 @@ processChatCommand' vr = \case
pure $ CRChatCleared user (AChatInfo SCTLocal $ LocalChat nf)
_ -> throwCmdError "not supported"
APIAcceptContact incognito connReqId -> withUser $ \user@User {userId} -> do
(uclId, (ucl, gLinkInfo_)) <- withFastStore $ \db -> do
uclId <- getUserContactLinkIdByCReq db connReqId
uclGLinkInfo <- getUserContactLinkById db userId uclId
pure (uclId, uclGLinkInfo)
let UserContactLink {shortLinkDataSet, addressSettings} = ucl
when (shortLinkDataSet && incognito) $ throwCmdError "incognito not allowed for address with short link data"
withUserContactLock "acceptContact" uclId $ do
cReq@UserContactRequest {welcomeSharedMsgId} <- withFastStore $ \db -> getContactRequest db user connReqId
(ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito
let contactUsed = isNothing gLinkInfo_
ct' <- withStore' $ \db -> do
updateContactAccepted db user ct contactUsed
conn' <-
if sqSecured
then conn {connStatus = ConnSndReady} <$ updateConnectionStatusFromTo db connId ConnNew ConnSndReady
else pure conn
pure ct {contactUsed, activeConn = Just conn'}
when sqSecured $ forM_ (autoReply addressSettings) $ \mc -> case welcomeSharedMsgId of
Just smId ->
void $ sendDirectContactMessage user ct' $ XMsgUpdate smId mc M.empty Nothing Nothing Nothing
Nothing -> do
(msg, _) <- sendDirectContactMessage user ct' $ XMsgNew $ MCSimple $ extMsgContent mc Nothing
ci <- saveSndChatItem user (CDDirectSnd ct') msg (CISndMsgContent mc)
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci]
pure $ CRAcceptingContactRequest user ct'
uclData_ <- withFastStore $ \db -> do
uclId_ <- getUserContactLinkIdByCReq db connReqId
forM uclId_ $ \uclId -> do -- address may be deleted
uclGLinkInfo <- getUserContactLinkById db userId uclId
pure (uclId, uclGLinkInfo)
withContactRequestLock "acceptContact" connReqId $ case uclData_ of
Nothing -> do -- address was deleted
when incognito $ throwCmdError "incognito not allowed when address is not found"
cReq <- withFastStore $ \db -> getContactRequest db user connReqId
(ct, _sqSecured) <- acceptCReq user cReq True
pure $ CRAcceptingContactRequest user ct
Just (uclId, (ucl@UserContactLink {shortLinkDataSet}, gLinkInfo_)) -> do
when (shortLinkDataSet && incognito) $ throwCmdError "incognito not allowed for address with short link data"
withUserContactLock "acceptContact" uclId $ do
cReq <- withFastStore $ \db -> getContactRequest db user connReqId
let contactUsed = isNothing gLinkInfo_ -- for redundancy, as group link requests are auto-accepted
(ct, sqSecured) <- acceptCReq user cReq contactUsed
when sqSecured $ sendWelcomeMsg user ct ucl cReq
pure $ CRAcceptingContactRequest user ct
where
acceptCReq user cReq contactUsed = do
(ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito
ct' <- withStore' $ \db -> do
updateContactAccepted db user ct contactUsed
conn' <-
if sqSecured
then conn {connStatus = ConnSndReady} <$ updateConnectionStatusFromTo db connId ConnNew ConnSndReady
else pure conn
pure ct {contactUsed, activeConn = Just conn'}
pure (ct', sqSecured)
sendWelcomeMsg user ct ucl UserContactRequest {welcomeSharedMsgId} =
forM_ (autoReply $ addressSettings ucl) $ \mc -> case welcomeSharedMsgId of
Just smId ->
void $ sendDirectContactMessage user ct $ XMsgUpdate smId mc M.empty Nothing Nothing Nothing
Nothing -> do
(msg, _) <- sendDirectContactMessage user ct $ XMsgNew $ MCSimple $ extMsgContent mc Nothing
ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc)
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci]
APIRejectContact connReqId -> withUser $ \user -> do
userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId
withUserContactLock "rejectContact" userContactLinkId $ do
(cReq@UserContactRequest {agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId}, ct_) <-
withFastStore $ \db -> do
cReq@UserContactRequest {contactId_} <- getContactRequest db user connReqId
ct_ <- forM contactId_ $ \contactId -> do
ct <- getContact db vr user contactId
deleteContact db user ct
pure ct
liftIO $ deleteContactRequest db user connReqId
pure (cReq, ct_)
withAgent $ \a -> rejectContact a connId invId
pure $ CRContactRequestRejected user cReq ct_
uclId_ <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId
withContactRequestLock "rejectContact" connReqId $ case uclId_ of
Nothing -> rejectCReq user -- address was deleted
Just uclId -> withUserContactLock "rejectContact" uclId $ rejectCReq user
where
rejectCReq user = do
(cReq@UserContactRequest {agentInvitationId = AgentInvId invId}, ct_) <-
withFastStore $ \db -> do
cReq@UserContactRequest {contactId_} <- getContactRequest db user connReqId
ct_ <- forM contactId_ $ \contactId -> do
ct <- getContact db vr user contactId
deleteContact db user ct
pure ct
liftIO $ deleteContactRequest db user connReqId
pure (cReq, ct_)
withAgent (`rejectContact` invId)
pure $ CRContactRequestRejected user cReq ct_
APISendCallInvitation contactId callType -> withUser $ \user -> do
-- party initiating call
ct <- withFastStore $ \db -> getContact db vr user contactId
@@ -2785,6 +2802,7 @@ processChatCommand' vr = \case
CLContact ctId -> "Contact " <> tshow ctId
CLGroup gId -> "Group " <> tshow gId
CLUserContact ucId -> "UserContact " <> tshow ucId
CLContactRequest crId -> "ContactRequest " <> tshow crId
CLFile fId -> "File " <> tshow fId
DebugEvent event -> toView event >> ok_
GetAgentSubsTotal userId -> withUserId userId $ \user -> do
+12 -8
View File
@@ -142,6 +142,10 @@ withUserContactLock :: Text -> Int64 -> CM a -> CM a
withUserContactLock name = withEntityLock name . CLUserContact
{-# INLINE withUserContactLock #-}
withContactRequestLock :: Text -> Int64 -> CM a -> CM a
withContactRequestLock name = withEntityLock name . CLContactRequest
{-# INLINE withContactRequestLock #-}
withFileLock :: Text -> Int64 -> CM a -> CM a
withFileLock name = withEntityLock name . CLFile
{-# INLINE withFileLock #-}
@@ -878,7 +882,7 @@ getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of
-- It may be reasonable to set it when contact is first prepared, but then we can't use it to ignore requests after acceptance,
-- and it may lead to race conditions with XInfo events.
acceptContactRequest :: User -> UserContactRequest -> IncognitoEnabled -> CM (Contact, Connection, SndQueueSecured)
acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId = AgentInvId invId, contactId_, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognito = do
acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId = AgentInvId invId, contactId_, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId_, xContactId, pqSupport} incognito = do
subMode <- chatReadVar subscriptionMode
let pqSup = PQSupportOn
pqSup' = pqSup `CR.pqSupportAnd` pqSupport
@@ -887,20 +891,20 @@ acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId =
(ct, conn, incognitoProfile) <- case contactId_ of
Nothing -> do
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
connId <- withAgent $ \a -> prepareConnectionToAccept a True invId pqSup'
connId <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) True invId pqSup'
(ct, conn) <- withStore' $ \db ->
createContactFromRequest db user userContactLinkId connId chatV cReqChatVRange cName profileId cp xContactId incognitoProfile subMode pqSup' False
createContactFromRequest db user userContactLinkId_ connId chatV cReqChatVRange cName profileId cp xContactId incognitoProfile subMode pqSup' False
pure (ct, conn, incognitoProfile)
Just contactId -> do
ct <- withFastStore $ \db -> getContact db vr user contactId
case contactConn ct of
Nothing -> do
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
connId <- withAgent $ \a -> prepareConnectionToAccept a True invId pqSup'
connId <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) True invId pqSup'
currentTs <- liftIO getCurrentTime
conn <- withStore' $ \db -> do
forM_ xContactId $ \xcId -> setContactAcceptedXContactId db ct xcId
createAcceptedContactConn db user userContactLinkId contactId connId chatV cReqChatVRange pqSup' incognitoProfile subMode currentTs
createAcceptedContactConn db user userContactLinkId_ contactId connId chatV cReqChatVRange pqSup' incognitoProfile subMode currentTs
pure (ct {activeConn = Just conn} :: Contact, conn, incognitoProfile)
Just conn@Connection {customUserProfileId} -> do
incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId
@@ -908,7 +912,7 @@ acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId =
let profileToSend = profileToSendOnAccept user incognitoProfile False
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
-- TODO [certs rcv]
(ct,conn,) . fst <$> withAgent (\a -> acceptContact a (aConnId conn) True invId dm pqSup' subMode)
(ct,conn,) . fst <$> withAgent (\a -> acceptContact a (aUserId user) (aConnId conn) True invId dm pqSup' subMode)
acceptContactRequestAsync :: User -> Int64 -> Contact -> UserContactRequest -> Maybe IncognitoProfile -> CM Contact
acceptContactRequestAsync
@@ -925,7 +929,7 @@ acceptContactRequestAsync
currentTs <- liftIO getCurrentTime
withStore $ \db -> do
forM_ xContactId $ \xcId -> liftIO $ setContactAcceptedXContactId db ct xcId
Connection {connId} <- liftIO $ createAcceptedContactConn db user uclId contactId acId chatV cReqChatVRange cReqPQSup incognitoProfile subMode currentTs
Connection {connId} <- liftIO $ createAcceptedContactConn db user (Just uclId) contactId acId chatV cReqChatVRange cReqPQSup incognitoProfile subMode currentTs
liftIO $ setCommandConnId db user cmdId connId
getContact db vr user contactId
@@ -2194,7 +2198,7 @@ agentAcceptContactAsync :: MsgEncodingI e => User -> Bool -> InvitationId -> Cha
agentAcceptContactAsync user enableNtfs invId msg subMode pqSup chatV = do
cmdId <- withStore' $ \db -> createCommand db user Nothing CFAcceptContact
dm <- encodeConnInfoPQ pqSup chatV msg
connId <- withAgent $ \a -> acceptContactAsync a (aCorrId cmdId) enableNtfs invId dm pqSup subMode
connId <- withAgent $ \a -> acceptContactAsync a (aUserId user) (aCorrId cmdId) enableNtfs invId dm pqSup subMode
pure (cmdId, connId)
deleteAgentConnectionAsync :: ConnId -> CM ()
+1 -2
View File
@@ -143,12 +143,11 @@ createOrUpdateContactRequest
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c USING (user_contact_link_id)
JOIN contact_profiles p USING (contact_profile_id)
WHERE cr.user_id = ?
AND cr.xcontact_id = ?
+8 -9
View File
@@ -708,7 +708,7 @@ getUserContacts db vr user@User {userId} = do
contacts <- rights <$> mapM (runExceptT . getContact db vr user) contactIds
pure $ filter (\Contact {activeConn} -> isJust activeConn) contacts
getUserContactLinkIdByCReq :: DB.Connection -> Int64 -> ExceptT StoreError IO Int64
getUserContactLinkIdByCReq :: DB.Connection -> Int64 -> ExceptT StoreError IO (Maybe Int64)
getUserContactLinkIdByCReq db contactRequestId =
ExceptT . firstRow fromOnly (SEContactRequestNotFound contactRequestId) $
DB.query db "SELECT user_contact_link_id FROM contact_requests WHERE contact_request_id = ?" (Only contactRequestId)
@@ -734,12 +734,11 @@ contactRequestQuery =
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c USING (user_contact_link_id)
JOIN contact_profiles p USING (contact_profile_id)
|]
@@ -774,8 +773,8 @@ deleteContactRequest db User {userId} contactRequestId = do
(userId, userId, contactRequestId, userId)
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
createContactFromRequest :: DB.Connection -> User -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO (Contact, Connection)
createContactFromRequest db user@User {userId, profile = LocalProfile {preferences}} uclId agentConnId connChatVersion cReqChatVRange localDisplayName profileId profile xContactId incognitoProfile subMode pqSup contactUsed = do
createContactFromRequest :: DB.Connection -> User -> Maybe Int64 -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO (Contact, Connection)
createContactFromRequest db user@User {userId, profile = LocalProfile {preferences}} uclId_ agentConnId connChatVersion cReqChatVRange localDisplayName profileId profile xContactId incognitoProfile subMode pqSup contactUsed = do
currentTs <- getCurrentTime
let userPreferences = fromMaybe emptyChatPrefs $ incognitoProfile >> preferences
DB.execute
@@ -784,7 +783,7 @@ createContactFromRequest db user@User {userId, profile = LocalProfile {preferenc
(userId, localDisplayName, profileId, BI True, userPreferences, currentTs, currentTs, currentTs, xContactId, BI contactUsed)
contactId <- insertedRowId db
DB.execute db "UPDATE contact_requests SET contact_id = ? WHERE user_id = ? AND local_display_name = ?" (contactId, userId, localDisplayName)
conn <- createAcceptedContactConn db user uclId contactId agentConnId connChatVersion cReqChatVRange pqSup incognitoProfile subMode currentTs
conn <- createAcceptedContactConn db user uclId_ contactId agentConnId connChatVersion cReqChatVRange pqSup incognitoProfile subMode currentTs
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
ct =
Contact
@@ -813,12 +812,12 @@ createContactFromRequest db user@User {userId, profile = LocalProfile {preferenc
}
pure (ct, conn)
createAcceptedContactConn :: DB.Connection -> User -> Int64 -> ContactId -> ConnId -> VersionChat -> VersionRangeChat -> PQSupport -> Maybe IncognitoProfile -> SubscriptionMode -> UTCTime -> IO Connection
createAcceptedContactConn db User {userId} uclId contactId agentConnId connChatVersion cReqChatVRange pqSup incognitoProfile subMode currentTs = do
createAcceptedContactConn :: DB.Connection -> User -> Maybe Int64 -> ContactId -> ConnId -> VersionChat -> VersionRangeChat -> PQSupport -> Maybe IncognitoProfile -> SubscriptionMode -> UTCTime -> IO Connection
createAcceptedContactConn db User {userId} uclId_ contactId agentConnId connChatVersion cReqChatVRange pqSup incognitoProfile subMode currentTs = do
customUserProfileId <- forM incognitoProfile $ \case
NewIncognito p -> createIncognitoProfile_ db userId currentTs p
ExistingIncognito LocalProfile {profileId = pId} -> pure pId
createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just uclId) customUserProfileId 0 currentTs subMode pqSup
createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing uclId_ customUserProfileId 0 currentTs subMode pqSup
updateContactAccepted :: DB.Connection -> User -> Contact -> Bool -> IO ()
updateContactAccepted db User {userId} Contact {contactId} contactUsed =
+1 -2
View File
@@ -1051,12 +1051,11 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id
JOIN contact_profiles p ON p.contact_profile_id = cr.contact_profile_id
JOIN user_contact_links uc ON uc.user_contact_link_id = cr.user_contact_link_id
WHERE cr.user_id = ?
@@ -9,6 +9,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links
import Simplex.Chat.Store.Postgres.Migrations.M20250512_member_admission
import Simplex.Chat.Store.Postgres.Migrations.M20250513_group_scope
import Simplex.Chat.Store.Postgres.Migrations.M20250526_short_links
import Simplex.Chat.Store.Postgres.Migrations.M20250702_contact_requests_remove_cascade_delete
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Text, Maybe Text)]
@@ -17,7 +18,8 @@ schemaMigrations =
("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links),
("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission),
("20250513_group_scope", m20250513_group_scope, Just down_m20250513_group_scope),
("20250526_short_links", m20250526_short_links, Just down_m20250526_short_links)
("20250526_short_links", m20250526_short_links, Just down_m20250526_short_links),
("20250702_contact_requests_remove_cascade_delete", m20250702_contact_requests_remove_cascade_delete, Just down_m20250702_contact_requests_remove_cascade_delete)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,39 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.Postgres.Migrations.M20250702_contact_requests_remove_cascade_delete where
import Data.Text (Text)
import qualified Data.Text as T
import Text.RawString.QQ (r)
m20250702_contact_requests_remove_cascade_delete :: Text
m20250702_contact_requests_remove_cascade_delete =
T.pack
[r|
ALTER TABLE contact_requests DROP CONSTRAINT contact_requests_user_contact_link_id_fkey;
ALTER TABLE contact_requests ALTER COLUMN user_contact_link_id DROP NOT NULL;
ALTER TABLE contact_requests
ADD CONSTRAINT contact_requests_user_contact_link_id_fkey
FOREIGN KEY (user_contact_link_id)
REFERENCES user_contact_links(user_contact_link_id)
ON UPDATE CASCADE
ON DELETE SET NULL;
|]
down_m20250702_contact_requests_remove_cascade_delete :: Text
down_m20250702_contact_requests_remove_cascade_delete =
T.pack
[r|
ALTER TABLE contact_requests DROP CONSTRAINT contact_requests_user_contact_link_id_fkey;
ALTER TABLE contact_requests ALTER COLUMN user_contact_link_id SET NOT NULL;
ALTER TABLE contact_requests
ADD CONSTRAINT contact_requests_user_contact_link_id_fkey
FOREIGN KEY (user_contact_link_id)
REFERENCES user_contact_links(user_contact_link_id)
ON UPDATE CASCADE
ON DELETE CASCADE;
|]
@@ -349,7 +349,7 @@ ALTER TABLE test_chat_schema.contact_profiles ALTER COLUMN contact_profile_id AD
CREATE TABLE test_chat_schema.contact_requests (
contact_request_id bigint NOT NULL,
user_contact_link_id bigint NOT NULL,
user_contact_link_id bigint,
agent_invitation_id bytea NOT NULL,
contact_profile_id bigint,
local_display_name text NOT NULL,
@@ -2313,7 +2313,7 @@ ALTER TABLE ONLY test_chat_schema.contact_requests
ALTER TABLE ONLY test_chat_schema.contact_requests
ADD CONSTRAINT contact_requests_user_contact_link_id_fkey FOREIGN KEY (user_contact_link_id) REFERENCES test_chat_schema.user_contact_links(user_contact_link_id) ON UPDATE CASCADE ON DELETE CASCADE;
ADD CONSTRAINT contact_requests_user_contact_link_id_fkey FOREIGN KEY (user_contact_link_id) REFERENCES test_chat_schema.user_contact_links(user_contact_link_id) ON UPDATE CASCADE ON DELETE SET NULL;
@@ -2689,3 +2689,6 @@ ALTER TABLE ONLY test_chat_schema.user_contact_links
ALTER TABLE ONLY test_chat_schema.xftp_file_descriptions
ADD CONSTRAINT xftp_file_descriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE;
+3 -1
View File
@@ -132,6 +132,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links
import Simplex.Chat.Store.SQLite.Migrations.M20250512_member_admission
import Simplex.Chat.Store.SQLite.Migrations.M20250513_group_scope
import Simplex.Chat.Store.SQLite.Migrations.M20250526_short_links
import Simplex.Chat.Store.SQLite.Migrations.M20250702_contact_requests_remove_cascade_delete
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -263,7 +264,8 @@ schemaMigrations =
("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links),
("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission),
("20250513_group_scope", m20250513_group_scope, Just down_m20250513_group_scope),
("20250526_short_links", m20250526_short_links, Just down_m20250526_short_links)
("20250526_short_links", m20250526_short_links, Just down_m20250526_short_links),
("20250702_contact_requests_remove_cascade_delete", m20250702_contact_requests_remove_cascade_delete, Just down_m20250702_contact_requests_remove_cascade_delete)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,46 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.SQLite.Migrations.M20250702_contact_requests_remove_cascade_delete where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20250702_contact_requests_remove_cascade_delete :: Query
m20250702_contact_requests_remove_cascade_delete =
[sql|
PRAGMA writable_schema=1;
UPDATE sqlite_master
SET sql = replace(
replace(
sql,
'user_contact_link_id INTEGER NOT NULL REFERENCES user_contact_links',
'user_contact_link_id INTEGER REFERENCES user_contact_links'
),
'ON UPDATE CASCADE ON DELETE CASCADE,',
'ON UPDATE CASCADE ON DELETE SET NULL,'
)
WHERE name = 'contact_requests' AND type = 'table';
PRAGMA writable_schema=0;
|]
down_m20250702_contact_requests_remove_cascade_delete :: Query
down_m20250702_contact_requests_remove_cascade_delete =
[sql|
PRAGMA writable_schema=1;
UPDATE sqlite_master
SET sql = replace(
replace(
sql,
'ON UPDATE CASCADE ON DELETE SET NULL,',
'ON UPDATE CASCADE ON DELETE CASCADE,'
),
'user_contact_link_id INTEGER REFERENCES user_contact_links',
'user_contact_link_id INTEGER NOT NULL REFERENCES user_contact_links'
)
WHERE name = 'contact_requests' AND type = 'table';
PRAGMA writable_schema=0;
|]
@@ -274,7 +274,7 @@ Plan:
Query:
INSERT INTO conn_invitations
(invitation_id, contact_conn_id, cr_invitation, recipient_conn_info, accepted) VALUES (?, ?, ?, ?, 0);
(invitation_id, contact_conn_id, cr_invitation, recipient_conn_info, accepted) VALUES (?, ?, ?, ?, 0);
Plan:
@@ -822,7 +822,7 @@ Query: DELETE FROM commands WHERE command_id = ?
Plan:
SEARCH commands USING INTEGER PRIMARY KEY (rowid=?)
Query: DELETE FROM conn_invitations WHERE contact_conn_id = ? AND invitation_id = ?
Query: DELETE FROM conn_invitations WHERE invitation_id = ?
Plan:
SEARCH conn_invitations USING PRIMARY KEY (invitation_id=?)
@@ -398,12 +398,11 @@ Query:
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c USING (user_contact_link_id)
JOIN contact_profiles p USING (contact_profile_id)
WHERE cr.user_id = ?
AND cr.xcontact_id = ?
@@ -412,7 +411,6 @@ Query:
Plan:
SEARCH cr USING INDEX idx_contact_requests_updated_at (user_id=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?)
Query:
SELECT COUNT(1)
@@ -1631,12 +1629,11 @@ Query:
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id
JOIN contact_profiles p ON p.contact_profile_id = cr.contact_profile_id
JOIN user_contact_links uc ON uc.user_contact_link_id = cr.user_contact_link_id
WHERE cr.user_id = ?
@@ -1655,18 +1652,16 @@ Plan:
SEARCH uc USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?)
SEARCH cr USING INDEX idx_contact_requests_updated_at (user_id=? AND updated_at<?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?)
Query:
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id
JOIN contact_profiles p ON p.contact_profile_id = cr.contact_profile_id
JOIN user_contact_links uc ON uc.user_contact_link_id = cr.user_contact_link_id
WHERE cr.user_id = ?
@@ -1685,18 +1680,16 @@ Plan:
SEARCH uc USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?)
SEARCH cr USING INDEX idx_contact_requests_updated_at (user_id=? AND updated_at>?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?)
Query:
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id
JOIN contact_profiles p ON p.contact_profile_id = cr.contact_profile_id
JOIN user_contact_links uc ON uc.user_contact_link_id = cr.user_contact_link_id
WHERE cr.user_id = ?
@@ -1715,7 +1708,6 @@ Plan:
SEARCH uc USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?)
SEARCH cr USING INDEX idx_contact_requests_updated_at (user_id=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?)
Query:
SELECT
@@ -4742,35 +4734,31 @@ Query:
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c USING (user_contact_link_id)
JOIN contact_profiles p USING (contact_profile_id)
WHERE cr.user_id = ? AND cr.business_group_id = ?
Plan:
SEARCH cr USING INDEX idx_contact_requests_business_group_id (business_group_id=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?)
Query:
SELECT
cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id,
cr.contact_id, cr.business_group_id, cr.user_contact_link_id,
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id,
cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences,
cr.created_at, cr.updated_at,
cr.peer_chat_min_version, cr.peer_chat_max_version
FROM contact_requests cr
JOIN connections c USING (user_contact_link_id)
JOIN contact_profiles p USING (contact_profile_id)
WHERE cr.user_id = ? AND cr.contact_request_id = ?
Plan:
SEARCH cr USING INTEGER PRIMARY KEY (rowid=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?)
Query:
SELECT
@@ -344,8 +344,8 @@ CREATE TABLE user_contact_links(
);
CREATE TABLE contact_requests(
contact_request_id INTEGER PRIMARY KEY,
user_contact_link_id INTEGER NOT NULL REFERENCES user_contact_links
ON UPDATE CASCADE ON DELETE CASCADE,
user_contact_link_id INTEGER REFERENCES user_contact_links
ON UPDATE CASCADE ON DELETE SET NULL,
agent_invitation_id BLOB NOT NULL,
contact_profile_id INTEGER REFERENCES contact_profiles
ON DELETE SET NULL
+4 -3
View File
@@ -64,6 +64,7 @@ data ChatLockEntity
| CLContact ContactId
| CLGroup GroupId
| CLUserContact Int64
| CLContactRequest Int64
| CLFile Int64
deriving (Eq, Ord)
@@ -488,13 +489,13 @@ getProfileById db userId profileId =
toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) -> LocalProfile
toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Maybe GroupId, Int64) :. (AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact) :. (Maybe XContactId, PQSupport, Maybe SharedMsgId, Maybe SharedMsgId, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat)
type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Maybe GroupId, Maybe Int64) :. (Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact) :. (Maybe XContactId, PQSupport, Maybe SharedMsgId, Maybe SharedMsgId, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat)
toContactRequest :: ContactRequestRow -> UserContactRequest
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, businessGroupId_, userContactLinkId) :. (agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, preferences, createdAt, updatedAt, minVer, maxVer)) = do
toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_) :. (profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, preferences, createdAt, updatedAt, minVer, maxVer)) = do
let profile = Profile {displayName, fullName, image, contactLink, preferences}
cReqChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
in UserContactRequest {contactRequestId, agentInvitationId, contactId_, businessGroupId_, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, createdAt, updatedAt}
in UserContactRequest {contactRequestId, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, createdAt, updatedAt}
userQuery :: Query
userQuery =
+1 -2
View File
@@ -347,8 +347,7 @@ data UserContactRequest = UserContactRequest
agentInvitationId :: AgentInvId,
contactId_ :: Maybe ContactId,
businessGroupId_ :: Maybe GroupId,
userContactLinkId :: Int64,
agentContactConnId :: AgentConnId, -- connection id of user contact
userContactLinkId_ :: Maybe Int64,
cReqChatVRange :: VersionRangeChat,
localDisplayName :: ContactName,
profileId :: Int64,