]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/community.rs
Implement instance actor (#1798)
[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 use url::Url;
28
29 mod safe_type {
30   use crate::{schema::community::*, source::community::Community, traits::ToSafe};
31
32   type Columns = (
33     id,
34     name,
35     title,
36     description,
37     removed,
38     published,
39     updated,
40     deleted,
41     nsfw,
42     actor_id,
43     local,
44     icon,
45     banner,
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       )
66     }
67   }
68 }
69
70 impl Crud for Community {
71   type Form = CommunityForm;
72   type IdType = CommunityId;
73   fn read(conn: &PgConnection, community_id: CommunityId) -> Result<Self, Error> {
74     use crate::schema::community::dsl::*;
75     community.find(community_id).first::<Self>(conn)
76   }
77
78   fn delete(conn: &PgConnection, community_id: CommunityId) -> Result<usize, Error> {
79     use crate::schema::community::dsl::*;
80     diesel::delete(community.find(community_id)).execute(conn)
81   }
82
83   fn create(conn: &PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
84     use crate::schema::community::dsl::*;
85     insert_into(community)
86       .values(new_community)
87       .get_result::<Self>(conn)
88   }
89
90   fn update(
91     conn: &PgConnection,
92     community_id: CommunityId,
93     new_community: &CommunityForm,
94   ) -> Result<Self, Error> {
95     use crate::schema::community::dsl::*;
96     diesel::update(community.find(community_id))
97       .set(new_community)
98       .get_result::<Self>(conn)
99   }
100 }
101
102 impl Community {
103   pub fn update_deleted(
104     conn: &PgConnection,
105     community_id: CommunityId,
106     new_deleted: bool,
107   ) -> Result<Community, Error> {
108     use crate::schema::community::dsl::*;
109     diesel::update(community.find(community_id))
110       .set((deleted.eq(new_deleted), updated.eq(naive_now())))
111       .get_result::<Self>(conn)
112   }
113
114   pub fn update_removed(
115     conn: &PgConnection,
116     community_id: CommunityId,
117     new_removed: bool,
118   ) -> Result<Community, Error> {
119     use crate::schema::community::dsl::*;
120     diesel::update(community.find(community_id))
121       .set((removed.eq(new_removed), updated.eq(naive_now())))
122       .get_result::<Self>(conn)
123   }
124
125   pub fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<DbUrl>, Error> {
126     use crate::schema::community::dsl::*;
127     community.select(actor_id).distinct().load::<DbUrl>(conn)
128   }
129
130   pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
131     use crate::schema::community::dsl::*;
132     insert_into(community)
133       .values(community_form)
134       .on_conflict(actor_id)
135       .do_update()
136       .set(community_form)
137       .get_result::<Self>(conn)
138   }
139 }
140
141 impl Joinable for CommunityModerator {
142   type Form = CommunityModeratorForm;
143   fn join(
144     conn: &PgConnection,
145     community_moderator_form: &CommunityModeratorForm,
146   ) -> Result<Self, Error> {
147     use crate::schema::community_moderator::dsl::*;
148     insert_into(community_moderator)
149       .values(community_moderator_form)
150       .get_result::<Self>(conn)
151   }
152
153   fn leave(
154     conn: &PgConnection,
155     community_moderator_form: &CommunityModeratorForm,
156   ) -> Result<usize, Error> {
157     use crate::schema::community_moderator::dsl::*;
158     diesel::delete(
159       community_moderator
160         .filter(community_id.eq(community_moderator_form.community_id))
161         .filter(person_id.eq(community_moderator_form.person_id)),
162     )
163     .execute(conn)
164   }
165 }
166
167 impl DeleteableOrRemoveable for CommunitySafe {
168   fn blank_out_deleted_or_removed_info(mut self) -> Self {
169     self.title = "".into();
170     self.description = None;
171     self.icon = None;
172     self.banner = None;
173     self
174   }
175 }
176
177 impl DeleteableOrRemoveable for Community {
178   fn blank_out_deleted_or_removed_info(mut self) -> Self {
179     self.title = "".into();
180     self.description = None;
181     self.icon = None;
182     self.banner = None;
183     self
184   }
185 }
186
187 impl CommunityModerator {
188   pub fn delete_for_community(
189     conn: &PgConnection,
190     for_community_id: CommunityId,
191   ) -> Result<usize, Error> {
192     use crate::schema::community_moderator::dsl::*;
193     diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
194   }
195
196   pub fn get_person_moderated_communities(
197     conn: &PgConnection,
198     for_person_id: PersonId,
199   ) -> Result<Vec<CommunityId>, Error> {
200     use crate::schema::community_moderator::dsl::*;
201     community_moderator
202       .filter(person_id.eq(for_person_id))
203       .select(community_id)
204       .load::<CommunityId>(conn)
205   }
206 }
207
208 impl Bannable for CommunityPersonBan {
209   type Form = CommunityPersonBanForm;
210   fn ban(
211     conn: &PgConnection,
212     community_person_ban_form: &CommunityPersonBanForm,
213   ) -> Result<Self, Error> {
214     use crate::schema::community_person_ban::dsl::*;
215     insert_into(community_person_ban)
216       .values(community_person_ban_form)
217       .on_conflict((community_id, person_id))
218       .do_update()
219       .set(community_person_ban_form)
220       .get_result::<Self>(conn)
221   }
222
223   fn unban(
224     conn: &PgConnection,
225     community_person_ban_form: &CommunityPersonBanForm,
226   ) -> Result<usize, Error> {
227     use crate::schema::community_person_ban::dsl::*;
228     diesel::delete(
229       community_person_ban
230         .filter(community_id.eq(community_person_ban_form.community_id))
231         .filter(person_id.eq(community_person_ban_form.person_id)),
232     )
233     .execute(conn)
234   }
235 }
236
237 impl Followable for CommunityFollower {
238   type Form = CommunityFollowerForm;
239   fn follow(
240     conn: &PgConnection,
241     community_follower_form: &CommunityFollowerForm,
242   ) -> Result<Self, Error> {
243     use crate::schema::community_follower::dsl::*;
244     insert_into(community_follower)
245       .values(community_follower_form)
246       .on_conflict((community_id, person_id))
247       .do_update()
248       .set(community_follower_form)
249       .get_result::<Self>(conn)
250   }
251   fn follow_accepted(
252     conn: &PgConnection,
253     community_id_: CommunityId,
254     person_id_: PersonId,
255   ) -> Result<Self, Error>
256   where
257     Self: Sized,
258   {
259     use crate::schema::community_follower::dsl::*;
260     diesel::update(
261       community_follower
262         .filter(community_id.eq(community_id_))
263         .filter(person_id.eq(person_id_)),
264     )
265     .set(pending.eq(true))
266     .get_result::<Self>(conn)
267   }
268   fn unfollow(
269     conn: &PgConnection,
270     community_follower_form: &CommunityFollowerForm,
271   ) -> Result<usize, Error> {
272     use crate::schema::community_follower::dsl::*;
273     diesel::delete(
274       community_follower
275         .filter(community_id.eq(&community_follower_form.community_id))
276         .filter(person_id.eq(&community_follower_form.person_id)),
277     )
278     .execute(conn)
279   }
280   // TODO: this function name only makes sense if you call it with a remote community. for a local
281   //       community, it will also return true if only remote followers exist
282   fn has_local_followers(conn: &PgConnection, community_id_: CommunityId) -> Result<bool, Error> {
283     use crate::schema::community_follower::dsl::*;
284     diesel::select(exists(
285       community_follower.filter(community_id.eq(community_id_)),
286     ))
287     .get_result(conn)
288   }
289 }
290
291 impl ApubActor for Community {
292   fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> {
293     use crate::schema::community::dsl::*;
294     let object_id: DbUrl = object_id.into();
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     };
376
377     let community_follower_form = CommunityFollowerForm {
378       community_id: inserted_community.id,
379       person_id: inserted_person.id,
380       pending: false,
381     };
382
383     let inserted_community_follower =
384       CommunityFollower::follow(&conn, &community_follower_form).unwrap();
385
386     let expected_community_follower = CommunityFollower {
387       id: inserted_community_follower.id,
388       community_id: inserted_community.id,
389       person_id: inserted_person.id,
390       pending: Some(false),
391       published: inserted_community_follower.published,
392     };
393
394     let community_moderator_form = CommunityModeratorForm {
395       community_id: inserted_community.id,
396       person_id: inserted_person.id,
397     };
398
399     let inserted_community_moderator =
400       CommunityModerator::join(&conn, &community_moderator_form).unwrap();
401
402     let expected_community_moderator = CommunityModerator {
403       id: inserted_community_moderator.id,
404       community_id: inserted_community.id,
405       person_id: inserted_person.id,
406       published: inserted_community_moderator.published,
407     };
408
409     let community_person_ban_form = CommunityPersonBanForm {
410       community_id: inserted_community.id,
411       person_id: inserted_person.id,
412       expires: None,
413     };
414
415     let inserted_community_person_ban =
416       CommunityPersonBan::ban(&conn, &community_person_ban_form).unwrap();
417
418     let expected_community_person_ban = CommunityPersonBan {
419       id: inserted_community_person_ban.id,
420       community_id: inserted_community.id,
421       person_id: inserted_person.id,
422       published: inserted_community_person_ban.published,
423       expires: None,
424     };
425
426     let read_community = Community::read(&conn, inserted_community.id).unwrap();
427     let updated_community =
428       Community::update(&conn, inserted_community.id, &new_community).unwrap();
429     let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
430     let left_community = CommunityModerator::leave(&conn, &community_moderator_form).unwrap();
431     let unban = CommunityPersonBan::unban(&conn, &community_person_ban_form).unwrap();
432     let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
433     Person::delete(&conn, inserted_person.id).unwrap();
434
435     assert_eq!(expected_community, read_community);
436     assert_eq!(expected_community, inserted_community);
437     assert_eq!(expected_community, updated_community);
438     assert_eq!(expected_community_follower, inserted_community_follower);
439     assert_eq!(expected_community_moderator, inserted_community_moderator);
440     assert_eq!(expected_community_person_ban, inserted_community_person_ban);
441     assert_eq!(1, ignored_community);
442     assert_eq!(1, left_community);
443     assert_eq!(1, unban);
444     // assert_eq!(2, loaded_count);
445     assert_eq!(1, num_deleted);
446   }
447 }