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