This commit is contained in:
spaced4ndy
2025-10-28 20:38:35 +04:00
parent c64b921a71
commit ac40e56e44
22 changed files with 381 additions and 147 deletions

View File

@@ -906,6 +906,7 @@ Create group.
**Parameters**:
- userId: int64
- incognito: bool
- useRelays: bool
- groupProfile: [GroupProfile](./TYPES.md#groupprofile)
**Syntax**:

View File

@@ -102,6 +102,7 @@ This file is generated automatically.
- [GroupPreference](#grouppreference)
- [GroupPreferences](#grouppreferences)
- [GroupProfile](#groupprofile)
- [GroupRelay](#grouprelay)
- [GroupShortLinkData](#groupshortlinkdata)
- [GroupSummary](#groupsummary)
- [GroupSupportChat](#groupsupportchat)
@@ -140,6 +141,7 @@ This file is generated automatically.
- [RcvFileStatus](#rcvfilestatus)
- [RcvFileTransfer](#rcvfiletransfer)
- [RcvGroupEvent](#rcvgroupevent)
- [RelayStatus](#relaystatus)
- [ReportReason](#reportreason)
- [RoleGroupPreference](#rolegrouppreference)
- [SMPAgentError](#smpagenterror)
@@ -2134,6 +2136,7 @@ MemberSupport:
**Record type**:
- groupId: int64
- useRelays: bool
- relayOwnStatus: [RelayStatus](#relaystatus)?
- localDisplayName: string
- groupProfile: [GroupProfile](#groupprofile)
- localAlias: string
@@ -2219,6 +2222,7 @@ Known:
- updatedAt: UTCTime
- supportChat: [GroupSupportChat](#groupsupportchat)?
- isChatRelay: bool
- relayData: [GroupRelay](#grouprelay)?
---
@@ -2329,10 +2333,21 @@ Known:
- shortDescr: string?
- description: string?
- image: string?
- groupLink: string?
- groupPreferences: [GroupPreferences](#grouppreferences)?
- memberAdmission: [GroupMemberAdmission](#groupmemberadmission)?
---
## GroupRelay
**Record type**:
- groupRelayId: int64
- relayStatus: [RelayStatus](#relaystatus)
- relayLink: string?
---
## GroupShortLinkData
@@ -3041,6 +3056,17 @@ NewMemberPendingReview:
- type: "newMemberPendingReview"
---
## RelayStatus
**Enum type**:
- "new"
- "invited"
- "accepted"
- "active"
---
## ReportReason

View File

@@ -280,6 +280,7 @@ chatTypesDocsData =
(sti @GroupPreference, STRecord, "", [], "", ""),
(sti @GroupPreferences, STRecord, "", [], "", ""),
(sti @GroupProfile, STRecord, "", [], "", ""),
(sti @GroupRelay, STRecord, "", [], "", ""),
(sti @GroupShortLinkData, STRecord, "", [], "", ""),
(sti @GroupSummary, STRecord, "", [], "", ""),
(sti @GroupSupportChat, STRecord, "", [], "", ""),
@@ -319,6 +320,7 @@ chatTypesDocsData =
(sti @RcvFileStatus, STUnion, "RFS", [], "", ""),
(sti @RcvFileTransfer, STRecord, "", [], "", ""),
(sti @RcvGroupEvent, STUnion, "RGE", [], "", ""),
(sti @RelayStatus, STEnum, "RS", [], "", ""),
(sti @ReportReason, (STEnum' $ dropPfxSfx "RR" ""), "", ["RRUnknown"], "", ""),
(sti @RoleGroupPreference, STRecord, "", [], "", ""),
(sti @SecurityCode, STRecord, "", [], "", ""),
@@ -466,6 +468,7 @@ deriving instance Generic GroupMemberStatus
deriving instance Generic GroupPreference
deriving instance Generic GroupPreferences
deriving instance Generic GroupProfile
deriving instance Generic GroupRelay
deriving instance Generic GroupShortLinkData
deriving instance Generic GroupSummary
deriving instance Generic GroupSupportChat
@@ -511,6 +514,7 @@ deriving instance Generic RcvFileDescr
deriving instance Generic RcvFileStatus
deriving instance Generic RcvFileTransfer
deriving instance Generic RcvGroupEvent
deriving instance Generic RelayStatus
deriving instance Generic ReportReason
deriving instance Generic SecurityCode
deriving instance Generic SimplexLinkType

View File

@@ -330,6 +330,7 @@ export namespace APIListMembers {
export interface APINewGroup {
userId: number // int64
incognito: boolean
useRelays: boolean
groupProfile: T.GroupProfile
}

View File

@@ -2421,6 +2421,7 @@ export enum GroupFeatureEnabled {
export interface GroupInfo {
groupId: number // int64
useRelays: boolean
relayOwnStatus?: RelayStatus
localDisplayName: string
groupProfile: GroupProfile
localAlias: string
@@ -2511,6 +2512,7 @@ export interface GroupMember {
updatedAt: string // ISO-8601 timestamp
supportChat?: GroupSupportChat
isChatRelay: boolean
relayData?: GroupRelay
}
export interface GroupMemberAdmission {
@@ -2585,10 +2587,17 @@ export interface GroupProfile {
shortDescr?: string
description?: string
image?: string
groupLink?: string
groupPreferences?: GroupPreferences
memberAdmission?: GroupMemberAdmission
}
export interface GroupRelay {
groupRelayId: number // int64
relayStatus: RelayStatus
relayLink?: string
}
export interface GroupShortLinkData {
groupProfile: GroupProfile
}
@@ -3443,6 +3452,13 @@ export namespace RcvGroupEvent {
}
}
export enum RelayStatus {
New = "new",
Invited = "invited",
Accepted = "accepted",
Active = "active",
}
export enum ReportReason {
Spam = "spam",
Content = "content",

View File

@@ -172,6 +172,7 @@ newChatController
expireCIThreads <- TM.emptyIO
expireCIFlags <- TM.emptyIO
cleanupManagerAsync <- newTVarIO Nothing
relayChecksAsync <- newTVarIO Nothing
timedItemThreads <- TM.emptyIO
chatActivated <- newTVarIO True
showLiveItems <- newTVarIO False
@@ -213,6 +214,7 @@ newChatController
expireCIThreads,
expireCIFlags,
cleanupManagerAsync,
relayChecksAsync,
timedItemThreads,
chatActivated,
showLiveItems,

View File

@@ -250,6 +250,7 @@ data ChatController = ChatController
expireCIThreads :: TMap UserId (Maybe (Async ())),
expireCIFlags :: TMap UserId Bool,
cleanupManagerAsync :: TVar (Maybe (Async ())),
relayChecksAsync :: TVar (Maybe (Async ())),
chatActivated :: TVar Bool,
timedItemThreads :: TMap (ChatRef, ChatItemId) (TVar (Maybe (Weak ThreadId))),
showLiveItems :: TVar Bool,
@@ -391,7 +392,7 @@ data ChatCommand
| TestProtoServer AProtoServerWithAuth
| GetUserChatRelays
| SetUserChatRelays [CLINewRelay]
-- TODO [chat relays] commands to test chat relay
-- TODO [relays] commands to test chat relay
-- | APITestChatRelay UserId ConnLinkContact
-- | TestChatRelay ConnLinkContact
| APIGetServerOperators
@@ -503,8 +504,8 @@ data ChatCommand
| EditMessage {chatName :: ChatName, editedMsg :: Text, message :: Text}
| UpdateLiveMessage {chatName :: ChatName, chatItemId :: ChatItemId, liveMessage :: Bool, message :: Text}
| ReactToMessage {add :: Bool, reaction :: MsgReaction, chatName :: ChatName, reactToMessage :: Text}
| APINewGroup {userId :: UserId, incognito :: IncognitoEnabled, groupProfile :: GroupProfile}
| NewGroup IncognitoEnabled GroupProfile
| APINewGroup {userId :: UserId, incognito :: IncognitoEnabled, useRelays :: Bool, groupProfile :: GroupProfile}
| NewGroup IncognitoEnabled Bool GroupProfile
| AddMember GroupName ContactName GroupMemberRole
| JoinGroup {groupName :: GroupName, enableNtfs :: MsgFilter}
| AcceptMember GroupName ContactName GroupMemberRole

View File

@@ -210,6 +210,15 @@ startChatController mainApp enableSndFiles = do
a <- Just <$> async (void $ runExceptT cleanupManager)
atomically $ writeTVar cleanupAsync a
_ -> pure ()
startRelayChecks users = do
let relayUser_ = find (\User {userChatRelay} -> isTrue userChatRelay) users
forM_ relayUser_ $ \relayUser -> do
relayAsync <- asks relayChecksAsync
readTVarIO relayAsync >>= \case
Nothing -> do
a <- Just <$> async (void $ runExceptT $ runRelayChecks relayUser)
atomically $ writeTVar relayAsync a
_ -> pure ()
startExpireCIs user = whenM shouldExpireChats $ do
startExpireCIThread user
setExpireCIFlag user True
@@ -220,6 +229,28 @@ startChatController mainApp enableSndFiles = do
ttlCount <- getChatTTLCount db user
pure $ ttl > 0 || ttlCount > 0
-- startExpireCIThread :: User -> CM' ()
-- startExpireCIThread user@User {userId} = do
-- expireThreads <- asks expireCIThreads
-- atomically (TM.lookup userId expireThreads) >>= \case
-- Nothing -> do
-- a <- Just <$> async runExpireCIs
-- atomically $ TM.insert userId a expireThreads
-- _ -> pure ()
-- where
-- runExpireCIs = do
-- delay <- asks (initialCleanupManagerDelay . config)
-- liftIO $ threadDelay' delay
-- interval <- asks $ ciExpirationInterval . config
-- forever $ do
-- flip catchAllErrors' (eToView') $ do
-- expireFlags <- asks expireCIFlags
-- atomically $ TM.lookup userId expireFlags >>= \b -> unless (b == Just True) retry
-- lift waitChatStartedAndActivated
-- ttl <- withStore' (`getChatItemTTL` user)
-- expireChatItems user ttl False
-- liftIO $ threadDelay' interval
getConnsToSub :: User -> CM [ConnId]
getConnsToSub user =
withFastStore' $ \db -> do
@@ -1195,8 +1226,8 @@ processChatCommand vr nm = \case
withFastStore' $ \db -> deleteGroup db user gInfo
pure $ CRGroupDeletedUser user gInfo
where
getRecipients gInfo@GroupInfo {useRelays}
| isTrue useRelays = do
getRecipients gInfo
| useRelays' gInfo = do
relays <- withFastStore' $ \db -> getGroupRelays db vr user gInfo
pure (relays, relays)
| otherwise = do
@@ -1630,8 +1661,8 @@ processChatCommand vr nm = \case
withAgent (\a -> toggleConnectionNtfs a connId $ chatHasNtfs chatSettings) `catchAllErrors` eToView
ok user
where
getMembers db gInfo@GroupInfo {useRelays}
| isTrue useRelays = getGroupRelays db vr user gInfo
getMembers db gInfo
| useRelays' gInfo = getGroupRelays db vr user gInfo
| otherwise = getGroupMembers db vr user gInfo
_ -> throwCmdError "not supported"
APISetMemberSettings gId gMemberId settings -> withUser $ \user -> do
@@ -2032,9 +2063,9 @@ processChatCommand vr nm = \case
Left e -> throwError $ ChatErrorStore e
Right _ -> throwError $ ChatErrorStore SEDuplicateContactLink
subMode <- chatReadVar subscriptionMode
-- TODO [chat relays] relay address creation:
-- TODO - add relay key, identity to link data
-- TODO - validate short link is created (returned by agent)
-- TODO [relays] relay: address creation
-- TODO - add relay key, identity to link data
-- TODO - validate short link is created (returned by agent)
let userData = contactShortLinkData (userProfileDirect user Nothing Nothing True) Nothing
-- TODO [certs rcv]
(connId, (ccLink, _serviceId)) <- withAgent $ \a -> createConnection a nm (aUserId user) True True SCMContact (Just userData) Nothing IKPQOn subMode
@@ -2228,19 +2259,26 @@ processChatCommand vr nm = \case
chatRef <- getChatRef user chatName
chatItemId <- getChatItemIdByText user chatRef msg
processChatCommand vr nm $ APIChatItemReaction chatRef chatItemId add reaction
APINewGroup userId incognito gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do
APINewGroup userId incognito useRelays gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do
checkValidName displayName
gVar <- asks random
-- [incognito] generate incognito profile for group membership
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
gInfo <- withFastStore $ \db -> createNewGroup db vr gVar user gProfile incognitoProfile
gInfo <- withFastStore $ \db -> createNewGroup db vr gVar user gProfile incognitoProfile useRelays
let cd = CDGroupSnd gInfo Nothing
createInternalChatItem user cd CIChatBanner (Just epochStart)
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
createGroupFeatureItems user cd CISndGroupFeature gInfo
-- TODO [relays] owner: create group using relays
-- TODO - prepare group link
-- TODO - add link, owner key to group profile, sign
-- TODO - create group link on server, use signed profile as data
-- TODO - choose chat relays: auto or user action (new api in case of latter)
-- TODO - send contact requests to relays
-- TODO - create relay member connections, relay records, relay status: RSInvited
pure $ CRGroupCreated user gInfo
NewGroup incognito gProfile -> withUser $ \User {userId} ->
processChatCommand vr nm $ APINewGroup userId incognito gProfile
NewGroup incognito useRelays gProfile -> withUser $ \User {userId} ->
processChatCommand vr nm $ APINewGroup userId incognito useRelays gProfile
APIAddMember groupId contactId memRole -> withUser $ \user -> withGroupLock "addMember" groupId $ do
-- TODO for large groups: no need to load all members to determine if contact is a member
(group, contact) <- withFastStore $ \db -> (,) <$> getGroup db vr user groupId <*> getContact db vr user contactId
@@ -2589,8 +2627,8 @@ processChatCommand vr nm = \case
withFastStore' $ \db -> updateGroupMemberStatus db userId membership GSMemLeft
pure $ CRLeftMemberUser user gInfo' {membership = membership {memberStatus = GSMemLeft}}
where
getRecipients user gInfo@GroupInfo {useRelays}
| isTrue useRelays = do
getRecipients user gInfo
| useRelays' gInfo = do
relays <- withFastStore' $ \db -> getGroupRelays db vr user gInfo
pure (relays, relays)
| otherwise = do
@@ -3371,7 +3409,7 @@ processChatCommand vr nm = \case
sendGroupMessage user gInfo' Nothing recipients (XGrpInfo p')
where
getRecipients
| isTrue (useRelays gInfo') = withFastStore' $ \db -> getGroupRelays db vr user gInfo'
| useRelays' gInfo' = withFastStore' $ \db -> getGroupRelays db vr user gInfo'
| otherwise = do
ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo'
pure $ filter memberCurrentOrPending ms
@@ -3627,6 +3665,19 @@ processChatCommand vr nm = \case
knownLinkPlans >>= \case
Just r -> pure r
Nothing -> do
-- TODO [relays] member: connect to relays
-- TODO - get ContactLinkData.relays data
-- TODO - see decodeShortLinkData -> linkUserData', retrieve relays in addition to userData
-- TODO - if relay list is non-empty, connect to relays
-- TODO - or, base on group profile? add useRelays/group type to group link data? (e.g. "channel")
-- TODO note:
-- TODO this is Connection Plan api - we're not connecting here yet;
-- TODO options:
-- TODO - save relay links on prepared group record, connect to them in APIConnectPreparedGroup
-- TODO - only mark group as `useRelays`, repeat retrieving link data in APIConnectPreparedGroup
-- TODO retreiving relays at point of conenctions seems better, as arbitrary time
-- TODO can pass between creating prepared group from plan and connecting to it,
-- TODO during which relays can change.
(cReq, cData) <- getShortLinkConnReq user l'
groupSLinkData_ <- liftIO $ decodeShortLinkData cData
plan <- groupJoinRequestPlan user cReq groupSLinkData_
@@ -4257,6 +4308,16 @@ cleanupManager = do
let cutoffTs = addUTCTime (-(14 * nominalDay)) ts
withStore' (`deleteOldProbes` cutoffTs)
runRelayChecks :: User -> CM ()
runRelayChecks user = do
-- TODO [relays] relay: periodically check served groups
-- TODO - get groups where user is chat relay
-- TODO - retrive group link data, check presence of relay link
-- TODO - if relay link is present, update relay status to RSActive
-- TODO - if relay link is absent and staus was RSActive -> update to new "Removed" status?
-- TODO - * recovery for joining served group (add relay protocol) - also in this thread?
pure ()
expireChatItems :: User -> Int64 -> Bool -> CM ()
expireChatItems user@User {userId} globalTTL sync = do
currentTs <- liftIO getCurrentTime
@@ -4537,8 +4598,8 @@ chatCommandP =
("/help settings" <|> "/hs") $> ChatHelp HSSettings,
("/help db" <|> "/hd") $> ChatHelp HSDatabase,
("/help" <|> "/h") $> ChatHelp HSMain,
("/group" <|> "/g") *> (NewGroup <$> incognitoP <* A.space <* char_ '#' <*> groupProfile),
"/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <* A.space <*> jsonP),
("/group" <|> "/g") *> (NewGroup <$> incognitoP <*> (" use_relays=" *> onOffP <|> pure False) <* A.space <* char_ '#' <*> groupProfile),
"/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <*> (" use_relays=" *> onOffP <|> pure False) <* A.space <*> jsonP),
("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> (memberRole <|> pure GRMember)),
("/join " <|> "/j ") *> char_ '#' *> (JoinGroup <$> displayNameP <*> (" mute" $> MFNone <|> pure MFAll)),
"/accept member " *> char_ '#' *> (AcceptMember <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> (memberRole <|> pure GRMember)),
@@ -4800,7 +4861,7 @@ chatCommandP =
{ directMessages = Just DirectMessagesGroupPreference {enable = FEOn, role = Nothing},
history = Just HistoryGroupPreference {enable = FEOn}
}
pure GroupProfile {displayName = gName, fullName = "", shortDescr, description = Nothing, image = Nothing, groupPreferences, memberAdmission = Nothing}
pure GroupProfile {displayName = gName, fullName = "", shortDescr, description = Nothing, image = Nothing, groupLink = Nothing, groupPreferences, memberAdmission = Nothing}
memberCriteriaP = ("all" $> Just MCAll) <|> ("off" $> Nothing)
shortDescrP = do
descr <- A.takeWhile1 isSpace *> (T.dropWhileEnd isSpace <$> textP) <|> pure ""

View File

@@ -1018,7 +1018,7 @@ acceptBusinessJoinRequestAsync
businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
businessGroupProfile Profile {displayName, fullName, shortDescr, image} groupPreferences =
GroupProfile {displayName, fullName, description = Nothing, shortDescr, image, groupPreferences = Just groupPreferences, memberAdmission = Nothing}
GroupProfile {displayName, fullName, description = Nothing, shortDescr, image, groupLink = Nothing, groupPreferences = Just groupPreferences, memberAdmission = Nothing}
introduceToModerators :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
introduceToModerators vr user gInfo@GroupInfo {groupId} m@GroupMember {memberRole, memberId} = do
@@ -1456,8 +1456,8 @@ getChatScopeInfo vr user = \case
pure $ GCSIMemberSupport (Just supportMem)
getGroupRecipients :: VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> VersionChat -> CM [GroupMember]
getGroupRecipients vr user gInfo@GroupInfo {useRelays, membership} scopeInfo modsCompatVersion
| isTrue useRelays && not (isMemberRelay membership) = do
getGroupRecipients vr user gInfo@GroupInfo {membership} scopeInfo modsCompatVersion
| useRelays' gInfo && not (isMemberRelay membership) = do
unless (memberCurrent membership && memberActive membership) $ throwChatError $ CECommandError "not current member"
withFastStore' $ \db -> getGroupRelays db vr user gInfo
| otherwise = case scopeInfo of
@@ -1994,9 +1994,9 @@ sendGroupMessages_ _user gInfo@GroupInfo {groupId} recipientMembers events = do
data MemberSendAction = MSASend Connection | MSASendBatched Connection | MSAPending | MSAForwarded
memberSendAction :: GroupInfo -> NonEmpty (ChatMsgEvent e) -> [GroupMember] -> GroupMember -> Maybe MemberSendAction
memberSendAction GroupInfo {useRelays, membership} events members m@GroupMember {memberRole, memberStatus}
memberSendAction gInfo@GroupInfo {membership} events members m@GroupMember {memberRole, memberStatus}
-- groups with relays require newer version - we don't need to check member version for batching and forwarding support
| isTrue useRelays =
| useRelays' gInfo =
if
-- if user is chat relay, send to all non chat relay members
| isMemberRelay membership && not (isMemberRelay m) -> MSASendBatched . snd <$> readyMemberConn m
@@ -2109,7 +2109,7 @@ saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta
pure (am', conn', msg)
saveGroupFwdRcvMsg :: MsgEncodingI e => User -> GroupInfo -> GroupMember -> GroupMember -> MsgBody -> ChatMessage e -> UTCTime -> CM (Maybe RcvMessage)
saveGroupFwdRcvMsg user GroupInfo {groupId, useRelays} forwardingMember refAuthorMember@GroupMember {memberId = refMemberId} msgBody ChatMessage {msgId = sharedMsgId_, chatMsgEvent} brokerTs = do
saveGroupFwdRcvMsg user gInfo@GroupInfo {groupId} forwardingMember refAuthorMember@GroupMember {memberId = refMemberId} msgBody ChatMessage {msgId = sharedMsgId_, chatMsgEvent} brokerTs = do
let newMsg = NewRcvMessage {chatMsgEvent, msgBody, brokerTs}
fwdMemberId = Just $ groupMemberId' forwardingMember
refAuthorId = Just $ groupMemberId' refAuthorMember
@@ -2117,7 +2117,7 @@ saveGroupFwdRcvMsg user GroupInfo {groupId, useRelays} forwardingMember refAutho
withStore' (\db -> runExceptT $ createNewRcvMessage db (GroupId groupId) newMsg sharedMsgId_ refAuthorId fwdMemberId) >>= \case
Right msg -> pure $ Just msg
Left e@SEDuplicateGroupMessage {authorGroupMemberId, forwardedByGroupMemberId}
| isTrue useRelays -> pure Nothing -- with chat relays, duplicates are expected
| useRelays' gInfo -> pure Nothing -- with chat relays, duplicates are expected
| otherwise -> case (authorGroupMemberId, forwardedByGroupMemberId) of
(Just authorGMId, Nothing) -> do
vr <- chatVersionRange

View File

@@ -220,7 +220,7 @@ processAgentMsgSndFile _corrId aFileId msg = do
toView $ CEvtSndFileCompleteXFTP user ci' ft
where
getRecipients
| isTrue (useRelays g) = withStore' $ \db -> getGroupRelays db vr user g
| useRelays' g = withStore' $ \db -> getGroupRelays db vr user g
| otherwise = withStore' $ \db -> getGroupMembers db vr user g
memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)]
memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts')
@@ -728,6 +728,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
allowAgentConnectionAsync user conn' confId XOk
| otherwise -> messageError "x.grp.acpt: memberId is different from expected"
XGrpRelayAcpt relayLink -> do
-- TODO [relays] owner: process relay acceptance
-- TODO - * relay is invitee? other processing branch?
-- TODO - * check processing client is owner, otherwise error
-- TODO - update relay record with relay link, relay status: RSAccepted
-- TODO - update group link (add relay link)
-- TODO - agent async setConnShortLink api; agent api to allow setting ContactLinkData.relays
-- TODO - on group link updated: relay status: RSActive (can share group link with members)
pure ()
_ -> messageError "CONF from invited member must have x.grp.acpt"
GCHostMember ->
case chatMsgEvent of
@@ -800,6 +809,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let welcomeMsgId_ = (\PreparedGroup {welcomeSharedMsgId = mId} -> mId) <$> prepared
unless (memberPending membership || isJust welcomeMsgId_) $ maybeCreateGroupDescrLocal gInfo'' m''
GCInviteeMember -> do
-- TODO [relays] relay: don't introduce new member to other members
(gInfo', mStatus) <-
if not (memberPending m)
then do
@@ -1140,6 +1150,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
case chatMsgEvent of
XContact p xContactId_ welcomeMsgId_ requestMsg_ -> profileContactRequest invId chatVRange p xContactId_ welcomeMsgId_ requestMsg_ pqSupport
XInfo p -> profileContactRequest invId chatVRange p Nothing Nothing Nothing pqSupport
XGrpRelayInv groupLink -> relayContactRequest groupLink
-- TODO show/log error, other events in contact request
_ -> pure ()
MERR _ err -> do
@@ -1308,6 +1319,18 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise -> do
mem <- acceptGroupJoinSendRejectAsync user uclId gInfo invId chatVRange p xContactId_ rjctReason
toViewTE $ TERejectingGroupJoinRequestMember user gInfo mem rjctReason
relayContactRequest :: ShortLinkContact -> CM ()
relayContactRequest groupLink = do
-- TODO [relays] relay: process contact request to server group
-- TODO - retrieve group link data, validate group profile, verify owner's signature
-- TODO - create group record, relay status: RSInvited
-- TODO - create relay link (async)
-- TODO - new user contact link referencing this group
-- TODO - link data: relay key for group, relay identity (profile, certificate, relay identity key)
-- TODO - accept request - send XGrpRelayAcpt to owner (continuation on link created)
-- TODO - create owner member connection, relay status: RSAccepted
-- TODO - * duplicate requests can be deduplicated by group link
pure ()
memberCanSend ::
GroupMember ->
@@ -2813,8 +2836,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- TODO [channels fwd] base on differentiation between groups and channels
isUserGrpFwdRelay :: GroupInfo -> Bool
isUserGrpFwdRelay GroupInfo {useRelays, membership = membership@GroupMember {memberRole}}
| isTrue useRelays = isMemberRelay membership
isUserGrpFwdRelay gInfo@GroupInfo {membership = membership@GroupMember {memberRole}}
| useRelays' gInfo = isMemberRelay membership
| otherwise = memberRole >= GRAdmin
xGrpLeave :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> CM (Maybe DeliveryJobScope)
@@ -3095,7 +3118,7 @@ deleteGroupConnections user gInfo waitDelivery = do
deleteMembersConnections' user members waitDelivery
where
getMembers vr
| isTrue (useRelays gInfo) = withStore' $ \db -> getGroupRelays db vr user gInfo
| useRelays' gInfo = withStore' $ \db -> getGroupRelays db vr user gInfo
| otherwise = withStore' $ \db -> getGroupMembers db vr user gInfo
startDeliveryTaskWorkers :: CM ()
@@ -3219,7 +3242,7 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
MessageDeliveryJob {jobId, jobScope, singleSenderGMId_, body, cursorGMId_ = startingCursor} = job
sendBodyToMembers :: CM ()
sendBodyToMembers
| isTrue (useRelays gInfo) = -- channel
| useRelays' gInfo = -- channel
case jobScope of
-- there's no member review in channels, so job spec includePending is ignored
DJSGroup {} -> do

View File

@@ -88,7 +88,7 @@ disabledSimplexChatSMPServers =
"smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion"
]
-- TODO [chat relays] real chat relays
-- TODO [relays] real chat relays
simplexChatRelays :: [NewUserChatRelay]
simplexChatRelays =
[ presetChatRelay True "chat_relay_1" ["simplex.im"] (either error id $ strDecode "https://smp111.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"),

View File

@@ -328,8 +328,8 @@ data ChatMsgEvent (e :: MsgEncoding) where
XGrpLinkReject :: GroupLinkRejection -> ChatMsgEvent 'Json
XGrpLinkMem :: Profile -> ChatMsgEvent 'Json
XGrpLinkAcpt :: GroupAcceptance -> GroupMemberRole -> MemberId -> ChatMsgEvent 'Json
XGrpRelayInv :: ConnLinkContact -> ChatMsgEvent 'Json
XGrpRelayAcpt :: ConnLinkContact -> ChatMsgEvent 'Json
XGrpRelayInv :: ShortLinkContact -> ChatMsgEvent 'Json
XGrpRelayAcpt :: ShortLinkContact -> ChatMsgEvent 'Json
XGrpMemNew :: MemberInfo -> Maybe MsgScope -> ChatMsgEvent 'Json
XGrpMemIntro :: MemberInfo -> Maybe MemberRestrictions -> ChatMsgEvent 'Json
XGrpMemInv :: MemberId -> IntroInvitation -> ChatMsgEvent 'Json

View File

@@ -135,26 +135,30 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
[sql|
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
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.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, 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.is_chat_relay, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
mu.is_chat_relay, NULL, NULL, NULL,
-- from GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
JOIN groups g ON g.group_id = m.group_id
JOIN group_profiles gp USING (group_profile_id)
JOIN group_members mu ON g.group_id = mu.group_id

View File

@@ -161,11 +161,11 @@ getNextDeliveryTasks :: DB.Connection -> GroupInfo -> MessageDeliveryTask -> IO
getNextDeliveryTasks db gInfo task =
getWorkItems "message delivery task" getTaskIds (getMsgDeliveryTask_ db) (markDeliveryTaskFailed_ db)
where
GroupInfo {groupId, useRelays} = gInfo
GroupInfo {groupId} = gInfo
MessageDeliveryTask {jobScope, senderGMId} = task
getTaskIds :: IO [Int64]
getTaskIds
| isTrue useRelays =
| useRelays' gInfo =
map fromOnly
<$> DB.query
db

View File

@@ -190,11 +190,11 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
#endif
type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus, Maybe BoolInt) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId) :. (Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime)
type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId) :. (Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime) :. (Maybe BoolInt, Maybe Int64, Maybe RelayStatus, Maybe ShortLinkContact)
toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember
toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked', Just isChatRelay) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId) :. (Just profileId, Just displayName, Just fullName, shortDescr, image, contactLink, peerType, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs)) =
Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked', isChatRelay) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. (profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs))
toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId) :. (Just profileId, Just displayName, Just fullName, shortDescr, image, contactLink, peerType, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs) :. (Just isChatRelay, groupRelayId, relayStatus, relayLink)) =
Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. (profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs) :. (isChatRelay, groupRelayId, relayStatus, relayLink))
toMaybeGroupMember _ _ = Nothing
createGroupLink :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO GroupLink
@@ -309,8 +309,8 @@ setGroupLinkShortLink db gLnk@GroupLink {userContactLinkId, connLinkContact = CC
pure gLnk {connLinkContact = CCLink connFullLink (Just shortLink), shortLinkDataSet = True, shortLinkLargeDataSet = BoolDef True}
-- | creates completely new group with a single member - the current user
createNewGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupProfile -> Maybe Profile -> ExceptT StoreError IO GroupInfo
createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = ExceptT $ do
createNewGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupProfile -> Maybe Profile -> Bool -> ExceptT StoreError IO GroupInfo
createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile useRelays = ExceptT $ do
let GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} = groupProfile
fullGroupPreferences = mergeGroupPreferences groupPreferences
currentTs <- getCurrentTime
@@ -326,11 +326,11 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
db
[sql|
INSERT INTO groups
(local_display_name, user_id, group_profile_id, enable_ntfs,
(use_relays, local_display_name, user_id, group_profile_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at)
VALUES (?,?,?,?,?,?,?,?)
VALUES (?,?,?,?,?,?,?,?,?)
|]
(ldn, userId, profileId, BI True, currentTs, currentTs, currentTs, currentTs)
(BI useRelays, ldn, userId, profileId, BI True, currentTs, currentTs, currentTs, currentTs)
insertedRowId db
memberId <- liftIO $ encodedRandomBytes gVar 12
membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser customUserProfileId currentTs vr
@@ -338,7 +338,8 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
pure
GroupInfo
{ groupId,
useRelays = BoolDef False,
useRelays = BoolDef useRelays,
relayOwnStatus = Nothing,
localDisplayName = ldn,
groupProfile,
localAlias = "",
@@ -414,6 +415,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
( GroupInfo
{ groupId,
useRelays = BoolDef False,
relayOwnStatus = Nothing,
localDisplayName,
groupProfile,
localAlias = "",
@@ -481,7 +483,8 @@ createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMe
createdAt,
updatedAt = createdAt,
supportChat = Nothing,
isChatRelay = BoolDef False
isChatRelay = BoolDef False,
relayData = Nothing
}
where
memberChatVRange@(VersionRange minV maxV) = vr
@@ -1100,7 +1103,8 @@ createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId,
createdAt,
updatedAt = createdAt,
supportChat = Nothing,
isChatRelay = BoolDef False
isChatRelay = BoolDef False,
relayData = Nothing
}
where
insertMember_ =
@@ -1457,14 +1461,14 @@ createNewMember_
db
[sql|
INSERT INTO group_members
(group_id, member_id, member_role, member_category, member_status, member_restriction, is_chat_relay, invited_by, invited_by_group_member_id,
(group_id, member_id, member_role, member_category, member_status, member_restriction, invited_by, invited_by_group_member_id,
user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
peer_chat_min_version, peer_chat_max_version)
peer_chat_min_version, peer_chat_max_version, is_chat_relay)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
( (groupId, memberId, memberRole, memberCategory, memberStatus, memRestriction, BI isChatRelay, invitedById, memInvitedByGroupMemberId)
( (groupId, memberId, memberRole, memberCategory, memberStatus, memRestriction, invitedById, memInvitedByGroupMemberId)
:. (userId, localDisplayName, memberContactId, memberContactProfileId, createdAt, createdAt)
:. (minV, maxV)
:. (minV, maxV, BI isChatRelay)
)
groupMemberId <- insertedRowId db
pure
@@ -1488,7 +1492,8 @@ createNewMember_
createdAt,
updatedAt = createdAt,
supportChat = Nothing,
isChatRelay = BoolDef isChatRelay
isChatRelay = BoolDef isChatRelay,
relayData = Nothing
}
checkGroupMemberHasItems :: DB.Connection -> User -> GroupMember -> IO (Maybe ChatItemId)
@@ -1820,14 +1825,14 @@ updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName
DB.query
db
[sql|
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.preferences, gp.member_admission
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.group_link, gp.preferences, gp.member_admission
FROM group_profiles gp
JOIN groups g ON gp.group_profile_id = g.group_profile_id
WHERE g.group_id = ?
|]
(Only groupId)
toGroupProfile (displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission) =
GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission}
toGroupProfile (displayName, fullName, shortDescr, description, image, groupLink, groupPreferences, memberAdmission) =
GroupProfile {displayName, fullName, shortDescr, description, image, groupLink, groupPreferences, memberAdmission}
getGroupInfo :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO GroupInfo
getGroupInfo db vr User {userId, userContactId} groupId = ExceptT $ do

View File

@@ -675,12 +675,14 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
SELECT i.chat_item_id,
-- GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN contacts c ON m.contact_id = c.contact_id
LEFT JOIN chat_items i ON i.user_id = m.user_id
AND i.group_id = m.group_id
@@ -2999,35 +3001,39 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
i.forwarded_by_group_member_id, i.show_group_as_sender,
-- GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
-- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category,
rm.member_status, rm.show_messages, rm.member_restriction, rm.is_chat_relay, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
rp.display_name, rp.full_name, rp.short_descr, rp.image, rp.contact_link, rp.chat_peer_type, rp.local_alias, rp.preferences,
rm.created_at, rm.updated_at,
rm.support_chat_ts, rm.support_chat_items_unread, rm.support_chat_items_member_attention, rm.support_chat_items_mentions, rm.support_chat_last_msg_from_member_ts,
rm.is_chat_relay, rr.group_relay_id, rr.relay_status, rr.relay_link,
-- deleted by GroupMember
dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category,
dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.is_chat_relay, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbp.display_name, dbp.full_name, dbp.short_descr, dbp.image, dbp.contact_link, dbp.chat_peer_type, dbp.local_alias, dbp.preferences,
dbm.created_at, dbm.updated_at,
dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts
dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts,
dbm.is_chat_relay, dbr.group_relay_id, dbr.relay_status, dbr.relay_link
FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
LEFT JOIN group_members gsm ON gsm.group_member_id = i.group_scope_group_member_id
LEFT JOIN contact_profiles gsp ON gsp.contact_profile_id = COALESCE(gsm.member_profile_id, gsm.contact_profile_id)
LEFT JOIN group_members m ON m.group_member_id = i.group_member_id
LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN chat_items ri ON ri.shared_msg_id = i.quoted_shared_msg_id AND ri.group_id = i.group_id
LEFT JOIN group_members rm ON rm.group_member_id = ri.group_member_id
LEFT JOIN contact_profiles rp ON rp.contact_profile_id = COALESCE(rm.member_profile_id, rm.contact_profile_id)
LEFT JOIN group_relays rr ON rr.group_relay_id = rm.group_relay_id
LEFT JOIN group_members dbm ON dbm.group_member_id = i.item_deleted_by_group_member_id
LEFT JOIN contact_profiles dbp ON dbp.contact_profile_id = COALESCE(dbm.member_profile_id, dbm.contact_profile_id)
LEFT JOIN group_relays dbr ON dbr.group_relay_id = dbm.group_relay_id
WHERE i.user_id = ? AND i.group_id = ? AND i.chat_item_id = ?
|]
(userId, groupId, itemId)

View File

@@ -10,7 +10,7 @@ import Database.SQLite.Simple.QQ (sql)
-- (TBC usage, e.g. agree to invitations to be relay)
-- - group_relays - group owner's list of relays for a group
-- - group_relays.relay_link - links for all relays of a group are included in GroupShortLinkData
-- - group_relays.relay_status - group owner's status for each relay (GroupRelayStatus)
-- - group_relays.relay_status - group owner's status for each relay (RelayStatus)
-- - group_relays.chat_relay_id - associates group_relays record with a chat_relays record,
-- chat_relays.deleted is to keep associated record if user removes chat relay from configuration,
-- but has group relays using it
@@ -19,8 +19,7 @@ import Database.SQLite.Simple.QQ (sql)
-- receiving event to member connection, owner can match it to the relay;
-- TBC inverse association - from group_relays to group_members?
-- - TBC also inverse link from group_relays to group_members? (group_relays.group_member_id)
-- - groups.relay_status_self - indicates for a relay client that it is chat relay for the group (GroupRelayStatus)
-- - connections.group_member_id_messaging - secondary connection for a group member in relayed group
-- - groups.relay_own_status - indicates for a relay client that it is chat relay for the group (RelayStatus)
m20251018_chat_relays :: Query
m20251018_chat_relays =
[sql|
@@ -43,6 +42,12 @@ CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id);
ALTER TABLE users ADD COLUMN is_user_chat_relay INTEGER NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN use_relays INTEGER NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN relay_own_status TEXT;
ALTER TABLE group_profiles ADD COLUMN group_link BLOB;
CREATE TABLE group_relays(
group_relay_id INTEGER PRIMARY KEY,
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
@@ -57,11 +62,6 @@ ALTER TABLE group_members ADD COLUMN is_chat_relay INTEGER NOT NULL DEFAULT 0;
ALTER TABLE group_members ADD COLUMN group_relay_id INTEGER REFERENCES group_relays ON DELETE SET NULL;
CREATE INDEX idx_group_members_group_relay_id ON group_members(group_relay_id);
ALTER TABLE groups ADD COLUMN relay_status_self TEXT;
ALTER TABLE connections ADD COLUMN group_member_id_messaging INTEGER REFERENCES group_members ON DELETE CASCADE;
CREATE INDEX idx_connections_group_member_id_messaging ON connections(group_member_id_messaging);
|]
down_m20251018_chat_relays :: Query
@@ -72,6 +72,12 @@ DROP TABLE chat_relays;
ALTER TABLE users DROP COLUMN is_user_chat_relay;
ALTER TABLE groups DROP COLUMN use_relays;
ALTER TABLE groups DROP COLUMN relay_own_status;
ALTER TABLE group_profiles DROP COLUMN group_link;
DROP INDEX idx_group_relays_group_id;
DROP INDEX idx_group_relays_chat_relay_id;
DROP TABLE group_relays;
@@ -80,9 +86,4 @@ ALTER TABLE group_members DROP COLUMN is_chat_relay;
DROP INDEX idx_group_members_group_relay_id;
ALTER TABLE group_members DROP COLUMN group_relay_id;
ALTER TABLE groups DROP COLUMN relay_status_self;
DROP INDEX idx_connections_group_member_id_messaging;
ALTER TABLE connections DROP COLUMN group_member_id_messaging;
|]

View File

@@ -106,26 +106,30 @@ SEARCH c USING INDEX idx_connections_contact_id (contact_id=?) LEFT-JOIN
Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
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.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, 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.is_chat_relay, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
mu.is_chat_relay, NULL, NULL, NULL,
-- from GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
JOIN groups g ON g.group_id = m.group_id
JOIN group_profiles gp USING (group_profile_id)
JOIN group_members mu ON g.group_id = mu.group_id
@@ -137,6 +141,7 @@ Plan:
SEARCH m USING INTEGER PRIMARY KEY (rowid=?)
SEARCH g USING INTEGER PRIMARY KEY (rowid=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH gp USING INTEGER PRIMARY KEY (rowid=?)
SEARCH mu USING INDEX idx_group_members_contact_id (contact_id=?)
SEARCH pu USING INTEGER PRIMARY KEY (rowid=?)
@@ -817,7 +822,7 @@ Plan:
SEARCH delivery_tasks USING COVERING INDEX idx_delivery_tasks_next (group_id=? AND worker_scope=? AND failed=? AND task_status=?)
Query:
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.preferences, gp.member_admission
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.group_link, gp.preferences, gp.member_admission
FROM group_profiles gp
JOIN groups g ON gp.group_profile_id = g.group_profile_id
WHERE g.group_id = ?
@@ -845,12 +850,14 @@ Query:
SELECT i.chat_item_id,
-- GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN contacts c ON m.contact_id = c.contact_id
LEFT JOIN chat_items i ON i.user_id = m.user_id
AND i.group_id = m.group_id
@@ -861,6 +868,7 @@ Query:
Plan:
SEARCH m USING INDEX sqlite_autoindex_group_members_1 (group_id=? AND member_id=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH i USING COVERING INDEX idx_chat_items_group_shared_msg_id (user_id=? AND group_id=? AND group_member_id=? AND shared_msg_id=?) LEFT-JOIN
Query:
@@ -969,6 +977,7 @@ Query:
RETURNING chat_relay_id
Plan:
SEARCH group_relays USING COVERING INDEX idx_group_relays_chat_relay_id (chat_relay_id=?)
Query:
INSERT INTO group_members
@@ -1015,9 +1024,9 @@ Plan:
Query:
INSERT INTO groups
(local_display_name, user_id, group_profile_id, enable_ntfs,
(use_relays, local_display_name, user_id, group_profile_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at)
VALUES (?,?,?,?,?,?,?,?)
VALUES (?,?,?,?,?,?,?,?,?)
Plan:
@@ -1073,48 +1082,54 @@ Query:
i.forwarded_by_group_member_id, i.show_group_as_sender,
-- GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
-- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category,
rm.member_status, rm.show_messages, rm.member_restriction, rm.is_chat_relay, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
rp.display_name, rp.full_name, rp.short_descr, rp.image, rp.contact_link, rp.chat_peer_type, rp.local_alias, rp.preferences,
rm.created_at, rm.updated_at,
rm.support_chat_ts, rm.support_chat_items_unread, rm.support_chat_items_member_attention, rm.support_chat_items_mentions, rm.support_chat_last_msg_from_member_ts,
rm.is_chat_relay, rr.group_relay_id, rr.relay_status, rr.relay_link,
-- deleted by GroupMember
dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category,
dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.is_chat_relay, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbp.display_name, dbp.full_name, dbp.short_descr, dbp.image, dbp.contact_link, dbp.chat_peer_type, dbp.local_alias, dbp.preferences,
dbm.created_at, dbm.updated_at,
dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts
dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts,
dbm.is_chat_relay, dbr.group_relay_id, dbr.relay_status, dbr.relay_link
FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
LEFT JOIN group_members gsm ON gsm.group_member_id = i.group_scope_group_member_id
LEFT JOIN contact_profiles gsp ON gsp.contact_profile_id = COALESCE(gsm.member_profile_id, gsm.contact_profile_id)
LEFT JOIN group_members m ON m.group_member_id = i.group_member_id
LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN chat_items ri ON ri.shared_msg_id = i.quoted_shared_msg_id AND ri.group_id = i.group_id
LEFT JOIN group_members rm ON rm.group_member_id = ri.group_member_id
LEFT JOIN contact_profiles rp ON rp.contact_profile_id = COALESCE(rm.member_profile_id, rm.contact_profile_id)
LEFT JOIN group_relays rr ON rr.group_relay_id = rm.group_relay_id
LEFT JOIN group_members dbm ON dbm.group_member_id = i.item_deleted_by_group_member_id
LEFT JOIN contact_profiles dbp ON dbp.contact_profile_id = COALESCE(dbm.member_profile_id, dbm.contact_profile_id)
LEFT JOIN group_relays dbr ON dbr.group_relay_id = dbm.group_relay_id
WHERE i.user_id = ? AND i.group_id = ? AND i.chat_item_id = ?
Plan:
SEARCH i USING INTEGER PRIMARY KEY (rowid=?)
SEARCH f USING INDEX idx_files_chat_item_id (chat_item_id=?) LEFT-JOIN
SEARCH gsm USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH p USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH ri USING INDEX idx_chat_items_group_id_shared_msg_id (group_id=? AND shared_msg_id=?) LEFT-JOIN
SEARCH rm USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH rp USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH rr USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH dbm USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH dbp USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH dbr USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
Query:
SELECT
@@ -1653,9 +1668,9 @@ SEARCH contacts USING COVERING INDEX idx_contacts_contact_group_member_id (conta
Query:
INSERT INTO group_members
(group_id, member_id, member_role, member_category, member_status, member_restriction, is_chat_relay, invited_by, invited_by_group_member_id,
(group_id, member_id, member_role, member_category, member_status, member_restriction, invited_by, invited_by_group_member_id,
user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
peer_chat_min_version, peer_chat_max_version)
peer_chat_min_version, peer_chat_max_version, is_chat_relay)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
Plan:
@@ -4979,18 +4994,20 @@ SEARCH pending_group_messages USING COVERING INDEX idx_pending_group_messages_me
Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
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.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, 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.is_chat_relay, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
mu.is_chat_relay, NULL, NULL, NULL
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
@@ -5013,18 +5030,20 @@ SEARCH pu USING INTEGER PRIMARY KEY (rowid=?)
Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
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.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, 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.is_chat_relay, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
mu.is_chat_relay, NULL, NULL, NULL
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
@@ -5040,18 +5059,20 @@ SEARCH pu USING INTEGER PRIMARY KEY (rowid=?)
Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
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.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, 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.is_chat_relay, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
mu.is_chat_relay, NULL, NULL, NULL
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
@@ -5096,16 +5117,18 @@ SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
Query:
SELECT
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
WHERE m.group_id = ? AND m.user_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?)
@@ -5119,120 +5142,139 @@ SEARCH m USING INDEX idx_group_members_group_id (user_id=? AND group_id=?)
LIST SUBQUERY 1
SCAN chat_items USING COVERING INDEX idx_chat_items_group_member_id
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
Query:
SELECT
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
WHERE m.group_id = ? AND m.group_member_id = ? AND m.user_id = ?
Plan:
SEARCH m USING INTEGER PRIMARY KEY (rowid=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
Query:
SELECT
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
WHERE m.group_id = ? AND m.member_category = ?
Plan:
SEARCH m USING INDEX sqlite_autoindex_group_members_1 (group_id=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
Query:
SELECT
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
WHERE m.group_id = ? AND m.member_id = ?
Plan:
SEARCH m USING INDEX sqlite_autoindex_group_members_1 (group_id=? AND member_id=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
Query:
SELECT
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
WHERE m.group_member_id = ? AND m.user_id = ?
Plan:
SEARCH m USING INTEGER PRIMARY KEY (rowid=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
Query:
SELECT
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?)
Plan:
SEARCH m USING INDEX idx_group_members_group_id (user_id=? AND group_id=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
Query:
SELECT
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND m.member_role IN (?,?,?)
Plan:
SEARCH m USING INDEX idx_group_members_group_id (user_id=? AND group_id=?)
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH r USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
Query:
@@ -5678,6 +5720,7 @@ SEARCH groups USING COVERING INDEX idx_groups_chat_item_id (chat_item_id=?)
Query: DELETE FROM chat_relays WHERE user_id = ? AND chat_relay_id = ? AND preset = ?
Plan:
SEARCH chat_relays USING INTEGER PRIMARY KEY (rowid=?)
SEARCH group_relays USING COVERING INDEX idx_group_relays_chat_relay_id (chat_relay_id=?)
Query: DELETE FROM commands WHERE user_id = ? AND command_id = ?
Plan:
@@ -5831,6 +5874,7 @@ SEARCH contacts USING COVERING INDEX idx_contacts_contact_group_member_id (conta
Query: DELETE FROM groups WHERE user_id = ? AND group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
SEARCH group_relays USING COVERING INDEX idx_group_relays_group_id (group_id=?)
SEARCH delivery_jobs USING COVERING INDEX idx_delivery_jobs_group_id (group_id=?)
SEARCH delivery_tasks USING COVERING INDEX idx_delivery_tasks_group_id (group_id=?)
SEARCH chat_item_mentions USING COVERING INDEX idx_chat_item_mentions_group_id (group_id=?)
@@ -6216,7 +6260,7 @@ SEARCH chat_items USING INTEGER PRIMARY KEY (rowid>?)
Query: SELECT count(1) FROM group_members
Plan:
SCAN group_members USING COVERING INDEX idx_group_members_invited_by_group_member_id
SCAN group_members USING COVERING INDEX idx_group_members_group_relay_id
Query: SELECT count(1) FROM pending_group_messages
Plan:

View File

@@ -122,7 +122,8 @@ CREATE TABLE group_profiles(
preferences TEXT,
description TEXT NULL,
member_admission TEXT,
short_descr TEXT
short_descr TEXT,
group_link BLOB
);
CREATE TABLE groups(
group_id INTEGER PRIMARY KEY, -- local group ID
@@ -156,7 +157,9 @@ CREATE TABLE groups(
request_shared_msg_id BLOB,
conn_link_prepared_connection INTEGER NOT NULL DEFAULT 0,
via_group_link_uri BLOB,
summary_current_members_count INTEGER NOT NULL DEFAULT 0, -- received
summary_current_members_count INTEGER NOT NULL DEFAULT 0,
use_relays INTEGER NOT NULL DEFAULT 0,
relay_own_status TEXT, -- received
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -197,6 +200,7 @@ CREATE TABLE group_members(
member_xcontact_id BLOB,
member_welcome_shared_msg_id BLOB,
is_chat_relay INTEGER NOT NULL DEFAULT 0,
group_relay_id INTEGER REFERENCES group_relays ON DELETE SET NULL,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -732,11 +736,19 @@ CREATE TABLE chat_relays(
tested INTEGER,
enabled INTEGER NOT NULL DEFAULT 1,
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
deleted INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT(datetime('now')),
updated_at TEXT NOT NULL DEFAULT(datetime('now')),
UNIQUE(user_id, address),
UNIQUE(user_id, name)
);
CREATE TABLE group_relays(
group_relay_id INTEGER PRIMARY KEY,
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
chat_relay_id INTEGER NOT NULL REFERENCES chat_relays ON DELETE CASCADE,
relay_status TEXT NOT NULL,
relay_link BLOB
);
CREATE INDEX contact_profiles_index ON contact_profiles(
display_name,
full_name
@@ -1201,6 +1213,9 @@ CREATE INDEX idx_connections_to_subscribe ON connections(
to_subscribe
);
CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id);
CREATE INDEX idx_group_relays_group_id ON group_relays(group_id);
CREATE INDEX idx_group_relays_chat_relay_id ON group_relays(chat_relay_id);
CREATE INDEX idx_group_members_group_relay_id ON group_members(group_relay_id);
CREATE TRIGGER on_group_members_insert_update_summary
AFTER INSERT ON group_members
FOR EACH ROW

View File

@@ -654,22 +654,22 @@ 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, Int64, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact) :. GroupMemberRow
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact) :. GroupMemberRow
type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus, BoolInt) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId) :. ProfileRow :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime)
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) :. (BoolInt, Maybe Int64, Maybe RelayStatus, Maybe ShortLinkContact)
type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, 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, currentMembers, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. userMemberRow) =
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupLink) :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, 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}
groupProfile = GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission, groupLink}
businessChat = toBusinessChatInfo businessRow
preparedGroup = toPreparedGroup preparedGroupRow
groupSummary = GroupSummary {currentMembers}
in GroupInfo {groupId, useRelays = BoolDef False, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, preparedGroup, chatTags, chatItemTTL, uiThemes, groupSummary, customData, membersRequireAttention, viaGroupLinkUri}
in GroupInfo {groupId, useRelays = BoolDef useRelays, relayOwnStatus, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, preparedGroup, chatTags, chatItemTTL, uiThemes, groupSummary, customData, membersRequireAttention, viaGroupLinkUri}
toPreparedGroup :: PreparedGroupRow -> Maybe PreparedGroup
toPreparedGroup = \case
@@ -678,14 +678,13 @@ toPreparedGroup = \case
_ -> Nothing
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_, BI isCRelay) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. profileRow :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs)) =
toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. profileRow :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs) :. (BI isCRelay, groupRelayId_, relayStatus_, relayLink)) =
let memberProfile = rowToLocalProfile profileRow
memberSettings = GroupMemberSettings {showMessages}
blockedByAdmin = maybe False mrsBlocked memberRestriction_
invitedBy = toInvitedBy userContactId invitedById
activeConn = Nothing
memberChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
isChatRelay = BoolDef isCRelay
supportChat = case supportChatTs_ of
Just chatTs ->
Just
@@ -697,22 +696,28 @@ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer,
lastMsgFromMemberTs = supportChatLastMsgFromMemberTs
}
_ -> Nothing
isChatRelay = BoolDef isCRelay
relayData = case (groupRelayId_, relayStatus_) of
(Just groupRelayId, Just relayStatus) -> Just GroupRelay {groupRelayId, relayStatus, relayLink}
_ -> Nothing
in GroupMember {..}
groupMemberQuery :: Query
groupMemberQuery =
[sql|
SELECT
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.is_chat_relay,
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
m.created_at, m.updated_at,
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
m.is_chat_relay, r.group_relay_id, r.relay_status, r.relay_link,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
|]
@@ -731,23 +736,26 @@ toBusinessChatInfo _ = Nothing
groupInfoQuery :: Query
groupInfoQuery = groupInfoQueryFields <> " " <> groupInfoQueryFrom
-- membership "member" never references group_relays, therefore `NULL, NULL, NULL` which avoids extra join
groupInfoQueryFields :: Query
groupInfoQueryFields =
[sql|
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
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.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, 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.is_chat_relay, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
mu.is_chat_relay, NULL, NULL, NULL
|]
groupInfoQueryFrom :: Query

View File

@@ -118,7 +118,7 @@ instance ToField AgentUserId where toField (AgentUserId uId) = toField uId
aUserId :: User -> UserId
aUserId User {agentUserId = AgentUserId uId} = uId
-- TODO [chat relays] filter out chat relay users where necessary (e.g. loading list of users for UI)
-- TODO [relays] filter out chat relay users where necessary (e.g. loading list of users for UI)
data User = User
{ userId :: UserId,
agentUserId :: AgentUserId,
@@ -449,6 +449,7 @@ type GroupId = Int64
data GroupInfo = GroupInfo
{ groupId :: GroupId,
useRelays :: BoolDef,
relayOwnStatus :: Maybe RelayStatus,
localDisplayName :: GroupName,
groupProfile :: GroupProfile,
localAlias :: Text,
@@ -467,11 +468,13 @@ data GroupInfo = GroupInfo
customData :: Maybe CustomData,
groupSummary :: GroupSummary,
membersRequireAttention :: Int,
viaGroupLinkUri :: Maybe ConnReqContact,
relayOwnStatus :: Maybe GroupRelayStatus
viaGroupLinkUri :: Maybe ConnReqContact
}
deriving (Eq, Show)
useRelays' :: GroupInfo -> Bool
useRelays' GroupInfo {useRelays} = isTrue useRelays
data BusinessChatType
= BCBusiness -- used on the customer side
| BCCustomer -- used on the business side
@@ -732,9 +735,9 @@ data GroupProfile = GroupProfile
shortDescr :: Maybe Text, -- short description limited to 160 characters
description :: Maybe Text, -- this has been repurposed as welcome message
image :: Maybe ImageData,
groupLink :: Maybe ConnLinkContact,
groupPreferences :: Maybe GroupPreferences,
memberAdmission :: Maybe GroupMemberAdmission,
groupLink :: Maybe ConnLinkContact
memberAdmission :: Maybe GroupMemberAdmission
}
deriving (Eq, Show)
@@ -963,24 +966,37 @@ data GroupMember = GroupMember
}
deriving (Eq, Show)
-- TODO [chat relays] review; consider where to use it:
-- TODO - GroupMember? (now)
-- TODO - separate list of relays in GroupInfo?
-- TODO - only on request?
data GroupRelay = GroupRelay
{ groupRelayId :: Int64,
relayStatus :: GroupRelayStatus,
relayLink :: ConnLinkContact
relayStatus :: RelayStatus,
relayLink :: Maybe ShortLinkContact
}
deriving (Eq, Show)
data GroupRelayStatus
= GRSNew -- only for owner
| GRSInvited
| GRSAccepted
| GRSActive
data RelayStatus
= RSNew -- only for owner
| RSInvited
| RSAccepted
| RSActive
deriving (Eq, Show)
instance TextEncoding RelayStatus where
textEncode = \case
RSNew -> "new"
RSInvited -> "invited"
RSAccepted -> "accepted"
RSActive -> "active"
textDecode = \case
"new" -> Just RSNew
"invited" -> Just RSInvited
"accepted" -> Just RSAccepted
"active" -> Just RSActive
_ -> Nothing
instance FromField RelayStatus where fromField = fromTextField_ textDecode
instance ToField RelayStatus where toField = toField . textEncode
data GroupSupportChat = GroupSupportChat
{ chatTs :: UTCTime,
unread :: Int64,
@@ -2016,7 +2032,7 @@ $(JQ.deriveJSON defaultJSON ''PendingContactConnection)
$(JQ.deriveJSON defaultJSON ''GroupSupportChat)
$(JQ.deriveJSON (enumJSON $ dropPrefix "GRS") ''GroupRelayStatus)
$(JQ.deriveJSON (enumJSON $ dropPrefix "RS") ''RelayStatus)
$(JQ.deriveJSON defaultJSON ''GroupRelay)

View File

@@ -107,7 +107,7 @@ testProfile :: Profile
testProfile = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), peerType = Nothing, contactLink = Nothing, preferences = testChatPreferences}
testGroupProfile :: GroupProfile
testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, shortDescr = Nothing, image = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing}
testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, shortDescr = Nothing, image = Nothing, groupLink = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing}
decodeChatMessageTest :: Spec
decodeChatMessageTest = describe "Chat message encoding/decoding" $ do