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