Merge branch 'f/chat-relays-protocol' into f/chat-relays-stress-test

This commit is contained in:
spaced4ndy
2025-11-12 18:09:34 +04:00
15 changed files with 261 additions and 122 deletions

View File

@@ -2223,7 +2223,6 @@ Known:
- updatedAt: UTCTime
- supportChat: [GroupSupportChat](#groupsupportchat)?
- isRelay: bool
- relayData: [GroupRelay](#grouprelay)?
---
@@ -2345,6 +2344,7 @@ Known:
**Record type**:
- groupRelayId: int64
- groupMemberId: int64
- userChatRelayId: int64
- relayStatus: [RelayStatus](#relaystatus)
- relayLink: string?
@@ -3598,6 +3598,10 @@ GroupRelayNotFound:
- type: "groupRelayNotFound"
- groupRelayId: int64
GroupRelayNotFoundByMemberId:
- type: "groupRelayNotFoundByMemberId"
- groupMemberId: int64
InvalidQuote:
- type: "invalidQuote"

View File

@@ -2513,7 +2513,6 @@ export interface GroupMember {
updatedAt: string // ISO-8601 timestamp
supportChat?: GroupSupportChat
isRelay: boolean
relayData?: GroupRelay
}
export interface GroupMemberAdmission {
@@ -2595,6 +2594,7 @@ export interface GroupProfile {
export interface GroupRelay {
groupRelayId: number // int64
groupMemberId: number // int64
userChatRelayId: number // int64
relayStatus: RelayStatus
relayLink?: string
@@ -3816,6 +3816,7 @@ export type StoreError =
| StoreError.UsageConditionsNotFound
| StoreError.UserChatRelayNotFound
| StoreError.GroupRelayNotFound
| StoreError.GroupRelayNotFoundByMemberId
| StoreError.InvalidQuote
| StoreError.InvalidMention
| StoreError.InvalidDeliveryTask
@@ -3903,6 +3904,7 @@ export namespace StoreError {
| "usageConditionsNotFound"
| "userChatRelayNotFound"
| "groupRelayNotFound"
| "groupRelayNotFoundByMemberId"
| "invalidQuote"
| "invalidMention"
| "invalidDeliveryTask"
@@ -4290,6 +4292,11 @@ export namespace StoreError {
groupRelayId: number // int64
}
export interface GroupRelayNotFoundByMemberId extends Interface {
type: "groupRelayNotFoundByMemberId"
groupMemberId: number // int64
}
export interface InvalidQuote extends Interface {
type: "invalidQuote"
}

View File

@@ -3660,8 +3660,8 @@ processChatCommand vr nm = \case
-- TODO - or make "add relays" api retriable, via prepared connection
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff
(relayMember, conn, groupRelay) <- withFastStore $ \db -> do
groupRelay <- createGroupRelayRecord db gInfo relay
relayMember <- createRelayForOwner db vr gVar user gInfo relay groupRelay
relayMember <- createRelayForOwner db vr gVar user gInfo relay
groupRelay <- createGroupRelayRecord db gInfo relayMember relay
conn <- createRelayConnection db vr user (groupMemberId' relayMember) connId ConnPrepared chatV subMode
pure (relayMember, conn, groupRelay)
let GroupMember {memberRole = userRole, memberId = userMemberId} = membership

View File

@@ -728,18 +728,13 @@ 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"
-- TODO [relays] owner: XGrpRelayAcpt processing branch
-- TODO - TBC relay category
XGrpRelayAcpt relayLink
| memberRole' membership == GROwner ->
case relayData m of
Just relay -> do
-- TODO [relays] owner: check current relay status?
withStore' $ \db -> do
updateGroupMemberStatus db userId m GSMemAccepted
void $ setRelayLinkAccepted db relay relayLink
allowAgentConnectionAsync user conn' confId XOk
Nothing -> messageError "x.grp.relay.acpt: member is not saved as relay"
| memberRole' membership == GROwner && isRelay' m -> do
withStore $ \db -> do
relay <- getGroupRelayByGMId db (groupMemberId' m)
liftIO $ updateGroupMemberStatus db userId m GSMemAccepted
void $ liftIO $ setRelayLinkAccepted db relay relayLink
allowAgentConnectionAsync user conn' confId XOk
| otherwise -> messageError "x.grp.relay.acpt: only owner can add relay"
_ -> messageError "CONF from invited member must have x.grp.acpt"
GCHostMember ->
@@ -803,8 +798,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
pure gInfo {membership = membership {memberStatus = GSMemConnected}}
else pure gInfo
pure (m {memberStatus = GSMemConnected}, gInfo')
-- TODO [relays] member: only show "joined group" event/output once
-- TODO - or different output "connected to relay"
-- TODO [relays] member: don't duplicate e2ee and descr chat items for each relay
toView $ CEvtUserJoinedGroup user gInfo' m'
(gInfo'', m'', scopeInfo) <- mkGroupChatScope gInfo' m'
let cd = CDGroupRcv gInfo'' scopeInfo m''
@@ -814,23 +808,21 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
memberConnectedChatItem gInfo'' scopeInfo m''
let welcomeMsgId_ = (\PreparedGroup {welcomeSharedMsgId = mId} -> mId) <$> prepared
unless (memberPending membership || isJust welcomeMsgId_) $ maybeCreateGroupDescrLocal gInfo'' m''
GCInviteeMember ->
case relayData m of
-- TODO [relays] owner: relay CON processing branch
-- TODO - TBC relay category
Just relay -> do
-- TODO [relays] owner: agent async setConnShortLink api
GCInviteeMember
| isRelay' m -> do
withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected
let m' = m {memberStatus = GSMemConnected}
-- TODO [relays] owner: agent async setConnShortLink api
setGroupLinkData' NRMBackground user gInfo >>= \case
Just gLink -> do
(relay', relays) <- withStore' $ \db ->
(,) <$> updateRelayStatus db relay RSActive <*> getGroupRelays db gInfo
let m'' = m' {relayData = Just relay'}
relays <- withStore $ \db -> do
relay <- getGroupRelayByGMId db (groupMemberId' m)
void $ liftIO $ updateRelayStatus db relay RSActive
liftIO $ getGroupRelays db gInfo
-- TODO [relays] owner: relay added chat item?
toView $ CEvtRelayJoined user gInfo m'' gLink relays
toView $ CEvtRelayJoined user gInfo m' gLink relays
Nothing -> messageError "x.grp.relay.acpt: group link not updated"
Nothing -> do
| otherwise -> do
(gInfo', mStatus) <-
if not (memberPending m)
then do

View File

@@ -149,16 +149,15 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
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_relay, NULL, NULL, NULL, NULL,
mu.is_relay,
-- 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.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_relay, r.group_relay_id, r.chat_relay_id, r.relay_status, r.relay_link
m.is_relay
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

@@ -76,6 +76,7 @@ module Simplex.Chat.Store.Groups
createNewContactMember,
createGroupRelayRecord,
getGroupRelayById,
getGroupRelayByGMId,
getGroupRelays,
createRelayForOwner,
createRelayForMember,
@@ -204,11 +205,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 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 Int64, Maybe RelayStatus, Maybe ShortLinkContact)
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)
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') :. (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 isRelay, groupRelayId, chatRelayId, 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) :. (isRelay, groupRelayId, chatRelayId, relayStatus, relayLink))
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 isRelay)) =
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, isRelay))
toMaybeGroupMember _ _ = Nothing
createGroupLink :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO GroupLink
@@ -497,8 +498,7 @@ createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMe
createdAt,
updatedAt = createdAt,
supportChat = Nothing,
isRelay = BoolDef isRelay,
relayData = Nothing
isRelay = BoolDef isRelay
}
where
memberChatVRange@(VersionRange minV maxV) = vr
@@ -1116,8 +1116,7 @@ createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId,
createdAt,
updatedAt = createdAt,
supportChat = Nothing,
isRelay = BoolDef False,
relayData = Nothing
isRelay = BoolDef False
}
where
insertMember_ =
@@ -1135,18 +1134,18 @@ createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId,
:. (minV, maxV)
)
createGroupRelayRecord :: DB.Connection -> GroupInfo -> UserChatRelay -> ExceptT StoreError IO GroupRelay
createGroupRelayRecord db GroupInfo {groupId} UserChatRelay {chatRelayId} = do
createGroupRelayRecord :: DB.Connection -> GroupInfo -> GroupMember -> UserChatRelay -> ExceptT StoreError IO GroupRelay
createGroupRelayRecord db GroupInfo {groupId} GroupMember {groupMemberId} UserChatRelay {chatRelayId} = do
currentTs <- liftIO getCurrentTime
liftIO $
DB.execute
db
[sql|
INSERT INTO group_relays
(group_id, chat_relay_id, relay_status, created_at, updated_at)
VALUES (?,?,?,?,?)
(group_id, group_member_id, chat_relay_id, relay_status, created_at, updated_at)
VALUES (?,?,?,?,?,?)
|]
(groupId, chatRelayId, RSNew, currentTs, currentTs)
(groupId, groupMemberId, chatRelayId, RSNew, currentTs, currentTs)
relayId <- liftIO $ insertedRowId db
getGroupRelayById db relayId
@@ -1158,6 +1157,14 @@ getGroupRelayById db relayId =
(groupRelayQuery <> " WHERE group_relay_id = ?")
(Only relayId)
getGroupRelayByGMId :: DB.Connection -> GroupMemberId -> ExceptT StoreError IO GroupRelay
getGroupRelayByGMId db groupMemberId =
ExceptT . firstRow toGroupRelay (SEGroupRelayNotFoundByMemberId groupMemberId) $
DB.query
db
(groupRelayQuery <> " WHERE group_member_id = ?")
(Only groupMemberId)
getGroupRelays :: DB.Connection -> GroupInfo -> IO [GroupRelay]
getGroupRelays db GroupInfo {groupId} =
map toGroupRelay
@@ -1169,21 +1176,21 @@ getGroupRelays db GroupInfo {groupId} =
groupRelayQuery :: Query
groupRelayQuery =
[sql|
SELECT group_relay_id, chat_relay_id, relay_status, relay_link
SELECT group_relay_id, group_member_id, chat_relay_id, relay_status, relay_link
FROM group_relays
|]
toGroupRelay :: (Int64, Int64, RelayStatus, Maybe ShortLinkContact) -> GroupRelay
toGroupRelay (groupRelayId, userChatRelayId, relayStatus, relayLink) =
GroupRelay {groupRelayId, userChatRelayId, relayStatus, relayLink}
toGroupRelay :: (Int64, GroupMemberId, Int64, RelayStatus, Maybe ShortLinkContact) -> GroupRelay
toGroupRelay (groupRelayId, groupMemberId, userChatRelayId, relayStatus, relayLink) =
GroupRelay {groupRelayId, groupMemberId, userChatRelayId, relayStatus, relayLink}
-- TODO [relays] TBC role, category, relay profile
-- TODO - GCInviteeMember -> GCRelayMember?
-- TODO - GRMember -> GRRelay?
-- TODO - create 1 profile per relay, link to chat_relays?
-- TODO - retrieve profile from relay address
createRelayForOwner :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> GroupRelay -> ExceptT StoreError IO GroupMember
createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {name} GroupRelay {groupRelayId} = do
createRelayForOwner :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> ExceptT StoreError IO GroupMember
createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {name} = do
currentTs <- liftIO getCurrentTime
let relayProfile = profileFromName name
(localDisplayName, memProfileId) <- createNewMemberProfile_ db user relayProfile currentTs
@@ -1193,14 +1200,11 @@ createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {grou
[sql|
INSERT INTO group_members
( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
user_id, local_display_name, contact_profile_id, created_at, updated_at,
is_relay, group_relay_id
)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
user_id, local_display_name, contact_profile_id, created_at, updated_at, is_relay)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
( (groupId, MemberId memId, GRMember, GCInviteeMember, GSMemInvited, fromInvitedBy userContactId IBUser, groupMemberId' membership)
:. (userId, localDisplayName, memProfileId, currentTs, currentTs)
:. (BI True, groupRelayId)
:. (userId, localDisplayName, memProfileId, currentTs, currentTs, BI True)
)
insertedRowId db
getGroupMemberById db vr user groupMemberId
@@ -1689,8 +1693,7 @@ createNewMember_
createdAt,
updatedAt = createdAt,
supportChat = Nothing,
isRelay = BoolDef isRelay,
relayData = Nothing
isRelay = BoolDef isRelay
}
checkGroupMemberHasItems :: DB.Connection -> User -> GroupMember -> IO (Maybe ChatItemId)

View File

@@ -679,10 +679,9 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
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_relay, r.group_relay_id, r.chat_relay_id, r.relay_status, r.relay_link
m.is_relay
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
@@ -3005,7 +3004,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
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_relay, r.group_relay_id, r.chat_relay_id, r.relay_status, r.relay_link,
m.is_relay,
-- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
@@ -3014,26 +3013,23 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
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_relay, rr.group_relay_id, rr.chat_relay_id, rr.relay_status, rr.relay_link,
rm.is_relay,
-- 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.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.is_relay, dbr.group_relay_id, dbr.chat_relay_id, dbr.relay_status, dbr.relay_link
dbm.is_relay
FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_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

@@ -12,23 +12,43 @@ m20251018_chat_relays =
[r|
CREATE TABLE chat_relays(
chat_relay_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
address TEXT NOT NULL,
address BYTEA NOT NULL,
name TEXT NOT NULL,
domains TEXT NOT NULL,
preset SMALLINT NOT NULL DEFAULT 0,
tested SMALLINT,
enabled SMALLINT NOT NULL DEFAULT 1,
user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE,
deleted SMALLINT NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (now()),
updated_at TEXT NOT NULL DEFAULT (now()),
UNIQUE(user_id, address),
UNIQUE(user_id, name)
updated_at TEXT NOT NULL DEFAULT (now())
);
CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id);
CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON chat_relays(user_id, address);
CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON chat_relays(user_id, name);
ALTER TABLE users ADD COLUMN is_user_chat_relay SMALLINT NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN use_relays SMALLINT NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN relay_own_status TEXT;
ALTER TABLE group_profiles ADD COLUMN group_link BYTEA;
CREATE TABLE group_relays(
group_relay_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
group_id BIGINT NOT NULL REFERENCES groups ON DELETE CASCADE,
group_member_id BIGINT NOT NULL REFERENCES group_members ON DELETE CASCADE,
chat_relay_id BIGINT NOT NULL REFERENCES chat_relays ON DELETE CASCADE,
relay_status TEXT NOT NULL,
relay_link BYTEA,
created_at TEXT NOT NULL DEFAULT (now()),
updated_at TEXT NOT NULL DEFAULT (now())
);
CREATE INDEX idx_group_relays_group_id ON group_relays(group_id);
CREATE UNIQUE INDEX idx_group_relays_group_member_id ON group_relays(group_member_id);
CREATE INDEX idx_group_relays_chat_relay_id ON group_relays(chat_relay_id);
ALTER TABLE group_members ADD COLUMN is_relay SMALLINT NOT NULL DEFAULT 0;
|]
@@ -36,11 +56,23 @@ down_m20251018_chat_relays :: Text
down_m20251018_chat_relays =
T.pack
[r|
ALTER TABLE group_members DROP COLUMN is_relay;
DROP INDEX idx_chat_relays_user_id;
DROP INDEX idx_chat_relays_user_id_address;
DROP INDEX idx_chat_relays_user_id_name;
DROP TABLE chat_relays;
ALTER TABLE users DROP COLUMN is_user_chat_relay;
DROP INDEX idx_chat_relays_user_id;
ALTER TABLE groups DROP COLUMN use_relays;
DROP TABLE chat_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_group_member_id;
DROP INDEX idx_group_relays_chat_relay_id;
DROP TABLE group_relays;
ALTER TABLE group_members DROP COLUMN is_relay;
|]

View File

@@ -284,6 +284,33 @@ ALTER TABLE test_chat_schema.chat_items ALTER COLUMN chat_item_id ADD GENERATED
CREATE TABLE test_chat_schema.chat_relays (
chat_relay_id bigint NOT NULL,
address bytea NOT NULL,
name text NOT NULL,
domains text NOT NULL,
preset smallint DEFAULT 0 NOT NULL,
tested smallint,
enabled smallint DEFAULT 1 NOT NULL,
user_id bigint NOT NULL,
deleted smallint DEFAULT 0 NOT NULL,
created_at text DEFAULT now() NOT NULL,
updated_at text DEFAULT now() NOT NULL
);
ALTER TABLE test_chat_schema.chat_relays ALTER COLUMN chat_relay_id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME test_chat_schema.chat_relays_chat_relay_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE test_chat_schema.chat_tags (
chat_tag_id bigint NOT NULL,
user_id bigint,
@@ -706,7 +733,8 @@ CREATE TABLE test_chat_schema.group_members (
support_chat_items_mentions bigint DEFAULT 0 NOT NULL,
support_chat_last_msg_from_member_ts timestamp with time zone,
member_xcontact_id bytea,
member_welcome_shared_msg_id bytea
member_welcome_shared_msg_id bytea,
is_relay smallint DEFAULT 0 NOT NULL
);
@@ -734,7 +762,8 @@ CREATE TABLE test_chat_schema.group_profiles (
preferences text,
description text,
member_admission text,
short_descr text
short_descr text,
group_link bytea
);
@@ -750,6 +779,30 @@ ALTER TABLE test_chat_schema.group_profiles ALTER COLUMN group_profile_id ADD GE
CREATE TABLE test_chat_schema.group_relays (
group_relay_id bigint NOT NULL,
group_id bigint NOT NULL,
group_member_id bigint NOT NULL,
chat_relay_id bigint NOT NULL,
relay_status text NOT NULL,
relay_link bytea,
created_at text DEFAULT now() NOT NULL,
updated_at text DEFAULT now() NOT NULL
);
ALTER TABLE test_chat_schema.group_relays ALTER COLUMN group_relay_id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME test_chat_schema.group_relays_group_relay_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE test_chat_schema.group_snd_item_statuses (
group_snd_item_status_id bigint NOT NULL,
chat_item_id bigint NOT NULL,
@@ -805,7 +858,9 @@ CREATE TABLE test_chat_schema.groups (
request_shared_msg_id bytea,
conn_link_prepared_connection smallint DEFAULT 0 NOT NULL,
via_group_link_uri bytea,
summary_current_members_count bigint DEFAULT 0 NOT NULL
summary_current_members_count bigint DEFAULT 0 NOT NULL,
use_relays smallint DEFAULT 0 NOT NULL,
relay_own_status text
);
@@ -1273,7 +1328,8 @@ CREATE TABLE test_chat_schema.users (
user_member_profile_updated_at timestamp with time zone,
ui_themes text,
active_order bigint DEFAULT 0 NOT NULL,
auto_accept_member_contacts smallint DEFAULT 0 NOT NULL
auto_accept_member_contacts smallint DEFAULT 0 NOT NULL,
is_user_chat_relay smallint DEFAULT 0 NOT NULL
);
@@ -1357,6 +1413,11 @@ ALTER TABLE ONLY test_chat_schema.chat_items
ALTER TABLE ONLY test_chat_schema.chat_relays
ADD CONSTRAINT chat_relays_pkey PRIMARY KEY (chat_relay_id);
ALTER TABLE ONLY test_chat_schema.chat_tags
ADD CONSTRAINT chat_tags_pkey PRIMARY KEY (chat_tag_id);
@@ -1472,6 +1533,11 @@ ALTER TABLE ONLY test_chat_schema.group_profiles
ALTER TABLE ONLY test_chat_schema.group_relays
ADD CONSTRAINT group_relays_pkey PRIMARY KEY (group_relay_id);
ALTER TABLE ONLY test_chat_schema.group_snd_item_statuses
ADD CONSTRAINT group_snd_item_statuses_pkey PRIMARY KEY (group_snd_item_status_id);
@@ -1837,6 +1903,18 @@ CREATE INDEX idx_chat_items_user_id_item_status ON test_chat_schema.chat_items U
CREATE INDEX idx_chat_relays_user_id ON test_chat_schema.chat_relays USING btree (user_id);
CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON test_chat_schema.chat_relays USING btree (user_id, address);
CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON test_chat_schema.chat_relays USING btree (user_id, name);
CREATE INDEX idx_chat_tags_chats_chat_tag_id ON test_chat_schema.chat_tags_chats USING btree (chat_tag_id);
@@ -2105,6 +2183,18 @@ CREATE INDEX idx_group_profiles_user_id ON test_chat_schema.group_profiles USING
CREATE INDEX idx_group_relays_chat_relay_id ON test_chat_schema.group_relays USING btree (chat_relay_id);
CREATE INDEX idx_group_relays_group_id ON test_chat_schema.group_relays USING btree (group_id);
CREATE UNIQUE INDEX idx_group_relays_group_member_id ON test_chat_schema.group_relays USING btree (group_member_id);
CREATE INDEX idx_group_snd_item_statuses_chat_item_id ON test_chat_schema.group_snd_item_statuses USING btree (chat_item_id);
@@ -2463,6 +2553,11 @@ ALTER TABLE ONLY test_chat_schema.chat_items
ALTER TABLE ONLY test_chat_schema.chat_relays
ADD CONSTRAINT chat_relays_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.chat_tags_chats
ADD CONSTRAINT chat_tags_chats_chat_tag_id_fkey FOREIGN KEY (chat_tag_id) REFERENCES test_chat_schema.chat_tags(chat_tag_id) ON DELETE CASCADE;
@@ -2783,6 +2878,21 @@ ALTER TABLE ONLY test_chat_schema.group_profiles
ALTER TABLE ONLY test_chat_schema.group_relays
ADD CONSTRAINT group_relays_chat_relay_id_fkey FOREIGN KEY (chat_relay_id) REFERENCES test_chat_schema.chat_relays(chat_relay_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.group_relays
ADD CONSTRAINT group_relays_group_id_fkey FOREIGN KEY (group_id) REFERENCES test_chat_schema.groups(group_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.group_relays
ADD CONSTRAINT group_relays_group_member_id_fkey FOREIGN KEY (group_member_id) REFERENCES test_chat_schema.group_members(group_member_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.group_snd_item_statuses
ADD CONSTRAINT group_snd_item_statuses_chat_item_id_fkey FOREIGN KEY (chat_item_id) REFERENCES test_chat_schema.chat_items(chat_item_id) ON DELETE CASCADE;

View File

@@ -5,13 +5,6 @@ module Simplex.Chat.Store.SQLite.Migrations.M20251018_chat_relays where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
-- TODO [relays] owner: consider flipping group_members.group_relay_id fkey to group_relays.group_member_id
-- TODO - pros: less joins in all member queries,
-- TODO - can discern relay by is_relay,
-- TODO - little places need additional relay data
-- TODO - cons: in some places will need extra query:
-- TODO - SELECT ... FROM group_relays WHERE group_member_id = ?
-- - chat_relays - user's list of chat relays to choose from (similar to protocol_servers)
-- - users.is_user_chat_relay - indicates that the user can serve as a chat relay
-- (TBC usage, e.g. agree to invitations to be relay)
@@ -21,18 +14,14 @@ import Database.SQLite.Simple.QQ (sql)
-- - 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
-- - group_members.is_relay - indicates that the member is a chat relay (to all group members)
-- - group_members.group_relay_id - associates group_members record with a group_relays record for a group owner;
-- 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)
-- - group_members.is_relay - indicates that the member is a chat relay
-- - 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|
CREATE TABLE chat_relays(
chat_relay_id INTEGER PRIMARY KEY,
address TEXT NOT NULL,
address BLOB NOT NULL,
name TEXT NOT NULL,
domains TEXT NOT NULL,
preset INTEGER NOT NULL DEFAULT 0,
@@ -41,11 +30,11 @@ CREATE TABLE chat_relays(
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)
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
);
CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id);
CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON chat_relays(user_id, address);
CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON chat_relays(user_id, name);
ALTER TABLE users ADD COLUMN is_user_chat_relay INTEGER NOT NULL DEFAULT 0;
@@ -58,6 +47,7 @@ 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,
group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE,
chat_relay_id INTEGER NOT NULL REFERENCES chat_relays ON DELETE CASCADE,
relay_status TEXT NOT NULL,
relay_link BLOB,
@@ -65,18 +55,18 @@ CREATE TABLE group_relays(
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
);
CREATE INDEX idx_group_relays_group_id ON group_relays(group_id);
CREATE UNIQUE INDEX idx_group_relays_group_member_id ON group_relays(group_member_id);
CREATE INDEX idx_group_relays_chat_relay_id ON group_relays(chat_relay_id);
ALTER TABLE group_members ADD COLUMN is_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);
|]
down_m20251018_chat_relays :: Query
down_m20251018_chat_relays =
[sql|
DROP INDEX idx_chat_relays_user_id;
DROP INDEX idx_chat_relays_user_id_address;
DROP INDEX idx_chat_relays_user_id_name;
DROP TABLE chat_relays;
ALTER TABLE users DROP COLUMN is_user_chat_relay;
@@ -88,11 +78,9 @@ 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_group_member_id;
DROP INDEX idx_group_relays_chat_relay_id;
DROP TABLE group_relays;
ALTER TABLE group_members DROP COLUMN is_relay;
DROP INDEX idx_group_members_group_relay_id;
ALTER TABLE group_members DROP COLUMN group_relay_id;
|]

View File

@@ -200,7 +200,6 @@ CREATE TABLE group_members(
member_xcontact_id BLOB,
member_welcome_shared_msg_id BLOB,
is_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
@@ -745,6 +744,7 @@ CREATE TABLE chat_relays(
CREATE TABLE group_relays(
group_relay_id INTEGER PRIMARY KEY,
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE,
chat_relay_id INTEGER NOT NULL REFERENCES chat_relays ON DELETE CASCADE,
relay_status TEXT NOT NULL,
relay_link BLOB,
@@ -1216,8 +1216,10 @@ CREATE INDEX idx_connections_to_subscribe ON connections(
);
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 UNIQUE INDEX idx_group_relays_group_member_id ON group_relays(
group_member_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

@@ -149,6 +149,7 @@ data StoreError
| SEUsageConditionsNotFound
| SEUserChatRelayNotFound {chatRelayId :: Int64}
| SEGroupRelayNotFound {groupRelayId :: Int64}
| SEGroupRelayNotFoundByMemberId {groupMemberId :: GroupMemberId}
| SEInvalidQuote
| SEInvalidMention
| SEInvalidDeliveryTask {taskId :: Int64}
@@ -658,7 +659,7 @@ type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe Member
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe ShortLinkContact) :. (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) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId) :. ProfileRow :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime) :. (BoolInt, Maybe Int64, Maybe Int64, Maybe RelayStatus, Maybe ShortLinkContact)
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)
type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences)
@@ -680,7 +681,7 @@ toPreparedGroup = \case
_ -> Nothing
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
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 isRel, groupRelayId_, chatRelayId_, relayStatus_, relayLink)) =
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 isRel)) =
let memberProfile = rowToLocalProfile profileRow
memberSettings = GroupMemberSettings {showMessages}
blockedByAdmin = maybe False mrsBlocked memberRestriction_
@@ -699,9 +700,6 @@ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer,
}
_ -> Nothing
isRelay = BoolDef isRel
relayData = case (groupRelayId_, chatRelayId_, relayStatus_) of
(Just groupRelayId, Just userChatRelayId, Just relayStatus) -> Just GroupRelay {groupRelayId, userChatRelayId, relayStatus, relayLink}
_ -> Nothing
in GroupMember {..}
groupMemberQuery :: Query
@@ -712,14 +710,13 @@ groupMemberQuery =
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_relay, r.group_relay_id, r.chat_relay_id, r.relay_status, r.relay_link,
m.is_relay,
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
|]
@@ -738,7 +735,6 @@ toBusinessChatInfo _ = Nothing
groupInfoQuery :: Query
groupInfoQuery = groupInfoQueryFields <> " " <> groupInfoQueryFrom
-- membership "member" never references group_relays, therefore `NULL, NULL, NULL, NULL` which avoids extra join
groupInfoQueryFields :: Query
groupInfoQueryFields =
[sql|
@@ -757,7 +753,7 @@ groupInfoQueryFields =
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_relay, NULL, NULL, NULL, NULL
mu.is_relay
|]
groupInfoQueryFrom :: Query

