Merge branch 'master' into short-links

This commit is contained in:
spaced4ndy
2025-06-11 20:31:54 +04:00
76 changed files with 5203 additions and 3129 deletions

View File

@@ -747,7 +747,7 @@ data ChatResponse
| CRArchiveImported {archiveErrors :: [ArchiveError]}
| CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]}
#endif
| CRDebugLocks {chatLockName :: Maybe String, chatEntityLocks :: Map String String, agentLocks :: AgentLocks}
| CRDebugLocks {chatLockName :: Maybe Text, chatEntityLocks :: Map Text Text, agentLocks :: AgentLocks}
| CRAgentSubsTotal {user :: User, subsTotal :: SMPServerSubs, hasSession :: Bool}
| CRAgentServersSummary {user :: User, serversSummary :: PresentedServersSummary}
| CRAgentWorkersDetails {agentWorkersDetails :: AgentWorkersDetails}

View File

@@ -1679,7 +1679,8 @@ processChatCommand' vr = \case
if short
then Just $ encodeShortLinkData (ContactShortLinkData (userProfileToSend user incognitoProfile Nothing False) Nothing)
else Nothing
(connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation userData Nothing IKPQOn subMode
-- TODO [certs rcv]
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation userData Nothing IKPQOn subMode
ccLink' <- shortenCreatedLink ccLink
-- TODO PQ pass minVersion from the current range
conn <- withFastStore' $ \db -> createDirectConnection db user connId ccLink' Nothing ConnNew incognitoProfile subMode initialChatVersion PQSupportOn
@@ -1737,7 +1738,8 @@ processChatCommand' vr = \case
if short
then Just $ encodeShortLinkData (ContactShortLinkData (userProfileToSend user Nothing Nothing False) Nothing)
else Nothing
(agConnId, ccLink) <- withAgent $ \a -> createConnection a (aUserId newUser) True SCMInvitation userData Nothing IKPQOn subMode
-- TODO [certs rcv]
(agConnId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a (aUserId newUser) True SCMInvitation userData Nothing IKPQOn subMode
ccLink' <- shortenCreatedLink ccLink
conn' <- withFastStore' $ \db -> do
deleteConnectionRecord db user connId
@@ -1838,7 +1840,8 @@ processChatCommand' vr = \case
if short
then Just $ encodeShortLinkData (ContactShortLinkData (userProfileToSend user Nothing Nothing False) Nothing)
else Nothing
(connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact userData Nothing IKPQOn subMode
-- TODO [certs rcv]
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact userData Nothing IKPQOn subMode
ccLink' <- shortenCreatedLink ccLink
withFastStore $ \db -> createUserContactLink db user connId ccLink' short subMode
pure $ CRUserContactLinkCreated user ccLink'
@@ -2061,7 +2064,8 @@ processChatCommand' vr = \case
Nothing -> do
gVar <- asks random
subMode <- chatReadVar subscriptionMode
(agentConnId, CCLink cReq _) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode
-- TODO [certs rcv]
(agentConnId, (CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode
member <- withFastStore $ \db -> createNewContactMember db gVar user gInfo contact memRole agentConnId cReq subMode
sendInvitation member cReq
pure $ CRSentGroupInvitation user gInfo contact member
@@ -2453,7 +2457,8 @@ processChatCommand' vr = \case
then Just $ encodeShortLinkData (GroupShortLinkData groupProfile)
else Nothing
crClientData = encodeJSON $ CRDataGroup groupLinkId
(connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact userData (Just crClientData) IKPQOff subMode
-- TODO [certs rcv]
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact userData (Just crClientData) IKPQOff subMode
ccLink' <- createdGroupLink <$> shortenCreatedLink ccLink
gLink <- withFastStore $ \db -> createGroupLink db user gInfo connId ccLink' groupLinkId mRole short subMode
pure $ CRGroupLinkCreated user gInfo gLink
@@ -2491,7 +2496,8 @@ processChatCommand' vr = \case
when (isJust $ memberContactId m) $ throwCmdError "member contact already exists"
subMode <- chatReadVar subscriptionMode
-- TODO PQ should negotitate contact connection with PQSupportOn?
(connId, CCLink cReq _) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode
-- TODO [certs rcv]
(connId, (CCLink cReq _, _serviceId)) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode
-- [incognito] reuse membership incognito profile
ct <- withFastStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode
-- TODO not sure it is correct to set connections status here?
@@ -2743,12 +2749,12 @@ processChatCommand' vr = \case
where
getLocks ls = atomically $ M.mapKeys enityLockString . M.mapMaybe id <$> (mapM tryReadTMVar =<< readTVar ls)
enityLockString cle = case cle of
CLInvitation bs -> "Invitation " <> B.unpack bs
CLConnection connId -> "Connection " <> show connId
CLContact ctId -> "Contact " <> show ctId
CLGroup gId -> "Group " <> show gId
CLUserContact ucId -> "UserContact " <> show ucId
CLFile fId -> "File " <> show fId
CLInvitation bs -> "Invitation " <> safeDecodeUtf8 bs
CLConnection connId -> "Connection " <> tshow connId
CLContact ctId -> "Contact " <> tshow ctId
CLGroup gId -> "Group " <> tshow gId
CLUserContact ucId -> "UserContact " <> tshow ucId
CLFile fId -> "File " <> tshow fId
DebugEvent event -> toView event >> ok_
GetAgentSubsTotal userId -> withUserId userId $ \user -> do
users <- withStore' $ \db -> getUsers db
@@ -2881,7 +2887,7 @@ processChatCommand' vr = \case
pcc <- withFastStore' $ \db -> createDirectConnection db user connId ccLink contactId_ ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup'
joinPreparedConn connId pcc dm
joinPreparedConn connId pcc@PendingContactConnection {pccConnId} dm = do
sqSecured <- withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode
(sqSecured, _serviceId) <- withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode
let newStatus = if sqSecured then ConnSndReady else ConnJoined
withFastStore' $ \db -> updateConnectionStatusFromTo db pccConnId ConnPrepared newStatus
pure $ CRSentConfirmation user pcc {pccConnStatus = newStatus}
@@ -2990,7 +2996,7 @@ processChatCommand' vr = \case
pure $ CRUserProfileUpdated user' (fromLocalProfile p) p' summary
where
setMyAddressData' :: User -> CM ()
setMyAddressData' user' = do
setMyAddressData' user' =
withFastStore' (\db -> runExceptT $ getUserAddress db user) >>= \case
Right ucl@UserContactLink {shortLinkDataSet}
| shortLinkDataSet -> void $ setMyAddressData user' ucl
@@ -3076,12 +3082,12 @@ processChatCommand' vr = \case
recipients = filter memberCurrentOrPending newMs
sendGroupMessage user g' Nothing recipients $ XGrpPrefs ps'
Nothing -> do
setGroupLinkData' g'
setGroupLinkData'
let recipients = filter memberCurrentOrPending ms
sendGroupMessage user g' Nothing recipients (XGrpInfo p')
where
setGroupLinkData' :: GroupInfo -> CM ()
setGroupLinkData' g' = do
setGroupLinkData' :: CM ()
setGroupLinkData' =
withFastStore' (\db -> runExceptT $ getGroupLink db user g') >>= \case
Right gLink@GroupLink {shortLinkDataSet}
| shortLinkDataSet -> void $ setGroupLinkData user g' gLink
@@ -3875,8 +3881,9 @@ agentSubscriber = do
where
run action = action `catchChatError'` (eToView')
type AgentBatchSubscribe = AgentClient -> [ConnId] -> ExceptT AgentErrorType IO (Map ConnId (Either AgentErrorType ()))
type AgentBatchSubscribe = AgentClient -> [ConnId] -> ExceptT AgentErrorType IO (Map ConnId (Either AgentErrorType (Maybe ClientServiceId)))
-- TODO [certs rcv]
subscribeUserConnections :: VersionRangeChat -> Bool -> AgentBatchSubscribe -> User -> CM ()
subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do
-- get user connections
@@ -3970,7 +3977,7 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do
pcs <- withStore_ getPendingContactConnections
let connIds = map aConnId' pcs
pure (connIds, M.fromList $ zip connIds pcs)
contactSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId Contact -> Bool -> CM ()
contactSubsToView :: Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> Map ConnId Contact -> Bool -> CM ()
contactSubsToView rs cts ce = do
chatModifyVar connNetworkStatuses $ M.union (M.fromList statuses)
ifM (asks $ coreApi . config) (notifyAPI statuses) notifyCLI
@@ -3996,9 +4003,9 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do
ChatErrorAgent (SMP _ SMP.AUTH) _ -> "contact deleted"
e -> show e
-- TODO possibly below could be replaced with less noisy events for API
contactLinkSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId UserContact -> CM ()
contactLinkSubsToView :: Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> Map ConnId UserContact -> CM ()
contactLinkSubsToView rs = toView . CEvtUserContactSubSummary user . map (uncurry UserContactSubStatus) . resultsFor rs
groupSubsToView :: Map ConnId (Either AgentErrorType ()) -> [ShortGroup] -> Map ConnId ShortGroupMember -> Bool -> CM ()
groupSubsToView :: Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> [ShortGroup] -> Map ConnId ShortGroupMember -> Bool -> CM ()
groupSubsToView rs gs ms ce = do
mapM_ groupSub $
sortOn (\(ShortGroup ShortGroupInfo {groupName = g} _) -> g) gs
@@ -4020,7 +4027,7 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do
| membershipStatus == GSMemInvited = TEGroupInvitation user g
| null members = TEGroupEmpty user g
| otherwise = TEGroupSubscribed user g
sndFileSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId SndFileTransfer -> CM ()
sndFileSubsToView :: Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> Map ConnId SndFileTransfer -> CM ()
sndFileSubsToView rs sfts = do
let sftRs = resultsFor rs sfts
forM_ sftRs $ \(ft@SndFileTransfer {fileId, fileStatus}, err_) -> do
@@ -4029,20 +4036,20 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do
threadDelay 1000000
when (fileStatus == FSConnected) . unlessM (isFileActive fileId sndFiles) . withChatLock "subscribe sendFileChunk" $
sendFileChunk user ft
rcvFileSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId RcvFileTransfer -> CM ()
rcvFileSubsToView :: Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> Map ConnId RcvFileTransfer -> CM ()
rcvFileSubsToView rs = mapM_ (toViewTE . uncurry (TERcvFileSubError user)) . filterErrors . resultsFor rs
pendingConnSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId PendingContactConnection -> CM ()
pendingConnSubsToView :: Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> Map ConnId PendingContactConnection -> CM ()
pendingConnSubsToView rs = toViewTE . TEPendingSubSummary user . map (uncurry PendingSubStatus) . resultsFor rs
withStore_ :: (DB.Connection -> User -> IO [a]) -> CM [a]
withStore_ a = withStore' (`a` user) `catchChatError` \e -> eToView e $> []
filterErrors :: [(a, Maybe ChatError)] -> [(a, ChatError)]
filterErrors = mapMaybe (\(a, e_) -> (a,) <$> e_)
resultsFor :: Map ConnId (Either AgentErrorType ()) -> Map ConnId a -> [(a, Maybe ChatError)]
resultsFor :: Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> Map ConnId a -> [(a, Maybe ChatError)]
resultsFor rs = M.foldrWithKey' addResult []
where
addResult :: ConnId -> a -> [(a, Maybe ChatError)] -> [(a, Maybe ChatError)]
addResult connId = (:) . (,resultErr connId rs)
resultErr :: ConnId -> Map ConnId (Either AgentErrorType ()) -> Maybe ChatError
resultErr :: ConnId -> Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -> Maybe ChatError
resultErr connId rs = case M.lookup connId rs of
Just (Left e) -> Just $ ChatErrorAgent e Nothing
Just _ -> Nothing

View File

@@ -111,37 +111,37 @@ maxRcvMentions = 5
maxSndMentions :: Int
maxSndMentions = 3
withChatLock :: String -> CM a -> CM a
withChatLock :: Text -> CM a -> CM a
withChatLock name action = asks chatLock >>= \l -> withLock l name action
withEntityLock :: String -> ChatLockEntity -> CM a -> CM a
withEntityLock :: Text -> ChatLockEntity -> CM a -> CM a
withEntityLock name entity action = do
chatLock <- asks chatLock
ls <- asks entityLocks
atomically $ unlessM (isEmptyTMVar chatLock) retry
withLockMap ls entity name action
withInvitationLock :: String -> ByteString -> CM a -> CM a
withInvitationLock :: Text -> ByteString -> CM a -> CM a
withInvitationLock name = withEntityLock name . CLInvitation
{-# INLINE withInvitationLock #-}
withConnectionLock :: String -> Int64 -> CM a -> CM a
withConnectionLock :: Text -> Int64 -> CM a -> CM a
withConnectionLock name = withEntityLock name . CLConnection
{-# INLINE withConnectionLock #-}
withContactLock :: String -> ContactId -> CM a -> CM a
withContactLock :: Text -> ContactId -> CM a -> CM a
withContactLock name = withEntityLock name . CLContact
{-# INLINE withContactLock #-}
withGroupLock :: String -> GroupId -> CM a -> CM a
withGroupLock :: Text -> GroupId -> CM a -> CM a
withGroupLock name = withEntityLock name . CLGroup
{-# INLINE withGroupLock #-}
withUserContactLock :: String -> Int64 -> CM a -> CM a
withUserContactLock :: Text -> Int64 -> CM a -> CM a
withUserContactLock name = withEntityLock name . CLUserContact
{-# INLINE withUserContactLock #-}
withFileLock :: String -> Int64 -> CM a -> CM a
withFileLock :: Text -> Int64 -> CM a -> CM a
withFileLock name = withEntityLock name . CLFile
{-# INLINE withFileLock #-}
@@ -894,7 +894,8 @@ acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId =
pure (ct, conn, ExistingIncognito <$> incognitoProfile)
let profileToSend = profileToSendOnAccept user incognitoProfile False
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
(ct,conn,) <$> withAgent (\a -> acceptContact a (aConnId conn) True invId dm pqSup' subMode)
-- TODO [certs rcv]
(ct,conn,) . fst <$> withAgent (\a -> acceptContact a (aConnId conn) True invId dm pqSup' subMode)
acceptContactRequestAsync :: User -> Int64 -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> Maybe IncognitoProfile -> CM Contact
acceptContactRequestAsync

View File

@@ -385,7 +385,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
agentMsgConnStatus :: AEvent e -> Maybe ConnStatus
agentMsgConnStatus = \case
JOINED True -> Just ConnSndReady
JOINED True _ -> Just ConnSndReady
CONF {} -> Just ConnRequested
INFO {} -> Just ConnSndReady
CON _ -> Just ConnReady
@@ -430,7 +430,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
OK ->
-- [async agent commands] continuation on receiving OK
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
JOINED _ ->
-- TODO [certs rcv]
JOINED _ _serviceId ->
-- [async agent commands] continuation on receiving JOINED
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
QCONT ->
@@ -449,7 +450,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- TODO add debugging output
_ -> pure ()
Just ct@Contact {contactId} -> case agentMsg of
INV (ACR _ cReq) ->
-- TODO [certs rcv]
INV (ACR _ cReq) _serviceId ->
-- [async agent commands] XGrpMemIntro continuation on receiving INV
withCompletedCommand conn agentMsg $ \_ ->
case cReq of
@@ -637,7 +639,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
OK ->
-- [async agent commands] continuation on receiving OK
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
JOINED sqSecured ->
-- TODO [certs rcv]
JOINED sqSecured _serviceId ->
-- [async agent commands] continuation on receiving JOINED
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData ->
when (directOrUsed ct && sqSecured) $ do
@@ -675,7 +678,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM ()
processGroupMessage agentMsg connEntity conn@Connection {connId, connChatVersion, customUserProfileId, connectionCode} gInfo@GroupInfo {groupId, groupProfile, membership, chatSettings} m = case agentMsg of
INV (ACR _ cReq) ->
-- TODO [certs rcv]
INV (ACR _ cReq) _serviceId ->
withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} ->
case cReq of
groupConnReq@(CRInvitationUri _ _) -> case cmdFunction of
@@ -995,7 +999,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
OK ->
-- [async agent commands] continuation on receiving OK
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
JOINED sqSecured ->
-- TODO [certs rcv]
JOINED sqSecured _serviceId ->
-- [async agent commands] continuation on receiving JOINED
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData ->
when (sqSecured && connChatVersion >= batchSend2Version) sendGroupAutoReply
@@ -1111,7 +1116,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
OK ->
-- [async agent commands] continuation on receiving OK
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
JOINED _ ->
-- TODO [certs rcv]
JOINED _ _serviceId->
-- [async agent commands] continuation on receiving JOINED
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
ERR err -> do
@@ -1123,7 +1129,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
processRcvFileConn :: AEvent e -> ConnectionEntity -> Connection -> RcvFileTransfer -> CM ()
processRcvFileConn agentMsg connEntity conn ft@RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}, grpMemberId} =
case agentMsg of
INV (ACR _ cReq) ->
-- TODO [certs rcv]
INV (ACR _ cReq) _serviceId ->
withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} ->
case cReq of
fileInvConnReq@(CRInvitationUri _ _) -> case cmdFunction of
@@ -1160,7 +1167,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
OK ->
-- [async agent commands] continuation on receiving OK
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
JOINED _ ->
-- TODO [certs rcv]
JOINED _ _serviceId ->
-- [async agent commands] continuation on receiving JOINED
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
MERR _ err -> do

View File

@@ -75,11 +75,11 @@ remoteFilesFolder = "simplex_v1_files"
-- when acting as host
minRemoteCtrlVersion :: AppVersion
minRemoteCtrlVersion = AppVersion [6, 4, 0, 2]
minRemoteCtrlVersion = AppVersion [6, 4, 0, 3]
-- when acting as controller
minRemoteHostVersion :: AppVersion
minRemoteHostVersion = AppVersion [6, 4, 0, 2]
minRemoteHostVersion = AppVersion [6, 4, 0, 3]
currentAppVersion :: AppVersion
currentAppVersion = AppVersion SC.version

View File

@@ -177,7 +177,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
import Simplex.Messaging.Util (eitherToMaybe)
import UnliftIO.STM
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (FromRow, Only (..), Query, ToRow, (:.) (..))
import Database.PostgreSQL.Simple (FromRow, In (..), Only (..), Query, ToRow, (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (FromRow, Only (..), Query, ToRow, (:.) (..))
@@ -2676,8 +2676,14 @@ updateGroupChatItemModerated db User {userId} GroupInfo {groupId} ci m@GroupMemb
updateMemberCIsModerated :: MsgDirectionI d => DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> UTCTime -> IO ()
updateMemberCIsModerated db User {userId} GroupInfo {groupId, membership} member byGroupMember md deletedTs = do
itemIds <- updateCIs =<< getCurrentTime
#if defined(dbPostgres)
let inItemIds = Only $ In (map fromOnly itemIds)
DB.execute db "DELETE FROM messages WHERE message_id IN (SELECT message_id FROM chat_item_messages WHERE chat_item_id IN ?)" inItemIds
DB.execute db "DELETE FROM chat_item_versions WHERE chat_item_id IN ?" inItemIds
#else
DB.executeMany db deleteChatItemMessagesQuery itemIds
DB.executeMany db "DELETE FROM chat_item_versions WHERE chat_item_id = ?" itemIds
#endif
where
memId = groupMemberId' member
updateQuery =
@@ -3200,7 +3206,7 @@ getGroupCIMentions db ciId =
SELECT r.display_name, r.member_id, m.group_member_id, m.member_role, p.display_name, p.local_alias
FROM chat_item_mentions r
LEFT JOIN group_members m ON r.group_id = m.group_id AND r.member_id = m.member_id
LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
WHERE r.chat_item_id = ?
|]
(Only ciId)

View File

@@ -3289,7 +3289,7 @@ Query:
SELECT r.display_name, r.member_id, m.group_member_id, m.member_role, p.display_name, p.local_alias
FROM chat_item_mentions r
LEFT JOIN group_members m ON r.group_id = m.group_id AND r.member_id = m.member_id
LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
WHERE r.chat_item_id = ?
Plan: