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