]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/community.rs
fb8bd6e4a1cbdb6d8be34bcf16a2d6a6218d0fd3
[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::{ApubActor, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable},
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
144 impl Joinable for CommunityModerator {
145   type Form = CommunityModeratorForm;
146   fn join(
147     conn: &PgConnection,
148     community_moderator_form: &CommunityModeratorForm,
149   ) -> Result<Self, Error> {
150     use crate::schema::community_moderator::dsl::*;
151     insert_into(community_moderator)
152       .values(community_moderator_form)
153       .get_result::<Self>(conn)
154   }
155
156   fn leave(
157     conn: &PgConnection,
158     community_moderator_form: &CommunityModeratorForm,
159   ) -> Result<usize, Error> {
160     use crate::schema::community_moderator::dsl::*;
161     diesel::delete(
162       community_moderator
163         .filter(community_id.eq(community_moderator_form.community_id))
164         .filter(person_id.eq(community_moderator_form.person_id)),
165     )
166     .execute(conn)
167   }
168 }
169
170 impl DeleteableOrRemoveable for CommunitySafe {
171   fn blank_out_deleted_or_removed_info(mut self) -> Self {
172     self.title = "".into();
173     self.description = None;
174     self.icon = None;
175     self.banner = None;
176     self
177   }
178 }
179
180 impl DeleteableOrRemoveable for Community {
181   fn blank_out_deleted_or_removed_info(mut self) -> Self {
182     self.title = "".into();
183     self.description = None;
184     self.icon = None;
185     self.banner = None;
186     self
187   }
188 }
189
190 impl CommunityModerator {
191   pub fn delete_for_community(
192     conn: &PgConnection,
193     for_community_id: CommunityId,
194   ) -> Result<usize, Error> {
195     use crate::schema::community_moderator::dsl::*;
196     diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
197   }
198
199   pub fn get_person_moderated_communities(
200     conn: &PgConnection,
201     for_person_id: PersonId,
202   ) -> Result<Vec<CommunityId>, Error> {
203     use crate::schema::community_moderator::dsl::*;
204     community_moderator
205       .filter(person_id.eq(for_person_id))
206       .select(community_id)
207       .load::<CommunityId>(conn)
208   }
209 }
210
211 impl Bannable for CommunityPersonBan {
212   type Form = CommunityPersonBanForm;
213   fn ban(
214     conn: &PgConnection,
215     community_person_ban_form: &CommunityPersonBanForm,
216   ) -> Result<Self, Error> {
217     use crate::schema::community_person_ban::dsl::*;
218     insert_into(community_person_ban)
219       .values(community_person_ban_form)
220       .on_conflict((community_id, person_id))
221       .do_update()
222       .set(community_person_ban_form)
223       .get_result::<Self>(conn)
224   }
225
226   fn unban(
227     conn: &PgConnection,
228     community_person_ban_form: &CommunityPersonBanForm,
229   ) -> Result<usize, Error> {
230     use crate::schema::community_person_ban::dsl::*;
231     diesel::delete(
232       community_person_ban
233         .filter(community_id.eq(community_person_ban_form.community_id))
234         .filter(person_id.eq(community_person_ban_form.person_id)),
235     )
236     .execute(conn)
237   }
238 }
239
240 impl Followable for CommunityFollower {
241   type Form = CommunityFollowerForm;
242   fn follow(
243     conn: &PgConnection,
244     community_follower_form: &CommunityFollowerForm,
245   ) -> Result<Self, Error> {
246     use crate::schema::community_follower::dsl::*;
247     insert_into(community_follower)
248       .values(community_follower_form)
249       .on_conflict((community_id, person_id))
250       .do_update()
251       .set(community_follower_form)
252       .get_result::<Self>(conn)
253   }
254   fn follow_accepted(
255     conn: &PgConnection,
256     community_id_: CommunityId,
257     person_id_: PersonId,
258   ) -> Result<Self, Error>
259   where
260     Self: Sized,
261   {
262     use crate::schema::community_follower::dsl::*;
263     diesel::update(
264       community_follower
265         .filter(community_id.eq(community_id_))
266         .filter(person_id.eq(person_id_)),
267     )
268     .set(pending.eq(true))
269     .get_result::<Self>(conn)
270   }
271   fn unfollow(
272     conn: &PgConnection,
273     community_follower_form: &CommunityFollowerForm,
274   ) -> Result<usize, Error> {
275     use crate::schema::community_follower::dsl::*;
276     diesel::delete(
277       community_follower
278         .filter(community_id.eq(&community_follower_form.community_id))
279         .filter(person_id.eq(&community_follower_form.person_id)),
280     )
281     .execute(conn)
282   }
283   // TODO: this function name only makes sense if you call it with a remote community. for a local
284   //       community, it will also return true if only remote followers exist
285   fn has_local_followers(conn: &PgConnection, community_id_: CommunityId) -> Result<bool, Error> {
286     use crate::schema::community_follower::dsl::*;
287     diesel::select(exists(
288       community_follower.filter(community_id.eq(community_id_)),
289     ))
290     .get_result(conn)
291   }
292 }
293
294 impl ApubActor for Community {
295   fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Option<Self>, Error> {
296     use crate::schema::community::dsl::*;
297     Ok(
298       community
299         .filter(actor_id.eq(object_id))
300         .first::<Community>(conn)
301         .ok()
302         .map(Into::into),
303     )
304   }
305
306   fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Community, Error> {
307     use crate::schema::community::dsl::*;
308     community
309       .filter(local.eq(true))
310       .filter(lower(name).eq(lower(community_name)))
311       .first::<Self>(conn)
312   }
313
314   fn read_from_name_and_domain(
315     conn: &PgConnection,
316     community_name: &str,
317     protocol_domain: &str,
318   ) -> Result<Community, Error> {
319     use crate::schema::community::dsl::*;
320     community
321       .filter(lower(name).eq(lower(community_name)))
322       .filter(actor_id.like(format!("{}%", protocol_domain)))
323       .first::<Self>(conn)
324   }
325 }
326
327 #[cfg(test)]
328 mod tests {
329   use crate::{
330     establish_unpooled_connection,
331     source::{community::*, person::*},
332     traits::{Bannable, Crud, Followable, Joinable},
333   };
334   use serial_test::serial;
335
336   #[test]
337   #[serial]
338   fn test_crud() {
339     let conn = establish_unpooled_connection();
340
341     let new_person = PersonForm {
342       name: "bobbee".into(),
343       ..PersonForm::default()
344     };
345
346     let inserted_person = Person::create(&conn, &new_person).unwrap();
347
348     let new_community = CommunityForm {
349       name: "TIL".into(),
350       title: "nada".to_owned(),
351       public_key: "nada".to_owned(),
352       ..CommunityForm::default()
353     };
354
355     let inserted_community = Community::create(&conn, &new_community).unwrap();
356
357     let expected_community = Community {
358       id: inserted_community.id,
359       name: "TIL".into(),
360       title: "nada".to_owned(),
361       description: None,
362       nsfw: false,
363       removed: false,
364       deleted: false,
365       published: inserted_community.published,
366       updated: None,
367       actor_id: inserted_community.actor_id.to_owned(),
368       local: true,
369       private_key: None,
370       public_key: "nada".to_owned(),
371       last_refreshed_at: inserted_community.published,
372       icon: None,
373       banner: None,
374       followers_url: inserted_community.followers_url.to_owned(),
375       inbox_url: inserted_community.inbox_url.to_owned(),
376       shared_inbox_url: None,
377       hidden: false,
378       posting_restricted_to_mods: false,
379     };
380
381     let community_follower_form = CommunityFollowerForm {
382       community_id: inserted_community.id,
383       person_id: inserted_person.id,
384       pending: false,
385     };
386
387     let inserted_community_follower =
388       CommunityFollower::follow(&conn, &community_follower_form).unwrap();
389
390     let expected_community_follower = CommunityFollower {
391       id: inserted_community_follower.id,
392       community_id: inserted_community.id,
393       person_id: inserted_person.id,
394       pending: Some(false),
395       published: inserted_community_follower.published,
396     };
397
398     let community_moderator_form = CommunityModeratorForm {
399       community_id: inserted_community.id,
400       person_id: inserted_person.id,
401     };
402
403     let inserted_community_moderator =
404       CommunityModerator::join(&conn, &community_moderator_form).unwrap();
405
406     let expected_community_moderator = CommunityModerator {
407       id: inserted_community_moderator.id,
408       community_id: inserted_community.id,
409       person_id: inserted_person.id,
410       published: inserted_community_moderator.published,
411     };
412
413     let community_person_ban_form = CommunityPersonBanForm {
414       community_id: inserted_community.id,
415       person_id: inserted_person.id,
416       expires: None,
417     };
418
419     let inserted_community_person_ban =
420       CommunityPersonBan::ban(&conn, &community_person_ban_form).unwrap();
421
422     let expected_community_person_ban = CommunityPersonBan {
423       id: inserted_community_person_ban.id,
424       community_id: inserted_community.id,
425       person_id: inserted_person.id,
426       published: inserted_community_person_ban.published,
427       expires: None,
428     };
429
430     let read_community = Community::read(&conn, inserted_community.id).unwrap();
431     let updated_community =
432       Community::update(&conn, inserted_community.id, &new_community).unwrap();
433     let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
434     let left_community = CommunityModerator::leave(&conn, &community_moderator_form).unwrap();
435     let unban = CommunityPersonBan::unban(&conn, &community_person_ban_form).unwrap();
436     let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
437     Person::delete(&conn, inserted_person.id).unwrap();
438
439     assert_eq!(expected_community, read_community);
440     assert_eq!(expected_community, inserted_community);
441     assert_eq!(expected_community, updated_community);
442     assert_eq!(expected_community_follower, inserted_community_follower);
443     assert_eq!(expected_community_moderator, inserted_community_moderator);
444     assert_eq!(expected_community_person_ban, inserted_community_person_ban);
445     assert_eq!(1, ignored_community);
446     assert_eq!(1, left_community);
447     assert_eq!(1, unban);
448     // assert_eq!(2, loaded_count);
449     assert_eq!(1, num_deleted);
450   }
451 }