group.rs (10245B)
1 #![forbid(unsafe_code)] 2 3 use crate::envelope::kind::{ 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( 37 any(feature = "serde", test), 38 derive(serde::Serialize, serde::Deserialize) 39 )] 40 #[derive(Clone, Debug, PartialEq, Eq)] 41 pub struct GroupPutUser { 42 pub group_id: String, 43 pub message: Option<String>, 44 pub pubkey: String, 45 pub roles: Vec<String>, 46 } 47 48 #[cfg_attr( 49 any(feature = "serde", test), 50 derive(serde::Serialize, serde::Deserialize) 51 )] 52 #[derive(Clone, Debug, PartialEq, Eq)] 53 pub struct GroupRemoveUser { 54 pub group_id: String, 55 pub message: Option<String>, 56 pub pubkey: String, 57 } 58 59 #[cfg_attr( 60 any(feature = "serde", test), 61 derive(serde::Serialize, serde::Deserialize) 62 )] 63 #[derive(Clone, Debug, PartialEq, Eq)] 64 pub struct GroupCreateGroup { 65 pub group_id: String, 66 pub message: Option<String>, 67 pub metadata: GroupEditableMetadata, 68 } 69 70 #[cfg_attr( 71 any(feature = "serde", test), 72 derive(serde::Serialize, serde::Deserialize) 73 )] 74 #[derive(Clone, Debug, PartialEq, Eq)] 75 pub struct GroupEditMetadata { 76 pub group_id: String, 77 pub message: Option<String>, 78 pub metadata: GroupEditableMetadata, 79 } 80 81 #[cfg_attr( 82 any(feature = "serde", test), 83 derive(serde::Serialize, serde::Deserialize) 84 )] 85 #[derive(Clone, Debug, PartialEq, Eq)] 86 pub struct GroupDeleteGroup { 87 pub group_id: String, 88 pub message: Option<String>, 89 } 90 91 #[cfg_attr( 92 any(feature = "serde", test), 93 derive(serde::Serialize, serde::Deserialize) 94 )] 95 #[derive(Clone, Debug, PartialEq, Eq)] 96 pub struct GroupDeleteEvent { 97 pub group_id: String, 98 pub message: Option<String>, 99 pub event_id: String, 100 } 101 102 #[cfg_attr( 103 any(feature = "serde", test), 104 derive(serde::Serialize, serde::Deserialize) 105 )] 106 #[derive(Clone, Debug, PartialEq, Eq)] 107 pub struct GroupCreateInvite { 108 pub group_id: String, 109 pub message: Option<String>, 110 pub code: String, 111 } 112 113 #[cfg_attr( 114 any(feature = "serde", test), 115 derive(serde::Serialize, serde::Deserialize) 116 )] 117 #[derive(Clone, Debug, PartialEq, Eq)] 118 pub struct GroupJoinRequest { 119 pub group_id: String, 120 pub message: Option<String>, 121 pub code: Option<String>, 122 } 123 124 #[cfg_attr( 125 any(feature = "serde", test), 126 derive(serde::Serialize, serde::Deserialize) 127 )] 128 #[derive(Clone, Debug, PartialEq, Eq)] 129 pub struct GroupLeaveRequest { 130 pub group_id: String, 131 pub message: Option<String>, 132 } 133 134 #[cfg_attr( 135 any(feature = "serde", test), 136 derive(serde::Serialize, serde::Deserialize) 137 )] 138 #[derive(Clone, Debug, PartialEq, Eq)] 139 pub struct GroupMetadata { 140 pub d_tag: String, 141 pub metadata: GroupEditableMetadata, 142 } 143 144 #[cfg_attr( 145 any(feature = "serde", test), 146 derive(serde::Serialize, serde::Deserialize) 147 )] 148 #[derive(Clone, Debug, PartialEq, Eq)] 149 pub struct GroupAdmins { 150 pub d_tag: String, 151 pub description: Option<String>, 152 pub admins: Vec<GroupUserRef>, 153 } 154 155 #[cfg_attr( 156 any(feature = "serde", test), 157 derive(serde::Serialize, serde::Deserialize) 158 )] 159 #[derive(Clone, Debug, PartialEq, Eq)] 160 pub struct GroupMembers { 161 pub d_tag: String, 162 pub description: Option<String>, 163 pub members: Vec<GroupUserRef>, 164 } 165 166 #[cfg_attr( 167 any(feature = "serde", test), 168 derive(serde::Serialize, serde::Deserialize) 169 )] 170 #[derive(Clone, Debug, PartialEq, Eq)] 171 pub struct GroupRoles { 172 pub d_tag: String, 173 pub description: Option<String>, 174 pub roles: Vec<GroupRole>, 175 } 176 177 #[cfg_attr( 178 any(feature = "serde", test), 179 derive(serde::Serialize, serde::Deserialize) 180 )] 181 #[derive(Clone, Debug, Default, PartialEq, Eq)] 182 pub struct GroupEditableMetadata { 183 pub name: Option<String>, 184 pub about: Option<String>, 185 pub picture: Option<String>, 186 pub is_private: bool, 187 pub is_restricted: bool, 188 pub is_closed: bool, 189 pub is_hidden: bool, 190 pub supported_kinds: Option<Vec<u32>>, 191 } 192 193 #[cfg_attr( 194 any(feature = "serde", test), 195 derive(serde::Serialize, serde::Deserialize) 196 )] 197 #[derive(Clone, Debug, PartialEq, Eq)] 198 pub struct GroupUserRef { 199 pub pubkey: String, 200 pub roles: Vec<String>, 201 } 202 203 #[cfg_attr( 204 any(feature = "serde", test), 205 derive(serde::Serialize, serde::Deserialize) 206 )] 207 #[derive(Clone, Debug, PartialEq, Eq)] 208 pub struct GroupRole { 209 pub name: String, 210 pub description: Option<String>, 211 pub permissions: Vec<String>, 212 } 213 214 #[cfg(all(test, feature = "serde"))] 215 #[cfg_attr(coverage_nightly, coverage(off))] 216 mod tests { 217 use super::*; 218 219 #[test] 220 fn group_user_and_moderation_models_use_h_group_id_semantics() { 221 let put = GroupPutUser { 222 group_id: "field-group".to_string(), 223 message: Some("add member".to_string()), 224 pubkey: "member_pubkey".to_string(), 225 roles: vec!["member".to_string()], 226 }; 227 let delete = GroupDeleteEvent { 228 group_id: "field-group".to_string(), 229 message: Some("remove duplicate event".to_string()), 230 event_id: "event_id".to_string(), 231 }; 232 let join = GroupJoinRequest { 233 group_id: "field-group".to_string(), 234 message: Some("requesting access".to_string()), 235 code: Some("invite-code".to_string()), 236 }; 237 238 assert_eq!(put.group_id, "field-group"); 239 assert_eq!(delete.group_id, "field-group"); 240 assert_eq!(join.group_id, "field-group"); 241 assert_eq!(KIND_GROUP_PUT_USER, 9000); 242 assert_eq!(KIND_GROUP_DELETE_EVENT, 9005); 243 assert_eq!(KIND_GROUP_JOIN_REQUEST, 9021); 244 } 245 246 #[test] 247 fn group_metadata_and_lists_use_d_tag_semantics() { 248 let metadata = GroupMetadata { 249 d_tag: "field-group".to_string(), 250 metadata: sample_metadata(), 251 }; 252 let members = GroupMembers { 253 d_tag: "field-group".to_string(), 254 description: Some("group members".to_string()), 255 members: vec![sample_user_ref()], 256 }; 257 let roles = GroupRoles { 258 d_tag: "field-group".to_string(), 259 description: Some("group roles".to_string()), 260 roles: vec![GroupRole { 261 name: "member".to_string(), 262 description: Some("can read and write group events".to_string()), 263 permissions: vec!["read".to_string(), "write".to_string()], 264 }], 265 }; 266 267 assert_eq!(metadata.d_tag, "field-group"); 268 assert_eq!(members.d_tag, "field-group"); 269 assert_eq!(roles.d_tag, "field-group"); 270 assert_eq!(KIND_GROUP_METADATA, 39000); 271 assert_eq!(KIND_GROUP_MEMBERS, 39002); 272 assert_eq!(KIND_GROUP_ROLES, 39003); 273 } 274 275 #[test] 276 fn group_models_are_infrastructure_not_field_business_authorization() { 277 let admins = GroupAdmins { 278 d_tag: "field-group".to_string(), 279 description: Some("group admins".to_string()), 280 admins: vec![sample_user_ref()], 281 }; 282 283 assert_eq!(admins.admins[0].roles, vec!["admin".to_string()]); 284 assert_eq!(KIND_GROUP_ADMINS, 39001); 285 } 286 287 #[test] 288 fn group_models_serialize_stable_shapes() { 289 let create = GroupCreateGroup { 290 group_id: "field-group".to_string(), 291 message: None, 292 metadata: sample_metadata(), 293 }; 294 let invite = GroupCreateInvite { 295 group_id: "field-group".to_string(), 296 message: Some("join the field group".to_string()), 297 code: "invite-code".to_string(), 298 }; 299 300 let create_value = serde_json::to_value(create).unwrap(); 301 let invite_value = serde_json::to_value(invite).unwrap(); 302 303 assert_eq!(create_value["group_id"], "field-group"); 304 assert_eq!(create_value["metadata"]["name"], "Small Regen Farm"); 305 assert_eq!(invite_value["code"], "invite-code"); 306 assert_eq!(invite_value["message"], "join the field group"); 307 assert_eq!(KIND_GROUP_CREATE_GROUP, 9007); 308 assert_eq!(KIND_GROUP_CREATE_INVITE, 9009); 309 } 310 311 fn sample_metadata() -> GroupEditableMetadata { 312 GroupEditableMetadata { 313 name: Some("Small Regen Farm".to_string()), 314 about: Some("Field app group".to_string()), 315 picture: Some("https://media.example.invalid/group.png".to_string()), 316 is_private: false, 317 is_restricted: true, 318 is_closed: false, 319 is_hidden: false, 320 supported_kinds: Some(vec![78, 30078]), 321 } 322 } 323 324 fn sample_user_ref() -> GroupUserRef { 325 GroupUserRef { 326 pubkey: "admin_pubkey".to_string(), 327 roles: vec!["admin".to_string()], 328 } 329 } 330 }