From 8e827c649f408208d60db74a17d81f163970f8f5 Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Tue, 2 Jul 2024 15:03:02 -0700 Subject: [PATCH] added pinned frame metadata field --- bindings_ffi/src/mls.rs | 41 ++++++++++ xmtp_mls/src/configuration.rs | 1 + xmtp_mls/src/groups/group_mutable_metadata.rs | 10 ++- xmtp_mls/src/groups/intents.rs | 7 ++ xmtp_mls/src/groups/mod.rs | 78 ++++++++++++++++++- 5 files changed, 134 insertions(+), 3 deletions(-) diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index 174614936..fb6daab32 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -448,6 +448,7 @@ pub struct FfiPermissionPolicySet { pub update_group_name_policy: FfiPermissionPolicy, pub update_group_description_policy: FfiPermissionPolicy, pub update_group_image_url_square_policy: FfiPermissionPolicy, + pub update_pinned_frame_policy: FfiPermissionPolicy, } impl From for FfiGroupPermissionsOptions { @@ -464,6 +465,7 @@ pub enum FfiMetadataField { GroupName, Description, ImageUrlSquare, + PinnedFrame, } impl From<&FfiMetadataField> for MetadataField { @@ -472,6 +474,7 @@ impl From<&FfiMetadataField> for MetadataField { FfiMetadataField::GroupName => MetadataField::GroupName, FfiMetadataField::Description => MetadataField::Description, FfiMetadataField::ImageUrlSquare => MetadataField::GroupImageUrlSquare, + FfiMetadataField::PinnedFrame => MetadataField::PinnedFrame, } } } @@ -639,6 +642,7 @@ pub struct FfiCreateGroupOptions { pub group_name: Option, pub group_image_url_square: Option, pub group_description: Option, + pub pinned_frame: Option, } impl FfiCreateGroupOptions { @@ -647,6 +651,7 @@ impl FfiCreateGroupOptions { name: self.group_name, image_url_square: self.group_image_url_square, description: self.group_description, + pinned_frame: self.pinned_frame, } } } @@ -897,6 +902,35 @@ impl FfiGroup { Ok(group_description) } + pub async fn update_pinned_frame( + &self, + pinned_frame: String, + ) -> Result<(), GenericError> { + let group = MlsGroup::new( + self.inner_client.context().clone(), + self.group_id.clone(), + self.created_at_ns, + ); + + group + .update_pinned_frame(&self.inner_client, pinned_frame) + .await?; + + Ok(()) + } + + pub fn group_pinned_frame(&self) -> Result { + let group = MlsGroup::new( + self.inner_client.context().clone(), + self.group_id.clone(), + self.created_at_ns, + ); + + let group_pinned_frame = group.group_pinned_frame()?; + + Ok(group_pinned_frame) + } + pub fn admin_list(&self) -> Result, GenericError> { let group = MlsGroup::new( self.inner_client.context().clone(), @@ -1241,6 +1275,7 @@ impl FfiGroupPermissions { update_group_image_url_square_policy: get_policy( MetadataField::GroupImageUrlSquare.as_str(), ), + update_pinned_frame_policy: get_policy(MetadataField::PinnedFrame.as_str()), }) } } @@ -1570,6 +1605,7 @@ mod tests { group_name: Some("Group Name".to_string()), group_image_url_square: Some("url".to_string()), group_description: Some("group description".to_string()), + pinned_frame: Some("pinned frame".to_string()), }, ) .await @@ -1580,6 +1616,7 @@ mod tests { assert_eq!(group.group_name().unwrap(), "Group Name"); assert_eq!(group.group_image_url_square().unwrap(), "url"); assert_eq!(group.group_description().unwrap(), "group description"); + assert_eq!(group.group_pinned_frame().unwrap(), "pinned frame"); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -2131,6 +2168,7 @@ mod tests { update_group_name_policy: FfiPermissionPolicy::Admin, update_group_description_policy: FfiPermissionPolicy::Admin, update_group_image_url_square_policy: FfiPermissionPolicy::Admin, + update_pinned_frame_policy: FfiPermissionPolicy::Admin, }; assert_eq!(alix_permission_policy_set, expected_permission_policy_set); @@ -2159,6 +2197,7 @@ mod tests { update_group_name_policy: FfiPermissionPolicy::Allow, update_group_description_policy: FfiPermissionPolicy::Allow, update_group_image_url_square_policy: FfiPermissionPolicy::Allow, + update_pinned_frame_policy: FfiPermissionPolicy::Allow, }; assert_eq!(alix_permission_policy_set, expected_permission_policy_set); } @@ -2191,6 +2230,7 @@ mod tests { update_group_name_policy: FfiPermissionPolicy::Admin, update_group_description_policy: FfiPermissionPolicy::Admin, update_group_image_url_square_policy: FfiPermissionPolicy::Admin, + update_pinned_frame_policy: FfiPermissionPolicy::Admin, }; assert_eq!(alix_group_permissions, expected_permission_policy_set); @@ -2217,6 +2257,7 @@ mod tests { update_group_name_policy: FfiPermissionPolicy::Admin, update_group_description_policy: FfiPermissionPolicy::Admin, update_group_image_url_square_policy: FfiPermissionPolicy::Allow, + update_pinned_frame_policy: FfiPermissionPolicy::Admin, }; assert_eq!(alix_group_permissions, new_expected_permission_policy_set); diff --git a/xmtp_mls/src/configuration.rs b/xmtp_mls/src/configuration.rs index 31671db79..ac1a41176 100644 --- a/xmtp_mls/src/configuration.rs +++ b/xmtp_mls/src/configuration.rs @@ -44,6 +44,7 @@ pub const GROUP_PERMISSIONS_EXTENSION_ID: u16 = 0xff02; pub const DEFAULT_GROUP_NAME: &str = ""; pub const DEFAULT_GROUP_DESCRIPTION: &str = ""; pub const DEFAULT_GROUP_IMAGE_URL_SQUARE: &str = ""; +pub const DEFAULT_PINNED_FRAME: &str = ""; // If a metadata field name starts with this character, // and it does not have a policy set, it is a super admin only field diff --git a/xmtp_mls/src/groups/group_mutable_metadata.rs b/xmtp_mls/src/groups/group_mutable_metadata.rs index fa706cb1b..a35301dc6 100644 --- a/xmtp_mls/src/groups/group_mutable_metadata.rs +++ b/xmtp_mls/src/groups/group_mutable_metadata.rs @@ -12,8 +12,7 @@ use xmtp_proto::xmtp::mls::message_contents::{ }; use crate::configuration::{ - DEFAULT_GROUP_DESCRIPTION, DEFAULT_GROUP_IMAGE_URL_SQUARE, DEFAULT_GROUP_NAME, - MUTABLE_METADATA_EXTENSION_ID, + DEFAULT_GROUP_DESCRIPTION, DEFAULT_GROUP_IMAGE_URL_SQUARE, DEFAULT_GROUP_NAME, DEFAULT_PINNED_FRAME, MUTABLE_METADATA_EXTENSION_ID }; use super::GroupMetadataOptions; @@ -42,6 +41,7 @@ pub enum MetadataField { GroupName, Description, GroupImageUrlSquare, + PinnedFrame, } impl MetadataField { @@ -50,6 +50,7 @@ impl MetadataField { MetadataField::GroupName => "group_name", MetadataField::Description => "description", MetadataField::GroupImageUrlSquare => "group_image_url_square", + MetadataField::PinnedFrame => "pinned_frame", } } } @@ -97,6 +98,10 @@ impl GroupMutableMetadata { opts.image_url_square .unwrap_or_else(|| DEFAULT_GROUP_IMAGE_URL_SQUARE.to_string()), ); + attributes.insert( + MetadataField::PinnedFrame.to_string(), + opts.pinned_frame.unwrap_or_else(|| DEFAULT_PINNED_FRAME.to_string()), + ); let admin_list = vec![]; let super_admin_list = vec![creator_inbox_id.clone()]; Self { @@ -112,6 +117,7 @@ impl GroupMutableMetadata { MetadataField::GroupName, MetadataField::Description, MetadataField::GroupImageUrlSquare, + MetadataField::PinnedFrame, ] } diff --git a/xmtp_mls/src/groups/intents.rs b/xmtp_mls/src/groups/intents.rs index 15f61c044..0efca0dba 100644 --- a/xmtp_mls/src/groups/intents.rs +++ b/xmtp_mls/src/groups/intents.rs @@ -181,6 +181,13 @@ impl UpdateMetadataIntentData { field_value: group_description, } } + + pub fn new_update_pinned_frame(pinned_frame: String) -> Self { + Self { + field_name: MetadataField::PinnedFrame.to_string(), + field_value: pinned_frame, + } + } } impl From for Vec { diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index b70aec2d2..f3f1aaa2f 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -209,6 +209,7 @@ pub struct GroupMetadataOptions { pub name: Option, pub image_url_square: Option, pub description: Option, + pub pinned_frame: Option, } impl Clone for MlsGroup { @@ -736,6 +737,41 @@ impl MlsGroup { } } + pub async fn update_pinned_frame( + &self, + client: &Client, + pinned_frame: String, + ) -> Result<(), GroupError> + where + ApiClient: XmtpApi, + { + let conn = self.context.store.conn()?; + let intent_data: Vec = + UpdateMetadataIntentData::new_update_pinned_frame(pinned_frame) + .into(); + let intent = conn.insert_group_intent(NewGroupIntent::new( + IntentKind::MetadataUpdate, + self.group_id.clone(), + intent_data, + ))?; + + self.sync_until_intent_resolved(conn, intent.id, client) + .await + } + + pub fn group_pinned_frame(&self) -> Result { + let mutable_metadata = self.mutable_metadata()?; + match mutable_metadata + .attributes + .get(&MetadataField::PinnedFrame.to_string()) + { + Some(pinned_frame) => Ok(pinned_frame.clone()), + None => Err(GroupError::GroupMutableMetadata( + GroupMutableMetadataError::MissingExtension, + )), + } + } + pub fn admin_list(&self) -> Result, GroupError> { let mutable_metadata = self.mutable_metadata()?; Ok(mutable_metadata.admin_list) @@ -1785,6 +1821,7 @@ mod tests { name: Some("Group Name".to_string()), image_url_square: Some("url".to_string()), description: Some("group description".to_string()), + pinned_frame: Some("pinned frame".to_string()), }, ) .unwrap(); @@ -1802,10 +1839,15 @@ mod tests { .attributes .get(&MetadataField::Description.to_string()) .unwrap(); + let amal_group_pinned_frame: &String = binding + .attributes + .get(&MetadataField::PinnedFrame.to_string()) + .unwrap(); assert_eq!(amal_group_name, "Group Name"); assert_eq!(amal_group_image_url, "url"); assert_eq!(amal_group_description, "group description"); + assert_eq!(amal_group_pinned_frame, "pinned frame"); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -1848,7 +1890,7 @@ mod tests { amal_group.sync(&amal).await.unwrap(); let group_mutable_metadata = amal_group.mutable_metadata().unwrap(); - assert!(group_mutable_metadata.attributes.len().eq(&3)); + assert!(group_mutable_metadata.attributes.len().eq(&4)); assert!(group_mutable_metadata .attributes .get(&MetadataField::GroupName.to_string()) @@ -1951,6 +1993,40 @@ mod tests { assert_eq!(amal_group_image_url, "a url"); } + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + async fn test_update_group_pinned_frame() { + let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; + + // Create a group and verify it has the default group name + let policies = Some(PreconfiguredPolicies::AdminsOnly); + let amal_group: MlsGroup = amal + .create_group(policies, GroupMetadataOptions::default()) + .unwrap(); + amal_group.sync(&amal).await.unwrap(); + + let group_mutable_metadata = amal_group.mutable_metadata().unwrap(); + assert!(group_mutable_metadata + .attributes + .get(&MetadataField::PinnedFrame.to_string()) + .unwrap() + .eq("")); + + // Update group name + amal_group + .update_pinned_frame(&amal, "a frame url".to_string()) + .await + .unwrap(); + + // Verify amal group sees update + amal_group.sync(&amal).await.unwrap(); + let binding = amal_group.mutable_metadata().expect("msg"); + let amal_group_pinned_frame: &String = binding + .attributes + .get(&MetadataField::PinnedFrame.to_string()) + .unwrap(); + assert_eq!(amal_group_pinned_frame, "a frame url"); + } + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_group_mutable_data_group_permissions() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await;