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