tetratto_core/database/
userfollows.rs

1use oiseau::cache::Cache;
2use crate::model::auth::{AchievementName, FollowResult};
3use crate::model::requests::{ActionRequest, ActionType};
4use crate::model::{Error, Result, auth::User, auth::UserFollow, permissions::FinePermission};
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_userfollow_from_row(x: &PostgresRow) -> UserFollow {
12        UserFollow {
13            id: get!(x->0(i64)) as usize,
14            created: get!(x->1(i64)) as usize,
15            initiator: get!(x->2(i64)) as usize,
16            receiver: get!(x->3(i64)) as usize,
17        }
18    }
19
20    auto_method!(get_userfollow_by_id()@get_userfollow_from_row -> "SELECT * FROM userfollows WHERE id = $1" --name="user follow" --returns=UserFollow --cache-key-tmpl="atto.userfollow:{}");
21
22    /// Filter to update userfollows to clean their users for public APIs.
23    pub fn userfollows_user_filter(&self, x: &Vec<(UserFollow, User)>) -> Vec<(UserFollow, User)> {
24        let mut out: Vec<(UserFollow, User)> = Vec::new();
25
26        for mut y in x.clone() {
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_userfollow_by_initiator_receiver(
36        &self,
37        initiator: usize,
38        receiver: usize,
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 userfollows WHERE initiator = $1 AND receiver = $2",
48            &[&(initiator as i64), &(receiver as i64)],
49            |x| { Ok(Self::get_userfollow_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_userfollow_by_receiver_initiator(
61        &self,
62        receiver: usize,
63        initiator: usize,
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 userfollows WHERE receiver = $1 AND initiator = $2",
73            &[&(receiver as i64), &(initiator as i64)],
74            |x| { Ok(Self::get_userfollow_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 userfollows in each page
89    /// * `page` - the page number
90    pub async fn get_userfollows_by_initiator(
91        &self,
92        id: usize,
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 userfollows WHERE initiator = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
104            &[&(id as i64), &(batch as i64), &((page * batch) as i64)],
105            |x| { Self::get_userfollow_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_userfollows_by_initiator_all(&self, id: usize) -> 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 userfollows WHERE initiator = $1",
128            &[&(id as i64)],
129            |x| { Self::get_userfollow_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 userfollows in each page
144    /// * `page` - the page number
145    pub async fn get_userfollows_by_receiver(
146        &self,
147        id: usize,
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 userfollows WHERE receiver = $1 ORDER BY created DESC LIMIT $2 OFFSET $3",
159            &[&(id as i64), &(batch as i64), &((page * batch) as i64)],
160            |x| { Self::get_userfollow_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_userfollows_by_receiver_all(&self, id: usize) -> 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 userfollows WHERE receiver = $1",
183            &[&(id as i64)],
184            |x| { Self::get_userfollow_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 userfollows with their receiver as well.
195    pub async fn fill_userfollows_with_receiver(
196        &self,
197        userfollows: 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 userfollows {
204            let receiver = userfollow.receiver;
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::MANAGE_USERS) {
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 userfollows with their initiator as well.
227    pub async fn fill_userfollows_with_initiator(
228        &self,
229        userfollows: 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 userfollows {
236            let initiator = userfollow.initiator;
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::MANAGE_USERS) {
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_userfollow(
264        &self,
265        data: UserFollow,
266        initiator: &User,
267        force: bool,
268    ) -> Result<FollowResult> {
269        if !force {
270            let mut other_user = self.get_user_by_id(data.receiver).await?;
271
272            if other_user.settings.private_profile {
273                // send follow request instead
274                self.create_request(ActionRequest::with_id(
275                    data.initiator,
276                    data.receiver,
277                    ActionType::Follow,
278                    data.receiver,
279                    None,
280                ))
281                .await?;
282
283                return Ok(FollowResult::Requested);
284            }
285
286            // check if we're staff
287            if initiator.permissions.check(FinePermission::STAFF_BADGE) {
288                self.add_achievement(
289                    &mut other_user,
290                    AchievementName::FollowedByStaff.into(),
291                    true,
292                )
293                .await?;
294            }
295
296            // other achivements
297            self.add_achievement(&mut other_user, AchievementName::Get1Follower.into(), true)
298                .await?;
299
300            if other_user.follower_count >= 9 {
301                self.add_achievement(
302                    &mut other_user,
303                    AchievementName::Get10Followers.into(),
304                    true,
305                )
306                .await?;
307            }
308
309            if other_user.follower_count >= 49 {
310                self.add_achievement(
311                    &mut other_user,
312                    AchievementName::Get50Followers.into(),
313                    true,
314                )
315                .await?;
316            }
317
318            if other_user.follower_count >= 99 {
319                self.add_achievement(
320                    &mut other_user,
321                    AchievementName::Get100Followers.into(),
322                    true,
323                )
324                .await?;
325            }
326
327            if initiator.following_count >= 9 {
328                self.add_achievement(
329                    &mut initiator.clone(),
330                    AchievementName::Follow10Users.into(),
331                    true,
332                )
333                .await?;
334            }
335        }
336
337        // ...
338        let conn = match self.0.connect().await {
339            Ok(c) => c,
340            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
341        };
342
343        let res = execute!(
344            &conn,
345            "INSERT INTO userfollows VALUES ($1, $2, $3, $4)",
346            params![
347                &(data.id as i64),
348                &(data.created as i64),
349                &(data.initiator as i64),
350                &(data.receiver as i64)
351            ]
352        );
353
354        if let Err(e) = res {
355            return Err(Error::DatabaseError(e.to_string()));
356        }
357
358        // incr counts
359        self.incr_user_following_count(data.initiator)
360            .await
361            .unwrap();
362
363        self.incr_user_follower_count(data.receiver).await.unwrap();
364
365        // return
366        Ok(FollowResult::Followed)
367    }
368
369    pub async fn delete_userfollow(
370        &self,
371        id: usize,
372        user: &User,
373        is_deleting_user: bool,
374    ) -> Result<()> {
375        let follow = self.get_userfollow_by_id(id).await?;
376
377        if (user.id != follow.initiator)
378            && (user.id != follow.receiver)
379            && !user.permissions.check(FinePermission::MANAGE_FOLLOWS)
380            && !is_deleting_user
381        {
382            return Err(Error::NotAllowed);
383        }
384
385        let conn = match self.0.connect().await {
386            Ok(c) => c,
387            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
388        };
389
390        let res = execute!(
391            &conn,
392            "DELETE FROM userfollows WHERE id = $1",
393            &[&(id as i64)]
394        );
395
396        if let Err(e) = res {
397            return Err(Error::DatabaseError(e.to_string()));
398        }
399
400        self.0.1.remove(format!("atto.userfollow:{}", id)).await;
401
402        // decr counts (if we aren't deleting the user OR the user id isn't the deleted user id)
403        if !is_deleting_user | (follow.initiator != user.id) {
404            if self
405                .decr_user_following_count(follow.initiator)
406                .await
407                .is_err()
408            {
409                println!("ERR_TETRATTO_DECR_FOLLOWS: could not decr initiator follow count")
410            }
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_userfollow_sudo(&self, id: usize, user_id: usize) -> Result<()> {
424        let follow = self.get_userfollow_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 userfollows WHERE id = $1",
434            &[&(id as i64)]
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.userfollow:{}", 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}