]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/person.rs
Addressing slow profile queries. #2777 (#2830)
[lemmy.git] / crates / db_schema / src / impls / person.rs
1 use crate::{
2   newtypes::{CommunityId, DbUrl, PersonId},
3   schema::{instance, local_user, person, person_follower},
4   source::person::{
5     Person,
6     PersonFollower,
7     PersonFollowerForm,
8     PersonInsertForm,
9     PersonUpdateForm,
10   },
11   traits::{ApubActor, Crud, Followable},
12   utils::{functions::lower, get_conn, naive_now, DbPool},
13 };
14 use diesel::{dsl::insert_into, result::Error, ExpressionMethods, JoinOnDsl, QueryDsl};
15 use diesel_async::RunQueryDsl;
16
17 #[async_trait]
18 impl Crud for Person {
19   type InsertForm = PersonInsertForm;
20   type UpdateForm = PersonUpdateForm;
21   type IdType = PersonId;
22   async fn read(pool: &DbPool, person_id: PersonId) -> Result<Self, Error> {
23     let conn = &mut get_conn(pool).await?;
24     person::table
25       .filter(person::deleted.eq(false))
26       .find(person_id)
27       .first::<Self>(conn)
28       .await
29   }
30   async fn delete(pool: &DbPool, person_id: PersonId) -> Result<usize, Error> {
31     let conn = &mut get_conn(pool).await?;
32     diesel::delete(person::table.find(person_id))
33       .execute(conn)
34       .await
35   }
36   async fn create(pool: &DbPool, form: &PersonInsertForm) -> Result<Self, Error> {
37     let conn = &mut get_conn(pool).await?;
38     insert_into(person::table)
39       .values(form)
40       .on_conflict(person::actor_id)
41       .do_update()
42       .set(form)
43       .get_result::<Self>(conn)
44       .await
45   }
46   async fn update(
47     pool: &DbPool,
48     person_id: PersonId,
49     form: &PersonUpdateForm,
50   ) -> Result<Self, Error> {
51     let conn = &mut get_conn(pool).await?;
52     diesel::update(person::table.find(person_id))
53       .set(form)
54       .get_result::<Self>(conn)
55       .await
56   }
57 }
58
59 impl Person {
60   pub async fn delete_account(pool: &DbPool, person_id: PersonId) -> Result<Person, Error> {
61     let conn = &mut get_conn(pool).await?;
62
63     // Set the local user info to none
64     diesel::update(local_user::table.filter(local_user::person_id.eq(person_id)))
65       .set((
66         local_user::email.eq::<Option<String>>(None),
67         local_user::validator_time.eq(naive_now()),
68       ))
69       .execute(conn)
70       .await?;
71
72     diesel::update(person::table.find(person_id))
73       .set((
74         person::display_name.eq::<Option<String>>(None),
75         person::avatar.eq::<Option<String>>(None),
76         person::banner.eq::<Option<String>>(None),
77         person::bio.eq::<Option<String>>(None),
78         person::matrix_user_id.eq::<Option<String>>(None),
79         person::deleted.eq(true),
80         person::updated.eq(naive_now()),
81       ))
82       .get_result::<Self>(conn)
83       .await
84   }
85 }
86
87 pub fn is_banned(banned_: bool, expires: Option<chrono::NaiveDateTime>) -> bool {
88   if let Some(expires) = expires {
89     banned_ && expires.gt(&naive_now())
90   } else {
91     banned_
92   }
93 }
94
95 #[async_trait]
96 impl ApubActor for Person {
97   async fn read_from_apub_id(pool: &DbPool, object_id: &DbUrl) -> Result<Option<Self>, Error> {
98     let conn = &mut get_conn(pool).await?;
99     Ok(
100       person::table
101         .filter(person::deleted.eq(false))
102         .filter(person::actor_id.eq(object_id))
103         .first::<Person>(conn)
104         .await
105         .ok()
106         .map(Into::into),
107     )
108   }
109
110   async fn read_from_name(
111     pool: &DbPool,
112     from_name: &str,
113     include_deleted: bool,
114   ) -> Result<Person, Error> {
115     let conn = &mut get_conn(pool).await?;
116     let mut q = person::table
117       .into_boxed()
118       .filter(person::local.eq(true))
119       .filter(lower(person::name).eq(from_name.to_lowercase()));
120     if !include_deleted {
121       q = q.filter(person::deleted.eq(false))
122     }
123     q.first::<Self>(conn).await
124   }
125
126   async fn read_from_name_and_domain(
127     pool: &DbPool,
128     person_name: &str,
129     for_domain: &str,
130   ) -> Result<Person, Error> {
131     let conn = &mut get_conn(pool).await?;
132
133     person::table
134       .inner_join(instance::table)
135       .filter(lower(person::name).eq(person_name.to_lowercase()))
136       .filter(instance::domain.eq(for_domain))
137       .select(person::all_columns)
138       .first::<Self>(conn)
139       .await
140   }
141 }
142
143 #[async_trait]
144 impl Followable for PersonFollower {
145   type Form = PersonFollowerForm;
146   async fn follow(pool: &DbPool, form: &PersonFollowerForm) -> Result<Self, Error> {
147     use crate::schema::person_follower::dsl::{follower_id, person_follower, person_id};
148     let conn = &mut get_conn(pool).await?;
149     insert_into(person_follower)
150       .values(form)
151       .on_conflict((follower_id, person_id))
152       .do_update()
153       .set(form)
154       .get_result::<Self>(conn)
155       .await
156   }
157   async fn follow_accepted(_: &DbPool, _: CommunityId, _: PersonId) -> Result<Self, Error> {
158     unimplemented!()
159   }
160   async fn unfollow(pool: &DbPool, form: &PersonFollowerForm) -> Result<usize, Error> {
161     use crate::schema::person_follower::dsl::{follower_id, person_follower, person_id};
162     let conn = &mut get_conn(pool).await?;
163     diesel::delete(
164       person_follower
165         .filter(follower_id.eq(&form.follower_id))
166         .filter(person_id.eq(&form.person_id)),
167     )
168     .execute(conn)
169     .await
170   }
171 }
172
173 impl PersonFollower {
174   pub async fn list_followers(
175     pool: &DbPool,
176     for_person_id: PersonId,
177   ) -> Result<Vec<Person>, Error> {
178     let conn = &mut get_conn(pool).await?;
179     person_follower::table
180       .inner_join(person::table.on(person_follower::follower_id.eq(person::id)))
181       .filter(person_follower::person_id.eq(for_person_id))
182       .select(person::all_columns)
183       .load(conn)
184       .await
185   }
186 }
187
188 #[cfg(test)]
189 mod tests {
190   use crate::{
191     source::{
192       instance::Instance,
193       person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm},
194     },
195     traits::{Crud, Followable},
196     utils::build_db_pool_for_tests,
197   };
198   use serial_test::serial;
199
200   #[tokio::test]
201   #[serial]
202   async fn test_crud() {
203     let pool = &build_db_pool_for_tests().await;
204
205     let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
206       .await
207       .unwrap();
208
209     let new_person = PersonInsertForm::builder()
210       .name("holly".into())
211       .public_key("nada".to_owned())
212       .instance_id(inserted_instance.id)
213       .build();
214
215     let inserted_person = Person::create(pool, &new_person).await.unwrap();
216
217     let expected_person = Person {
218       id: inserted_person.id,
219       name: "holly".into(),
220       display_name: None,
221       avatar: None,
222       banner: None,
223       banned: false,
224       deleted: false,
225       published: inserted_person.published,
226       updated: None,
227       actor_id: inserted_person.actor_id.clone(),
228       bio: None,
229       local: true,
230       bot_account: false,
231       admin: false,
232       private_key: None,
233       public_key: "nada".to_owned(),
234       last_refreshed_at: inserted_person.published,
235       inbox_url: inserted_person.inbox_url.clone(),
236       shared_inbox_url: None,
237       matrix_user_id: None,
238       ban_expires: None,
239       instance_id: inserted_instance.id,
240     };
241
242     let read_person = Person::read(pool, inserted_person.id).await.unwrap();
243
244     let update_person_form = PersonUpdateForm::builder()
245       .actor_id(Some(inserted_person.actor_id.clone()))
246       .build();
247     let updated_person = Person::update(pool, inserted_person.id, &update_person_form)
248       .await
249       .unwrap();
250
251     let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap();
252     Instance::delete(pool, inserted_instance.id).await.unwrap();
253
254     assert_eq!(expected_person, read_person);
255     assert_eq!(expected_person, inserted_person);
256     assert_eq!(expected_person, updated_person);
257     assert_eq!(1, num_deleted);
258   }
259
260   #[tokio::test]
261   #[serial]
262   async fn follow() {
263     let pool = &build_db_pool_for_tests().await;
264     let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
265       .await
266       .unwrap();
267
268     let person_form_1 = PersonInsertForm::builder()
269       .name("erich".into())
270       .public_key("pubkey".to_string())
271       .instance_id(inserted_instance.id)
272       .build();
273     let person_1 = Person::create(pool, &person_form_1).await.unwrap();
274     let person_form_2 = PersonInsertForm::builder()
275       .name("michele".into())
276       .public_key("pubkey".to_string())
277       .instance_id(inserted_instance.id)
278       .build();
279     let person_2 = Person::create(pool, &person_form_2).await.unwrap();
280
281     let follow_form = PersonFollowerForm {
282       person_id: person_1.id,
283       follower_id: person_2.id,
284       pending: false,
285     };
286     let person_follower = PersonFollower::follow(pool, &follow_form).await.unwrap();
287     assert_eq!(person_1.id, person_follower.person_id);
288     assert_eq!(person_2.id, person_follower.follower_id);
289     assert!(!person_follower.pending);
290
291     let followers = PersonFollower::list_followers(pool, person_1.id)
292       .await
293       .unwrap();
294     assert_eq!(vec![person_2], followers);
295
296     let unfollow = PersonFollower::unfollow(pool, &follow_form).await.unwrap();
297     assert_eq!(1, unfollow);
298   }
299 }