coop_content_types/
group_entry.rs

1use crate::hdi;
2
3use std::collections::BTreeMap;
4use hdi::prelude::*;
5
6
7
8// Trait for common fields
9/// Common fields that are expected on some entry structs
10pub trait CommonFields<'a> {
11    /// A timestamp that indicates when the original create entry was made
12    fn published_at(&'a self) -> &'a u64;
13    /// A timestamp that indicates when this entry was created
14    fn last_updated(&'a self) -> &'a u64;
15    /// A spot for holding data that is not relevant to integrity validation
16    fn metadata(&'a self) -> &'a BTreeMap<String, rmpv::Value>;
17}
18
19/// Auto-implement the [`CommonFields`] trait
20///
21/// The input must be a struct with fields matching each common field method.
22///
23/// #### Example
24/// ```ignore
25/// struct PostEntry {
26///     pub message: String,
27///
28///     // Common fields
29///     pub published_at: u64,
30///     pub last_updated: u64,
31///     pub metadata: BTreeMap<String, rmpv::Value>,
32/// }
33/// common_fields!( PostEntry );
34/// ```
35#[macro_export]
36macro_rules! common_fields {
37    ( $name:ident ) => {
38        impl<'a> CommonFields<'a> for $name {
39            fn published_at(&'a self) -> &'a u64 {
40                &self.published_at
41            }
42            fn last_updated(&'a self) -> &'a u64 {
43                &self.last_updated
44            }
45            fn metadata(&'a self) -> &'a BTreeMap<String, rmpv::Value> {
46                &self.metadata
47            }
48        }
49    };
50}
51
52
53
54//
55// Group Entry
56//
57/// An entry struct for defining a group and its members
58#[hdk_entry_helper]
59#[derive(Clone)]
60pub struct GroupEntry {
61    /// The list of agents with admin authority in this group
62    pub admins: Vec<AgentPubKey>,
63    /// The list of agents with write authority in this group
64    pub members: Vec<AgentPubKey>,
65    /// An indicator of whether this group is still active
66    pub deleted: Option<bool>,
67
68    // common fields
69    pub published_at: u64,
70    pub last_updated: u64,
71    pub metadata: BTreeMap<String, rmpv::Value>,
72}
73common_fields!( GroupEntry );
74
75impl GroupEntry {
76    /// Get a list of the admins and members of this group
77    pub fn contributors(&self) -> Vec<AgentPubKey> {
78        vec![ self.admins.clone(), self.members.clone() ]
79            .into_iter()
80            .flatten()
81            .collect()
82    }
83
84    /// Check if the given agent is an admin or member
85    pub fn is_contributor(&self, agent: &AgentPubKey) -> bool {
86        self.contributors().contains( agent )
87    }
88
89    /// Check if the given agent is an admin
90    pub fn is_admin(&self, agent: &AgentPubKey) -> bool {
91        self.admins.contains( agent )
92    }
93
94    /// Check if the given agent is a member (not an admin)
95    pub fn is_member(&self, agent: &AgentPubKey) -> bool {
96        self.members.contains( agent )
97    }
98
99    /// Return the differences between this group and the given group
100    pub fn contributors_diff(&self, other: &GroupEntry) -> ContributorsDiff {
101        let added: Vec<AgentPubKey> = other.contributors()
102            .into_iter()
103            .filter(|pubkey| !self.is_contributor(pubkey) )
104            .collect();
105
106        let removed: Vec<AgentPubKey> = self.contributors()
107            .into_iter()
108            .filter(|pubkey| !other.is_contributor(pubkey) )
109            .collect();
110
111        let intersection: Vec<AgentPubKey> = self.contributors()
112            .into_iter()
113            .filter(|pubkey| other.is_contributor(pubkey) )
114            .collect();
115
116        ContributorsDiff {
117            added,
118            removed,
119            intersection,
120        }
121    }
122}
123
124/// The result of a group comparison
125#[derive(Clone, Debug, Serialize, Deserialize)]
126pub struct ContributorsDiff {
127    pub added: Vec<AgentPubKey>,
128    pub removed: Vec<AgentPubKey>,
129    pub intersection: Vec<AgentPubKey>,
130}
131
132
133
134//
135// Group Member Anchor Entry
136//
137/// An entry struct (anchor) representing a group contributor's personal anchor
138#[hdk_entry_helper]
139#[derive(Clone)]
140pub struct ContributionsAnchorEntry( pub ActionHash, pub AgentPubKey );
141
142impl ContributionsAnchorEntry {
143    /// Get the agent pubkey of this auth anchor
144    pub fn author(&self) -> &AgentPubKey {
145        &self.1
146    }
147
148    /// Get the group revision (action hash) of this auth anchor
149    pub fn group(&self) -> &ActionHash {
150        &self.0
151    }
152}
153
154
155
156//
157// Group Member Archive Anchor Entry
158//
159/// An entry struct (anchor) representing a former authority of a group
160#[hdk_entry_helper]
161#[derive(Clone)]
162pub struct ArchivedContributionsAnchorEntry( String, pub ActionHash, pub AgentPubKey );
163
164impl ArchivedContributionsAnchorEntry {
165    pub fn new(group_id: ActionHash, agent: AgentPubKey) -> Self {
166        ArchivedContributionsAnchorEntry("archive".to_string(), group_id, agent)
167    }
168}
169
170impl ArchivedContributionsAnchorEntry {
171    /// Get the agent pubkey of this auth anchor
172    pub fn author(&self) -> &AgentPubKey {
173        &self.2
174    }
175
176    /// Get the group revision (action hash) of this auth anchor
177    pub fn group(&self) -> &ActionHash {
178        &self.1
179    }
180}
181
182
183/// An enum that represents an authority anchor (active/archived)
184#[hdk_entry_helper]
185#[serde(untagged)]
186#[derive(Clone)]
187pub enum ContributionAnchors {
188    Active(ContributionsAnchorEntry),
189    Archive(ArchivedContributionsAnchorEntry),
190}
191
192impl ContributionAnchors {
193    /// Get the agent pubkey of this auth anchor
194    pub fn author(&self) -> &AgentPubKey {
195        match &self {
196            ContributionAnchors::Active(anchor) => &anchor.1,
197            ContributionAnchors::Archive(anchor) => &anchor.2,
198        }
199    }
200
201    /// Get the group revision (action hash) of this auth anchor
202    pub fn group(&self) -> &ActionHash {
203        match &self {
204            ContributionAnchors::Active(anchor) => &anchor.0,
205            ContributionAnchors::Archive(anchor) => &anchor.1,
206        }
207    }
208
209    /// Determine if this enum's item is [`ContributionAnchors::Archive`]
210    pub fn is_archive(&self) -> bool {
211        match &self {
212            ContributionAnchors::Active(_) => false,
213            ContributionAnchors::Archive(_) => true,
214        }
215    }
216}
217
218
219/// Indicates the intended contributions anchor type
220///
221/// Since the variable content is the same for both anchor types, this enum helps declare the
222/// intended type when passing around the group/author anchor values.
223#[derive(Clone, Debug, Serialize)]
224#[serde(untagged)]
225pub enum ContributionAnchorTypes {
226    Active,
227    Archive,
228}
229
230impl<'de> serde::Deserialize<'de> for ContributionAnchorTypes {
231    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
232    where
233        D: serde::Deserializer<'de>,
234    {
235        let input : Option<String> = Deserialize::deserialize(deserializer)?;
236
237        Ok(
238            match input {
239                Some(name) => match name.to_lowercase().as_str() {
240                    "active" => ContributionAnchorTypes::Active,
241                    "archive" | "inactive" => ContributionAnchorTypes::Archive,
242                    lw_name => Err(serde::de::Error::custom(
243                        format!("No match for '{}' in ContributionAnchorTypes enum", lw_name )
244                    ))?,
245                },
246                None => ContributionAnchorTypes::Active,
247            }
248        )
249    }
250}