]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/community.rs
Fix follow being stuck as pending after accept (#2366)
[lemmy.git] / crates / db_schema / src / impls / community.rs
1 use crate::{
2   newtypes::{CommunityId, DbUrl, PersonId},
3   source::community::{
4     Community,
5     CommunityFollower,
6     CommunityFollowerForm,
7     CommunityForm,
8     CommunityModerator,
9     CommunityModeratorForm,
10     CommunityPersonBan,
11     CommunityPersonBanForm,
12     CommunitySafe,
13   },
14   traits::{ApubActor, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable},
15   utils::{functions::lower, naive_now},
16   SubscribedType,
17 };
18 use diesel::{
19   dsl::*,
20   result::Error,
21   ExpressionMethods,
22   PgConnection,
23   QueryDsl,
24   RunQueryDsl,
25   TextExpressionMethods,
26 };
27
28 mod safe_type {
29   use crate::{schema::community::*, source::community::Community, traits::ToSafe};
30
31   type Columns = (
32     id,
33     name,
34     title,
35     description,
36     removed,
37     published,
38     updated,
39     deleted,
40     nsfw,
41     actor_id,
42     local,
43     icon,
44     banner,
45     hidden,
46     posting_restricted_to_mods,
47   );
48
49   impl ToSafe for Community {
50     type SafeColumns = Columns;
51     fn safe_columns_tuple() -> Self::SafeColumns {
52       (
53         id,
54         name,
55         title,
56         description,
57         removed,
58         published,
59         updated,
60         deleted,
61         nsfw,
62         actor_id,
63         local,
64         icon,
65         banner,
66         hidden,
67         posting_restricted_to_mods,
68       )
69     }
70   }
71 }
72
73 impl Crud for Community {
74   type Form = CommunityForm;
75   type IdType = CommunityId;
76   fn read(conn: &PgConnection, community_id: CommunityId) -> Result<Self, Error> {
77     use crate::schema::community::dsl::*;
78     community.find(community_id).first::<Self>(conn)
79   }
80
81   fn delete(conn: &PgConnection, community_id: CommunityId) -> Result<usize, Error> {
82     use crate::schema::community::dsl::*;
83     diesel::delete(community.find(community_id)).execute(conn)
84   }
85
86   fn create(conn: &PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
87     use crate::schema::community::dsl::*;
88     insert_into(community)
89       .values(new_community)
90       .get_result::<Self>(conn)
91   }
92
93   fn update(
94     conn: &PgConnection,
95     community_id: CommunityId,
96     new_community: &CommunityForm,
97   ) -> Result<Self, Error> {
98     use crate::schema::community::dsl::*;
99     diesel::update(community.find(community_id))
100       .set(new_community)
101       .get_result::<Self>(conn)
102   }
103 }
104
105 impl Community {
106   pub fn update_deleted(
107     conn: &PgConnection,
108     community_id: CommunityId,
109     new_deleted: bool,
110   ) -> Result<Community, Error> {
111     use crate::schema::community::dsl::*;
112     diesel::update(community.find(community_id))
113       .set((deleted.eq(new_deleted), updated.eq(naive_now())))
114       .get_result::<Self>(conn)
115   }
116
117   pub fn update_removed(
118     conn: &PgConnection,
119     community_id: CommunityId,
120     new_removed: bool,
121   ) -> Result<Community, Error> {
122     use crate::schema::community::dsl::*;
123     diesel::update(community.find(community_id))
124       .set((removed.eq(new_removed), updated.eq(naive_now())))
125       .get_result::<Self>(conn)
126   }
127
128   pub fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<DbUrl>, Error> {
129     use crate::schema::community::dsl::*;
130     community.select(actor_id).distinct().load::<DbUrl>(conn)
131   }
132
133   pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
134     use crate::schema::community::dsl::*;
135     insert_into(community)
136       .values(community_form)
137       .on_conflict(actor_id)
138       .do_update()
139       .set(community_form)
140       .get_result::<Self>(conn)
141   }
142
143   pub fn remove_avatar_and_banner(
144     conn: &PgConnection,
145     community_id: CommunityId,
146   ) -> Result<Self, Error> {
147     use crate::schema::community::dsl::*;
148     diesel::update(community.find(community_id))
149       .set((
150         icon.eq::<Option<String>>(None),
151         banner.eq::<Option<String>>(None),
152       ))
153       .get_result::<Self>(conn)
154   }
155 }
156
157 impl Joinable for CommunityModerator {
158   type Form = CommunityModeratorForm;
159   fn join(
160     conn: &PgConnection,
161     community_moderator_form: &CommunityModeratorForm,
162   ) -> Result<Self, Error> {
163     use crate::schema::community_moderator::dsl::*;
164     insert_into(community_moderator)
165       .values(community_moderator_form)
166       .get_result::<Self>(conn)
167   }
168
169   fn leave(
170     conn: &PgConnection,
171     community_moderator_form: &CommunityModeratorForm,
172   ) -> Result<usize, Error> {
173     use crate::schema::community_moderator::dsl::*;
174     diesel::delete(
175       community_moderator
176         .filter(community_id.eq(community_moderator_form.community_id))
177         .filter(person_id.eq(community_moderator_form.person_id)),
178     )
179     .execute(conn)
180   }
181 }
182
183 impl DeleteableOrRemoveable for CommunitySafe {
184   fn blank_out_deleted_or_removed_info(mut self) -> Self {
185     self.title = "".into();
186     self.description = None;
187     self.icon = None;
188     self.banner = None;
189     self
190   }
191 }
192
193 impl DeleteableOrRemoveable for Community {
194   fn blank_out_deleted_or_removed_info(mut self) -> Self {
195     self.title = "".into();
196     self.description = None;
197     self.icon = None;
198     self.banner = None;
199     self
200   }
201 }
202
203 impl CommunityModerator {
204   pub fn delete_for_community(
205     conn: &PgConnection,
206     for_community_id: CommunityId,
207   ) -> Result<usize, Error> {
208     use crate::schema::community_moderator::dsl::*;
209     diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
210   }
211
212   pub fn get_person_moderated_communities(
213     conn: &PgConnection,
214     for_person_id: PersonId,
215   ) -> Result<Vec<CommunityId>, Error> {
216     use crate::schema::community_moderator::dsl::*;
217     community_moderator
218       .filter(person_id.eq(for_person_id))
219       .select(community_id)
220       .load::<CommunityId>(conn)
221   }
222 }
223
224 impl Bannable for CommunityPersonBan {
225   type Form = CommunityPersonBanForm;
226   fn ban(
227     conn: &PgConnection,
228     community_person_ban_form: &CommunityPersonBanForm,
229   ) -> Result<Self, Error> {
230     use crate::schema::community_person_ban::dsl::*;
231     insert_into(community_person_ban)
232       .values(community_person_ban_form)
233       .on_conflict((community_id, person_id))
234       .do_update()
235       .set(community_person_ban_form)
236       .get_result::<Self>(conn)
237   }
238
239   fn unban(
240     conn: &PgConnection,
241     community_person_ban_form: &CommunityPersonBanForm,
242   ) -> Result<usize, Error> {
243     use crate::schema::community_person_ban::dsl::*;
244     diesel::delete(
245       community_person_ban
246         .filter(community_id.eq(community_person_ban_form.community_id))
247         .filter(person_id.eq(community_person_ban_form.person_id)),
248     )
249     .execute(conn)
250   }
251 }
252
253 impl CommunityFollower {
254   pub fn to_subscribed_type(follower: &Option<Self>) -> SubscribedType {
255     match follower {
256       Some(f) => {
257         if f.pending.unwrap_or(false) {
258           SubscribedType::Pending
259         } else {
260           SubscribedType::Subscribed
261         }
262       }
263       // If the row doesn't exist, the person isn't a follower.
264       None => SubscribedType::NotSubscribed,
265     }
266   }
267 }
268
269 impl Followable for CommunityFollower {
270   type Form = CommunityFollowerForm;
271   fn follow(
272     conn: &PgConnection,
273     community_follower_form: &CommunityFollowerForm,
274   ) -> Result<Self, Error> {
275     use crate::schema::community_follower::dsl::*;
276     insert_into(community_follower)
277       .values(community_follower_form)
278       .on_conflict((community_id, person_id))
279       .do_update()
280       .set(community_follower_form)
281       .get_result::<Self>(conn)
282   }
283   fn follow_accepted(
284     conn: &PgConnection,
285     community_id_: CommunityId,
286     person_id_: PersonId,
287   ) -> Result<Self, Error>
288   where
289     Self: Sized,
290   {
291     use crate::schema::community_follower::dsl::*;
292     diesel::update(
293       community_follower
294         .filter(community_id.eq(community_id_))
295         .filter(person_id.eq(person_id_)),
296     )
297     .set(pending.eq(false))
298     .get_result::<Self>(conn)
299   }
300   fn unfollow(
301     conn: &PgConnection,
302     community_follower_form: &CommunityFollowerForm,
303   ) -> Result<usize, Error> {
304     use crate::schema::community_follower::dsl::*;
305     diesel::delete(
306       community_follower
307         .filter(community_id.eq(&community_follower_form.community_id))
308         .filter(person_id.eq(&community_follower_form.person_id)),
309     )
310     .execute(conn)
311   }
312   // TODO: this function name only makes sense if you call it with a remote community. for a local
313   //       community, it will also return true if only remote followers exist
314   fn has_local_followers(conn: &PgConnection, community_id_: CommunityId) -> Result<bool, Error> {
315     use crate::schema::community_follower::dsl::*;
316     diesel::select(exists(
317       community_follower.filter(community_id.eq(community_id_)),
318     ))
319     .get_result(conn)
320   }
321 }
322
323 impl ApubActor for Community {
324   fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Option<Self>, Error> {
325     use crate::schema::community::dsl::*;
326     Ok(
327       community
328         .filter(actor_id.eq(object_id))
329         .first::<Community>(conn)
330         .ok()
331         .map(Into::into),
332     )
333   }
334
335   fn read_from_name(
336     conn: &PgConnection,
337     community_name: &str,
338     include_deleted: bool,
339   ) -> Result<Community, Error> {
340     use crate::schema::community::dsl::*;
341     let mut q = community
342       .into_boxed()
343       .filter(local.eq(true))
344       .filter(lower(name).eq(lower(community_name)));
345     if !include_deleted {
346       q = q.filter(deleted.eq(false)).filter(removed.eq(false));
347     }
348     q.first::<Self>(conn)
349   }
350
351   fn read_from_name_and_domain(
352     conn: &PgConnection,
353     community_name: &str,
354     protocol_domain: &str,
355   ) -> Result<Community, Error> {
356     use crate::schema::community::dsl::*;
357     community
358       .filter(lower(name).eq(lower(community_name)))
359       .filter(actor_id.like(format!("{}%", protocol_domain)))
360       .first::<Self>(conn)
361   }
362 }
363
364 #[cfg(test)]
365 mod tests {
366   use crate::{
367     source::{community::*, person::*},
368     traits::{Bannable, Crud, Followable, Joinable},
369     utils::establish_unpooled_connection,
370   };
371   use serial_test::serial;
372
373   #[test]
374   #[serial]
375   fn test_crud() {
376     let conn = establish_unpooled_connection();
377
378     let new_person = PersonForm {
379       name: "bobbee".into(),
380       public_key: Some("pubkey".to_string()),
381       ..PersonForm::default()
382     };
383
384     let inserted_person = Person::create(&conn, &new_person).unwrap();
385
386     let new_community = CommunityForm {
387       name: "TIL".into(),
388       title: "nada".to_owned(),
389       public_key: Some("pubkey".to_string()),
390       ..CommunityForm::default()
391     };
392
393     let inserted_community = Community::create(&conn, &new_community).unwrap();
394
395     let expected_community = Community {
396       id: inserted_community.id,
397       name: "TIL".into(),
398       title: "nada".to_owned(),
399       description: None,
400       nsfw: false,
401       removed: false,
402       deleted: false,
403       published: inserted_community.published,
404       updated: None,
405       actor_id: inserted_community.actor_id.to_owned(),
406       local: true,
407       private_key: None,
408       public_key: "pubkey".to_owned(),
409       last_refreshed_at: inserted_community.published,
410       icon: None,
411       banner: None,
412       followers_url: inserted_community.followers_url.to_owned(),
413       inbox_url: inserted_community.inbox_url.to_owned(),
414       shared_inbox_url: None,
415       hidden: false,
416       posting_restricted_to_mods: false,
417     };
418
419     let community_follower_form = CommunityFollowerForm {
420       community_id: inserted_community.id,
421       person_id: inserted_person.id,
422       pending: false,
423     };
424
425     let inserted_community_follower =
426       CommunityFollower::follow(&conn, &community_follower_form).unwrap();
427
428     let expected_community_follower = CommunityFollower {
429       id: inserted_community_follower.id,
430       community_id: inserted_community.id,
431       person_id: inserted_person.id,
432       pending: Some(false),
433       published: inserted_community_follower.published,
434     };
435
436     let community_moderator_form = CommunityModeratorForm {
437       community_id: inserted_community.id,
438       person_id: inserted_person.id,
439     };
440
441     let inserted_community_moderator =
442       CommunityModerator::join(&conn, &community_moderator_form).unwrap();
443
444     let expected_community_moderator = CommunityModerator {
445       id: inserted_community_moderator.id,
446       community_id: inserted_community.id,
447       person_id: inserted_person.id,
448       published: inserted_community_moderator.published,
449     };
450
451     let community_person_ban_form = CommunityPersonBanForm {
452       community_id: inserted_community.id,
453       person_id: inserted_person.id,
454       expires: None,
455     };
456
457     let inserted_community_person_ban =
458       CommunityPersonBan::ban(&conn, &community_person_ban_form).unwrap();
459
460     let expected_community_person_ban = CommunityPersonBan {
461       id: inserted_community_person_ban.id,
462       community_id: inserted_community.id,
463       person_id: inserted_person.id,
464       published: inserted_community_person_ban.published,
465       expires: None,
466     };
467
468     let read_community = Community::read(&conn, inserted_community.id).unwrap();
469     let updated_community =
470       Community::update(&conn, inserted_community.id, &new_community).unwrap();
471     let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
472     let left_community = CommunityModerator::leave(&conn, &community_moderator_form).unwrap();
473     let unban = CommunityPersonBan::unban(&conn, &community_person_ban_form).unwrap();
474     let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
475     Person::delete(&conn, inserted_person.id).unwrap();
476
477     assert_eq!(expected_community, read_community);
478     assert_eq!(expected_community, inserted_community);
479     assert_eq!(expected_community, updated_community);
480     assert_eq!(expected_community_follower, inserted_community_follower);
481     assert_eq!(expected_community_moderator, inserted_community_moderator);
482     assert_eq!(expected_community_person_ban, inserted_community_person_ban);
483     assert_eq!(1, ignored_community);
484     assert_eq!(1, left_community);
485     assert_eq!(1, unban);
486     // assert_eq!(2, loaded_count);
487     assert_eq!(1, num_deleted);
488   }
489 }