]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/community.rs
fe41d4d587247ee0d71fa4d4550731f598cf196c
[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,
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 #[async_trait]
26 impl Crud for Community {
27   type InsertForm = CommunityInsertForm;
28   type UpdateForm = CommunityUpdateForm;
29   type IdType = CommunityId;
30   async fn read(pool: &DbPool, community_id: CommunityId) -> Result<Self, Error> {
31     let conn = &mut get_conn(pool).await?;
32     community.find(community_id).first::<Self>(conn).await
33   }
34
35   async fn delete(pool: &DbPool, community_id: CommunityId) -> Result<usize, Error> {
36     let conn = &mut get_conn(pool).await?;
37     diesel::delete(community.find(community_id))
38       .execute(conn)
39       .await
40   }
41
42   async fn create(pool: &DbPool, form: &Self::InsertForm) -> Result<Self, Error> {
43     let conn = &mut get_conn(pool).await?;
44     let is_new_community = match &form.actor_id {
45       Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(),
46       None => true,
47     };
48
49     // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
50     let community_ = insert_into(community)
51       .values(form)
52       .on_conflict(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: &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.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: &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: &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: &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
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
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: &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 get_person_moderated_communities(
156     pool: &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: &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: &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: &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: &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: &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(pool: &DbPool, object_id: &DbUrl) -> Result<Option<Self>, Error> {
270     let conn = &mut get_conn(pool).await?;
271     Ok(
272       community
273         .filter(actor_id.eq(object_id))
274         .first::<Community>(conn)
275         .await
276         .ok()
277         .map(Into::into),
278     )
279   }
280
281   async fn read_from_name(
282     pool: &DbPool,
283     community_name: &str,
284     include_deleted: bool,
285   ) -> Result<Community, Error> {
286     let conn = &mut get_conn(pool).await?;
287     let mut q = community
288       .into_boxed()
289       .filter(local.eq(true))
290       .filter(lower(name).eq(lower(community_name)));
291     if !include_deleted {
292       q = q.filter(deleted.eq(false)).filter(removed.eq(false));
293     }
294     q.first::<Self>(conn).await
295   }
296
297   async fn read_from_name_and_domain(
298     pool: &DbPool,
299     community_name: &str,
300     protocol_domain: &str,
301   ) -> Result<Community, Error> {
302     let conn = &mut get_conn(pool).await?;
303     community
304       .filter(lower(name).eq(lower(community_name)))
305       .filter(actor_id.like(format!("{protocol_domain}%")))
306       .first::<Self>(conn)
307       .await
308   }
309 }
310
311 #[cfg(test)]
312 mod tests {
313   use crate::{
314     source::{
315       community::{
316         Community,
317         CommunityFollower,
318         CommunityFollowerForm,
319         CommunityInsertForm,
320         CommunityModerator,
321         CommunityModeratorForm,
322         CommunityPersonBan,
323         CommunityPersonBanForm,
324         CommunityUpdateForm,
325       },
326       instance::Instance,
327       person::{Person, PersonInsertForm},
328     },
329     traits::{Bannable, Crud, Followable, Joinable},
330     utils::build_db_pool_for_tests,
331   };
332   use serial_test::serial;
333
334   #[tokio::test]
335   #[serial]
336   async fn test_crud() {
337     let pool = &build_db_pool_for_tests().await;
338
339     let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
340       .await
341       .unwrap();
342
343     let new_person = PersonInsertForm::builder()
344       .name("bobbee".into())
345       .public_key("pubkey".to_string())
346       .instance_id(inserted_instance.id)
347       .build();
348
349     let inserted_person = Person::create(pool, &new_person).await.unwrap();
350
351     let new_community = CommunityInsertForm::builder()
352       .name("TIL".into())
353       .title("nada".to_owned())
354       .public_key("pubkey".to_string())
355       .instance_id(inserted_instance.id)
356       .build();
357
358     let inserted_community = Community::create(pool, &new_community).await.unwrap();
359
360     let expected_community = Community {
361       id: inserted_community.id,
362       name: "TIL".into(),
363       title: "nada".to_owned(),
364       description: None,
365       nsfw: false,
366       removed: false,
367       deleted: false,
368       published: inserted_community.published,
369       updated: None,
370       actor_id: inserted_community.actor_id.clone(),
371       local: true,
372       private_key: None,
373       public_key: "pubkey".to_owned(),
374       last_refreshed_at: inserted_community.published,
375       icon: None,
376       banner: None,
377       followers_url: inserted_community.followers_url.clone(),
378       inbox_url: inserted_community.inbox_url.clone(),
379       shared_inbox_url: None,
380       moderators_url: None,
381       featured_url: None,
382       hidden: false,
383       posting_restricted_to_mods: false,
384       instance_id: inserted_instance.id,
385     };
386
387     let community_follower_form = CommunityFollowerForm {
388       community_id: inserted_community.id,
389       person_id: inserted_person.id,
390       pending: false,
391     };
392
393     let inserted_community_follower = CommunityFollower::follow(pool, &community_follower_form)
394       .await
395       .unwrap();
396
397     let expected_community_follower = CommunityFollower {
398       id: inserted_community_follower.id,
399       community_id: inserted_community.id,
400       person_id: inserted_person.id,
401       pending: false,
402       published: inserted_community_follower.published,
403     };
404
405     let community_moderator_form = CommunityModeratorForm {
406       community_id: inserted_community.id,
407       person_id: inserted_person.id,
408     };
409
410     let inserted_community_moderator = CommunityModerator::join(pool, &community_moderator_form)
411       .await
412       .unwrap();
413
414     let expected_community_moderator = CommunityModerator {
415       id: inserted_community_moderator.id,
416       community_id: inserted_community.id,
417       person_id: inserted_person.id,
418       published: inserted_community_moderator.published,
419     };
420
421     let community_person_ban_form = CommunityPersonBanForm {
422       community_id: inserted_community.id,
423       person_id: inserted_person.id,
424       expires: None,
425     };
426
427     let inserted_community_person_ban = CommunityPersonBan::ban(pool, &community_person_ban_form)
428       .await
429       .unwrap();
430
431     let expected_community_person_ban = CommunityPersonBan {
432       id: inserted_community_person_ban.id,
433       community_id: inserted_community.id,
434       person_id: inserted_person.id,
435       published: inserted_community_person_ban.published,
436       expires: None,
437     };
438
439     let read_community = Community::read(pool, inserted_community.id).await.unwrap();
440
441     let update_community_form = CommunityUpdateForm::builder()
442       .title(Some("nada".to_owned()))
443       .build();
444     let updated_community = Community::update(pool, inserted_community.id, &update_community_form)
445       .await
446       .unwrap();
447
448     let ignored_community = CommunityFollower::unfollow(pool, &community_follower_form)
449       .await
450       .unwrap();
451     let left_community = CommunityModerator::leave(pool, &community_moderator_form)
452       .await
453       .unwrap();
454     let unban = CommunityPersonBan::unban(pool, &community_person_ban_form)
455       .await
456       .unwrap();
457     let num_deleted = Community::delete(pool, inserted_community.id)
458       .await
459       .unwrap();
460     Person::delete(pool, inserted_person.id).await.unwrap();
461     Instance::delete(pool, inserted_instance.id).await.unwrap();
462
463     assert_eq!(expected_community, read_community);
464     assert_eq!(expected_community, inserted_community);
465     assert_eq!(expected_community, updated_community);
466     assert_eq!(expected_community_follower, inserted_community_follower);
467     assert_eq!(expected_community_moderator, inserted_community_moderator);
468     assert_eq!(expected_community_person_ban, inserted_community_person_ban);
469     assert_eq!(1, ignored_community);
470     assert_eq!(1, left_community);
471     assert_eq!(1, unban);
472     // assert_eq!(2, loaded_count);
473     assert_eq!(1, num_deleted);
474   }
475 }