]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/community.rs
Various pedantic clippy fixes (#2568)
[lemmy.git] / crates / db_schema / src / impls / community.rs
1 use crate::{
2   newtypes::{CommunityId, DbUrl, PersonId},
3   schema::community::dsl::{actor_id, community, deleted, local, name, removed},
4   source::{
5     actor_language::{CommunityLanguage, SiteLanguage},
6     community::{
7       Community,
8       CommunityFollower,
9       CommunityFollowerForm,
10       CommunityInsertForm,
11       CommunityModerator,
12       CommunityModeratorForm,
13       CommunityPersonBan,
14       CommunityPersonBanForm,
15       CommunitySafe,
16       CommunityUpdateForm,
17     },
18   },
19   traits::{ApubActor, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable},
20   utils::{functions::lower, get_conn, DbPool},
21   SubscribedType,
22 };
23 use diesel::{
24   dsl::{exists, insert_into},
25   result::Error,
26   ExpressionMethods,
27   QueryDsl,
28   TextExpressionMethods,
29 };
30 use diesel_async::RunQueryDsl;
31
32 mod safe_type {
33   use crate::{
34     schema::community::{
35       actor_id,
36       banner,
37       deleted,
38       description,
39       hidden,
40       icon,
41       id,
42       instance_id,
43       local,
44       name,
45       nsfw,
46       posting_restricted_to_mods,
47       published,
48       removed,
49       title,
50       updated,
51     },
52     source::community::Community,
53     traits::ToSafe,
54   };
55
56   type Columns = (
57     id,
58     name,
59     title,
60     description,
61     removed,
62     published,
63     updated,
64     deleted,
65     nsfw,
66     actor_id,
67     local,
68     icon,
69     banner,
70     hidden,
71     posting_restricted_to_mods,
72     instance_id,
73   );
74
75   impl ToSafe for Community {
76     type SafeColumns = Columns;
77     fn safe_columns_tuple() -> Self::SafeColumns {
78       (
79         id,
80         name,
81         title,
82         description,
83         removed,
84         published,
85         updated,
86         deleted,
87         nsfw,
88         actor_id,
89         local,
90         icon,
91         banner,
92         hidden,
93         posting_restricted_to_mods,
94         instance_id,
95       )
96     }
97   }
98 }
99
100 #[async_trait]
101 impl Crud for Community {
102   type InsertForm = CommunityInsertForm;
103   type UpdateForm = CommunityUpdateForm;
104   type IdType = CommunityId;
105   async fn read(pool: &DbPool, community_id: CommunityId) -> Result<Self, Error> {
106     let conn = &mut get_conn(pool).await?;
107     community.find(community_id).first::<Self>(conn).await
108   }
109
110   async fn delete(pool: &DbPool, community_id: CommunityId) -> Result<usize, Error> {
111     let conn = &mut get_conn(pool).await?;
112     diesel::delete(community.find(community_id))
113       .execute(conn)
114       .await
115   }
116
117   async fn create(pool: &DbPool, form: &Self::InsertForm) -> Result<Self, Error> {
118     let conn = &mut get_conn(pool).await?;
119     let community_ = insert_into(community)
120       .values(form)
121       .on_conflict(actor_id)
122       .do_update()
123       .set(form)
124       .get_result::<Self>(conn)
125       .await?;
126
127     let site_languages = SiteLanguage::read_local(pool).await;
128     if let Ok(langs) = site_languages {
129       // if site exists, init user with site languages
130       CommunityLanguage::update(pool, langs, community_.id).await?;
131     } else {
132       // otherwise, init with all languages (this only happens during tests)
133       CommunityLanguage::update(pool, vec![], community_.id).await?;
134     }
135
136     Ok(community_)
137   }
138
139   async fn update(
140     pool: &DbPool,
141     community_id: CommunityId,
142     form: &Self::UpdateForm,
143   ) -> Result<Self, Error> {
144     let conn = &mut get_conn(pool).await?;
145     diesel::update(community.find(community_id))
146       .set(form)
147       .get_result::<Self>(conn)
148       .await
149   }
150 }
151
152 #[async_trait]
153 impl Joinable for CommunityModerator {
154   type Form = CommunityModeratorForm;
155   async fn join(
156     pool: &DbPool,
157     community_moderator_form: &CommunityModeratorForm,
158   ) -> Result<Self, Error> {
159     use crate::schema::community_moderator::dsl::community_moderator;
160     let conn = &mut get_conn(pool).await?;
161     insert_into(community_moderator)
162       .values(community_moderator_form)
163       .get_result::<Self>(conn)
164       .await
165   }
166
167   async fn leave(
168     pool: &DbPool,
169     community_moderator_form: &CommunityModeratorForm,
170   ) -> Result<usize, Error> {
171     use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
172     let conn = &mut get_conn(pool).await?;
173     diesel::delete(
174       community_moderator
175         .filter(community_id.eq(community_moderator_form.community_id))
176         .filter(person_id.eq(community_moderator_form.person_id)),
177     )
178     .execute(conn)
179     .await
180   }
181 }
182
183 impl DeleteableOrRemoveable for CommunitySafe {
184   fn blank_out_deleted_or_removed_info(mut self) -> Self {
185     self.title = String::new();
186     self.description = None;
187     self.icon = None;
188     self.banner = None;
189     self
190   }
191 }
192
193 impl DeleteableOrRemoveable for Community {
194   fn blank_out_deleted_or_removed_info(mut self) -> Self {
195     self.title = String::new();
196     self.description = None;
197     self.icon = None;
198     self.banner = None;
199     self
200   }
201 }
202
203 impl CommunityModerator {
204   pub async fn delete_for_community(
205     pool: &DbPool,
206     for_community_id: CommunityId,
207   ) -> Result<usize, Error> {
208     use crate::schema::community_moderator::dsl::{community_id, community_moderator};
209     let conn = &mut get_conn(pool).await?;
210
211     diesel::delete(community_moderator.filter(community_id.eq(for_community_id)))
212       .execute(conn)
213       .await
214   }
215
216   pub async fn get_person_moderated_communities(
217     pool: &DbPool,
218     for_person_id: PersonId,
219   ) -> Result<Vec<CommunityId>, Error> {
220     use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
221     let conn = &mut get_conn(pool).await?;
222     community_moderator
223       .filter(person_id.eq(for_person_id))
224       .select(community_id)
225       .load::<CommunityId>(conn)
226       .await
227   }
228 }
229
230 #[async_trait]
231 impl Bannable for CommunityPersonBan {
232   type Form = CommunityPersonBanForm;
233   async fn ban(
234     pool: &DbPool,
235     community_person_ban_form: &CommunityPersonBanForm,
236   ) -> Result<Self, Error> {
237     use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
238     let conn = &mut get_conn(pool).await?;
239     insert_into(community_person_ban)
240       .values(community_person_ban_form)
241       .on_conflict((community_id, person_id))
242       .do_update()
243       .set(community_person_ban_form)
244       .get_result::<Self>(conn)
245       .await
246   }
247
248   async fn unban(
249     pool: &DbPool,
250     community_person_ban_form: &CommunityPersonBanForm,
251   ) -> Result<usize, Error> {
252     use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
253     let conn = &mut get_conn(pool).await?;
254     diesel::delete(
255       community_person_ban
256         .filter(community_id.eq(community_person_ban_form.community_id))
257         .filter(person_id.eq(community_person_ban_form.person_id)),
258     )
259     .execute(conn)
260     .await
261   }
262 }
263
264 impl CommunityFollower {
265   pub fn to_subscribed_type(follower: &Option<Self>) -> SubscribedType {
266     match follower {
267       Some(f) => {
268         if f.pending.unwrap_or(false) {
269           SubscribedType::Pending
270         } else {
271           SubscribedType::Subscribed
272         }
273       }
274       // If the row doesn't exist, the person isn't a follower.
275       None => SubscribedType::NotSubscribed,
276     }
277   }
278 }
279
280 #[async_trait]
281 impl Followable for CommunityFollower {
282   type Form = CommunityFollowerForm;
283   async fn follow(
284     pool: &DbPool,
285     community_follower_form: &CommunityFollowerForm,
286   ) -> Result<Self, Error> {
287     use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
288     let conn = &mut get_conn(pool).await?;
289     insert_into(community_follower)
290       .values(community_follower_form)
291       .on_conflict((community_id, person_id))
292       .do_update()
293       .set(community_follower_form)
294       .get_result::<Self>(conn)
295       .await
296   }
297   async fn follow_accepted(
298     pool: &DbPool,
299     community_id_: CommunityId,
300     person_id_: PersonId,
301   ) -> Result<Self, Error> {
302     use crate::schema::community_follower::dsl::{
303       community_follower,
304       community_id,
305       pending,
306       person_id,
307     };
308     let conn = &mut get_conn(pool).await?;
309     diesel::update(
310       community_follower
311         .filter(community_id.eq(community_id_))
312         .filter(person_id.eq(person_id_)),
313     )
314     .set(pending.eq(false))
315     .get_result::<Self>(conn)
316     .await
317   }
318   async fn unfollow(
319     pool: &DbPool,
320     community_follower_form: &CommunityFollowerForm,
321   ) -> Result<usize, Error> {
322     use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
323     let conn = &mut get_conn(pool).await?;
324     diesel::delete(
325       community_follower
326         .filter(community_id.eq(&community_follower_form.community_id))
327         .filter(person_id.eq(&community_follower_form.person_id)),
328     )
329     .execute(conn)
330     .await
331   }
332   // TODO: this function name only makes sense if you call it with a remote community. for a local
333   //       community, it will also return true if only remote followers exist
334   async fn has_local_followers(pool: &DbPool, community_id_: CommunityId) -> Result<bool, Error> {
335     use crate::schema::community_follower::dsl::{community_follower, community_id};
336     let conn = &mut get_conn(pool).await?;
337     diesel::select(exists(
338       community_follower.filter(community_id.eq(community_id_)),
339     ))
340     .get_result(conn)
341     .await
342   }
343 }
344
345 #[async_trait]
346 impl ApubActor for Community {
347   async fn read_from_apub_id(pool: &DbPool, object_id: &DbUrl) -> Result<Option<Self>, Error> {
348     let conn = &mut get_conn(pool).await?;
349     Ok(
350       community
351         .filter(actor_id.eq(object_id))
352         .first::<Community>(conn)
353         .await
354         .ok()
355         .map(Into::into),
356     )
357   }
358
359   async fn read_from_name(
360     pool: &DbPool,
361     community_name: &str,
362     include_deleted: bool,
363   ) -> Result<Community, Error> {
364     let conn = &mut get_conn(pool).await?;
365     let mut q = community
366       .into_boxed()
367       .filter(local.eq(true))
368       .filter(lower(name).eq(lower(community_name)));
369     if !include_deleted {
370       q = q.filter(deleted.eq(false)).filter(removed.eq(false));
371     }
372     q.first::<Self>(conn).await
373   }
374
375   async fn read_from_name_and_domain(
376     pool: &DbPool,
377     community_name: &str,
378     protocol_domain: &str,
379   ) -> Result<Community, Error> {
380     let conn = &mut get_conn(pool).await?;
381     community
382       .filter(lower(name).eq(lower(community_name)))
383       .filter(actor_id.like(format!("{}%", protocol_domain)))
384       .first::<Self>(conn)
385       .await
386   }
387 }
388
389 #[cfg(test)]
390 mod tests {
391   use crate::{
392     source::{
393       community::{
394         Community,
395         CommunityFollower,
396         CommunityFollowerForm,
397         CommunityInsertForm,
398         CommunityModerator,
399         CommunityModeratorForm,
400         CommunityPersonBan,
401         CommunityPersonBanForm,
402         CommunityUpdateForm,
403       },
404       instance::Instance,
405       person::{Person, PersonInsertForm},
406     },
407     traits::{Bannable, Crud, Followable, Joinable},
408     utils::build_db_pool_for_tests,
409   };
410   use serial_test::serial;
411
412   #[tokio::test]
413   #[serial]
414   async fn test_crud() {
415     let pool = &build_db_pool_for_tests().await;
416
417     let inserted_instance = Instance::create(pool, "my_domain.tld").await.unwrap();
418
419     let new_person = PersonInsertForm::builder()
420       .name("bobbee".into())
421       .public_key("pubkey".to_string())
422       .instance_id(inserted_instance.id)
423       .build();
424
425     let inserted_person = Person::create(pool, &new_person).await.unwrap();
426
427     let new_community = CommunityInsertForm::builder()
428       .name("TIL".into())
429       .title("nada".to_owned())
430       .public_key("pubkey".to_string())
431       .instance_id(inserted_instance.id)
432       .build();
433
434     let inserted_community = Community::create(pool, &new_community).await.unwrap();
435
436     let expected_community = Community {
437       id: inserted_community.id,
438       name: "TIL".into(),
439       title: "nada".to_owned(),
440       description: None,
441       nsfw: false,
442       removed: false,
443       deleted: false,
444       published: inserted_community.published,
445       updated: None,
446       actor_id: inserted_community.actor_id.clone(),
447       local: true,
448       private_key: None,
449       public_key: "pubkey".to_owned(),
450       last_refreshed_at: inserted_community.published,
451       icon: None,
452       banner: None,
453       followers_url: inserted_community.followers_url.clone(),
454       inbox_url: inserted_community.inbox_url.clone(),
455       shared_inbox_url: None,
456       hidden: false,
457       posting_restricted_to_mods: false,
458       instance_id: inserted_instance.id,
459     };
460
461     let community_follower_form = CommunityFollowerForm {
462       community_id: inserted_community.id,
463       person_id: inserted_person.id,
464       pending: false,
465     };
466
467     let inserted_community_follower = CommunityFollower::follow(pool, &community_follower_form)
468       .await
469       .unwrap();
470
471     let expected_community_follower = CommunityFollower {
472       id: inserted_community_follower.id,
473       community_id: inserted_community.id,
474       person_id: inserted_person.id,
475       pending: Some(false),
476       published: inserted_community_follower.published,
477     };
478
479     let community_moderator_form = CommunityModeratorForm {
480       community_id: inserted_community.id,
481       person_id: inserted_person.id,
482     };
483
484     let inserted_community_moderator = CommunityModerator::join(pool, &community_moderator_form)
485       .await
486       .unwrap();
487
488     let expected_community_moderator = CommunityModerator {
489       id: inserted_community_moderator.id,
490       community_id: inserted_community.id,
491       person_id: inserted_person.id,
492       published: inserted_community_moderator.published,
493     };
494
495     let community_person_ban_form = CommunityPersonBanForm {
496       community_id: inserted_community.id,
497       person_id: inserted_person.id,
498       expires: None,
499     };
500
501     let inserted_community_person_ban = CommunityPersonBan::ban(pool, &community_person_ban_form)
502       .await
503       .unwrap();
504
505     let expected_community_person_ban = CommunityPersonBan {
506       id: inserted_community_person_ban.id,
507       community_id: inserted_community.id,
508       person_id: inserted_person.id,
509       published: inserted_community_person_ban.published,
510       expires: None,
511     };
512
513     let read_community = Community::read(pool, inserted_community.id).await.unwrap();
514
515     let update_community_form = CommunityUpdateForm::builder()
516       .title(Some("nada".to_owned()))
517       .build();
518     let updated_community = Community::update(pool, inserted_community.id, &update_community_form)
519       .await
520       .unwrap();
521
522     let ignored_community = CommunityFollower::unfollow(pool, &community_follower_form)
523       .await
524       .unwrap();
525     let left_community = CommunityModerator::leave(pool, &community_moderator_form)
526       .await
527       .unwrap();
528     let unban = CommunityPersonBan::unban(pool, &community_person_ban_form)
529       .await
530       .unwrap();
531     let num_deleted = Community::delete(pool, inserted_community.id)
532       .await
533       .unwrap();
534     Person::delete(pool, inserted_person.id).await.unwrap();
535     Instance::delete(pool, inserted_instance.id).await.unwrap();
536
537     assert_eq!(expected_community, read_community);
538     assert_eq!(expected_community, inserted_community);
539     assert_eq!(expected_community, updated_community);
540     assert_eq!(expected_community_follower, inserted_community_follower);
541     assert_eq!(expected_community_moderator, inserted_community_moderator);
542     assert_eq!(expected_community_person_ban, inserted_community_person_ban);
543     assert_eq!(1, ignored_community);
544     assert_eq!(1, left_community);
545     assert_eq!(1, unban);
546     // assert_eq!(2, loaded_count);
547     assert_eq!(1, num_deleted);
548   }
549 }