From 8241cc0f453f852ace79f4dc26e6bb255f30c1ab Mon Sep 17 00:00:00 2001 From: megrogan Date: Mon, 25 Sep 2023 12:12:12 +0100 Subject: [PATCH 1/6] Follow/unfollow thread backend --- backend/canisters/community/api/can.did | 34 ++++++++ backend/canisters/community/api/src/main.rs | 2 + .../api/src/updates/follow_thread.rs | 21 +++++ .../community/api/src/updates/mod.rs | 2 + .../api/src/updates/unfollow_thread.rs | 21 +++++ .../impl/src/updates/follow_thread.rs | 43 ++++++++++ .../community/impl/src/updates/mod.rs | 2 + .../impl/src/updates/unfollow_thread.rs | 43 ++++++++++ backend/canisters/group/api/can.did | 30 +++++++ backend/canisters/group/api/src/main.rs | 2 + .../group/api/src/updates/follow_thread.rs | 19 +++++ .../canisters/group/api/src/updates/mod.rs | 2 + .../group/api/src/updates/unfollow_thread.rs | 19 +++++ .../group/impl/src/new_joiner_rewards.rs | 1 + .../group/impl/src/updates/follow_thread.rs | 39 +++++++++ .../canisters/group/impl/src/updates/mod.rs | 2 + .../group/impl/src/updates/unfollow_thread.rs | 39 +++++++++ .../impl/src/updates/c2c_send_messages.rs | 1 + .../user/impl/src/updates/send_message.rs | 2 + .../chat_events/src/chat_event_internal.rs | 8 +- .../libraries/chat_events/src/chat_events.rs | 84 +++++++++++++++++-- .../chat_events/src/chat_events_list.rs | 1 + backend/libraries/group_chat_core/src/lib.rs | 74 +++++++++++++++- backend/libraries/types/can.did | 1 + backend/libraries/types/src/thread_summary.rs | 2 + 25 files changed, 483 insertions(+), 11 deletions(-) create mode 100644 backend/canisters/community/api/src/updates/follow_thread.rs create mode 100644 backend/canisters/community/api/src/updates/unfollow_thread.rs create mode 100644 backend/canisters/community/impl/src/updates/follow_thread.rs create mode 100644 backend/canisters/community/impl/src/updates/unfollow_thread.rs create mode 100644 backend/canisters/group/api/src/updates/follow_thread.rs create mode 100644 backend/canisters/group/api/src/updates/unfollow_thread.rs create mode 100644 backend/canisters/group/impl/src/updates/follow_thread.rs create mode 100644 backend/canisters/group/impl/src/updates/unfollow_thread.rs diff --git a/backend/canisters/community/api/can.did b/backend/canisters/community/api/can.did index 1a1ef56e64..e9bfe8bd83 100644 --- a/backend/canisters/community/api/can.did +++ b/backend/canisters/community/api/can.did @@ -891,6 +891,38 @@ type UpdateUserGroupResponse = variant { UserSuspended; }; +type FollowThreadArgs = record { + channel_id : ChannelId; + thread_root_message_index : MessageIndex; +}; + +type FollowThreadResponse = variant { + Success; + AlreadyFollowing; + ThreadNotFound; + ChannelNotFound; + UserNotInChannel; + UserNotInCommunity; + UserSuspended; + CommunityFrozen; +}; + +type UnfollowThreadArgs = record { + channel_id : ChannelId; + thread_root_message_index : MessageIndex; +}; + +type UnfollowThreadResponse = variant { + Success; + NotFollowing; + ThreadNotFound; + ChannelNotFound; + UserNotInChannel; + UserNotInCommunity; + UserSuspended; + CommunityFrozen; +}; + service : { channel_summary : (ChannelSummaryArgs) -> (ChannelSummaryResponse) query; channel_summary_updates : (ChannelSummaryUpdatesArgs) -> (ChannelSummaryUpdatesResponse) query; @@ -947,4 +979,6 @@ service : { update_channel : (UpdateChannelArgs) -> (UpdateChannelResponse); update_community : (UpdateCommunityArgs) -> (UpdateCommunityResponse); update_user_group : (UpdateUserGroupArgs) -> (UpdateUserGroupResponse); + follow_thread : (FollowThreadArgs) -> (FollowThreadResponse); + unfollow_thread : (UnfollowThreadArgs) -> (UnfollowThreadResponse); }; diff --git a/backend/canisters/community/api/src/main.rs b/backend/canisters/community/api/src/main.rs index 468fb7c8d9..e45c02470a 100644 --- a/backend/canisters/community/api/src/main.rs +++ b/backend/canisters/community/api/src/main.rs @@ -38,6 +38,7 @@ fn main() { generate_candid_method!(community, disable_invite_code, update); generate_candid_method!(community, edit_message, update); generate_candid_method!(community, enable_invite_code, update); + generate_candid_method!(community, follow_thread, update); generate_candid_method!(community, import_group, update); generate_candid_method!(community, leave_channel, update); generate_candid_method!(community, pin_message, update); @@ -53,6 +54,7 @@ fn main() { generate_candid_method!(community, toggle_mute_notifications, update); generate_candid_method!(community, unblock_user, update); generate_candid_method!(community, undelete_messages, update); + generate_candid_method!(community, unfollow_thread, update); generate_candid_method!(community, unpin_message, update); generate_candid_method!(community, update_channel, update); generate_candid_method!(community, update_community, update); diff --git a/backend/canisters/community/api/src/updates/follow_thread.rs b/backend/canisters/community/api/src/updates/follow_thread.rs new file mode 100644 index 0000000000..122a5817c7 --- /dev/null +++ b/backend/canisters/community/api/src/updates/follow_thread.rs @@ -0,0 +1,21 @@ +use candid::CandidType; +use serde::{Deserialize, Serialize}; +use types::{ChannelId, MessageIndex}; + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub struct Args { + pub channel_id: ChannelId, + pub thread_root_message_index: MessageIndex, +} + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub enum Response { + Success, + AlreadyFollowing, + ThreadNotFound, + ChannelNotFound, + UserNotInChannel, + UserNotInCommunity, + UserSuspended, + CommunityFrozen, +} diff --git a/backend/canisters/community/api/src/updates/mod.rs b/backend/canisters/community/api/src/updates/mod.rs index 53f1b57664..4886bd8e9c 100644 --- a/backend/canisters/community/api/src/updates/mod.rs +++ b/backend/canisters/community/api/src/updates/mod.rs @@ -26,6 +26,7 @@ pub mod delete_user_groups; pub mod disable_invite_code; pub mod edit_message; pub mod enable_invite_code; +pub mod follow_thread; pub mod import_group; pub mod leave_channel; pub mod pin_message; @@ -41,6 +42,7 @@ pub mod set_member_display_name; pub mod toggle_mute_notifications; pub mod unblock_user; pub mod undelete_messages; +pub mod unfollow_thread; pub mod unpin_message; pub mod update_channel; pub mod update_community; diff --git a/backend/canisters/community/api/src/updates/unfollow_thread.rs b/backend/canisters/community/api/src/updates/unfollow_thread.rs new file mode 100644 index 0000000000..5b35bfdf1a --- /dev/null +++ b/backend/canisters/community/api/src/updates/unfollow_thread.rs @@ -0,0 +1,21 @@ +use candid::CandidType; +use serde::{Deserialize, Serialize}; +use types::{ChannelId, MessageIndex}; + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub struct Args { + pub channel_id: ChannelId, + pub thread_root_message_index: MessageIndex, +} + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub enum Response { + Success, + NotFollowing, + ThreadNotFound, + ChannelNotFound, + UserNotInCommunity, + UserNotInChannel, + UserSuspended, + CommunityFrozen, +} diff --git a/backend/canisters/community/impl/src/updates/follow_thread.rs b/backend/canisters/community/impl/src/updates/follow_thread.rs new file mode 100644 index 0000000000..874a6d7ee0 --- /dev/null +++ b/backend/canisters/community/impl/src/updates/follow_thread.rs @@ -0,0 +1,43 @@ +use crate::{activity_notifications::handle_activity_notification, mutate_state, run_regular_jobs, RuntimeState}; +use canister_tracing_macros::trace; +use community_canister::follow_thread::{Response::*, *}; +use group_chat_core::FollowThreadResult; +use ic_cdk_macros::update; + +#[update] +#[trace] +fn follow_thread(args: Args) -> Response { + run_regular_jobs(); + + mutate_state(|state| follow_thread_impl(args, state)) +} + +fn follow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { + if state.data.is_frozen() { + return CommunityFrozen; + } + + let caller = state.env.caller(); + let now = state.env.now(); + + let user_id = match state.data.members.get(caller) { + Some(member) if member.suspended.value => return UserSuspended, + Some(member) => member.user_id, + None => return UserNotInCommunity, + }; + + if let Some(channel) = state.data.channels.get_mut(&args.channel_id) { + match channel.chat.follow_thread(user_id, args.thread_root_message_index, now) { + FollowThreadResult::Success => { + handle_activity_notification(state); + Success + } + FollowThreadResult::AlreadyFollowing => AlreadyFollowing, + FollowThreadResult::ThreadNotFound => ThreadNotFound, + FollowThreadResult::UserNotInGroup => UserNotInChannel, + FollowThreadResult::UserSuspended => UserSuspended, + } + } else { + ChannelNotFound + } +} diff --git a/backend/canisters/community/impl/src/updates/mod.rs b/backend/canisters/community/impl/src/updates/mod.rs index 68de868873..8ae9a96d59 100644 --- a/backend/canisters/community/impl/src/updates/mod.rs +++ b/backend/canisters/community/impl/src/updates/mod.rs @@ -23,6 +23,7 @@ pub mod delete_user_groups; pub mod disable_invite_code; pub mod edit_message; pub mod enable_invite_code; +pub mod follow_thread; pub mod import_group; pub mod leave_channel; pub mod pin_message; @@ -37,6 +38,7 @@ pub mod set_member_display_name; pub mod toggle_mute_notifications; pub mod unblock_user; pub mod undelete_messages; +pub mod unfollow_thread; pub mod update_channel; pub mod update_community; pub mod update_user_group; diff --git a/backend/canisters/community/impl/src/updates/unfollow_thread.rs b/backend/canisters/community/impl/src/updates/unfollow_thread.rs new file mode 100644 index 0000000000..4ebef1e33b --- /dev/null +++ b/backend/canisters/community/impl/src/updates/unfollow_thread.rs @@ -0,0 +1,43 @@ +use crate::{activity_notifications::handle_activity_notification, mutate_state, run_regular_jobs, RuntimeState}; +use canister_tracing_macros::trace; +use community_canister::unfollow_thread::{Response::*, *}; +use group_chat_core::UnfollowThreadResult; +use ic_cdk_macros::update; + +#[update] +#[trace] +fn unfollow_thread(args: Args) -> Response { + run_regular_jobs(); + + mutate_state(|state| unfollow_thread_impl(args, state)) +} + +fn unfollow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { + if state.data.is_frozen() { + return CommunityFrozen; + } + + let caller = state.env.caller(); + let now = state.env.now(); + + let user_id = match state.data.members.get(caller) { + Some(member) if member.suspended.value => return UserSuspended, + Some(member) => member.user_id, + None => return UserNotInCommunity, + }; + + if let Some(channel) = state.data.channels.get_mut(&args.channel_id) { + match channel.chat.unfollow_thread(user_id, args.thread_root_message_index, now) { + UnfollowThreadResult::Success => { + handle_activity_notification(state); + Success + } + UnfollowThreadResult::NotFollowing => NotFollowing, + UnfollowThreadResult::ThreadNotFound => ThreadNotFound, + UnfollowThreadResult::UserNotInGroup => UserNotInChannel, + UnfollowThreadResult::UserSuspended => UserSuspended, + } + } else { + ChannelNotFound + } +} diff --git a/backend/canisters/group/api/can.did b/backend/canisters/group/api/can.did index 247ae919a2..e5b38398dd 100644 --- a/backend/canisters/group/api/can.did +++ b/backend/canisters/group/api/can.did @@ -564,6 +564,34 @@ type ResetInviteCodeResponse = variant { ChatFrozen; }; +type FollowThreadArgs = record { + channel_id : ChannelId; + thread_root_message_index : MessageIndex; +}; + +type FollowThreadResponse = variant { + Success; + AlreadyFollowing; + ThreadNotFound; + UserNotInGroup; + UserSuspended; + GroupFrozen; +}; + +type UnfollowThreadArgs = record { + channel_id : ChannelId; + thread_root_message_index : MessageIndex; +}; + +type UnfollowThreadResponse = variant { + Success; + NotFollowing; + ThreadNotFound; + UserNotInGroup; + UserSuspended; + GroupFrozen; +}; + service : { // Owner only convert_into_community : (ConvertIntoCommunityArgs) -> (ConvertIntoCommunityResponse); @@ -594,6 +622,8 @@ service : { claim_prize : (ClaimPrizeArgs) -> (ClaimPrizeResponse); decline_invitation : (EmptyArgs) -> (DeclineInvitationResponse); toggle_mute_notifications : (ToggleMuteNotificationsArgs) -> (ToggleMuteNotificationsResponse); + follow_thread : (FollowThreadArgs) -> (FollowThreadResponse); + unfollow_thread : (UnfollowThreadArgs) -> (UnfollowThreadResponse); summary : (SummaryArgs) -> (SummaryResponse) query; summary_updates : (SummaryUpdatesArgs) -> (SummaryUpdatesResponse) query; diff --git a/backend/canisters/group/api/src/main.rs b/backend/canisters/group/api/src/main.rs index e077997648..857c909e1c 100644 --- a/backend/canisters/group/api/src/main.rs +++ b/backend/canisters/group/api/src/main.rs @@ -28,6 +28,7 @@ fn main() { generate_candid_method!(group, disable_invite_code, update); generate_candid_method!(group, edit_message_v2, update); generate_candid_method!(group, enable_invite_code, update); + generate_candid_method!(group, follow_thread, update); generate_candid_method!(group, pin_message_v2, update); generate_candid_method!(group, register_poll_vote, update); generate_candid_method!(group, register_proposal_vote, update); @@ -39,6 +40,7 @@ fn main() { generate_candid_method!(group, toggle_mute_notifications, update); generate_candid_method!(group, unblock_user, update); generate_candid_method!(group, undelete_messages, update); + generate_candid_method!(group, unfollow_thread, update); generate_candid_method!(group, unpin_message, update); generate_candid_method!(group, update_group_v2, update); diff --git a/backend/canisters/group/api/src/updates/follow_thread.rs b/backend/canisters/group/api/src/updates/follow_thread.rs new file mode 100644 index 0000000000..a6b8461f8b --- /dev/null +++ b/backend/canisters/group/api/src/updates/follow_thread.rs @@ -0,0 +1,19 @@ +use candid::CandidType; +use serde::{Deserialize, Serialize}; +use types::{ChannelId, MessageIndex}; + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub struct Args { + pub channel_id: ChannelId, + pub thread_root_message_index: MessageIndex, +} + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub enum Response { + Success, + AlreadyFollowing, + ThreadNotFound, + UserNotInGroup, + UserSuspended, + GroupFrozen, +} diff --git a/backend/canisters/group/api/src/updates/mod.rs b/backend/canisters/group/api/src/updates/mod.rs index c759a1fa94..79099bfdf9 100644 --- a/backend/canisters/group/api/src/updates/mod.rs +++ b/backend/canisters/group/api/src/updates/mod.rs @@ -23,6 +23,7 @@ pub mod delete_messages; pub mod disable_invite_code; pub mod edit_message_v2; pub mod enable_invite_code; +pub mod follow_thread; pub mod pin_message_v2; pub mod register_poll_vote; pub mod register_proposal_vote; @@ -34,5 +35,6 @@ pub mod send_message_v2; pub mod toggle_mute_notifications; pub mod unblock_user; pub mod undelete_messages; +pub mod unfollow_thread; pub mod unpin_message; pub mod update_group_v2; diff --git a/backend/canisters/group/api/src/updates/unfollow_thread.rs b/backend/canisters/group/api/src/updates/unfollow_thread.rs new file mode 100644 index 0000000000..4b26a4fbc3 --- /dev/null +++ b/backend/canisters/group/api/src/updates/unfollow_thread.rs @@ -0,0 +1,19 @@ +use candid::CandidType; +use serde::{Deserialize, Serialize}; +use types::{ChannelId, MessageIndex}; + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub struct Args { + pub channel_id: ChannelId, + pub thread_root_message_index: MessageIndex, +} + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub enum Response { + Success, + NotFollowing, + ThreadNotFound, + UserNotInGroup, + UserSuspended, + GroupFrozen, +} diff --git a/backend/canisters/group/impl/src/new_joiner_rewards.rs b/backend/canisters/group/impl/src/new_joiner_rewards.rs index cb31021523..422877b1a7 100644 --- a/backend/canisters/group/impl/src/new_joiner_rewards.rs +++ b/backend/canisters/group/impl/src/new_joiner_rewards.rs @@ -81,6 +81,7 @@ fn send_reward_transferred_message(user_id: UserId, transfer: nns::CompletedCryp transfer: CryptoTransaction::Completed(CompletedCryptoTransaction::NNS(transfer)), caption: None, }), + mentioned: Vec::new(), replies_to: None, forwarded: false, correlation_id: 0, diff --git a/backend/canisters/group/impl/src/updates/follow_thread.rs b/backend/canisters/group/impl/src/updates/follow_thread.rs new file mode 100644 index 0000000000..6813252a60 --- /dev/null +++ b/backend/canisters/group/impl/src/updates/follow_thread.rs @@ -0,0 +1,39 @@ +use crate::{activity_notifications::handle_activity_notification, mutate_state, run_regular_jobs, RuntimeState}; +use canister_tracing_macros::trace; +use group_canister::follow_thread::{Response::*, *}; +use group_chat_core::FollowThreadResult; +use ic_cdk_macros::update; + +#[update] +#[trace] +fn follow_thread(args: Args) -> Response { + run_regular_jobs(); + + mutate_state(|state| follow_thread_impl(args, state)) +} + +fn follow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { + if state.data.is_frozen() { + return GroupFrozen; + } + + let caller = state.env.caller(); + + let user_id = match state.data.lookup_user_id(caller) { + Some(uid) => uid, + None => return UserNotInGroup, + }; + + let now = state.env.now(); + + match state.data.chat.follow_thread(user_id, args.thread_root_message_index, now) { + FollowThreadResult::Success => { + handle_activity_notification(state); + Success + } + FollowThreadResult::AlreadyFollowing => AlreadyFollowing, + FollowThreadResult::ThreadNotFound => ThreadNotFound, + FollowThreadResult::UserNotInGroup => UserNotInGroup, + FollowThreadResult::UserSuspended => UserSuspended, + } +} diff --git a/backend/canisters/group/impl/src/updates/mod.rs b/backend/canisters/group/impl/src/updates/mod.rs index d108db845d..6e86023cfc 100644 --- a/backend/canisters/group/impl/src/updates/mod.rs +++ b/backend/canisters/group/impl/src/updates/mod.rs @@ -21,6 +21,7 @@ pub mod delete_messages; pub mod disable_invite_code; pub mod edit_message; pub mod enable_invite_code; +pub mod follow_thread; pub mod pin_message; pub mod register_poll_vote; pub mod register_proposal_vote; @@ -31,6 +32,7 @@ pub mod send_message; pub mod toggle_mute_notifications; pub mod unblock_user; pub mod undelete_messages; +pub mod unfollow_thread; pub mod unpin_message; pub mod update_group_v2; pub mod wallet_receive; diff --git a/backend/canisters/group/impl/src/updates/unfollow_thread.rs b/backend/canisters/group/impl/src/updates/unfollow_thread.rs new file mode 100644 index 0000000000..0a3e82f36b --- /dev/null +++ b/backend/canisters/group/impl/src/updates/unfollow_thread.rs @@ -0,0 +1,39 @@ +use crate::{activity_notifications::handle_activity_notification, mutate_state, run_regular_jobs, RuntimeState}; +use canister_tracing_macros::trace; +use group_canister::unfollow_thread::{Response::*, *}; +use group_chat_core::UnfollowThreadResult; +use ic_cdk_macros::update; + +#[update] +#[trace] +fn unfollow_thread(args: Args) -> Response { + run_regular_jobs(); + + mutate_state(|state| unfollow_thread_impl(args, state)) +} + +fn unfollow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { + if state.data.is_frozen() { + return GroupFrozen; + } + + let caller = state.env.caller(); + + let user_id = match state.data.lookup_user_id(caller) { + Some(uid) => uid, + None => return UserNotInGroup, + }; + + let now = state.env.now(); + + match state.data.chat.unfollow_thread(user_id, args.thread_root_message_index, now) { + UnfollowThreadResult::Success => { + handle_activity_notification(state); + Success + } + UnfollowThreadResult::NotFollowing => NotFollowing, + UnfollowThreadResult::ThreadNotFound => ThreadNotFound, + UnfollowThreadResult::UserNotInGroup => UserNotInGroup, + UnfollowThreadResult::UserSuspended => UserSuspended, + } +} diff --git a/backend/canisters/user/impl/src/updates/c2c_send_messages.rs b/backend/canisters/user/impl/src/updates/c2c_send_messages.rs index b78363531d..40f53324c1 100644 --- a/backend/canisters/user/impl/src/updates/c2c_send_messages.rs +++ b/backend/canisters/user/impl/src/updates/c2c_send_messages.rs @@ -185,6 +185,7 @@ pub(crate) fn handle_message_impl( message_id: args.message_id.unwrap_or_else(|| state.env.rng().gen()), sender, content, + mentioned: Vec::new(), replies_to, forwarded: args.forwarding, correlation_id: args.correlation_id, diff --git a/backend/canisters/user/impl/src/updates/send_message.rs b/backend/canisters/user/impl/src/updates/send_message.rs index a66df707ab..af930be9bd 100644 --- a/backend/canisters/user/impl/src/updates/send_message.rs +++ b/backend/canisters/user/impl/src/updates/send_message.rs @@ -162,6 +162,7 @@ fn send_message_impl( message_id: args.message_id, sender: my_user_id, content: args.content.clone().into(), + mentioned: Vec::new(), replies_to: args.replies_to.as_ref().map(|r| r.into()), forwarded: args.forwarding, correlation_id: args.correlation_id, @@ -298,6 +299,7 @@ async fn send_to_bot_canister(recipient: UserId, message_index: MessageIndex, ar thread_root_message_index: None, message_id: message.message_id.unwrap_or_else(|| state.env.rng().gen()), content: message.content.into(), + mentioned: Vec::new(), replies_to: None, forwarded: false, correlation_id: 0, diff --git a/backend/libraries/chat_events/src/chat_event_internal.rs b/backend/libraries/chat_events/src/chat_event_internal.rs index f43480f795..30bd1bd9b7 100644 --- a/backend/libraries/chat_events/src/chat_event_internal.rs +++ b/backend/libraries/chat_events/src/chat_event_internal.rs @@ -183,7 +183,7 @@ impl MessageInternal { tips: self.tips.clone(), edited: self.last_edited.is_some(), forwarded: self.forwarded, - thread_summary: self.thread_summary.as_ref().map(|t| t.hydrate()), + thread_summary: self.thread_summary.as_ref().map(|t| t.hydrate(my_user_id)), last_updated: self.last_updated, } } @@ -446,6 +446,8 @@ impl MessageContentInternal { pub struct ThreadSummaryInternal { #[serde(rename = "i")] pub participant_ids: Vec, + #[serde(default, rename = "f")] + pub follower_ids: HashSet, #[serde(rename = "r")] pub reply_count: u32, #[serde(rename = "e")] @@ -455,9 +457,10 @@ pub struct ThreadSummaryInternal { } impl ThreadSummaryInternal { - pub fn hydrate(&self) -> ThreadSummary { + pub fn hydrate(&self, my_user_id: Option) -> ThreadSummary { ThreadSummary { participant_ids: self.participant_ids.clone(), + followed_by_me: my_user_id.map_or(false, |u| self.follower_ids.contains(&u)), reply_count: self.reply_count, latest_event_index: self.latest_event_index, latest_event_timestamp: self.latest_event_timestamp, @@ -830,6 +833,7 @@ mod tests { }), thread_summary: Some(ThreadSummaryInternal { participant_ids: vec![principal.into()], + follower_ids: HashSet::new(), reply_count: 1, latest_event_index: 1.into(), latest_event_timestamp: 1, diff --git a/backend/libraries/chat_events/src/chat_events.rs b/backend/libraries/chat_events/src/chat_events.rs index 4dcab63ede..896763ab8b 100644 --- a/backend/libraries/chat_events/src/chat_events.rs +++ b/backend/libraries/chat_events/src/chat_events.rs @@ -138,7 +138,7 @@ impl ChatEvents { self.update_thread_summary( root_message_index, args.sender, - Some(message_index), + args.mentioned, push_event_result.index, args.now, ); @@ -643,6 +643,7 @@ impl ChatEvents { transaction, prize_message: message_index, }), + mentioned: Vec::new(), replies_to: None, forwarded: false, correlation_id: 0, @@ -757,6 +758,7 @@ impl ChatEvents { notes, }], }), + mentioned: Vec::new(), replies_to: Some(ReplyContextInternal { chat_if_other: Some((chat.into(), thread_root_message_index)), event_index, @@ -780,11 +782,63 @@ impl ChatEvents { } } + pub fn follow_thread( + &mut self, + thread_root_message_index: MessageIndex, + user_id: UserId, + min_visible_event_index: EventIndex, + now: TimestampMillis, + ) -> FollowThreadResult { + use FollowThreadResult::*; + + if let Some((root_message, event_index)) = + self.message_internal_mut(min_visible_event_index, None, thread_root_message_index.into(), now) + { + if let Some(summary) = &mut root_message.thread_summary { + if !summary.participant_ids.contains(&user_id) && summary.follower_ids.insert(user_id) { + root_message.last_updated = Some(now); + self.last_updated_timestamps.mark_updated(None, event_index, now); + return Success; + } else { + return AlreadyFollowing; + } + } + } + + ThreadNotFound + } + + pub fn unfollow_thread( + &mut self, + thread_root_message_index: MessageIndex, + user_id: UserId, + min_visible_event_index: EventIndex, + now: TimestampMillis, + ) -> UnfollowThreadResult { + use UnfollowThreadResult::*; + + if let Some((root_message, event_index)) = + self.message_internal_mut(min_visible_event_index, None, thread_root_message_index.into(), now) + { + if let Some(summary) = &mut root_message.thread_summary { + if summary.follower_ids.remove(&user_id) { + root_message.last_updated = Some(now); + self.last_updated_timestamps.mark_updated(None, event_index, now); + return Success; + } else { + return NotFollowing; + } + } + } + + ThreadNotFound + } + fn update_thread_summary( &mut self, thread_root_message_index: MessageIndex, user_id: UserId, - latest_thread_message_index_if_updated: Option, + mentioned_users: Vec, latest_event_index: EventIndex, now: TimestampMillis, ) { @@ -797,10 +851,15 @@ impl ChatEvents { let summary = root_message.thread_summary.get_or_insert_with(ThreadSummaryInternal::default); summary.latest_event_index = latest_event_index; summary.latest_event_timestamp = now; - - if latest_thread_message_index_if_updated.is_some() { - summary.reply_count += 1; - summary.participant_ids.push_if_not_contains(user_id); + summary.reply_count += 1; + summary.participant_ids.push_if_not_contains(user_id); + summary.follower_ids.remove(&user_id); + + // If a user is mentioned in a thread they automatically become a follower + for muid in mentioned_users { + if !summary.participant_ids.contains(&muid) { + summary.follower_ids.insert(muid); + } } self.last_updated_timestamps.mark_updated(None, event_index, now); @@ -1218,6 +1277,7 @@ pub struct PushMessageArgs { pub thread_root_message_index: Option, pub message_id: MessageId, pub content: MessageContentInternal, + pub mentioned: Vec, pub replies_to: Option, pub forwarded: bool, pub correlation_id: u64, @@ -1373,6 +1433,18 @@ pub enum UnreservePrizeResult { ReservationNotFound, } +pub enum FollowThreadResult { + Success, + AlreadyFollowing, + ThreadNotFound, +} + +pub enum UnfollowThreadResult { + Success, + NotFollowing, + ThreadNotFound, +} + #[derive(Copy, Clone)] pub enum EventKey { EventIndex(EventIndex), diff --git a/backend/libraries/chat_events/src/chat_events_list.rs b/backend/libraries/chat_events/src/chat_events_list.rs index 937fd6d391..623757735f 100644 --- a/backend/libraries/chat_events/src/chat_events_list.rs +++ b/backend/libraries/chat_events/src/chat_events_list.rs @@ -671,6 +671,7 @@ mod tests { content: MessageContentInternal::Text(TextContent { text: "hello".to_owned(), }), + mentioned: Vec::new(), replies_to: None, now, forwarded: false, diff --git a/backend/libraries/group_chat_core/src/lib.rs b/backend/libraries/group_chat_core/src/lib.rs index 51461732f5..e014118b0f 100644 --- a/backend/libraries/group_chat_core/src/lib.rs +++ b/backend/libraries/group_chat_core/src/lib.rs @@ -733,6 +733,7 @@ impl GroupChatCore { thread_root_message_index, message_id, content: content.into(), + mentioned: mentioned.clone(), replies_to: replies_to.as_ref().map(|r| r.into()), forwarded: forwarding, correlation_id: 0, @@ -745,7 +746,7 @@ impl GroupChatCore { let mut mentions: HashSet<_> = mentioned.into_iter().chain(user_being_replied_to).collect(); let mut users_to_notify = HashSet::new(); - let mut thread_participants: Option> = None; + let mut thread_followers: Option> = None; if let Some(thread_root_message) = thread_root_message_index.and_then(|root_message_index| { self.events @@ -758,7 +759,8 @@ impl GroupChatCore { } if let Some(thread_summary) = thread_root_message.thread_summary { - thread_participants = Some(HashSet::from_iter(thread_summary.participant_ids)); + let participants = HashSet::from_iter(thread_summary.participant_ids); + thread_followers = Some(thread_summary.follower_ids.union(&participants).copied().collect()); let is_first_reply = thread_summary.reply_count == 1; if is_first_reply { @@ -782,7 +784,7 @@ impl GroupChatCore { member.mentions.add(thread_root_message_index, message_index, now); } - let notification_candidate = thread_participants.as_ref().map_or(true, |ps| ps.contains(&member.user_id)); + let notification_candidate = thread_followers.as_ref().map_or(true, |ps| ps.contains(&member.user_id)); if mentioned || (notification_candidate && !member.notifications_muted.value) { // Notify this member @@ -1535,6 +1537,56 @@ impl GroupChatCore { .map_or(false, |accepted| accepted.value >= self.rules.text.version)) } + pub fn follow_thread( + &mut self, + user_id: UserId, + thread_root_message_index: MessageIndex, + now: TimestampMillis, + ) -> FollowThreadResult { + use FollowThreadResult::*; + + if let Some(member) = self.members.get_mut(&user_id) { + match self + .events + .follow_thread(thread_root_message_index, user_id, member.min_visible_event_index(), now) + { + chat_events::FollowThreadResult::Success => { + member.threads.insert(thread_root_message_index); + Success + } + chat_events::FollowThreadResult::AlreadyFollowing => AlreadyFollowing, + chat_events::FollowThreadResult::ThreadNotFound => ThreadNotFound, + } + } else { + UserNotInGroup + } + } + + pub fn unfollow_thread( + &mut self, + user_id: UserId, + thread_root_message_index: MessageIndex, + now: TimestampMillis, + ) -> UnfollowThreadResult { + use UnfollowThreadResult::*; + + if let Some(member) = self.members.get_mut(&user_id) { + match self + .events + .unfollow_thread(thread_root_message_index, user_id, member.min_visible_event_index(), now) + { + chat_events::UnfollowThreadResult::Success => { + member.threads.remove(&thread_root_message_index); + Success + } + chat_events::UnfollowThreadResult::NotFollowing => NotFollowing, + chat_events::UnfollowThreadResult::ThreadNotFound => ThreadNotFound, + } + } else { + UserNotInGroup + } + } + fn events_reader( &self, user_id: Option, @@ -1803,6 +1855,22 @@ pub struct InvitedUsersSuccess { pub group_name: String, } +pub enum FollowThreadResult { + Success, + AlreadyFollowing, + ThreadNotFound, + UserNotInGroup, + UserSuspended, +} + +pub enum UnfollowThreadResult { + Success, + NotFollowing, + ThreadNotFound, + UserNotInGroup, + UserSuspended, +} + #[derive(Default)] pub struct SummaryUpdatesFromEvents { pub name: Option, diff --git a/backend/libraries/types/can.did b/backend/libraries/types/can.did index 696d1b2e83..95aa7af2d9 100644 --- a/backend/libraries/types/can.did +++ b/backend/libraries/types/can.did @@ -1269,6 +1269,7 @@ type CommunityRole = variant { type ThreadSummary = record { participant_ids : vec UserId; + followed_by_me : bool; reply_count : nat32; latest_event_index : EventIndex; latest_event_timestamp : TimestampMillis; diff --git a/backend/libraries/types/src/thread_summary.rs b/backend/libraries/types/src/thread_summary.rs index 9da1e52ec0..f98083d6ff 100644 --- a/backend/libraries/types/src/thread_summary.rs +++ b/backend/libraries/types/src/thread_summary.rs @@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize}; #[derive(CandidType, Serialize, Deserialize, Clone, Debug)] pub struct ThreadSummary { pub participant_ids: Vec, + #[serde(default)] + pub followed_by_me: bool, pub reply_count: u32, pub latest_event_index: EventIndex, pub latest_event_timestamp: TimestampMillis, From a0215179eb8e0ac3038a0963c29e7c390e7f6035 Mon Sep 17 00:00:00 2001 From: megrogan Date: Mon, 25 Sep 2023 12:42:06 +0100 Subject: [PATCH 2/6] CHANGELOGs --- backend/canisters/community/CHANGELOG.md | 1 + backend/canisters/group/CHANGELOG.md | 1 + backend/canisters/user/CHANGELOG.md | 1 + 3 files changed, 3 insertions(+) diff --git a/backend/canisters/community/CHANGELOG.md b/backend/canisters/community/CHANGELOG.md index 9cff598073..1fbc9f681c 100644 --- a/backend/canisters/community/CHANGELOG.md +++ b/backend/canisters/community/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Implement tipping messages ([#4420](https://github.com/open-chat-labs/open-chat/pull/4420)) - Implement notifications for message tips ([#4427](https://github.com/open-chat-labs/open-chat/pull/4427)) +- Implement follow/unfollow thread ([#4431](https://github.com/open-chat-labs/open-chat/pull/4431)) ### Changed diff --git a/backend/canisters/group/CHANGELOG.md b/backend/canisters/group/CHANGELOG.md index 13cb2a24e9..e821937e79 100644 --- a/backend/canisters/group/CHANGELOG.md +++ b/backend/canisters/group/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Implement tipping messages ([#4420](https://github.com/open-chat-labs/open-chat/pull/4420)) - Implement notifications for message tips ([#4427](https://github.com/open-chat-labs/open-chat/pull/4427)) +- Implement follow/unfollow thread ([#4431](https://github.com/open-chat-labs/open-chat/pull/4431)) ### Changed diff --git a/backend/canisters/user/CHANGELOG.md b/backend/canisters/user/CHANGELOG.md index 48db10f494..dde09c0dc1 100644 --- a/backend/canisters/user/CHANGELOG.md +++ b/backend/canisters/user/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add `mention_all_members` group permission ([#4405](https://github.com/open-chat-labs/open-chat/pull/4405)) - Implement tipping messages ([#4420](https://github.com/open-chat-labs/open-chat/pull/4420)) - Implement notifications for message tips ([#4427](https://github.com/open-chat-labs/open-chat/pull/4427)) +- Add `followed_by_me` to the thread summary returned in GroupChatSummary ([#4431](https://github.com/open-chat-labs/open-chat/pull/4431)) ## [[2.0.852](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.852-user)] - 2023-09-18 From 33841fc4e24c0aeafa5078aea04ac8c6647d14bc Mon Sep 17 00:00:00 2001 From: megrogan Date: Mon, 25 Sep 2023 12:54:26 +0100 Subject: [PATCH 3/6] Mark community/group updated in user canister --- .../community/impl/src/updates/follow_thread.rs | 10 ++++++++-- .../community/impl/src/updates/unfollow_thread.rs | 10 ++++++++-- .../canisters/group/impl/src/updates/follow_thread.rs | 10 ++++++++-- .../group/impl/src/updates/unfollow_thread.rs | 10 ++++++++-- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/backend/canisters/community/impl/src/updates/follow_thread.rs b/backend/canisters/community/impl/src/updates/follow_thread.rs index 874a6d7ee0..4014cd2f28 100644 --- a/backend/canisters/community/impl/src/updates/follow_thread.rs +++ b/backend/canisters/community/impl/src/updates/follow_thread.rs @@ -1,8 +1,10 @@ -use crate::{activity_notifications::handle_activity_notification, mutate_state, run_regular_jobs, RuntimeState}; +use crate::{mutate_state, run_regular_jobs, RuntimeState}; use canister_tracing_macros::trace; use community_canister::follow_thread::{Response::*, *}; use group_chat_core::FollowThreadResult; use ic_cdk_macros::update; +use msgpack::serialize_then_unwrap; +use types::Empty; #[update] #[trace] @@ -29,7 +31,11 @@ fn follow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { if let Some(channel) = state.data.channels.get_mut(&args.channel_id) { match channel.chat.follow_thread(user_id, args.thread_root_message_index, now) { FollowThreadResult::Success => { - handle_activity_notification(state); + state.data.fire_and_forget_handler.send( + user_id.into(), + "c2c_mark_community_updated_for_user_msgpack".to_string(), + serialize_then_unwrap(Empty {}), + ); Success } FollowThreadResult::AlreadyFollowing => AlreadyFollowing, diff --git a/backend/canisters/community/impl/src/updates/unfollow_thread.rs b/backend/canisters/community/impl/src/updates/unfollow_thread.rs index 4ebef1e33b..96b8a337a7 100644 --- a/backend/canisters/community/impl/src/updates/unfollow_thread.rs +++ b/backend/canisters/community/impl/src/updates/unfollow_thread.rs @@ -1,8 +1,10 @@ -use crate::{activity_notifications::handle_activity_notification, mutate_state, run_regular_jobs, RuntimeState}; +use crate::{mutate_state, run_regular_jobs, RuntimeState}; use canister_tracing_macros::trace; use community_canister::unfollow_thread::{Response::*, *}; use group_chat_core::UnfollowThreadResult; use ic_cdk_macros::update; +use msgpack::serialize_then_unwrap; +use types::Empty; #[update] #[trace] @@ -29,7 +31,11 @@ fn unfollow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { if let Some(channel) = state.data.channels.get_mut(&args.channel_id) { match channel.chat.unfollow_thread(user_id, args.thread_root_message_index, now) { UnfollowThreadResult::Success => { - handle_activity_notification(state); + state.data.fire_and_forget_handler.send( + user_id.into(), + "c2c_mark_community_updated_for_user_msgpack".to_string(), + serialize_then_unwrap(Empty {}), + ); Success } UnfollowThreadResult::NotFollowing => NotFollowing, diff --git a/backend/canisters/group/impl/src/updates/follow_thread.rs b/backend/canisters/group/impl/src/updates/follow_thread.rs index 6813252a60..a6db1086ed 100644 --- a/backend/canisters/group/impl/src/updates/follow_thread.rs +++ b/backend/canisters/group/impl/src/updates/follow_thread.rs @@ -1,8 +1,10 @@ -use crate::{activity_notifications::handle_activity_notification, mutate_state, run_regular_jobs, RuntimeState}; +use crate::{mutate_state, run_regular_jobs, RuntimeState}; use canister_tracing_macros::trace; use group_canister::follow_thread::{Response::*, *}; use group_chat_core::FollowThreadResult; use ic_cdk_macros::update; +use msgpack::serialize_then_unwrap; +use types::Empty; #[update] #[trace] @@ -28,7 +30,11 @@ fn follow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { match state.data.chat.follow_thread(user_id, args.thread_root_message_index, now) { FollowThreadResult::Success => { - handle_activity_notification(state); + state.data.fire_and_forget_handler.send( + user_id.into(), + "c2c_mark_group_updated_for_user_msgpack".to_string(), + serialize_then_unwrap(Empty {}), + ); Success } FollowThreadResult::AlreadyFollowing => AlreadyFollowing, diff --git a/backend/canisters/group/impl/src/updates/unfollow_thread.rs b/backend/canisters/group/impl/src/updates/unfollow_thread.rs index 0a3e82f36b..9a4c2a328d 100644 --- a/backend/canisters/group/impl/src/updates/unfollow_thread.rs +++ b/backend/canisters/group/impl/src/updates/unfollow_thread.rs @@ -1,8 +1,10 @@ -use crate::{activity_notifications::handle_activity_notification, mutate_state, run_regular_jobs, RuntimeState}; +use crate::{mutate_state, run_regular_jobs, RuntimeState}; use canister_tracing_macros::trace; use group_canister::unfollow_thread::{Response::*, *}; use group_chat_core::UnfollowThreadResult; use ic_cdk_macros::update; +use msgpack::serialize_then_unwrap; +use types::Empty; #[update] #[trace] @@ -28,7 +30,11 @@ fn unfollow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { match state.data.chat.unfollow_thread(user_id, args.thread_root_message_index, now) { UnfollowThreadResult::Success => { - handle_activity_notification(state); + state.data.fire_and_forget_handler.send( + user_id.into(), + "c2c_mark_group_updated_for_user_msgpack".to_string(), + serialize_then_unwrap(Empty {}), + ); Success } UnfollowThreadResult::NotFollowing => NotFollowing, From dc104c1139e5d5656d0ba84256a920dd2428ad36 Mon Sep 17 00:00:00 2001 From: megrogan Date: Mon, 25 Sep 2023 12:58:57 +0100 Subject: [PATCH 4/6] Fix a serialization test --- backend/libraries/chat_events/src/chat_event_internal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/libraries/chat_events/src/chat_event_internal.rs b/backend/libraries/chat_events/src/chat_event_internal.rs index 30bd1bd9b7..543bec72f9 100644 --- a/backend/libraries/chat_events/src/chat_event_internal.rs +++ b/backend/libraries/chat_events/src/chat_event_internal.rs @@ -855,12 +855,12 @@ mod tests { let event_bytes_len = event_bytes.len(); // Before optimisation: 438 - // After optimisation: 202 - assert_eq!(message_bytes_len, 202); + // After optimisation: 205 + assert_eq!(message_bytes_len, 205); // Before optimisation: 500 - // After optimisation: 220 - assert_eq!(event_bytes_len, 220); + // After optimisation: 223 + assert_eq!(event_bytes_len, 223); let _deserialized: EventWrapperInternal = msgpack::deserialize_then_unwrap(&event_bytes); } From 8806463e84f8dfa7c0a4eb43045e4674501437f4 Mon Sep 17 00:00:00 2001 From: megrogan Date: Mon, 25 Sep 2023 13:06:09 +0100 Subject: [PATCH 5/6] Extract function --- backend/canisters/community/impl/src/lib.rs | 11 ++++++++++- .../community/impl/src/updates/follow_thread.rs | 8 +------- .../community/impl/src/updates/unfollow_thread.rs | 8 +------- backend/canisters/group/impl/src/lib.rs | 11 ++++++++++- .../canisters/group/impl/src/updates/follow_thread.rs | 8 +------- .../group/impl/src/updates/unfollow_thread.rs | 8 +------- 6 files changed, 24 insertions(+), 30 deletions(-) diff --git a/backend/canisters/community/impl/src/lib.rs b/backend/canisters/community/impl/src/lib.rs index 7c41b121f5..e1741bd9ca 100644 --- a/backend/canisters/community/impl/src/lib.rs +++ b/backend/canisters/community/impl/src/lib.rs @@ -12,6 +12,7 @@ use fire_and_forget_handler::FireAndForgetHandler; use group_chat_core::AccessRulesInternal; use instruction_counts_log::{InstructionCountEntry, InstructionCountFunctionId, InstructionCountsLog}; use model::{events::CommunityEvents, invited_users::InvitedUsers, members::CommunityMemberInternal}; +use msgpack::serialize_then_unwrap; use notifications_canister::c2c_push_notification; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; @@ -19,7 +20,7 @@ use std::cell::RefCell; use std::ops::Deref; use types::{ AccessGate, BuildVersion, CanisterId, ChannelId, ChatMetrics, CommunityCanisterCommunitySummary, CommunityMembership, - CommunityPermissions, Cryptocurrency, Cycles, Document, FrozenGroupInfo, Milliseconds, Notification, Rules, + CommunityPermissions, Cryptocurrency, Cycles, Document, Empty, FrozenGroupInfo, Milliseconds, Notification, Rules, TimestampMillis, Timestamped, UserId, }; use utils::env::Environment; @@ -320,6 +321,14 @@ impl Data { .record(function_id, instructions_count, wasm_version, now); } + pub fn mark_community_updated_in_user_canister(&self, user_id: UserId) { + self.fire_and_forget_handler.send( + user_id.into(), + "c2c_mark_community_updated_for_user_msgpack".to_string(), + serialize_then_unwrap(Empty {}), + ); + } + fn is_invite_code_valid(&self, invite_code: Option) -> bool { if self.invite_code_enabled { if let Some(provided_code) = invite_code { diff --git a/backend/canisters/community/impl/src/updates/follow_thread.rs b/backend/canisters/community/impl/src/updates/follow_thread.rs index 4014cd2f28..f93f546d19 100644 --- a/backend/canisters/community/impl/src/updates/follow_thread.rs +++ b/backend/canisters/community/impl/src/updates/follow_thread.rs @@ -3,8 +3,6 @@ use canister_tracing_macros::trace; use community_canister::follow_thread::{Response::*, *}; use group_chat_core::FollowThreadResult; use ic_cdk_macros::update; -use msgpack::serialize_then_unwrap; -use types::Empty; #[update] #[trace] @@ -31,11 +29,7 @@ fn follow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { if let Some(channel) = state.data.channels.get_mut(&args.channel_id) { match channel.chat.follow_thread(user_id, args.thread_root_message_index, now) { FollowThreadResult::Success => { - state.data.fire_and_forget_handler.send( - user_id.into(), - "c2c_mark_community_updated_for_user_msgpack".to_string(), - serialize_then_unwrap(Empty {}), - ); + state.data.mark_community_updated_in_user_canister(user_id); Success } FollowThreadResult::AlreadyFollowing => AlreadyFollowing, diff --git a/backend/canisters/community/impl/src/updates/unfollow_thread.rs b/backend/canisters/community/impl/src/updates/unfollow_thread.rs index 96b8a337a7..e54ce601ef 100644 --- a/backend/canisters/community/impl/src/updates/unfollow_thread.rs +++ b/backend/canisters/community/impl/src/updates/unfollow_thread.rs @@ -3,8 +3,6 @@ use canister_tracing_macros::trace; use community_canister::unfollow_thread::{Response::*, *}; use group_chat_core::UnfollowThreadResult; use ic_cdk_macros::update; -use msgpack::serialize_then_unwrap; -use types::Empty; #[update] #[trace] @@ -31,11 +29,7 @@ fn unfollow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { if let Some(channel) = state.data.channels.get_mut(&args.channel_id) { match channel.chat.unfollow_thread(user_id, args.thread_root_message_index, now) { UnfollowThreadResult::Success => { - state.data.fire_and_forget_handler.send( - user_id.into(), - "c2c_mark_community_updated_for_user_msgpack".to_string(), - serialize_then_unwrap(Empty {}), - ); + state.data.mark_community_updated_in_user_canister(user_id); Success } UnfollowThreadResult::NotFollowing => NotFollowing, diff --git a/backend/canisters/group/impl/src/lib.rs b/backend/canisters/group/impl/src/lib.rs index aefebba08e..b5f9158752 100644 --- a/backend/canisters/group/impl/src/lib.rs +++ b/backend/canisters/group/impl/src/lib.rs @@ -11,6 +11,7 @@ use chat_events::{ChatEventInternal, Reader}; use fire_and_forget_handler::FireAndForgetHandler; use group_chat_core::{AddResult as AddMemberResult, GroupChatCore, GroupMemberInternal, InvitedUsersResult, UserInvitation}; use instruction_counts_log::{InstructionCountEntry, InstructionCountFunctionId, InstructionCountsLog}; +use msgpack::serialize_then_unwrap; use notifications_canister::c2c_push_notification; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; @@ -18,7 +19,7 @@ use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::ops::Deref; use types::{ - AccessGate, BuildVersion, CanisterId, ChatMetrics, CommunityId, Cryptocurrency, Cycles, Document, EventIndex, + AccessGate, BuildVersion, CanisterId, ChatMetrics, CommunityId, Cryptocurrency, Cycles, Document, Empty, EventIndex, FrozenGroupInfo, GroupCanisterGroupChatSummary, GroupPermissions, GroupSubtype, MessageIndex, Milliseconds, Notification, Rules, TimestampMillis, Timestamped, UserId, MAX_THREADS_IN_SUMMARY, }; @@ -448,6 +449,14 @@ impl Data { .record(function_id, instructions_count, wasm_version, now); } + pub fn mark_group_updated_in_user_canister(&self, user_id: UserId) { + self.fire_and_forget_handler.send( + user_id.into(), + "c2c_mark_group_updated_for_user_msgpack".to_string(), + serialize_then_unwrap(Empty {}), + ); + } + fn is_invite_code_valid(&self, invite_code: Option) -> bool { if self.invite_code_enabled { if let Some(provided_code) = invite_code { diff --git a/backend/canisters/group/impl/src/updates/follow_thread.rs b/backend/canisters/group/impl/src/updates/follow_thread.rs index a6db1086ed..7e0df19a8d 100644 --- a/backend/canisters/group/impl/src/updates/follow_thread.rs +++ b/backend/canisters/group/impl/src/updates/follow_thread.rs @@ -3,8 +3,6 @@ use canister_tracing_macros::trace; use group_canister::follow_thread::{Response::*, *}; use group_chat_core::FollowThreadResult; use ic_cdk_macros::update; -use msgpack::serialize_then_unwrap; -use types::Empty; #[update] #[trace] @@ -30,11 +28,7 @@ fn follow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { match state.data.chat.follow_thread(user_id, args.thread_root_message_index, now) { FollowThreadResult::Success => { - state.data.fire_and_forget_handler.send( - user_id.into(), - "c2c_mark_group_updated_for_user_msgpack".to_string(), - serialize_then_unwrap(Empty {}), - ); + state.data.mark_group_updated_in_user_canister(user_id); Success } FollowThreadResult::AlreadyFollowing => AlreadyFollowing, diff --git a/backend/canisters/group/impl/src/updates/unfollow_thread.rs b/backend/canisters/group/impl/src/updates/unfollow_thread.rs index 9a4c2a328d..a149bf31e9 100644 --- a/backend/canisters/group/impl/src/updates/unfollow_thread.rs +++ b/backend/canisters/group/impl/src/updates/unfollow_thread.rs @@ -3,8 +3,6 @@ use canister_tracing_macros::trace; use group_canister::unfollow_thread::{Response::*, *}; use group_chat_core::UnfollowThreadResult; use ic_cdk_macros::update; -use msgpack::serialize_then_unwrap; -use types::Empty; #[update] #[trace] @@ -30,11 +28,7 @@ fn unfollow_thread_impl(args: Args, state: &mut RuntimeState) -> Response { match state.data.chat.unfollow_thread(user_id, args.thread_root_message_index, now) { UnfollowThreadResult::Success => { - state.data.fire_and_forget_handler.send( - user_id.into(), - "c2c_mark_group_updated_for_user_msgpack".to_string(), - serialize_then_unwrap(Empty {}), - ); + state.data.mark_group_updated_in_user_canister(user_id); Success } UnfollowThreadResult::NotFollowing => NotFollowing, From eeb851fbca348e7f571ab5eb6cc03d1ea691b508 Mon Sep 17 00:00:00 2001 From: megrogan Date: Mon, 25 Sep 2023 14:12:11 +0100 Subject: [PATCH 6/6] Use extracted function for toggle_mute too --- .../impl/src/updates/toggle_mute_notifications.rs | 10 ++-------- .../impl/src/updates/toggle_mute_notifications.rs | 11 +++-------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/backend/canisters/community/impl/src/updates/toggle_mute_notifications.rs b/backend/canisters/community/impl/src/updates/toggle_mute_notifications.rs index 499f0507f0..54fd332466 100644 --- a/backend/canisters/community/impl/src/updates/toggle_mute_notifications.rs +++ b/backend/canisters/community/impl/src/updates/toggle_mute_notifications.rs @@ -2,8 +2,6 @@ use crate::{model::channels::MuteChannelResult, mutate_state, run_regular_jobs, use canister_tracing_macros::trace; use community_canister::toggle_mute_notifications::{Response::*, *}; use ic_cdk_macros::update; -use msgpack::serialize_then_unwrap; -use types::Empty; #[update] #[trace] @@ -47,12 +45,8 @@ fn toggle_mute_notifications_impl(args: Args, state: &mut RuntimeState) -> Respo }; if updated { - let user_canister_id = member.user_id.into(); - state.data.fire_and_forget_handler.send( - user_canister_id, - "c2c_mark_community_updated_for_user_msgpack".to_string(), - serialize_then_unwrap(Empty {}), - ); + let user_id = member.user_id; + state.data.mark_community_updated_in_user_canister(user_id); } Success } diff --git a/backend/canisters/group/impl/src/updates/toggle_mute_notifications.rs b/backend/canisters/group/impl/src/updates/toggle_mute_notifications.rs index 4a35cbe3b5..27f06667c4 100644 --- a/backend/canisters/group/impl/src/updates/toggle_mute_notifications.rs +++ b/backend/canisters/group/impl/src/updates/toggle_mute_notifications.rs @@ -2,8 +2,7 @@ use crate::{mutate_state, run_regular_jobs, RuntimeState}; use canister_tracing_macros::trace; use group_canister::toggle_mute_notifications::{Response::*, *}; use ic_cdk_macros::update; -use msgpack::serialize_then_unwrap; -use types::{Empty, Timestamped}; +use types::Timestamped; #[update] #[trace] @@ -19,12 +18,8 @@ fn toggle_mute_notifications_impl(args: Args, state: &mut RuntimeState) -> Respo match state.data.get_member_mut(caller) { Some(member) => { member.notifications_muted = Timestamped::new(args.mute, now); - let user_canister_id = member.user_id.into(); - state.data.fire_and_forget_handler.send( - user_canister_id, - "c2c_mark_group_updated_for_user_msgpack".to_string(), - serialize_then_unwrap(Empty {}), - ); + let user_id = member.user_id; + state.data.mark_group_updated_in_user_canister(user_id); Success } None => CallerNotInGroup,