]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/community.rs
If viewed actor isnt in db, fetch it from other instance (#2145)
[lemmy.git] / crates / db_schema / src / impls / community.rs
1 use crate::{
2   functions::lower,
3   naive_now,
4   newtypes::{CommunityId, DbUrl, PersonId},
5   source::community::{
6     Community,
7     CommunityFollower,
8     CommunityFollowerForm,
9     CommunityForm,
10     CommunityModerator,
11     CommunityModeratorForm,
12     CommunityPersonBan,
13     CommunityPersonBanForm,
14     CommunitySafe,
15   },
16   traits::{ApubActor, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable},
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   );
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       )
67     }
68   }
69 }
70
71 impl Crud for Community {
72   type Form = CommunityForm;
73   type IdType = CommunityId;
74   fn read(conn: &PgConnection, community_id: CommunityId) -> Result<Self, Error> {
75     use crate::schema::community::dsl::*;
76     community.find(community_id).first::<Self>(conn)
77   }
78
79   fn delete(conn: &PgConnection, community_id: CommunityId) -> Result<usize, Error> {
80     use crate::schema::community::dsl::*;
81     diesel::delete(community.find(community_id)).execute(conn)
82   }
83
84   fn create(conn: &PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
85     use crate::schema::community::dsl::*;
86     insert_into(community)
87       .values(new_community)
88       .get_result::<Self>(conn)
89   }
90
91   fn update(
92     conn: &PgConnection,
93     community_id: CommunityId,
94     new_community: &CommunityForm,
95   ) -> Result<Self, Error> {
96     use crate::schema::community::dsl::*;
97     diesel::update(community.find(community_id))
98       .set(new_community)
99       .get_result::<Self>(conn)
100   }
101 }
102
103 impl Community {
104   pub fn update_deleted(
105     conn: &PgConnection,
106     community_id: CommunityId,
107     new_deleted: bool,
108   ) -> Result<Community, Error> {
109     use crate::schema::community::dsl::*;
110     diesel::update(community.find(community_id))
111       .set((deleted.eq(new_deleted), updated.eq(naive_now())))
112       .get_result::<Self>(conn)
113   }
114
115   pub fn update_removed(
116     conn: &PgConnection,
117     community_id: CommunityId,
118     new_removed: bool,
119   ) -> Result<Community, Error> {
120     use crate::schema::community::dsl::*;
121     diesel::update(community.find(community_id))
122       .set((removed.eq(new_removed), updated.eq(naive_now())))
123       .get_result::<Self>(conn)
124   }
125
126   pub fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<DbUrl>, Error> {
127     use crate::schema::community::dsl::*;
128     community.select(actor_id).distinct().load::<DbUrl>(conn)
129   }
130
131   pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
132     use crate::schema::community::dsl::*;
133     insert_into(community)
134       .values(community_form)
135       .on_conflict(actor_id)
136       .do_update()
137       .set(community_form)
138       .get_result::<Self>(conn)
139   }
140 }
141
142 impl Joinable for CommunityModerator {
143   type Form = CommunityModeratorForm;
144   fn join(
145     conn: &PgConnection,
146     community_moderator_form: &CommunityModeratorForm,
147   ) -> Result<Self, Error> {
148     use crate::schema::community_moderator::dsl::*;
149     insert_into(community_moderator)
150       .values(community_moderator_form)
151       .get_result::<Self>(conn)
152   }
153
154   fn leave(
155     conn: &PgConnection,
156     community_moderator_form: &CommunityModeratorForm,
157   ) -> Result<usize, Error> {
158     use crate::schema::community_moderator::dsl::*;
159     diesel::delete(
160       community_moderator
161         .filter(community_id.eq(community_moderator_form.community_id))
162         .filter(person_id.eq(community_moderator_form.person_id)),
163     )
164     .execute(conn)
165   }
166 }
167
168 impl DeleteableOrRemoveable for CommunitySafe {
169   fn blank_out_deleted_or_removed_info(mut self) -> Self {
170     self.title = "".into();
171     self.description = None;
172     self.icon = None;
173     self.banner = None;
174     self
175   }
176 }
177
178 impl DeleteableOrRemoveable for Community {
179   fn blank_out_deleted_or_removed_info(mut self) -> Self {
180     self.title = "".into();
181     self.description = None;
182     self.icon = None;
183     self.banner = None;
184     self
185   }
186 }
187
188 impl CommunityModerator {
189   pub fn delete_for_community(
190     conn: &PgConnection,
191     for_community_id: CommunityId,
192   ) -> Result<usize, Error> {
193     use crate::schema::community_moderator::dsl::*;
194     diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
195   }
196
197   pub fn get_person_moderated_communities(
198     conn: &PgConnection,
199     for_person_id: PersonId,
200   ) -> Result<Vec<CommunityId>, Error> {
201     use crate::schema::community_moderator::dsl::*;
202     community_moderator
203       .filter(person_id.eq(for_person_id))
204       .select(community_id)
205       .load::<CommunityId>(conn)
206   }
207 }
208
209 impl Bannable for CommunityPersonBan {
210   type Form = CommunityPersonBanForm;
211   fn ban(
212     conn: &PgConnection,
213     community_person_ban_form: &CommunityPersonBanForm,
214   ) -> Result<Self, Error> {
215     use crate::schema::community_person_ban::dsl::*;
216     insert_into(community_person_ban)
217       .values(community_person_ban_form)
218       .on_conflict((community_id, person_id))
219       .do_update()
220       .set(community_person_ban_form)
221       .get_result::<Self>(conn)
222   }
223
224   fn unban(
225     conn: &PgConnection,
226     community_person_ban_form: &CommunityPersonBanForm,
227   ) -> Result<usize, Error> {
228     use crate::schema::community_person_ban::dsl::*;
229     diesel::delete(
230       community_person_ban
231         .filter(community_id.eq(community_person_ban_form.community_id))
232         .filter(person_id.eq(community_person_ban_form.person_id)),
233     )
234     .execute(conn)
235   }
236 }
237
238 impl Followable for CommunityFollower {
239   type Form = CommunityFollowerForm;
240   fn follow(
241     conn: &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: &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(true))
267     .get_result::<Self>(conn)
268   }
269   fn unfollow(
270     conn: &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(conn: &PgConnection, community_id_: CommunityId) -> Result<bool, Error> {
284     use crate::schema::community_follower::dsl::*;
285     diesel::select(exists(
286       community_follower.filter(community_id.eq(community_id_)),
287     ))
288     .get_result(conn)
289   }
290 }
291
292 impl ApubActor for Community {
293   fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Option<Self>, Error> {
294     use crate::schema::community::dsl::*;
295     Ok(
296       community
297         .filter(actor_id.eq(object_id))
298         .first::<Community>(conn)
299         .ok()
300         .map(Into::into),
301     )
302   }
303
304   fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Community, Error> {
305     use crate::schema::community::dsl::*;
306     community
307       .filter(local.eq(true))
308       .filter(lower(name).eq(lower(community_name)))
309       .first::<Self>(conn)
310   }
311
312   fn read_from_name_and_domain(
313     conn: &PgConnection,
314     community_name: &str,
315     protocol_domain: &str,
316   ) -> Result<Community, Error> {
317     use crate::schema::community::dsl::*;
318     community
319       .filter(lower(name).eq(lower(community_name)))
320       .filter(actor_id.like(format!("{}%", protocol_domain)))
321       .first::<Self>(conn)
322   }
323 }
324
325 #[cfg(test)]
326 mod tests {
327   use crate::{
328     establish_unpooled_connection,
329     source::{community::*, person::*},
330     traits::{Bannable, Crud, Followable, Joinable},
331   };
332   use serial_test::serial;
333
334   #[test]
335   #[serial]
336   fn test_crud() {
337     let conn = establish_unpooled_connection();
338
339     let new_person = PersonForm {
340       name: "bobbee".into(),
341       ..PersonForm::default()
342     };
343
344     let inserted_person = Person::create(&conn, &new_person).unwrap();
345
346     let new_community = CommunityForm {
347       name: "TIL".into(),
348       title: "nada".to_owned(),
349       public_key: "nada".to_owned(),
350       ..CommunityForm::default()
351     };
352
353     let inserted_community = Community::create(&conn, &new_community).unwrap();
354
355     let expected_community = Community {
356       id: inserted_community.id,
357       name: "TIL".into(),
358       title: "nada".to_owned(),
359       description: None,
360       nsfw: false,
361       removed: false,
362       deleted: false,
363       published: inserted_community.published,
364       updated: None,
365       actor_id: inserted_community.actor_id.to_owned(),
366       local: true,
367       private_key: None,
368       public_key: "nada".to_owned(),
369       last_refreshed_at: inserted_community.published,
370       icon: None,
371       banner: None,
372       followers_url: inserted_community.followers_url.to_owned(),
373       inbox_url: inserted_community.inbox_url.to_owned(),
374       shared_inbox_url: None,
375       hidden: false,
376     };
377
378     let community_follower_form = CommunityFollowerForm {
379       community_id: inserted_community.id,
380       person_id: inserted_person.id,
381       pending: false,
382     };
383
384     let inserted_community_follower =
385       CommunityFollower::follow(&conn, &community_follower_form).unwrap();
386
387     let expected_community_follower = CommunityFollower {
388       id: inserted_community_follower.id,
389       community_id: inserted_community.id,
390       person_id: inserted_person.id,
391       pending: Some(false),
392       published: inserted_community_follower.published,
393     };
394
395     let community_moderator_form = CommunityModeratorForm {
396       community_id: inserted_community.id,
397       person_id: inserted_person.id,
398     };
399
400     let inserted_community_moderator =
401       CommunityModerator::join(&conn, &community_moderator_form).unwrap();
402
403     let expected_community_moderator = CommunityModerator {
404       id: inserted_community_moderator.id,
405       community_id: inserted_community.id,
406       person_id: inserted_person.id,
407       published: inserted_community_moderator.published,
408     };
409
410     let community_person_ban_form = CommunityPersonBanForm {
411       community_id: inserted_community.id,
412       person_id: inserted_person.id,
413       expires: None,
414     };
415
416     let inserted_community_person_ban =
417       CommunityPersonBan::ban(&conn, &community_person_ban_form).unwrap();
418
419     let expected_community_person_ban = CommunityPersonBan {
420       id: inserted_community_person_ban.id,
421       community_id: inserted_community.id,
422       person_id: inserted_person.id,
423       published: inserted_community_person_ban.published,
424       expires: None,
425     };
426
427     let read_community = Community::read(&conn, inserted_community.id).unwrap();
428     let updated_community =
429       Community::update(&conn, inserted_community.id, &new_community).unwrap();
430     let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
431     let left_community = CommunityModerator::leave(&conn, &community_moderator_form).unwrap();
432     let unban = CommunityPersonBan::unban(&conn, &community_person_ban_form).unwrap();
433     let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
434     Person::delete(&conn, inserted_person.id).unwrap();
435
436     assert_eq!(expected_community, read_community);
437     assert_eq!(expected_community, inserted_community);
438     assert_eq!(expected_community, updated_community);
439     assert_eq!(expected_community_follower, inserted_community_follower);
440     assert_eq!(expected_community_moderator, inserted_community_moderator);
441     assert_eq!(expected_community_person_ban, inserted_community_person_ban);
442     assert_eq!(1, ignored_community);
443     assert_eq!(1, left_community);
444     assert_eq!(1, unban);
445     // assert_eq!(2, loaded_count);
446     assert_eq!(1, num_deleted);
447   }
448 }