Skip to main content

tetratto_core2/database/
user_follows.rs

1use oiseau::cache::Cache;
2use crate::model::auth::AchievementName;
3use crate::model::requests::{ActionRequest, ActionType};
4use crate::model::{Error, Result, auth::User, auth::UserFollow, permissions::FinePermission, id::Id};
5use crate::{auto_method, DataManager};
6
7use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
8
9impl DataManager {
10    /// Get a [`UserFollow`] from an SQL row.
11    pub(crate) fn get_user_follow_from_row(x: &PostgresRow) -> UserFollow {
12        UserFollow {
13            id: Id::deserialize(&get!(x->0(String))),
14            created: get!(x->1(i64)) as u128,
15            initiator: Id::deserialize(&get!(x->2(String))),
16            receiver: Id::deserialize(&get!(x->3(String))),
17        }
18    }
19
20    auto_method!(get_user_follow_by_id()@get_user_follow_from_row -> "SELECT * FROM user_follows WHERE id = $1" --name="user follow" --returns=UserFollow --cache-key-tmpl="atto.user_follow:{}");
21
22    /// Filter to update user_follows to clean their users for public APIs.
23    pub fn user_follows_user_filter(&self, x: &[(UserFollow, User)]) -> Vec<(UserFollow, User)> {
24        let mut out: Vec<(UserFollow, User)> = Vec::new();
25
26        for mut y in x.iter().cloned() {
27            y.1.clean();
28            out.push(y);
29        }
30
31        out
32    }
33
34    /// Get a user follow by `initiator` and `receiver` (in that order).
35    pub async fn get_user_follow_by_initiator_receiver(
36        &self,
37        initiator: &Id,
38        receiver: &Id,
39    ) -> Result<UserFollow> {
40        let conn = match self.0.connect().await {
41            Ok(c) => c,
42            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
43        };
44
45        let res = query_row!(
46            &conn,
47            "SELECT * FROM user_follows WHERE initiator = $1 AND receiver = $2",
48            &[&initiator.printable(), &receiver.printable()],
49            |x| { Ok(Self::get_user_follow_from_row(x)) }
50        );
51
52        if res.is_err() {
53            return Err(Error::GeneralNotFound("user follow".to_string()));
54        }
55
56        Ok(res.unwrap())
57    }
58
59    /// Get a user follow by `receiver` and `initiator` (in that order).
60    pub async fn get_user_follow_by_receiver_initiator(
61        &self,
62        receiver: &Id,
63        initiator: &Id,
64    ) -> Result<UserFollow> {
65        let conn = match self.0.connect().await {
66            Ok(c) => c,
67            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
68        };
69
70        let res = query_row!(
71            &conn,
72            "SELECT * FROM user_follows WHERE receiver = $1 AND initiator = $2",
73            &[&receiver.printable(), &initiator.printable()],
74            |x| { Ok(Self::get_user_follow_from_row(x)) }
75        );
76
77        if res.is_err() {
78            return Err(Error::GeneralNotFound("user follow".to_string()));
79        }
80
81        Ok(res.unwrap())
82    }
83
84    /// Get users the given user is following.
85    ///
86    /// # Arguments
87    /// * `id` - the ID of the user
88    /// * `batch` - the limit of user_follows in each page
89    /// * `page` - the page number
90    pub async fn get_user_follows_by_initiator(
91        &self,
92        id: &Id,
93        batch: usize,
94        page: usize,
95    ) -> Result<Vec<UserFollow>> {
96        let conn = match self.0.connect().await {
97            Ok(c) => c,
98            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
99        };
100
101        let res = query_rows!(
102            &conn,
103            "SELECT * FROM user_follows WHERE initiator = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
104            &[&id.printable(), &(batch as i64), &((page * batch) as i64)],
105            |x| { Self::get_user_follow_from_row(x) }
106        );
107
108        if res.is_err() {
109            return Err(Error::GeneralNotFound("user follow".to_string()));
110        }
111
112        Ok(res.unwrap())
113    }
114
115    /// Get users the given user is following.
116    ///
117    /// # Arguments
118    /// * `id` - the ID of the user
119    pub async fn get_user_follows_by_initiator_all(&self, id: &Id) -> Result<Vec<UserFollow>> {
120        let conn = match self.0.connect().await {
121            Ok(c) => c,
122            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
123        };
124
125        let res = query_rows!(
126            &conn,
127            "SELECT * FROM user_follows WHERE initiator = $1",
128            &[&id.printable()],
129            |x| { Self::get_user_follow_from_row(x) }
130        );
131
132        if res.is_err() {
133            return Err(Error::GeneralNotFound("user follow".to_string()));
134        }
135
136        Ok(res.unwrap())
137    }
138
139    /// Get users following the given user.
140    ///
141    /// # Arguments
142    /// * `id` - the ID of the user
143    /// * `batch` - the limit of user_follows in each page
144    /// * `page` - the page number
145    pub async fn get_user_follows_by_receiver(
146        &self,
147        id: &Id,
148        batch: usize,
149        page: usize,
150    ) -> Result<Vec<UserFollow>> {
151        let conn = match self.0.connect().await {
152            Ok(c) => c,
153            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
154        };
155
156        let res = query_rows!(
157            &conn,
158            "SELECT * FROM user_follows WHERE receiver = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
159            &[&id.printable(), &(batch as i64), &((page * batch) as i64)],
160            |x| { Self::get_user_follow_from_row(x) }
161        );
162
163        if res.is_err() {
164            return Err(Error::GeneralNotFound("user follow".to_string()));
165        }
166
167        Ok(res.unwrap())
168    }
169
170    /// Get users following the given user.
171    ///
172    /// # Arguments
173    /// * `id` - the ID of the user
174    pub async fn get_user_follows_by_receiver_all(&self, id: &Id) -> Result<Vec<UserFollow>> {
175        let conn = match self.0.connect().await {
176            Ok(c) => c,
177            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
178        };
179
180        let res = query_rows!(
181            &conn,
182            "SELECT * FROM user_follows WHERE receiver = $1",
183            &[&id.printable()],
184            |x| { Self::get_user_follow_from_row(x) }
185        );
186
187        if res.is_err() {
188            return Err(Error::GeneralNotFound("user follow".to_string()));
189        }
190
191        Ok(res.unwrap())
192    }
193
194    /// Complete a vector of just user_follows with their receiver as well.
195    pub async fn fill_user_follows_with_receiver(
196        &self,
197        user_follows: Vec<UserFollow>,
198        as_user: &Option<User>,
199        do_check: bool,
200    ) -> Result<Vec<(UserFollow, User)>> {
201        let mut out: Vec<(UserFollow, User)> = Vec::new();
202
203        for userfollow in user_follows {
204            let receiver = userfollow.receiver.clone();
205            let user = match self.get_user_by_id(&receiver).await {
206                Ok(u) => u,
207                Err(_) => continue,
208            };
209
210            if user.settings.hide_from_social_lists && do_check {
211                if let Some(ua) = as_user {
212                    if !ua.permissions.check(FinePermission::ManageUsers) {
213                        continue;
214                    }
215                } else {
216                    continue;
217                }
218            }
219
220            out.push((userfollow, user));
221        }
222
223        Ok(out)
224    }
225
226    /// Complete a vector of just user_follows with their initiator as well.
227    pub async fn fill_user_follows_with_initiator(
228        &self,
229        user_follows: Vec<UserFollow>,
230        as_user: &Option<User>,
231        do_check: bool,
232    ) -> Result<Vec<(UserFollow, User)>> {
233        let mut out: Vec<(UserFollow, User)> = Vec::new();
234
235        for userfollow in user_follows {
236            let initiator = userfollow.initiator.clone();
237            let user = match self.get_user_by_id(&initiator).await {
238                Ok(u) => u,
239                Err(_) => continue,
240            };
241
242            if user.settings.hide_from_social_lists && do_check {
243                if let Some(ua) = as_user {
244                    if !ua.permissions.check(FinePermission::ManageUsers) {
245                        continue;
246                    }
247                } else {
248                    continue;
249                }
250            }
251
252            out.push((userfollow, user));
253        }
254
255        Ok(out)
256    }
257
258    /// Create a new user follow in the database.
259    ///
260    /// # Arguments
261    /// * `data` - a mock [`UserFollow`] object to insert
262    /// * `force` - if we should skip the request stage
263    pub async fn create_user_follow(
264        &self,
265        data: UserFollow,
266        initiator: &User,
267        force: bool,
268    ) -> Result<bool> {
269        let mut asked = false;
270        if !force {
271            let mut other_user = self.get_user_by_id(&data.receiver).await?;
272
273            if other_user.settings.private_profile {
274                // send follow request
275                self.create_request(ActionRequest::with_id(
276                    data.initiator.clone(),
277                    data.receiver.clone(),
278                    ActionType::Follow,
279                    data.receiver.clone(),
280                    None,
281                ))
282                .await?;
283
284                asked = true;
285            }
286
287            // check if we're staff
288            if initiator.permissions.check(FinePermission::StaffBadge) {
289                self.add_achievement(
290                    &mut other_user,
291                    AchievementName::FollowedByStaff.into(),
292                    true,
293                )
294                .await?;
295            }
296
297            // other achivements
298            self.add_achievement(&mut other_user, AchievementName::Get1Follower.into(), true)
299                .await?;
300
301            if other_user.follower_count >= 9 {
302                self.add_achievement(
303                    &mut other_user,
304                    AchievementName::Get10Followers.into(),
305                    true,
306                )
307                .await?;
308            }
309
310            if other_user.follower_count >= 49 {
311                self.add_achievement(
312                    &mut other_user,
313                    AchievementName::Get50Followers.into(),
314                    true,
315                )
316                .await?;
317            }
318
319            if other_user.follower_count >= 99 {
320                self.add_achievement(
321                    &mut other_user,
322                    AchievementName::Get100Followers.into(),
323                    true,
324                )
325                .await?;
326            }
327
328            if initiator.following_count >= 9 {
329                self.add_achievement(
330                    &mut initiator.clone(),
331                    AchievementName::Follow10Users.into(),
332                    true,
333                )
334                .await?;
335            }
336        }
337
338        // ...
339        let conn = match self.0.connect().await {
340            Ok(c) => c,
341            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
342        };
343
344        let res = execute!(
345            &conn,
346            "INSERT INTO user_follows VALUES ($1, $2, $3, $4)",
347            params![
348                &data.id.printable(),
349                &(data.created as i64),
350                &data.initiator.printable(),
351                &data.receiver.printable()
352            ]
353        );
354
355        if let Err(e) = res {
356            return Err(Error::DatabaseError(e.to_string()));
357        }
358
359        // incr counts
360        self.incr_user_following_count(&data.initiator)
361            .await
362            .unwrap();
363
364        self.incr_user_follower_count(&data.receiver).await.unwrap();
365
366        // return
367        Ok(asked)
368    }
369
370    pub async fn delete_user_follow(
371        &self,
372        id: &Id,
373        user: &User,
374        is_deleting_user: bool,
375    ) -> Result<()> {
376        let follow = self.get_user_follow_by_id(id).await?;
377
378        if (user.id != follow.initiator)
379            && (user.id != follow.receiver)
380            && !user.permissions.check(FinePermission::ManageFollows)
381            && !is_deleting_user
382        {
383            return Err(Error::NotAllowed);
384        }
385
386        let conn = match self.0.connect().await {
387            Ok(c) => c,
388            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
389        };
390
391        let res = execute!(
392            &conn,
393            "DELETE FROM user_follows WHERE id = $1",
394            &[&id.printable()]
395        );
396
397        if let Err(e) = res {
398            return Err(Error::DatabaseError(e.to_string()));
399        }
400
401        self.0.1.remove(format!("atto.user_follow:{}", id)).await;
402
403        // decr counts (if we aren't deleting the user OR the user id isn't the deleted user id)
404        if !is_deleting_user | (follow.initiator != user.id)
405            && self
406                .decr_user_following_count(&follow.initiator)
407                .await
408                .is_err()
409        {
410            println!("ERR_TETRATTO_DECR_FOLLOWS: could not decr initiator follow count")
411        }
412
413        if !is_deleting_user | (follow.receiver != user.id) {
414            self.decr_user_follower_count(&follow.receiver)
415                .await
416                .unwrap();
417        }
418
419        // return
420        Ok(())
421    }
422
423    pub async fn delete_user_follow_forced(&self, id: &Id, user_id: &Id) -> Result<()> {
424        let follow = self.get_user_follow_by_id(id).await?;
425
426        let conn = match self.0.connect().await {
427            Ok(c) => c,
428            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
429        };
430
431        let res = execute!(
432            &conn,
433            "DELETE FROM user_follows WHERE id = $1",
434            &[&id.printable()]
435        );
436
437        if let Err(e) = res {
438            return Err(Error::DatabaseError(e.to_string()));
439        }
440
441        self.0.1.remove(format!("atto.user_follow:{}", id)).await;
442
443        // decr counts
444        if follow.initiator != *user_id {
445            self.decr_user_following_count(&follow.initiator)
446                .await
447                .unwrap();
448        }
449
450        if follow.receiver != *user_id {
451            self.decr_user_follower_count(&follow.receiver)
452                .await
453                .unwrap();
454        }
455
456        // return
457        Ok(())
458    }
459}