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