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