]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/community.rs
e717b8a865e367a0361219cf7349cd872c2db4a9
[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 leave_all_communities(
156     pool: &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: &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: &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: &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
230 #[async_trait]
231 impl Followable for CommunityFollower {
232   type Form = CommunityFollowerForm;
233   async fn follow(pool: &DbPool, form: &CommunityFollowerForm) -> Result<Self, Error> {
234     use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
235     let conn = &mut get_conn(pool).await?;
236     insert_into(community_follower)
237       .values(form)
238       .on_conflict((community_id, person_id))
239       .do_update()
240       .set(form)
241       .get_result::<Self>(conn)
242       .await
243   }
244   async fn follow_accepted(
245     pool: &DbPool,
246     community_id_: CommunityId,
247     person_id_: PersonId,
248   ) -> Result<Self, Error> {
249     use crate::schema::community_follower::dsl::{
250       community_follower,
251       community_id,
252       pending,
253       person_id,
254     };
255     let conn = &mut get_conn(pool).await?;
256     diesel::update(
257       community_follower
258         .filter(community_id.eq(community_id_))
259         .filter(person_id.eq(person_id_)),
260     )
261     .set(pending.eq(false))
262     .get_result::<Self>(conn)
263     .await
264   }
265   async fn unfollow(pool: &DbPool, form: &CommunityFollowerForm) -> Result<usize, Error> {
266     use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
267     let conn = &mut get_conn(pool).await?;
268     diesel::delete(
269       community_follower
270         .filter(community_id.eq(&form.community_id))
271         .filter(person_id.eq(&form.person_id)),
272     )
273     .execute(conn)
274     .await
275   }
276 }
277
278 #[async_trait]
279 impl ApubActor for Community {
280   async fn read_from_apub_id(pool: &DbPool, object_id: &DbUrl) -> Result<Option<Self>, Error> {
281     let conn = &mut get_conn(pool).await?;
282     Ok(
283       community
284         .filter(actor_id.eq(object_id))
285         .first::<Community>(conn)
286         .await
287         .ok()
288         .map(Into::into),
289     )
290   }
291
292   async fn read_from_name(
293     pool: &DbPool,
294     community_name: &str,
295     include_deleted: bool,
296   ) -> Result<Community, Error> {
297     let conn = &mut get_conn(pool).await?;
298     let mut q = community
299       .into_boxed()
300       .filter(local.eq(true))
301       .filter(lower(name).eq(lower(community_name)));
302     if !include_deleted {
303       q = q.filter(deleted.eq(false)).filter(removed.eq(false));
304     }
305     q.first::<Self>(conn).await
306   }
307
308   async fn read_from_name_and_domain(
309     pool: &DbPool,
310     community_name: &str,
311     protocol_domain: &str,
312   ) -> Result<Community, Error> {
313     let conn = &mut get_conn(pool).await?;
314     community
315       .filter(lower(name).eq(lower(community_name)))
316       .filter(actor_id.like(format!("{protocol_domain}%")))
317       .first::<Self>(conn)
318       .await
319   }
320 }
321
322 #[cfg(test)]
323 mod tests {
324   use crate::{
325     source::{
326       community::{
327         Community,
328         CommunityFollower,
329         CommunityFollowerForm,
330         CommunityInsertForm,
331         CommunityModerator,
332         CommunityModeratorForm,
333         CommunityPersonBan,
334         CommunityPersonBanForm,
335         CommunityUpdateForm,
336       },
337       instance::Instance,
338       person::{Person, PersonInsertForm},
339     },
340     traits::{Bannable, Crud, Followable, Joinable},
341     utils::build_db_pool_for_tests,
342   };
343   use serial_test::serial;
344
345   #[tokio::test]
346   #[serial]
347   async fn test_crud() {
348     let pool = &build_db_pool_for_tests().await;
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 }