lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }