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