lib

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

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 }