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