fabrica

hanna/fabrica

4728 bytes
Raw
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Follows: one user following another. Public — profiles show follower and
6//! following counts and lists.
7
8// `follower_id` / `followee_id` are the natural, clear parameter names here.
9#![allow(clippy::similar_names)]
10
11use model::User;
12use sqlx::{AssertSqlSafe, Row};
13
14use crate::users::USER_COLUMNS;
15use crate::{Store, StoreError, new_id, now_ms};
16
17impl Store {
18 /// Follow `followee` as `follower`. Idempotent; a self-follow is a no-op.
19 ///
20 /// # Errors
21 ///
22 /// Returns [`StoreError::Query`] on a database failure.
23 pub async fn follow(&self, follower_id: &str, followee_id: &str) -> Result<(), StoreError> {
24 if follower_id == followee_id {
25 return Ok(());
26 }
27 sqlx::query(
28 "INSERT INTO follows (id, follower_id, followee_id, created_at) VALUES ($1, $2, $3, $4) \
29 ON CONFLICT (follower_id, followee_id) DO NOTHING",
30 )
31 .bind(new_id())
32 .bind(follower_id)
33 .bind(followee_id)
34 .bind(now_ms())
35 .execute(&self.pool)
36 .await?;
37 Ok(())
38 }
39
40 /// Unfollow `followee`.
41 ///
42 /// # Errors
43 ///
44 /// Returns [`StoreError::Query`] on a database failure.
45 pub async fn unfollow(&self, follower_id: &str, followee_id: &str) -> Result<(), StoreError> {
46 sqlx::query("DELETE FROM follows WHERE follower_id = $1 AND followee_id = $2")
47 .bind(follower_id)
48 .bind(followee_id)
49 .execute(&self.pool)
50 .await?;
51 Ok(())
52 }
53
54 /// Whether `follower` follows `followee`.
55 ///
56 /// # Errors
57 ///
58 /// Returns [`StoreError::Query`] on a database failure.
59 pub async fn is_following(
60 &self,
61 follower_id: &str,
62 followee_id: &str,
63 ) -> Result<bool, StoreError> {
64 let row =
65 sqlx::query("SELECT 1 AS n FROM follows WHERE follower_id = $1 AND followee_id = $2")
66 .bind(follower_id)
67 .bind(followee_id)
68 .fetch_optional(&self.pool)
69 .await?;
70 Ok(row.is_some())
71 }
72
73 /// The number of users following `user_id`.
74 ///
75 /// # Errors
76 ///
77 /// Returns [`StoreError::Query`] on a database failure.
78 pub async fn count_followers(&self, user_id: &str) -> Result<i64, StoreError> {
79 let n: i64 = sqlx::query("SELECT COUNT(*) AS n FROM follows WHERE followee_id = $1")
80 .bind(user_id)
81 .fetch_one(&self.pool)
82 .await?
83 .try_get("n")?;
84 Ok(n)
85 }
86
87 /// The number of users `user_id` follows.
88 ///
89 /// # Errors
90 ///
91 /// Returns [`StoreError::Query`] on a database failure.
92 pub async fn count_following(&self, user_id: &str) -> Result<i64, StoreError> {
93 let n: i64 = sqlx::query("SELECT COUNT(*) AS n FROM follows WHERE follower_id = $1")
94 .bind(user_id)
95 .fetch_one(&self.pool)
96 .await?
97 .try_get("n")?;
98 Ok(n)
99 }
100
101 /// The users following `user_id`, most recent first.
102 ///
103 /// # Errors
104 ///
105 /// Returns [`StoreError::Query`] on a database failure.
106 pub async fn followers(&self, user_id: &str) -> Result<Vec<User>, StoreError> {
107 let cols = format!("u.{}", USER_COLUMNS.replace(", ", ", u."));
108 let sql = format!(
109 "SELECT {cols} FROM users u JOIN follows f ON f.follower_id = u.id \
110 WHERE f.followee_id = $1 ORDER BY f.created_at DESC"
111 );
112 let rows = sqlx::query(AssertSqlSafe(sql))
113 .bind(user_id)
114 .fetch_all(&self.pool)
115 .await?;
116 rows.iter()
117 .map(|r| self.map_user(r))
118 .collect::<Result<_, _>>()
119 .map_err(Into::into)
120 }
121
122 /// The users `user_id` follows, most recent first.
123 ///
124 /// # Errors
125 ///
126 /// Returns [`StoreError::Query`] on a database failure.
127 pub async fn following(&self, user_id: &str) -> Result<Vec<User>, StoreError> {
128 let cols = format!("u.{}", USER_COLUMNS.replace(", ", ", u."));
129 let sql = format!(
130 "SELECT {cols} FROM users u JOIN follows f ON f.followee_id = u.id \
131 WHERE f.follower_id = $1 ORDER BY f.created_at DESC"
132 );
133 let rows = sqlx::query(AssertSqlSafe(sql))
134 .bind(user_id)
135 .fetch_all(&self.pool)
136 .await?;
137 rows.iter()
138 .map(|r| self.map_user(r))
139 .collect::<Result<_, _>>()
140 .map_err(Into::into)
141 }
142}