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