mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-05 19:29:47 +00:00
chat: fix review findings on simplex_name persistence
- updateUserProfile no longer writes contact_profiles.simplex_name on the user's own row (the column is reserved for peer claims; the user's broadcastable name lives on contacts.simplex_name via uct.simplex_name). - updateMemberContactProfile_'/Reset_' now write simplex_name; new updateMemberProfileWithConflict / updateContactMemberProfileWithConflict variants run conflict-clear and return the displaced name, with processMemberProfileUpdate emitting CEvtSimplexNameConflict. - createContact_ runs conflict-clear before INSERT to avoid UNIQUE constraint violations on first-write peer collisions, returning the displaced name; createPreparedContact / createDirectContact thread it through to APIPrepareContact and saveConnInfo XInfo for event emission. - groups conflict-clear takes ProfileId directly (avoids the NOT IN (NULL) silent-noop edge case when groups.group_profile_id is ON DELETE SET NULL). - Moves clearConflictingContactProfileSimplexName_ to Shared.hs so createContact_ can call it without inducing a circular import.
This commit is contained in:
@@ -2033,7 +2033,11 @@ processChatCommand vr nm = \case
|
||||
_ -> Chat cInfo [] emptyChatStats
|
||||
pure $ CRNewPreparedChat user $ AChat SCTGroup chat
|
||||
ACCL _ (CCLink cReq _) -> do
|
||||
ct <- withStore $ \db -> createPreparedContact db vr user profile accLink welcomeSharedMsgId Nothing
|
||||
(ct, displaced_) <- withStore $ \db -> createPreparedContact db vr user profile accLink welcomeSharedMsgId Nothing
|
||||
let Profile {simplexName = pSimplexName} = profile
|
||||
forM_ ((,) <$> pSimplexName <*> displaced_) $ \(ni, displaced) ->
|
||||
let Contact {localDisplayName = newLDN} = ct
|
||||
in toView $ CEvtSimplexNameConflict user ni SNCEContact newLDN displaced
|
||||
void $ createChatItem user (CDDirectSnd ct) False CIChatBanner Nothing (Just epochStart)
|
||||
let cd = CDDirectRcv ct
|
||||
createItem sharedMsgId content = createChatItem user cd False content sharedMsgId Nothing
|
||||
|
||||
@@ -2683,7 +2683,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
updateBusinessChatProfile gInfo
|
||||
case memberContactId of
|
||||
Nothing -> do
|
||||
m' <- withStore $ \db -> updateMemberProfile db user m p'
|
||||
(m', displaced_) <- withStore $ \db -> updateMemberProfileWithConflict db user m p'
|
||||
let GroupMember {localDisplayName = newLDN} = m'
|
||||
emitSimplexNameConflict newLDN displaced_
|
||||
unless (muteEventInChannel gInfo m') $ do
|
||||
forM_ msgTs_ $ createProfileUpdatedItem m'
|
||||
toView $ CEvtGroupMemberUpdated user gInfo m m'
|
||||
@@ -2692,7 +2694,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
mCt <- withStore $ \db -> getContact db vr user mContactId
|
||||
if canUpdateProfile mCt
|
||||
then do
|
||||
(m', ct') <- withStore $ \db -> updateContactMemberProfile db user m mCt p'
|
||||
(m', ct', displaced_) <- withStore $ \db -> updateContactMemberProfileWithConflict db user m mCt p'
|
||||
let Contact {localDisplayName = newLDN} = ct'
|
||||
emitSimplexNameConflict newLDN displaced_
|
||||
unless (muteEventInChannel gInfo m') $ do
|
||||
forM_ msgTs_ $ createProfileUpdatedItem m'
|
||||
toView $ CEvtGroupMemberUpdated user gInfo m m'
|
||||
@@ -2709,6 +2713,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
pure m
|
||||
where
|
||||
allowSimplexLinks = groupFeatureMemberAllowed SGFSimplexLinks m gInfo
|
||||
Profile {simplexName = p'SimplexName} = p'
|
||||
emitSimplexNameConflict newLDN displaced_ =
|
||||
forM_ ((,) <$> p'SimplexName <*> displaced_) $ \(ni, displaced) ->
|
||||
toView $ CEvtSimplexNameConflict user ni SNCEContact newLDN displaced
|
||||
updateBusinessChatProfile g@GroupInfo {businessChat} = case businessChat of
|
||||
Just bc | isMainBusinessMember bc m -> do
|
||||
g' <- withStore $ \db -> updateGroupProfileFromMember db user g p'
|
||||
@@ -2950,7 +2958,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
case chatMsgEvent of
|
||||
XInfo p -> do
|
||||
let Connection {simplexName} = conn'
|
||||
ct <- withStore $ \db -> createDirectContact db vr user conn' p simplexName
|
||||
Profile {simplexName = pSimplexName} = p
|
||||
(ct, displaced_) <- withStore $ \db -> createDirectContact db vr user conn' p simplexName
|
||||
forM_ ((,) <$> pSimplexName <*> displaced_) $ \(ni, displaced) ->
|
||||
let Contact {localDisplayName = newLDN} = ct
|
||||
in toView $ CEvtSimplexNameConflict user ni SNCEContact newLDN displaced
|
||||
toView $ CEvtContactConnecting user ct
|
||||
pure (conn', Nothing)
|
||||
XGrpLinkInv glInv -> do
|
||||
|
||||
@@ -400,13 +400,18 @@ createIncognitoProfile db User {userId} p = do
|
||||
createdAt <- getCurrentTime
|
||||
createIncognitoProfile_ db userId createdAt p
|
||||
|
||||
createPreparedContact :: DB.Connection -> VersionRangeChat -> User -> Profile -> ACreatedConnLink -> Maybe SharedMsgId -> Maybe SimplexNameInfo -> ExceptT StoreError IO Contact
|
||||
-- | Returns (contact, displaced) — displaced is Just the display_name of a
|
||||
-- contact_profiles row whose peer-claimed simplex_name was cleared to make
|
||||
-- room for the new contact's claim, so the caller can emit
|
||||
-- CEvtSimplexNameConflict.
|
||||
createPreparedContact :: DB.Connection -> VersionRangeChat -> User -> Profile -> ACreatedConnLink -> Maybe SharedMsgId -> Maybe SimplexNameInfo -> ExceptT StoreError IO (Contact, Maybe ContactName)
|
||||
createPreparedContact db vr user p connLinkToConnect welcomeSharedMsgId simplexName = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
let prepared = Just (connLinkToConnect, welcomeSharedMsgId)
|
||||
ctUserPreferences = newContactUserPrefs user p
|
||||
contactId <- createContact_ db user p ctUserPreferences prepared "" currentTs simplexName
|
||||
getContact db vr user contactId
|
||||
(contactId, displaced) <- createContact_ db user p ctUserPreferences prepared "" currentTs simplexName
|
||||
ct <- getContact db vr user contactId
|
||||
pure (ct, displaced)
|
||||
|
||||
updatePreparedContactUser :: DB.Connection -> VersionRangeChat -> User -> Contact -> User -> ExceptT StoreError IO Contact
|
||||
updatePreparedContactUser
|
||||
@@ -446,13 +451,15 @@ updatePreparedContactUser
|
||||
safeDeleteLDN db user oldLDN
|
||||
getContact db vr newUser contactId
|
||||
|
||||
createDirectContact :: DB.Connection -> VersionRangeChat -> User -> Connection -> Profile -> Maybe SimplexNameInfo -> ExceptT StoreError IO Contact
|
||||
-- | Returns (contact, displaced) — see createPreparedContact for displaced.
|
||||
createDirectContact :: DB.Connection -> VersionRangeChat -> User -> Connection -> Profile -> Maybe SimplexNameInfo -> ExceptT StoreError IO (Contact, Maybe ContactName)
|
||||
createDirectContact db vr user Connection {connId, localAlias} p simplexName = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
let ctUserPreferences = newContactUserPrefs user p
|
||||
contactId <- createContact_ db user p ctUserPreferences Nothing localAlias currentTs simplexName
|
||||
(contactId, displaced) <- createContact_ db user p ctUserPreferences Nothing localAlias currentTs simplexName
|
||||
liftIO $ DB.execute db "UPDATE connections SET contact_id = ?, updated_at = ? WHERE connection_id = ?" (contactId, currentTs, connId)
|
||||
getContact db vr user contactId
|
||||
ct <- getContact db vr user contactId
|
||||
pure (ct, displaced)
|
||||
|
||||
deleteContactConnections :: DB.Connection -> User -> Contact -> IO ()
|
||||
deleteContactConnections db User {userId} Contact {contactId} = do
|
||||
@@ -726,38 +733,6 @@ updateContactProfile_' db userId profileId Profile {displayName, fullName, short
|
||||
|]
|
||||
((displayName, fullName, shortDescr, image, contactLink, simplexName, preferences, peerType, updatedAt) :. (userId, profileId))
|
||||
|
||||
-- | Clears simplex_name on any other contact_profiles row that holds the same
|
||||
-- (user_id, simplex_name) so a subsequent UPDATE/INSERT setting that value
|
||||
-- won't trip the partial UNIQUE index. Pass the profileId being updated to
|
||||
-- exclude self; pass Nothing for the pre-INSERT case. Returns the displaced
|
||||
-- row's display_name when a conflict was resolved, for the caller to surface
|
||||
-- as CEvtSimplexNameConflict. Newer-claim-wins matches RSLV semantics: the
|
||||
-- latest broadcast is the canonical assignment.
|
||||
clearConflictingContactProfileSimplexName_ :: DB.Connection -> UserId -> Maybe ProfileId -> Maybe SimplexNameInfo -> IO (Maybe ContactName)
|
||||
clearConflictingContactProfileSimplexName_ _ _ _ Nothing = pure Nothing
|
||||
clearConflictingContactProfileSimplexName_ db userId Nothing (Just simplexName) =
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
UPDATE contact_profiles
|
||||
SET simplex_name = NULL
|
||||
WHERE user_id = ? AND simplex_name = ?
|
||||
RETURNING display_name
|
||||
|]
|
||||
(userId, simplexName)
|
||||
clearConflictingContactProfileSimplexName_ db userId (Just profileId) (Just simplexName) =
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
UPDATE contact_profiles
|
||||
SET simplex_name = NULL
|
||||
WHERE user_id = ? AND simplex_name = ? AND contact_profile_id <> ?
|
||||
RETURNING display_name
|
||||
|]
|
||||
(userId, simplexName, 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 -> IO ()
|
||||
updateMemberContactProfileReset_ db userId profileId profile = do
|
||||
@@ -765,15 +740,15 @@ updateMemberContactProfileReset_ db userId profileId profile = do
|
||||
updateMemberContactProfileReset_' db userId profileId profile currentTs
|
||||
|
||||
updateMemberContactProfileReset_' :: DB.Connection -> UserId -> ProfileId -> Profile -> UTCTime -> IO ()
|
||||
updateMemberContactProfileReset_' db userId profileId Profile {displayName, fullName, shortDescr, image} updatedAt = do
|
||||
updateMemberContactProfileReset_' db userId profileId Profile {displayName, fullName, shortDescr, image, simplexName} updatedAt = do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE contact_profiles
|
||||
SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = NULL, preferences = NULL, updated_at = ?
|
||||
SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = NULL, simplex_name = ?, preferences = NULL, updated_at = ?
|
||||
WHERE user_id = ? AND contact_profile_id = ?
|
||||
|]
|
||||
(displayName, fullName, shortDescr, image, updatedAt, userId, profileId)
|
||||
(displayName, fullName, shortDescr, image, simplexName, updatedAt, userId, profileId)
|
||||
|
||||
-- update only member profile fields (when member has associated contact - we keep contactLink and prefs)
|
||||
updateMemberContactProfile_ :: DB.Connection -> UserId -> ProfileId -> Profile -> IO ()
|
||||
@@ -782,15 +757,15 @@ updateMemberContactProfile_ db userId profileId profile = do
|
||||
updateMemberContactProfile_' db userId profileId profile currentTs
|
||||
|
||||
updateMemberContactProfile_' :: DB.Connection -> UserId -> ProfileId -> Profile -> UTCTime -> IO ()
|
||||
updateMemberContactProfile_' db userId profileId Profile {displayName, fullName, shortDescr, image} updatedAt = do
|
||||
updateMemberContactProfile_' db userId profileId Profile {displayName, fullName, shortDescr, image, simplexName} updatedAt = do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE contact_profiles
|
||||
SET display_name = ?, full_name = ?, short_descr = ?, image = ?, updated_at = ?
|
||||
SET display_name = ?, full_name = ?, short_descr = ?, image = ?, simplex_name = ?, updated_at = ?
|
||||
WHERE user_id = ? AND contact_profile_id = ?
|
||||
|]
|
||||
(displayName, fullName, shortDescr, image, updatedAt, userId, profileId)
|
||||
(displayName, fullName, shortDescr, image, simplexName, updatedAt, userId, profileId)
|
||||
|
||||
updateContactLDN_ :: DB.Connection -> User -> Int64 -> ContactName -> ContactName -> UTCTime -> IO ()
|
||||
updateContactLDN_ db user@User {userId} contactId displayName newName updatedAt = do
|
||||
|
||||
@@ -166,7 +166,9 @@ module Simplex.Chat.Store.Groups
|
||||
setMemberContactStartedConnection,
|
||||
resetMemberContactFields,
|
||||
updateMemberProfile,
|
||||
updateMemberProfileWithConflict,
|
||||
updateContactMemberProfile,
|
||||
updateContactMemberProfileWithConflict,
|
||||
getXGrpLinkMemReceived,
|
||||
setXGrpLinkMemReceived,
|
||||
createNewUnknownGroupMember,
|
||||
@@ -2379,13 +2381,15 @@ updateGroupProfileWithConflict :: DB.Connection -> User -> GroupInfo -> GroupPro
|
||||
updateGroupProfileWithConflict db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, shortDescr, description, image, publicGroup, simplexName, groupPreferences, memberAdmission}
|
||||
| displayName == newName = liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
displaced <- clearConflictingGroupProfileSimplexName_ db userId (Just groupId) simplexName
|
||||
profileId_ <- getGroupProfileId_
|
||||
displaced <- clearConflictingGroupProfileSimplexName_ db userId profileId_ simplexName
|
||||
updateGroupProfile_ currentTs
|
||||
pure ((g :: GroupInfo) {groupProfile = p', fullGroupPreferences}, displaced)
|
||||
| otherwise =
|
||||
ExceptT . withLocalDisplayName db userId newName $ \ldn -> do
|
||||
currentTs <- getCurrentTime
|
||||
displaced <- clearConflictingGroupProfileSimplexName_ db userId (Just groupId) simplexName
|
||||
profileId_ <- getGroupProfileId_
|
||||
displaced <- clearConflictingGroupProfileSimplexName_ db userId profileId_ simplexName
|
||||
updateGroupProfile_ currentTs
|
||||
updateGroup_ ldn currentTs
|
||||
pure $ Right ((g :: GroupInfo) {localDisplayName = ldn, groupProfile = p', fullGroupPreferences}, displaced)
|
||||
@@ -2394,6 +2398,13 @@ updateGroupProfileWithConflict db user@User {userId} g@GroupInfo {groupId, local
|
||||
(groupType_, groupLink_) = case publicGroup of
|
||||
Just PublicGroupProfile {groupType, groupLink} -> (Just groupType, Just groupLink)
|
||||
Nothing -> (Nothing, Nothing)
|
||||
-- groups.group_profile_id is ON DELETE SET NULL; treat the row as having
|
||||
-- no profile to exclude (Nothing) when it has been nulled out, so the
|
||||
-- conflict-clear sees every same-user row as a potential collision.
|
||||
getGroupProfileId_ :: IO (Maybe ProfileId)
|
||||
getGroupProfileId_ =
|
||||
fmap (>>= fromOnly) . maybeFirstRow id $
|
||||
DB.query db "SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ?" (userId, groupId)
|
||||
updateGroupProfile_ currentTs =
|
||||
DB.execute
|
||||
db
|
||||
@@ -2419,9 +2430,12 @@ updateGroupProfileWithConflict db user@User {userId} g@GroupInfo {groupId, local
|
||||
safeDeleteLDN db user localDisplayName
|
||||
|
||||
-- | Mirror of clearConflictingContactProfileSimplexName_ for group_profiles.
|
||||
-- Pass the groupId being updated to exclude its underlying group_profile_id
|
||||
-- from the clear; pass Nothing for the pre-INSERT case. See that helper.
|
||||
clearConflictingGroupProfileSimplexName_ :: DB.Connection -> UserId -> Maybe GroupId -> Maybe SimplexNameInfo -> IO (Maybe GroupName)
|
||||
-- Pass the group_profile_id being updated to exclude self from the clear;
|
||||
-- pass Nothing for the pre-INSERT case. The profileId is taken directly
|
||||
-- (rather than derived from groupId via a NOT IN subquery) because
|
||||
-- groups.group_profile_id is ON DELETE SET NULL, and NOT IN (NULL)
|
||||
-- evaluates to UNKNOWN — which would silently no-op the clear.
|
||||
clearConflictingGroupProfileSimplexName_ :: DB.Connection -> UserId -> Maybe ProfileId -> Maybe SimplexNameInfo -> IO (Maybe GroupName)
|
||||
clearConflictingGroupProfileSimplexName_ _ _ _ Nothing = pure Nothing
|
||||
clearConflictingGroupProfileSimplexName_ db userId Nothing (Just simplexName) =
|
||||
maybeFirstRow fromOnly $
|
||||
@@ -2434,21 +2448,17 @@ clearConflictingGroupProfileSimplexName_ db userId Nothing (Just simplexName) =
|
||||
RETURNING display_name
|
||||
|]
|
||||
(userId, simplexName)
|
||||
clearConflictingGroupProfileSimplexName_ db userId (Just groupId) (Just simplexName) =
|
||||
clearConflictingGroupProfileSimplexName_ db userId (Just profileId) (Just simplexName) =
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
UPDATE group_profiles
|
||||
SET simplex_name = NULL
|
||||
WHERE user_id = ?
|
||||
AND simplex_name = ?
|
||||
AND group_profile_id NOT IN (
|
||||
SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ?
|
||||
)
|
||||
WHERE user_id = ? AND simplex_name = ? AND group_profile_id <> ?
|
||||
RETURNING display_name
|
||||
|]
|
||||
(userId, simplexName, userId, groupId)
|
||||
(userId, simplexName, profileId)
|
||||
|
||||
updateGroupPreferences :: DB.Connection -> User -> GroupInfo -> GroupPreferences -> IO GroupInfo
|
||||
updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} ps = do
|
||||
@@ -3076,39 +3086,59 @@ 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
|
||||
liftIO $ updateMemberContactProfileReset_ db userId profileId p'
|
||||
pure m {memberProfile = profile}
|
||||
updateMemberProfile db user m p' = fst <$> updateMemberProfileWithConflict db user m p'
|
||||
|
||||
-- | Like updateMemberProfile but additionally clears the simplex_name on any
|
||||
-- other contact_profiles row in the same user that already holds the same
|
||||
-- (user_id, simplex_name) — returning that row's display_name so the caller
|
||||
-- can emit CEvtSimplexNameConflict. Used by the incoming XInfo (member) path.
|
||||
updateMemberProfileWithConflict :: DB.Connection -> User -> GroupMember -> Profile -> ExceptT StoreError IO (GroupMember, Maybe ContactName)
|
||||
updateMemberProfileWithConflict db user@User {userId} m p'
|
||||
| displayName == newName = liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
displaced <- clearConflictingContactProfileSimplexName_ db userId (Just profileId) profileSimplexName
|
||||
updateMemberContactProfileReset_' db userId profileId p' currentTs
|
||||
pure (m {memberProfile = profile}, displaced)
|
||||
| otherwise =
|
||||
ExceptT . withLocalDisplayName db userId newName $ \ldn -> do
|
||||
currentTs <- getCurrentTime
|
||||
displaced <- clearConflictingContactProfileSimplexName_ db userId (Just profileId) profileSimplexName
|
||||
updateMemberContactProfileReset_' db userId profileId p' 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 = profile}
|
||||
pure $ Right (m {localDisplayName = ldn, memberProfile = profile}, displaced)
|
||||
where
|
||||
GroupMember {groupMemberId, localDisplayName, memberProfile = LocalProfile {profileId, displayName, localAlias}} = m
|
||||
Profile {displayName = newName} = p'
|
||||
Profile {displayName = newName, simplexName = profileSimplexName} = p'
|
||||
profile = toLocalProfile profileId p' localAlias
|
||||
|
||||
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
|
||||
liftIO $ updateMemberContactProfile_ db userId profileId p'
|
||||
pure (m {memberProfile = profile}, ct {profile} :: Contact)
|
||||
updateContactMemberProfile db user m ct p' = (\(m', ct', _) -> (m', ct')) <$> updateContactMemberProfileWithConflict db user m ct p'
|
||||
|
||||
-- | Like updateContactMemberProfile but additionally clears the simplex_name
|
||||
-- on any other contact_profiles row in the same user that already holds the
|
||||
-- same (user_id, simplex_name) — returning that row's display_name so the
|
||||
-- caller can emit CEvtSimplexNameConflict.
|
||||
updateContactMemberProfileWithConflict :: DB.Connection -> User -> GroupMember -> Contact -> Profile -> ExceptT StoreError IO (GroupMember, Contact, Maybe ContactName)
|
||||
updateContactMemberProfileWithConflict db user@User {userId} m ct@Contact {contactId} p'
|
||||
| displayName == newName = liftIO $ do
|
||||
currentTs <- getCurrentTime
|
||||
displaced <- clearConflictingContactProfileSimplexName_ db userId (Just profileId) profileSimplexName
|
||||
updateMemberContactProfile_' db userId profileId p' currentTs
|
||||
pure (m {memberProfile = profile}, ct {profile} :: Contact, displaced)
|
||||
| otherwise =
|
||||
ExceptT . withLocalDisplayName db userId newName $ \ldn -> do
|
||||
currentTs <- getCurrentTime
|
||||
displaced <- clearConflictingContactProfileSimplexName_ db userId (Just profileId) profileSimplexName
|
||||
updateMemberContactProfile_' db userId profileId p' currentTs
|
||||
updateContactLDN_ db user contactId localDisplayName ldn currentTs
|
||||
pure $ Right (m {localDisplayName = ldn, memberProfile = profile}, ct {localDisplayName = ldn, profile} :: Contact)
|
||||
pure $ Right (m {localDisplayName = ldn, memberProfile = profile}, ct {localDisplayName = ldn, profile} :: Contact, displaced)
|
||||
where
|
||||
GroupMember {localDisplayName, memberProfile = LocalProfile {profileId, displayName, localAlias}} = m
|
||||
Profile {displayName = newName} = p'
|
||||
Profile {displayName = newName, simplexName = profileSimplexName} = p'
|
||||
profile = toLocalProfile profileId p' localAlias
|
||||
|
||||
getXGrpLinkMemReceived :: DB.Connection -> GroupMemberId -> ExceptT StoreError IO Bool
|
||||
|
||||
@@ -317,7 +317,7 @@ updateUserAutoAcceptMemberContacts db User {userId} autoAccept =
|
||||
updateUserProfile :: DB.Connection -> User -> Profile -> ExceptT StoreError IO User
|
||||
updateUserProfile db user p'
|
||||
| displayName == newName = liftIO $ do
|
||||
updateContactProfile_ db userId profileId p'
|
||||
updateContactProfile_ db userId profileId pNoSimplexName
|
||||
currentTs <- getCurrentTime
|
||||
userMemberProfileUpdatedAt' <- updateUserMemberProfileUpdatedAt_ currentTs
|
||||
pure user {profile, fullPreferences, userMemberProfileUpdatedAt = userMemberProfileUpdatedAt'}
|
||||
@@ -330,7 +330,7 @@ updateUserProfile db user p'
|
||||
db
|
||||
"INSERT INTO display_names (local_display_name, ldn_base, user_id, created_at, updated_at) VALUES (?,?,?,?,?)"
|
||||
(newName, newName, userId, currentTs, currentTs)
|
||||
updateContactProfile_' db userId profileId p' currentTs
|
||||
updateContactProfile_' db userId profileId pNoSimplexName currentTs
|
||||
updateContactLDN_ db user userContactId localDisplayName newName currentTs
|
||||
pure user {localDisplayName = newName, profile, fullPreferences, userMemberProfileUpdatedAt = userMemberProfileUpdatedAt'}
|
||||
where
|
||||
@@ -342,6 +342,12 @@ updateUserProfile db user p'
|
||||
userMemberProfileChanged = newName /= displayName || fn' /= fullName || d' /= shortDescr || img' /= image
|
||||
User {userId, userContactId, localDisplayName, profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, localAlias}, userMemberProfileUpdatedAt} = user
|
||||
Profile {displayName = newName, fullName = fn', shortDescr = d', image = img', preferences} = p'
|
||||
-- contact_profiles.simplex_name is reserved for peer claims received via XInfo.
|
||||
-- The user's own broadcastable simplex_name lives on contacts.simplex_name
|
||||
-- (loaded by toUser into User.profile.simplexName via uct.simplex_name);
|
||||
-- writing it here would (a) collide with peer claims on the partial UNIQUE
|
||||
-- index, and (b) make a subsequent peer claim displace the user's own row.
|
||||
pNoSimplexName = (p' :: Profile) {simplexName = Nothing}
|
||||
profile = toLocalProfile profileId p' localAlias
|
||||
fullPreferences = fullPreferences' preferences
|
||||
|
||||
|
||||
@@ -421,9 +421,50 @@ createContact db user profile = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
void $ createContact_ db user profile emptyChatPrefs Nothing "" currentTs Nothing
|
||||
|
||||
createContact_ :: DB.Connection -> User -> Profile -> Preferences -> Maybe (ACreatedConnLink, Maybe SharedMsgId) -> LocalAlias -> UTCTime -> Maybe SimplexNameInfo -> ExceptT StoreError IO ContactId
|
||||
-- | Clears simplex_name on any other contact_profiles row that holds the same
|
||||
-- (user_id, simplex_name) so a subsequent UPDATE/INSERT setting that value
|
||||
-- won't trip the partial UNIQUE index. Pass the profileId being updated to
|
||||
-- exclude self; pass Nothing for the pre-INSERT case. Returns the displaced
|
||||
-- row's display_name when a conflict was resolved, for the caller to surface
|
||||
-- as CEvtSimplexNameConflict. Newer-claim-wins matches RSLV semantics: the
|
||||
-- latest broadcast is the canonical assignment.
|
||||
clearConflictingContactProfileSimplexName_ :: DB.Connection -> UserId -> Maybe ProfileId -> Maybe SimplexNameInfo -> IO (Maybe ContactName)
|
||||
clearConflictingContactProfileSimplexName_ _ _ _ Nothing = pure Nothing
|
||||
clearConflictingContactProfileSimplexName_ db userId Nothing (Just simplexName) =
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
UPDATE contact_profiles
|
||||
SET simplex_name = NULL
|
||||
WHERE user_id = ? AND simplex_name = ?
|
||||
RETURNING display_name
|
||||
|]
|
||||
(userId, simplexName)
|
||||
clearConflictingContactProfileSimplexName_ db userId (Just profileId) (Just simplexName) =
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
UPDATE contact_profiles
|
||||
SET simplex_name = NULL
|
||||
WHERE user_id = ? AND simplex_name = ? AND contact_profile_id <> ?
|
||||
RETURNING display_name
|
||||
|]
|
||||
(userId, simplexName, profileId)
|
||||
|
||||
-- | Inserts a new contact and its profile. Returns the new contactId and,
|
||||
-- if the peer-claimed Profile.simplexName collided with an existing row
|
||||
-- (the partial UNIQUE index on contact_profiles.(user_id, simplex_name)),
|
||||
-- the display_name of the displaced row — newer-claim-wins. The caller
|
||||
-- is responsible for emitting CEvtSimplexNameConflict on displacement.
|
||||
createContact_ :: DB.Connection -> User -> Profile -> Preferences -> Maybe (ACreatedConnLink, Maybe SharedMsgId) -> LocalAlias -> UTCTime -> Maybe SimplexNameInfo -> ExceptT StoreError IO (ContactId, Maybe ContactName)
|
||||
createContact_ db User {userId} Profile {displayName, fullName, shortDescr, image, contactLink, simplexName = profileSimplexName, peerType, preferences} ctUserPreferences prepared localAlias currentTs simplexName =
|
||||
ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do
|
||||
-- Clear any existing peer claim on the same simplex_name before INSERT
|
||||
-- so the partial UNIQUE index doesn't reject the new row. Pass Nothing
|
||||
-- as the excluded profileId — there's no self-row yet.
|
||||
displaced <- clearConflictingContactProfileSimplexName_ db userId Nothing profileSimplexName
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, chat_peer_type, user_id, local_alias, preferences, simplex_name, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"
|
||||
@@ -434,7 +475,7 @@ createContact_ db User {userId} Profile {displayName, fullName, shortDescr, imag
|
||||
"INSERT INTO contacts (contact_profile_id, user_preferences, local_display_name, user_id, created_at, updated_at, chat_ts, contact_used, conn_full_link_to_connect, conn_short_link_to_connect, welcome_shared_msg_id, simplex_name) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"
|
||||
((profileId, ctUserPreferences, ldn, userId, currentTs, currentTs, currentTs, BI True) :. toPreparedContactRow prepared :. Only simplexName)
|
||||
contactId <- insertedRowId db
|
||||
pure $ Right contactId
|
||||
pure $ Right (contactId, displaced)
|
||||
|
||||
newContactUserPrefs :: User -> Profile -> Preferences
|
||||
newContactUserPrefs User {fullPreferences = FullPreferences {timedMessages = userTM}} Profile {preferences} =
|
||||
|
||||
Reference in New Issue
Block a user