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