]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/community.rs
Expose pending 2 (#2282)
[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(true))
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(conn: &PgConnection, community_name: &str) -> Result<Community, Error> {
336     use crate::schema::community::dsl::*;
337     community
338       .filter(local.eq(true))
339       .filter(lower(name).eq(lower(community_name)))
340       .first::<Self>(conn)
341   }
342
343   fn read_from_name_and_domain(
344     conn: &PgConnection,
345     community_name: &str,
346     protocol_domain: &str,
347   ) -> Result<Community, Error> {
348     use crate::schema::community::dsl::*;
349     community
350       .filter(lower(name).eq(lower(community_name)))
351       .filter(actor_id.like(format!("{}%", protocol_domain)))
352       .first::<Self>(conn)
353   }
354 }
355
356 #[cfg(test)]
357 mod tests {
358   use crate::{
359     source::{community::*, person::*},
360     traits::{Bannable, Crud, Followable, Joinable},
361     utils::establish_unpooled_connection,
362   };
363   use serial_test::serial;
364
365   #[test]
366   #[serial]
367   fn test_crud() {
368     let conn = establish_unpooled_connection();
369
370     let new_person = PersonForm {
371       name: "bobbee".into(),
372       ..PersonForm::default()
373     };
374
375     let inserted_person = Person::create(&conn, &new_person).unwrap();
376
377     let new_community = CommunityForm {
378       name: "TIL".into(),
379       title: "nada".to_owned(),
380       public_key: "nada".to_owned(),
381       ..CommunityForm::default()
382     };
383
384     let inserted_community = Community::create(&conn, &new_community).unwrap();
385
386     let expected_community = Community {
387       id: inserted_community.id,
388       name: "TIL".into(),
389       title: "nada".to_owned(),
390       description: None,
391       nsfw: false,
392       removed: false,
393       deleted: false,
394       published: inserted_community.published,
395       updated: None,
396       actor_id: inserted_community.actor_id.to_owned(),
397       local: true,
398       private_key: None,
399       public_key: "nada".to_owned(),
400       last_refreshed_at: inserted_community.published,
401       icon: None,
402       banner: None,
403       followers_url: inserted_community.followers_url.to_owned(),
404       inbox_url: inserted_community.inbox_url.to_owned(),
405       shared_inbox_url: None,
406       hidden: false,
407       posting_restricted_to_mods: false,
408     };
409
410     let community_follower_form = CommunityFollowerForm {
411       community_id: inserted_community.id,
412       person_id: inserted_person.id,
413       pending: false,
414     };
415
416     let inserted_community_follower =
417       CommunityFollower::follow(&conn, &community_follower_form).unwrap();
418
419     let expected_community_follower = CommunityFollower {
420       id: inserted_community_follower.id,
421       community_id: inserted_community.id,
422       person_id: inserted_person.id,
423       pending: Some(false),
424       published: inserted_community_follower.published,
425     };
426
427     let community_moderator_form = CommunityModeratorForm {
428       community_id: inserted_community.id,
429       person_id: inserted_person.id,
430     };
431
432     let inserted_community_moderator =
433       CommunityModerator::join(&conn, &community_moderator_form).unwrap();
434
435     let expected_community_moderator = CommunityModerator {
436       id: inserted_community_moderator.id,
437       community_id: inserted_community.id,
438       person_id: inserted_person.id,
439       published: inserted_community_moderator.published,
440     };
441
442     let community_person_ban_form = CommunityPersonBanForm {
443       community_id: inserted_community.id,
444       person_id: inserted_person.id,
445       expires: None,
446     };
447
448     let inserted_community_person_ban =
449       CommunityPersonBan::ban(&conn, &community_person_ban_form).unwrap();
450
451     let expected_community_person_ban = CommunityPersonBan {
452       id: inserted_community_person_ban.id,
453       community_id: inserted_community.id,
454       person_id: inserted_person.id,
455       published: inserted_community_person_ban.published,
456       expires: None,
457     };
458
459     let read_community = Community::read(&conn, inserted_community.id).unwrap();
460     let updated_community =
461       Community::update(&conn, inserted_community.id, &new_community).unwrap();
462     let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
463     let left_community = CommunityModerator::leave(&conn, &community_moderator_form).unwrap();
464     let unban = CommunityPersonBan::unban(&conn, &community_person_ban_form).unwrap();
465     let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
466     Person::delete(&conn, inserted_person.id).unwrap();
467
468     assert_eq!(expected_community, read_community);
469     assert_eq!(expected_community, inserted_community);
470     assert_eq!(expected_community, updated_community);
471     assert_eq!(expected_community_follower, inserted_community_follower);
472     assert_eq!(expected_community_moderator, inserted_community_moderator);
473     assert_eq!(expected_community_person_ban, inserted_community_person_ban);
474     assert_eq!(1, ignored_community);
475     assert_eq!(1, left_community);
476     assert_eq!(1, unban);
477     // assert_eq!(2, loaded_count);
478     assert_eq!(1, num_deleted);
479   }
480 }