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