oku_fs/database/
users.rs

1use super::core::*;
2use super::posts::core::OkuPost;
3use iroh_docs::rpc::client::docs::Entry;
4use iroh_docs::AuthorId;
5use log::error;
6use miette::IntoDiagnostic;
7use native_db::*;
8use native_model::{native_model, Model};
9use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
10use serde::{Deserialize, Serialize};
11use std::hash::{Hash, Hasher};
12use std::{collections::HashSet, time::SystemTime};
13
14#[derive(Serialize, Deserialize, Debug, Clone)]
15#[native_model(id = 1, version = 1)]
16#[native_db(
17    primary_key(author_id -> Vec<u8>)
18)]
19/// An Oku user.
20pub struct OkuUser {
21    /// The content authorship identifier associated with the Oku user.
22    pub author_id: AuthorId,
23    /// The system time of when this user's content was last retrieved from OkuNet.
24    pub last_fetched: SystemTime,
25    /// The posts made by this user on OkuNet.
26    pub posts: Vec<Entry>,
27    /// The OkuNet identity of the user.
28    pub identity: Option<OkuIdentity>,
29}
30
31impl PartialEq for OkuUser {
32    fn eq(&self, other: &Self) -> bool {
33        self.author_id == other.author_id
34    }
35}
36impl Eq for OkuUser {}
37impl Hash for OkuUser {
38    fn hash<H: Hasher>(&self, state: &mut H) {
39        self.author_id.hash(state);
40    }
41}
42
43impl OkuUser {
44    fn author_id(&self) -> Vec<u8> {
45        self.author_id.as_bytes().to_vec()
46    }
47}
48
49#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)]
50/// An OkuNet identity for an Oku user.
51pub struct OkuIdentity {
52    /// The display name of the Oku user.
53    pub name: String,
54    /// The content authors followed by the Oku user.
55    /// OkuNet content is retrieved from followed users and the users those users follow.
56    pub following: HashSet<AuthorId>,
57    /// The content authors blocked by the Oku user.
58    /// Blocked authors are ignored when fetching new OkuNet posts.
59    pub blocked: HashSet<AuthorId>,
60}
61
62impl OkuDatabase {
63    /// Insert or update an OkuNet user.
64    ///
65    /// # Arguments
66    ///
67    /// * `user` - An OkuNet user to upsert.
68    ///
69    /// # Returns
70    ///
71    /// The previous version of the user, if one existed.
72    pub fn upsert_user(&self, user: &OkuUser) -> miette::Result<Option<OkuUser>> {
73        let rw = self.database.rw_transaction().into_diagnostic()?;
74        let old_value: Option<OkuUser> = rw.upsert(user.to_owned()).into_diagnostic()?;
75        rw.commit().into_diagnostic()?;
76        Ok(old_value)
77    }
78
79    /// Delete an OkuNet user.
80    ///
81    /// # Arguments
82    ///
83    /// * `user` - An OkuNet user to delete.
84    ///
85    /// # Returns
86    ///
87    /// The deleted user.
88    pub fn delete_user(&self, user: &OkuUser) -> miette::Result<OkuUser> {
89        let rw = self.database.rw_transaction().into_diagnostic()?;
90        let removed_user = rw.remove(user.to_owned()).into_diagnostic()?;
91        rw.commit().into_diagnostic()?;
92        Ok(removed_user)
93    }
94
95    /// Delete multiple OkuNet users.
96    ///
97    /// # Arguments
98    ///
99    /// * `users` - A list of OkuNet users to delete.
100    ///
101    /// # Returns
102    ///
103    /// A list containing the deleted users.
104    pub fn delete_users(&self, users: &[OkuUser]) -> miette::Result<Vec<OkuUser>> {
105        let rw = self.database.rw_transaction().into_diagnostic()?;
106        let removed_users = users
107            .iter()
108            .filter_map(|user| rw.remove(user.to_owned()).ok())
109            .collect();
110        rw.commit().into_diagnostic()?;
111        Ok(removed_users)
112    }
113
114    /// Delete multiple OkuNet users and their posts.
115    ///
116    /// # Arguments
117    ///
118    /// * `users` - A list of OkuNet users to delete.
119    ///
120    /// # Returns
121    ///
122    /// A list containing the deleted posts.
123    pub fn delete_users_with_posts(&self, users: &[OkuUser]) -> miette::Result<Vec<OkuPost>> {
124        Ok(self
125            .delete_users(users)?
126            .par_iter()
127            .filter_map(|x| self.get_posts_by_author(&x.author_id).ok())
128            .collect::<Vec<_>>()
129            .into_par_iter()
130            .flat_map(|x| self.delete_posts(&x).ok())
131            .collect::<Vec<_>>()
132            .concat())
133    }
134
135    /// Deletes OkuNet users by their author IDs and posts by authors with those IDs.
136    ///
137    /// Differs from [`Self::delete_users_with_posts`] as a post will still be deleted even if a record for the authoring user is not found.
138    ///
139    /// # Arguments
140    ///
141    /// * `author_ids` - A list of content authorship IDs.
142    pub fn delete_by_author_ids(&self, author_ids: &Vec<AuthorId>) -> miette::Result<()> {
143        let users: Vec<_> = author_ids
144            .par_iter()
145            .filter_map(|x| self.get_user(x).ok().flatten())
146            .collect();
147        let posts: Vec<_> = author_ids
148            .into_par_iter()
149            .filter_map(|x| self.get_posts_by_author(x).ok())
150            .flatten()
151            .collect();
152        if let Err(e) = self.delete_users(&users) {
153            error!("{}", e);
154        }
155        if let Err(e) = self.delete_posts(&posts) {
156            error!("{}", e);
157        }
158        Ok(())
159    }
160
161    /// Gets the content authorship IDs of all locally-known users.
162    ///
163    /// This differs from [`Self::get_users`] as IDs of authors with posts but no user records are included.
164    ///
165    /// # Returns
166    ///
167    /// A list of IDs for all users that have content in the local database.
168    pub fn all_local_users(&self) -> Vec<AuthorId> {
169        let user_records: HashSet<_> = self
170            .get_users()
171            .unwrap_or_default()
172            .par_iter()
173            .map(|x| x.author_id)
174            .collect();
175        let post_record_users: HashSet<_> = self
176            .get_posts()
177            .unwrap_or_default()
178            .par_iter()
179            .map(|x| x.entry.author())
180            .collect();
181        user_records
182            .union(&post_record_users)
183            .map(|x| x.to_owned())
184            .collect()
185    }
186
187    /// Gets the OkuNet content of all known users.
188    ///
189    /// # Returns
190    ///
191    /// The OkuNet content of all users known to this node.
192    pub fn get_users(&self) -> miette::Result<Vec<OkuUser>> {
193        let r = self.database.r_transaction().into_diagnostic()?;
194        r.scan()
195            .primary()
196            .into_diagnostic()?
197            .all()
198            .into_diagnostic()?
199            .collect::<Result<Vec<_>, _>>()
200            .into_diagnostic()
201    }
202
203    /// Gets an OkuNet user's content by their content authorship ID.
204    ///
205    /// # Arguments
206    ///
207    /// * `author_id` - A content authorship ID.
208    ///
209    /// # Returns
210    ///
211    /// An OkuNet user's content.
212    pub fn get_user(&self, author_id: &AuthorId) -> miette::Result<Option<OkuUser>> {
213        let r = self.database.r_transaction().into_diagnostic()?;
214        r.get()
215            .primary(author_id.as_bytes().to_vec())
216            .into_diagnostic()
217    }
218}