View File

@@ -962,8 +962,7 @@ data GroupMember = GroupMember
createdAt :: UTCTime,
updatedAt :: UTCTime,
supportChat :: Maybe GroupSupportChat,
isRelay :: BoolDef, -- marker for all members that this member is a chat relay
relayData :: Maybe GroupRelay -- owner's additional data for a chat relay
isRelay :: BoolDef
}
deriving (Eq, Show)
@@ -972,6 +971,7 @@ memberRole' GroupMember {memberRole} = memberRole
data GroupRelay = GroupRelay
{ groupRelayId :: Int64,
groupMemberId :: GroupMemberId,
userChatRelayId :: Int64, -- ID of configured UserChatRelay
relayStatus :: RelayStatus,
relayLink :: Maybe ShortLinkContact

View File

@@ -416,7 +416,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
CEvtAcceptingBusinessRequest u g -> ttyUser u $ viewAcceptingBusinessRequest g
CEvtContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"]
CEvtBusinessRequestAlreadyAccepted u g -> ttyUser u [ttyFullGroup g <> ": sent you a duplicate connection request, but you are already connected, no action needed"]
CEvtGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."]
CEvtGroupLinkConnecting u g m -> ttyUser u $ viewUserJoiningGroup g m
CEvtBusinessLinkConnecting u g _ _ -> ttyUser u [ttyGroup' g <> ": joining the group..."]
CEvtUnknownMemberCreated u g fwdM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember fwdM <> " forwarded a message from an unknown member, creating unknown member record " <> ttyMember um]
CEvtUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um]
@@ -458,7 +458,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
in ttyUser u [sShow connId <> ": END"]
CEvtSubscriptionStatus srv status conns -> [plain $ subStatusStr status <> " " <> show (length conns) <> " connections on server " <> showSMPServer srv]
CEvtReceivedGroupInvitation {user = u, groupInfo = g, contact = c, memberRole = r} -> ttyUser u $ viewReceivedGroupInvitation g c r
CEvtUserJoinedGroup u g _ -> ttyUser u $ viewUserJoinedGroup g
CEvtUserJoinedGroup u g m -> ttyUser u $ viewUserJoinedGroup g m
CEvtRelayJoined u g relayMem groupLink relays -> ttyUser u $ viewRelayJoined g relayMem groupLink relays
CEvtJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m
CEvtHostConnected p h -> [plain $ "connected to " <> viewHostEvent p h]
@@ -1199,12 +1199,22 @@ viewDirectMessagesProhibited :: MsgDirection -> Contact -> [StyledString]
viewDirectMessagesProhibited MDSnd c = ["direct messages to indirect contact " <> ttyContact' c <> " are prohibited"]
viewDirectMessagesProhibited MDRcv c = ["received prohibited direct message from indirect contact " <> ttyContact' c <> " (discarded)"]
viewUserJoinedGroup :: GroupInfo -> [StyledString]
viewUserJoinedGroup g@GroupInfo {membership} =
case incognitoMembershipProfile g of
Just mp -> [ttyGroup' g <> ": you joined the group incognito as " <> incognitoProfile' (fromLocalProfile mp) <> pendingApproval_]
Nothing -> [ttyGroup' g <> ": you joined the group" <> pendingApproval_]
viewUserJoiningGroup :: GroupInfo -> GroupMember -> [StyledString]
viewUserJoiningGroup g m
| isRelay' m = [ttyGroup' g <> ": joining the group (connecting to relay)..."]
| otherwise = [ttyGroup' g <> ": joining the group..."]
viewUserJoinedGroup :: GroupInfo -> GroupMember -> [StyledString]
viewUserJoinedGroup g@GroupInfo {membership} m
| isRelay' membership = [ttyGroup' g <> ": you joined the group as relay"]
| otherwise =
case incognitoMembershipProfile g of
Just mp -> [ttyGroup' g <> ": you joined the group" <> connectedToRelay_ <> " incognito as " <> incognitoProfile' (fromLocalProfile mp) <> pendingApproval_]
Nothing -> [ttyGroup' g <> ": you joined the group" <> connectedToRelay_ <> pendingApproval_]
where
connectedToRelay_
| isRelay' m = " (connected to relay)"
| otherwise = ""
pendingApproval_ = case memberStatus membership of
GSMemPendingApproval -> ", pending approval"
GSMemPendingReview -> ", connecting to group moderators for admission to group"

View File

@@ -8215,7 +8215,7 @@ createChannel1Relay gName owner relay = do
owner <## "group link:"
_ <- getTermLine owner
pure (),
relay <## ("#" <> gName <> ": you joined the group")
relay <## ("#" <> gName <> ": you joined the group as relay")
]
owner ##> ("/show link #" <> gName)
@@ -8237,8 +8237,8 @@ memberJoinChannel gName relay shortLink fullLink member = do
member <## "ok"
concurrentlyN_
[ do
member <## ("#" <> gName <> ": joining the group...")
member <## ("#" <> gName <> ": you joined the group"),
member <## ("#" <> gName <> ": joining the group (connecting to relay)...")
member <## ("#" <> gName <> ": you joined the group (connected to relay)"),
do
relay <## (mFullName <> ": accepting request to join group #team...")
relay <## ("#" <> gName <> ": " <> mName <> " joined the group")