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