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