Skip to content

Commit

Permalink
Extract member to channel links out of each member instance (#6910)
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Nov 28, 2024
1 parent ca9deb4 commit eb0e56d
Show file tree
Hide file tree
Showing 16 changed files with 167 additions and 77 deletions.
1 change: 1 addition & 0 deletions backend/canisters/community/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed

- Simplify how we store and query proposal votes ([#6906](https://github.com/open-chat-labs/open-chat/pull/6906))
- Extract member to channel links out of each member instance ([#6910](https://github.com/open-chat-labs/open-chat/pull/6910))

### Removed

Expand Down
31 changes: 24 additions & 7 deletions backend/canisters/community/impl/src/jobs/import_groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ pub(crate) async fn process_channel_members(group_id: ChatId, channel_id: Channe
let mut to_add: HashMap<UserId, UserType> = HashMap::new();

for user_id in channel.chat.members.member_ids().iter() {
if let Some(member) = state.data.members.get_by_user_id_mut(user_id) {
member.channels.insert(channel_id);
if state.data.members.member_ids().contains(user_id) {
state.data.members.mark_member_joined_channel(*user_id, channel_id);
} else {
let user_type = bots.get(user_id).copied().unwrap_or_default();
to_add.insert(*user_id, user_type);
Expand Down Expand Up @@ -251,15 +251,25 @@ pub(crate) async fn process_channel_members(group_id: ChatId, channel_id: Channe
AddResult::Success(_) => {
state.data.invited_users.remove(&user_id, now);

let member = state.data.members.get_by_user_id_mut(&user_id).unwrap();
for channel_id in public_channel_ids.iter() {
let user_type = state.data.members.bots().get(&user_id).copied().unwrap_or_default();

for channel_id in public_channel_ids.iter().filter(|&c| *c != channel_id) {
if let Some(channel) = state.data.channels.get_mut(channel_id) {
if channel.chat.gate_config.is_none() {
join_channel_unchecked(channel, member, state.data.is_public, true, now);
join_channel_unchecked(
user_id,
user_type,
channel,
&mut state.data.members,
state.data.is_public,
true,
now,
);
}
}
}
member.channels.insert(channel_id);

state.data.members.mark_member_joined_channel(user_id, channel_id);
members_added.push(user_id);
}
AddResult::AlreadyInCommunity => {}
Expand Down Expand Up @@ -310,7 +320,14 @@ fn add_community_members_to_channel_if_public(channel_id: ChannelId, state: &mut
// If this is a public channel, add all community members to it
if channel.chat.is_public.value && channel.chat.gate_config.value.is_none() {
let now = state.env.now();
add_members_to_public_channel_unchecked(channel, state.data.is_public, state.data.members.iter_mut(), now);
let user_ids: Vec<_> = state.data.members.member_ids().iter().copied().collect();
add_members_to_public_channel_unchecked(
user_ids.into_iter(),
channel,
&mut state.data.members,
state.data.is_public,
now,
);
}
}
}
Expand Down
11 changes: 6 additions & 5 deletions backend/canisters/community/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,11 @@ impl RuntimeState {
};

// Return all the channels that the user is a member of
let channels: Vec<_> = m
.channels
.iter()
.filter_map(|c| self.data.channels.get(c))
let channels: Vec<_> = self
.data
.members
.channels_for_member(m.user_id)
.filter_map(|c| self.data.channels.get(&c))
.filter_map(|c| c.summary(Some(m.user_id), true, data.is_public, &data.members))
.collect();

Expand Down Expand Up @@ -569,7 +570,7 @@ impl Data {
}

pub fn remove_user_from_channel(&mut self, user_id: UserId, channel_id: ChannelId, now: TimestampMillis) {
self.members.mark_member_left_channel(&user_id, channel_id, now);
self.members.mark_member_left_channel(user_id, channel_id, now);
self.expiring_members.remove_member(user_id, Some(channel_id));
self.expiring_member_actions.remove_member(user_id, Some(channel_id));
}
Expand Down
69 changes: 56 additions & 13 deletions backend/canisters/community/impl/src/model/members.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const MAX_MEMBERS_PER_COMMUNITY: u32 = 100_000;
#[derive(Serialize, Deserialize)]
pub struct CommunityMembers {
members: BTreeMap<UserId, CommunityMemberInternal>,
#[serde(default)]
member_channel_links: BTreeSet<(UserId, ChannelId)>,
#[serde(default)]
member_channel_links_removed: BTreeSet<(UserId, ChannelId)>,
user_groups: UserGroups,
// This includes the userIds of community members and also users invited to the community
principal_to_user_id_map: BTreeMap<Principal, UserId>,
Expand All @@ -35,6 +39,18 @@ pub struct CommunityMembers {
}

impl CommunityMembers {
pub fn populate_member_channel_links(&mut self) {
for member in self.members.values() {
for channel in member.channels.iter() {
self.member_channel_links.insert((member.user_id, *channel));
}
for channel_removed in member.channels_removed.iter() {
self.member_channel_links_removed
.insert((member.user_id, channel_removed.value));
}
}
}

pub fn new(
creator_principal: Principal,
creator_user_id: UserId,
Expand All @@ -47,7 +63,7 @@ impl CommunityMembers {
date_added: now,
role: CommunityRole::Owner,
suspended: Timestamped::default(),
channels: public_channels.into_iter().collect(),
channels: public_channels.iter().copied().collect(),
channels_removed: Vec::new(),
rules_accepted: Some(Timestamped::new(Version::zero(), now)),
user_type: creator_user_type,
Expand All @@ -59,6 +75,8 @@ impl CommunityMembers {

CommunityMembers {
members: vec![(creator_user_id, member)].into_iter().collect(),
member_channel_links: public_channels.into_iter().map(|c| (creator_user_id, c)).collect(),
member_channel_links_removed: BTreeSet::new(),
user_groups: UserGroups::default(),
principal_to_user_id_map: vec![(creator_principal, creator_user_id)].into_iter().collect(),
member_ids: [creator_user_id].into_iter().collect(),
Expand Down Expand Up @@ -318,15 +336,26 @@ impl CommunityMembers {
}
}

pub fn mark_member_joined_channel(&mut self, user_id: &UserId, channel_id: ChannelId) {
if let Some(member) = self.members.get_mut(user_id) {
pub fn mark_member_joined_channel(&mut self, user_id: UserId, channel_id: ChannelId) {
if let Some(member) = self.members.get_mut(&user_id) {
member.channels.insert(channel_id);
self.member_channel_links.insert((user_id, channel_id));
self.member_channel_links_removed.remove(&(user_id, channel_id));
}
}

pub fn mark_member_left_channel(&mut self, user_id: UserId, channel_id: ChannelId, now: TimestampMillis) {
if let Some(member) = self.members.get_mut(&user_id) {
if member.leave(channel_id, now) {
self.member_channel_links.remove(&(user_id, channel_id));
self.member_channel_links_removed.insert((user_id, channel_id));
}
}
}

pub fn mark_member_left_channel(&mut self, user_id: &UserId, channel_id: ChannelId, now: TimestampMillis) {
pub fn mark_rules_accepted(&mut self, user_id: &UserId, version: Version, now: TimestampMillis) {
if let Some(member) = self.members.get_mut(user_id) {
member.leave(channel_id, now);
member.accept_rules(version, now);
}
}

Expand Down Expand Up @@ -354,14 +383,10 @@ impl CommunityMembers {
self.blocked.iter().copied().collect()
}

pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut CommunityMemberInternal> {
self.members.values_mut()
}

pub fn lookup_user_id(&self, user_id_or_principal: Principal) -> Option<UserId> {
self.principal_to_user_id_map.get(&user_id_or_principal).copied().or_else(|| {
let user_id: UserId = user_id_or_principal.into();
self.members.contains_key(&user_id).then_some(user_id)
self.member_ids.contains(&user_id).then_some(user_id)
})
}

Expand Down Expand Up @@ -405,6 +430,17 @@ impl CommunityMembers {
&self.member_ids
}

pub fn channels_for_member(&self, user_id: UserId) -> impl Iterator<Item = ChannelId> + '_ {
self.member_channel_links
.range((user_id, ChannelId::from(0u32))..)
.take_while(move |(u, _)| *u == user_id)
.map(|(_, c)| *c)
}

pub fn member_channel_links_removed(&self) -> &BTreeSet<(UserId, ChannelId)> {
&self.member_channel_links_removed
}

pub fn owners(&self) -> &BTreeSet<UserId> {
&self.owners
}
Expand All @@ -413,6 +449,10 @@ impl CommunityMembers {
&self.admins
}

pub fn bots(&self) -> &BTreeMap<UserId, UserType> {
&self.bots
}

pub fn lapsed(&self) -> &BTreeSet<UserId> {
&self.lapsed
}
Expand Down Expand Up @@ -563,9 +603,9 @@ pub struct CommunityMemberInternal {
#[serde(rename = "r", alias = "role", default, skip_serializing_if = "is_default")]
role: CommunityRole,
#[serde(rename = "c", alias = "channels")]
pub channels: BTreeSet<ChannelId>,
channels: BTreeSet<ChannelId>,
#[serde(rename = "cr", alias = "channel_removed", default, skip_serializing_if = "Vec::is_empty")]
pub channels_removed: Vec<Timestamped<ChannelId>>,
channels_removed: Vec<Timestamped<ChannelId>>,
#[serde(rename = "ra", alias = "rules_accepted", skip_serializing_if = "Option::is_none")]
pub rules_accepted: Option<Timestamped<Version>>,
#[serde(rename = "ut", alias = "user_type", default, skip_serializing_if = "is_default")]
Expand All @@ -583,10 +623,13 @@ pub struct CommunityMemberInternal {
}

impl CommunityMemberInternal {
pub fn leave(&mut self, channel_id: ChannelId, now: TimestampMillis) {
pub fn leave(&mut self, channel_id: ChannelId, now: TimestampMillis) -> bool {
if self.channels.remove(&channel_id) {
self.channels_removed.retain(|c| c.value != channel_id);
self.channels_removed.push(Timestamped::new(channel_id, now));
true
} else {
false
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ fn summary_updates_impl(
let member_last_updated = member.as_ref().map(|m| m.last_updated()).unwrap_or_default();

let (channels_with_updates, channels_removed) = if let Some(m) = member {
let channels_with_updates: Vec<_> = m
.channels
.iter()
.filter_map(|c| state.data.channels.get(c))
let channels_with_updates: Vec<_> = state
.data
.members
.channels_for_member(m.user_id)
.filter_map(|c| state.data.channels.get(&c))
.filter(|c| c.last_updated(Some(m.user_id)) > updates_since)
.collect();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ fn commit(
) {
AddResult::Success(_) => {
users_added.push(user_id);
state.data.members.mark_member_joined_channel(&user_id, channel_id);
state.data.members.mark_member_joined_channel(user_id, channel_id);

if let Some(gate_expiry) = gate_expiry {
state.data.expiring_members.push(ExpiringMember {
Expand Down
Loading

0 comments on commit eb0e56d

Please sign in to comment.