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