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