This commit is contained in:
spaced4ndy
2026-05-29 13:52:45 +04:00
parent 21f85b56a7
commit 49f53f758f
3 changed files with 43 additions and 23 deletions
+12 -9
View File
@@ -1180,18 +1180,21 @@ forwardCachedRoster user gInfo subscriber = do
introduceInChannel :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
introduceInChannel _ _ _ GroupMember {activeConn = Nothing} = throwChatError $ CEInternalError "member connection not active"
introduceInChannel vr user gInfo subscriber@GroupMember {activeConn = Just conn, indexInGroup = subscriberIdx} = do
-- roster first, so the joiner trusts mod/admin keys before relay intros
forwardCachedRoster user gInfo subscriber
-- filter to current members so left/removed mods/admins aren't re-introduced to the joiner
modMs <- filter memberCurrent <$> withStore' (\db -> getGroupModerators db vr user gInfo)
(owners, rosterMems) <- withStore' $ \db ->
(,) <$> getGroupOwners db vr user gInfo <*> getGroupRosterMembers db vr user gInfo
let modMs = owners <> rosterMems
void $ sendGroupMessage' user gInfo modMs $ XGrpMemNew (memberInfo gInfo subscriber) Nothing
withStore' $ \db ->
setMemberVectorNewRelations db subscriber [(indexInGroup m, (IDSubjectIntroduced, MRIntroduced)) | m <- modMs]
let introEvts = map (memberIntroEvt gInfo) modMs
forM_ (L.nonEmpty introEvts) $ \introEvts' ->
sendGroupMemberMessages user gInfo conn introEvts'
-- owner intros first so the joiner has the owner profile loaded before applying the cached roster (signed by the owner)
sendIntros owners
forwardCachedRoster user gInfo subscriber
sendIntros rosterMems
withStore' $ \db ->
setMembersVectorsNewRelation db modMs subscriberIdx IDSubjectIntroduced MRIntroduced
where
sendIntros ms = forM_ (L.nonEmpty $ map (memberIntroEvt gInfo) ms) $ \evts ->
sendGroupMemberMessages user gInfo conn evts
userProfileInGroup :: User -> GroupInfo -> Maybe Profile -> Profile
userProfileInGroup user = userProfileInGroup' user . groupFeatureUserAllowed SGFSimplexLinks
@@ -1228,8 +1231,8 @@ isRosterRole r = r == GRModerator || r == GRAdmin
-- Drop non-privileged-role entries and de-duplicate by memberId, keeping the first.
validateGroupRoster :: GroupRoster -> GroupRoster
validateGroupRoster GroupRoster {version, roster = entries} =
GroupRoster {version, roster = dedup [] $ filter (\RosterMember {role} -> isRosterRole role) entries}
validateGroupRoster GroupRoster {version = ver, roster = entries} =
GroupRoster {version = ver, roster = dedup [] $ filter (\RosterMember {role} -> isRosterRole role) entries}
where
dedup _ [] = []
dedup seen (rm@RosterMember {memberId} : rms)
+9
View File
@@ -68,6 +68,7 @@ module Simplex.Chat.Store.Groups
getSupportScopeMembersByIndexes,
getGroupModerators,
getGroupRosterMembers,
getGroupOwners,
getGroupRelayMembers,
getGroupMembersForExpiration,
getRemovedMembersToCleanup,
@@ -1215,6 +1216,14 @@ getGroupRosterMembers db vr user@User {userId, userContactId} GroupInfo {groupId
(groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND m.member_role IN (?,?)")
(userId, groupId, userContactId, GRModerator, GRAdmin)
getGroupOwners :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember]
getGroupOwners db vr user@User {userId, userContactId} GroupInfo {groupId} = do
filter memberCurrent . map (toContactMember vr user)
<$> DB.query
db
(groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND m.member_role = ?")
(userId, groupId, userContactId, GROwner)
getGroupRelayMembers :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember]
getGroupRelayMembers db vr user@User {userId, userContactId} GroupInfo {groupId} = do
map (toContactMember vr user)
+22 -14
View File
@@ -9646,30 +9646,38 @@ testChannelRoleTransitionsUpdateRoster ps =
withNewTestChat ps "eve" eveProfile $ \eve ->
withNewTestChat ps "frank" frankProfile $ \frank -> do
(shortLink, fullLink) <- prepareChannel1Relay "team" alice bob
forM_ [cath, dan, eve] $ \member ->
memberJoinChannel "team" [bob] [alice] shortLink fullLink member
-- member -> moderator: dan/eve XGrpMemRole skipped; roster apply creates cath and emits chat item
threadDelay 1000000
memberJoinChannel "team" [bob] [alice] shortLink fullLink cath
-- member -> moderator
threadDelay 100000
alice ##> "/mr #team cath moderator"
alice <## "#team: you changed the role of cath to moderator (signed)"
concurrentlyN_
[ bob <## "#team: alice changed the role of cath from member to moderator (signed)",
cath <## "#team: alice changed your role from member to moderator (signed)",
dan <## "#team: alice changed the role of cath from member to moderator (signed)",
eve <## "#team: alice changed the role of cath from member to moderator (signed)"
cath <## "#team: alice changed your role from member to moderator (signed)"
]
-- moderator -> admin (within roster): dan/eve now know cath, so role event lands cleanly
threadDelay 1000000
-- dan joins; cached roster has cath as moderator
threadDelay 100000
memberJoinChannel "team" [bob] [alice, cath] shortLink fullLink dan
dan <## "#team: alice changed the role of cath from member to moderator (signed)"
threadDelay 100000
checkMemberRow dan "cath" (Just "moderator")
-- moderator -> admin: dan now knows cath, role event lands cleanly
threadDelay 100000
alice ##> "/mr #team cath admin"
alice <## "#team: you changed the role of cath to admin (signed)"
concurrentlyN_
[ bob <## "#team: alice changed the role of cath from moderator to admin (signed)",
cath <## "#team: alice changed your role from moderator to admin (signed)",
dan <## "#team: alice changed the role of cath from moderator to admin (signed)",
eve <## "#team: alice changed the role of cath from moderator to admin (signed)"
dan <## "#team: alice changed the role of cath from moderator to admin (signed)"
]
-- eve joins; cached roster has cath as admin
threadDelay 100000
memberJoinChannel "team" [bob] [alice, cath] shortLink fullLink eve
eve <## "#team: alice changed the role of cath from member to admin (signed)"
threadDelay 100000
checkMemberRow eve "cath" (Just "admin")
-- admin -> member (crossing out of roster): roster drops cath
threadDelay 1000000
threadDelay 100000
alice ##> "/mr #team cath member"
alice <## "#team: you changed the role of cath to member (signed)"
concurrentlyN_
@@ -9678,8 +9686,8 @@ testChannelRoleTransitionsUpdateRoster ps =
dan <## "#team: alice changed the role of cath from admin to member (signed)",
eve <## "#team: alice changed the role of cath from admin to member (signed)"
]
-- frank joins; cath never posted, and isn't in the roster, so frank has no record of her
threadDelay 1000000
-- frank joins; cath isn't in the roster, so frank has no record of her
threadDelay 100000
memberJoinChannel "team" [bob] [alice] shortLink fullLink frank
threadDelay 100000
checkMemberRow frank "cath" Nothing