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