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