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