group.rs (10143B)
1 #![forbid(unsafe_code)] 2 3 use crate::kinds::{ 4 KIND_GROUP_ADMINS as KIND_GROUP_ADMINS_EVENT, 5 KIND_GROUP_CREATE_GROUP as KIND_GROUP_CREATE_GROUP_EVENT, 6 KIND_GROUP_CREATE_INVITE as KIND_GROUP_CREATE_INVITE_EVENT, 7 KIND_GROUP_DELETE_EVENT as KIND_GROUP_DELETE_EVENT_EVENT, 8 KIND_GROUP_DELETE_GROUP as KIND_GROUP_DELETE_GROUP_EVENT, 9 KIND_GROUP_EDIT_METADATA as KIND_GROUP_EDIT_METADATA_EVENT, 10 KIND_GROUP_JOIN_REQUEST as KIND_GROUP_JOIN_REQUEST_EVENT, 11 KIND_GROUP_LEAVE_REQUEST as KIND_GROUP_LEAVE_REQUEST_EVENT, 12 KIND_GROUP_MEMBERS as KIND_GROUP_MEMBERS_EVENT, 13 KIND_GROUP_METADATA as KIND_GROUP_METADATA_EVENT, 14 KIND_GROUP_PUT_USER as KIND_GROUP_PUT_USER_EVENT, 15 KIND_GROUP_REMOVE_USER as KIND_GROUP_REMOVE_USER_EVENT, 16 KIND_GROUP_ROLES as KIND_GROUP_ROLES_EVENT, 17 }; 18 19 #[cfg(not(feature = "std"))] 20 use alloc::{string::String, vec::Vec}; 21 22 pub const KIND_GROUP_PUT_USER: u32 = KIND_GROUP_PUT_USER_EVENT; 23 pub const KIND_GROUP_REMOVE_USER: u32 = KIND_GROUP_REMOVE_USER_EVENT; 24 pub const KIND_GROUP_EDIT_METADATA: u32 = KIND_GROUP_EDIT_METADATA_EVENT; 25 pub const KIND_GROUP_DELETE_EVENT: u32 = KIND_GROUP_DELETE_EVENT_EVENT; 26 pub const KIND_GROUP_CREATE_GROUP: u32 = KIND_GROUP_CREATE_GROUP_EVENT; 27 pub const KIND_GROUP_DELETE_GROUP: u32 = KIND_GROUP_DELETE_GROUP_EVENT; 28 pub const KIND_GROUP_CREATE_INVITE: u32 = KIND_GROUP_CREATE_INVITE_EVENT; 29 pub const KIND_GROUP_JOIN_REQUEST: u32 = KIND_GROUP_JOIN_REQUEST_EVENT; 30 pub const KIND_GROUP_LEAVE_REQUEST: u32 = KIND_GROUP_LEAVE_REQUEST_EVENT; 31 pub const KIND_GROUP_METADATA: u32 = KIND_GROUP_METADATA_EVENT; 32 pub const KIND_GROUP_ADMINS: u32 = KIND_GROUP_ADMINS_EVENT; 33 pub const KIND_GROUP_MEMBERS: u32 = KIND_GROUP_MEMBERS_EVENT; 34 pub const KIND_GROUP_ROLES: u32 = KIND_GROUP_ROLES_EVENT; 35 36 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 37 #[derive(Clone, Debug, PartialEq, Eq)] 38 pub struct RadrootsGroupPutUser { 39 pub group_id: String, 40 pub message: Option<String>, 41 pub pubkey: String, 42 pub roles: Vec<String>, 43 } 44 45 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 46 #[derive(Clone, Debug, PartialEq, Eq)] 47 pub struct RadrootsGroupRemoveUser { 48 pub group_id: String, 49 pub message: Option<String>, 50 pub pubkey: String, 51 } 52 53 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 54 #[derive(Clone, Debug, PartialEq, Eq)] 55 pub struct RadrootsGroupCreateGroup { 56 pub group_id: String, 57 pub message: Option<String>, 58 pub metadata: RadrootsGroupEditableMetadata, 59 } 60 61 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 62 #[derive(Clone, Debug, PartialEq, Eq)] 63 pub struct RadrootsGroupEditMetadata { 64 pub group_id: String, 65 pub message: Option<String>, 66 pub metadata: RadrootsGroupEditableMetadata, 67 } 68 69 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 70 #[derive(Clone, Debug, PartialEq, Eq)] 71 pub struct RadrootsGroupDeleteGroup { 72 pub group_id: String, 73 pub message: Option<String>, 74 } 75 76 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 77 #[derive(Clone, Debug, PartialEq, Eq)] 78 pub struct RadrootsGroupDeleteEvent { 79 pub group_id: String, 80 pub message: Option<String>, 81 pub event_id: String, 82 } 83 84 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 85 #[derive(Clone, Debug, PartialEq, Eq)] 86 pub struct RadrootsGroupCreateInvite { 87 pub group_id: String, 88 pub message: Option<String>, 89 pub code: String, 90 } 91 92 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 93 #[derive(Clone, Debug, PartialEq, Eq)] 94 pub struct RadrootsGroupJoinRequest { 95 pub group_id: String, 96 pub message: Option<String>, 97 pub code: Option<String>, 98 } 99 100 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 101 #[derive(Clone, Debug, PartialEq, Eq)] 102 pub struct RadrootsGroupLeaveRequest { 103 pub group_id: String, 104 pub message: Option<String>, 105 } 106 107 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 108 #[derive(Clone, Debug, PartialEq, Eq)] 109 pub struct RadrootsGroupMetadata { 110 pub d_tag: String, 111 pub metadata: RadrootsGroupEditableMetadata, 112 } 113 114 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 115 #[derive(Clone, Debug, PartialEq, Eq)] 116 pub struct RadrootsGroupAdmins { 117 pub d_tag: String, 118 pub description: Option<String>, 119 pub admins: Vec<RadrootsGroupUserRef>, 120 } 121 122 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 123 #[derive(Clone, Debug, PartialEq, Eq)] 124 pub struct RadrootsGroupMembers { 125 pub d_tag: String, 126 pub description: Option<String>, 127 pub members: Vec<RadrootsGroupUserRef>, 128 } 129 130 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 131 #[derive(Clone, Debug, PartialEq, Eq)] 132 pub struct RadrootsGroupRoles { 133 pub d_tag: String, 134 pub description: Option<String>, 135 pub roles: Vec<RadrootsGroupRole>, 136 } 137 138 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 139 #[derive(Clone, Debug, Default, PartialEq, Eq)] 140 pub struct RadrootsGroupEditableMetadata { 141 pub name: Option<String>, 142 pub about: Option<String>, 143 pub picture: Option<String>, 144 pub is_private: bool, 145 pub is_restricted: bool, 146 pub is_closed: bool, 147 pub is_hidden: bool, 148 pub supported_kinds: Option<Vec<u32>>, 149 } 150 151 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 152 #[derive(Clone, Debug, PartialEq, Eq)] 153 pub struct RadrootsGroupUserRef { 154 pub pubkey: String, 155 pub roles: Vec<String>, 156 } 157 158 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 159 #[derive(Clone, Debug, PartialEq, Eq)] 160 pub struct RadrootsGroupRole { 161 pub name: String, 162 pub description: Option<String>, 163 pub permissions: Vec<String>, 164 } 165 166 #[cfg(all(test, feature = "serde"))] 167 mod tests { 168 use super::*; 169 170 #[test] 171 fn group_user_and_moderation_models_use_h_group_id_semantics() { 172 let put = RadrootsGroupPutUser { 173 group_id: "field-group".to_string(), 174 message: Some("add member".to_string()), 175 pubkey: "member_pubkey".to_string(), 176 roles: vec!["member".to_string()], 177 }; 178 let delete = RadrootsGroupDeleteEvent { 179 group_id: "field-group".to_string(), 180 message: Some("remove duplicate event".to_string()), 181 event_id: "event_id".to_string(), 182 }; 183 let join = RadrootsGroupJoinRequest { 184 group_id: "field-group".to_string(), 185 message: Some("requesting access".to_string()), 186 code: Some("invite-code".to_string()), 187 }; 188 189 assert_eq!(put.group_id, "field-group"); 190 assert_eq!(delete.group_id, "field-group"); 191 assert_eq!(join.group_id, "field-group"); 192 assert_eq!(KIND_GROUP_PUT_USER, 9000); 193 assert_eq!(KIND_GROUP_DELETE_EVENT, 9005); 194 assert_eq!(KIND_GROUP_JOIN_REQUEST, 9021); 195 } 196 197 #[test] 198 fn group_metadata_and_lists_use_d_tag_semantics() { 199 let metadata = RadrootsGroupMetadata { 200 d_tag: "field-group".to_string(), 201 metadata: sample_metadata(), 202 }; 203 let members = RadrootsGroupMembers { 204 d_tag: "field-group".to_string(), 205 description: Some("group members".to_string()), 206 members: vec![sample_user_ref()], 207 }; 208 let roles = RadrootsGroupRoles { 209 d_tag: "field-group".to_string(), 210 description: Some("group roles".to_string()), 211 roles: vec![RadrootsGroupRole { 212 name: "member".to_string(), 213 description: Some("can read and write group events".to_string()), 214 permissions: vec!["read".to_string(), "write".to_string()], 215 }], 216 }; 217 218 assert_eq!(metadata.d_tag, "field-group"); 219 assert_eq!(members.d_tag, "field-group"); 220 assert_eq!(roles.d_tag, "field-group"); 221 assert_eq!(KIND_GROUP_METADATA, 39000); 222 assert_eq!(KIND_GROUP_MEMBERS, 39002); 223 assert_eq!(KIND_GROUP_ROLES, 39003); 224 } 225 226 #[test] 227 fn group_models_are_infrastructure_not_field_business_authorization() { 228 let admins = RadrootsGroupAdmins { 229 d_tag: "field-group".to_string(), 230 description: Some("group admins".to_string()), 231 admins: vec![sample_user_ref()], 232 }; 233 234 assert_eq!(admins.admins[0].roles, vec!["admin".to_string()]); 235 assert_eq!(KIND_GROUP_ADMINS, 39001); 236 } 237 238 #[test] 239 fn group_models_serialize_stable_shapes() { 240 let create = RadrootsGroupCreateGroup { 241 group_id: "field-group".to_string(), 242 message: None, 243 metadata: sample_metadata(), 244 }; 245 let invite = RadrootsGroupCreateInvite { 246 group_id: "field-group".to_string(), 247 message: Some("join the field group".to_string()), 248 code: "invite-code".to_string(), 249 }; 250 251 let create_value = serde_json::to_value(create).unwrap(); 252 let invite_value = serde_json::to_value(invite).unwrap(); 253 254 assert_eq!(create_value["group_id"], "field-group"); 255 assert_eq!(create_value["metadata"]["name"], "Small Regen Farm"); 256 assert_eq!(invite_value["code"], "invite-code"); 257 assert_eq!(invite_value["message"], "join the field group"); 258 assert_eq!(KIND_GROUP_CREATE_GROUP, 9007); 259 assert_eq!(KIND_GROUP_CREATE_INVITE, 9009); 260 } 261 262 fn sample_metadata() -> RadrootsGroupEditableMetadata { 263 RadrootsGroupEditableMetadata { 264 name: Some("Small Regen Farm".to_string()), 265 about: Some("Field app group".to_string()), 266 picture: Some("https://media.example.invalid/group.png".to_string()), 267 is_private: false, 268 is_restricted: true, 269 is_closed: false, 270 is_hidden: false, 271 supported_kinds: Some(vec![78, 30078]), 272 } 273 } 274 275 fn sample_user_ref() -> RadrootsGroupUserRef { 276 RadrootsGroupUserRef { 277 pubkey: "admin_pubkey".to_string(), 278 roles: vec!["admin".to_string()], 279 } 280 } 281 }