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