]> Untitled Git - lemmy.git/blob - lemmy_db/src/source/community.rs
Merge branch 'main' into move_views_to_diesel
[lemmy.git] / lemmy_db / src / source / community.rs
1 use crate::{
2   naive_now,
3   schema::{community, community_follower, community_moderator, community_user_ban},
4   ApubObject,
5   Bannable,
6   Crud,
7   Followable,
8   Joinable,
9 };
10 use diesel::{dsl::*, result::Error, *};
11 use serde::Serialize;
12
13 #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
14 #[table_name = "community"]
15 pub struct Community {
16   pub id: i32,
17   pub name: String,
18   pub title: String,
19   pub description: Option<String>,
20   pub category_id: i32,
21   pub creator_id: i32,
22   pub removed: bool,
23   pub published: chrono::NaiveDateTime,
24   pub updated: Option<chrono::NaiveDateTime>,
25   pub deleted: bool,
26   pub nsfw: bool,
27   pub actor_id: String,
28   pub local: bool,
29   pub private_key: Option<String>,
30   pub public_key: Option<String>,
31   pub last_refreshed_at: chrono::NaiveDateTime,
32   pub icon: Option<String>,
33   pub banner: Option<String>,
34 }
35
36 /// A safe representation of community, without the sensitive info
37 #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
38 #[table_name = "community"]
39 pub struct CommunitySafe {
40   pub id: i32,
41   pub name: String,
42   pub title: String,
43   pub description: Option<String>,
44   pub category_id: i32,
45   pub creator_id: i32,
46   pub removed: bool,
47   pub published: chrono::NaiveDateTime,
48   pub updated: Option<chrono::NaiveDateTime>,
49   pub deleted: bool,
50   pub nsfw: bool,
51   pub actor_id: String,
52   pub local: bool,
53   pub icon: Option<String>,
54   pub banner: Option<String>,
55 }
56
57 mod safe_type {
58   use crate::{schema::community::columns::*, source::community::Community, ToSafe};
59   type Columns = (
60     id,
61     name,
62     title,
63     description,
64     category_id,
65     creator_id,
66     removed,
67     published,
68     updated,
69     deleted,
70     nsfw,
71     actor_id,
72     local,
73     icon,
74     banner,
75   );
76
77   impl ToSafe for Community {
78     type SafeColumns = Columns;
79     fn safe_columns_tuple() -> Self::SafeColumns {
80       (
81         id,
82         name,
83         title,
84         description,
85         category_id,
86         creator_id,
87         removed,
88         published,
89         updated,
90         deleted,
91         nsfw,
92         actor_id,
93         local,
94         icon,
95         banner,
96       )
97     }
98   }
99 }
100
101 #[derive(Insertable, AsChangeset, Debug)]
102 #[table_name = "community"]
103 pub struct CommunityForm {
104   pub name: String,
105   pub title: String,
106   pub description: Option<String>,
107   pub category_id: i32,
108   pub creator_id: i32,
109   pub removed: Option<bool>,
110   pub published: Option<chrono::NaiveDateTime>,
111   pub updated: Option<chrono::NaiveDateTime>,
112   pub deleted: Option<bool>,
113   pub nsfw: bool,
114   pub actor_id: Option<String>,
115   pub local: bool,
116   pub private_key: Option<String>,
117   pub public_key: Option<String>,
118   pub last_refreshed_at: Option<chrono::NaiveDateTime>,
119   pub icon: Option<Option<String>>,
120   pub banner: Option<Option<String>>,
121 }
122
123 impl Crud<CommunityForm> for Community {
124   fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
125     use crate::schema::community::dsl::*;
126     community.find(community_id).first::<Self>(conn)
127   }
128
129   fn delete(conn: &PgConnection, community_id: i32) -> Result<usize, Error> {
130     use crate::schema::community::dsl::*;
131     diesel::delete(community.find(community_id)).execute(conn)
132   }
133
134   fn create(conn: &PgConnection, new_community: &CommunityForm) -> Result<Self, Error> {
135     use crate::schema::community::dsl::*;
136     insert_into(community)
137       .values(new_community)
138       .get_result::<Self>(conn)
139   }
140
141   fn update(
142     conn: &PgConnection,
143     community_id: i32,
144     new_community: &CommunityForm,
145   ) -> Result<Self, Error> {
146     use crate::schema::community::dsl::*;
147     diesel::update(community.find(community_id))
148       .set(new_community)
149       .get_result::<Self>(conn)
150   }
151 }
152
153 impl ApubObject<CommunityForm> for Community {
154   fn read_from_apub_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
155     use crate::schema::community::dsl::*;
156     community
157       .filter(actor_id.eq(for_actor_id))
158       .first::<Self>(conn)
159   }
160
161   fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
162     use crate::schema::community::dsl::*;
163     insert_into(community)
164       .values(community_form)
165       .on_conflict(actor_id)
166       .do_update()
167       .set(community_form)
168       .get_result::<Self>(conn)
169   }
170 }
171
172 impl Community {
173   pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> {
174     use crate::schema::community::dsl::*;
175     community
176       .filter(local.eq(true))
177       .filter(name.eq(community_name))
178       .first::<Self>(conn)
179   }
180
181   pub fn update_deleted(
182     conn: &PgConnection,
183     community_id: i32,
184     new_deleted: bool,
185   ) -> Result<Self, Error> {
186     use crate::schema::community::dsl::*;
187     diesel::update(community.find(community_id))
188       .set((deleted.eq(new_deleted), updated.eq(naive_now())))
189       .get_result::<Self>(conn)
190   }
191
192   pub fn update_removed(
193     conn: &PgConnection,
194     community_id: i32,
195     new_removed: bool,
196   ) -> Result<Self, Error> {
197     use crate::schema::community::dsl::*;
198     diesel::update(community.find(community_id))
199       .set((removed.eq(new_removed), updated.eq(naive_now())))
200       .get_result::<Self>(conn)
201   }
202
203   pub fn update_removed_for_creator(
204     conn: &PgConnection,
205     for_creator_id: i32,
206     new_removed: bool,
207   ) -> Result<Vec<Self>, Error> {
208     use crate::schema::community::dsl::*;
209     diesel::update(community.filter(creator_id.eq(for_creator_id)))
210       .set((removed.eq(new_removed), updated.eq(naive_now())))
211       .get_results::<Self>(conn)
212   }
213
214   pub fn update_creator(
215     conn: &PgConnection,
216     community_id: i32,
217     new_creator_id: i32,
218   ) -> Result<Self, Error> {
219     use crate::schema::community::dsl::*;
220     diesel::update(community.find(community_id))
221       .set((creator_id.eq(new_creator_id), updated.eq(naive_now())))
222       .get_result::<Self>(conn)
223   }
224
225   fn community_mods_and_admins(conn: &PgConnection, community_id: i32) -> Result<Vec<i32>, Error> {
226     use crate::views::{community_moderator_view::CommunityModeratorView, user_view::UserViewSafe};
227     let mut mods_and_admins: Vec<i32> = Vec::new();
228     mods_and_admins.append(
229       &mut CommunityModeratorView::for_community(conn, community_id)
230         .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?,
231     );
232     mods_and_admins
233       .append(&mut UserViewSafe::admins(conn).map(|v| v.into_iter().map(|a| a.user.id).collect())?);
234     Ok(mods_and_admins)
235   }
236
237   pub fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<String>, Error> {
238     use crate::schema::community::dsl::*;
239     community.select(actor_id).distinct().load::<String>(conn)
240   }
241
242   pub fn is_mod_or_admin(conn: &PgConnection, user_id: i32, community_id: i32) -> bool {
243     Self::community_mods_and_admins(conn, community_id)
244       .unwrap_or_default()
245       .contains(&user_id)
246   }
247 }
248
249 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
250 #[belongs_to(Community)]
251 #[table_name = "community_moderator"]
252 pub struct CommunityModerator {
253   pub id: i32,
254   pub community_id: i32,
255   pub user_id: i32,
256   pub published: chrono::NaiveDateTime,
257 }
258
259 #[derive(Insertable, AsChangeset, Clone)]
260 #[table_name = "community_moderator"]
261 pub struct CommunityModeratorForm {
262   pub community_id: i32,
263   pub user_id: i32,
264 }
265
266 impl Joinable<CommunityModeratorForm> for CommunityModerator {
267   fn join(
268     conn: &PgConnection,
269     community_user_form: &CommunityModeratorForm,
270   ) -> Result<Self, Error> {
271     use crate::schema::community_moderator::dsl::*;
272     insert_into(community_moderator)
273       .values(community_user_form)
274       .get_result::<Self>(conn)
275   }
276
277   fn leave(
278     conn: &PgConnection,
279     community_user_form: &CommunityModeratorForm,
280   ) -> Result<usize, Error> {
281     use crate::schema::community_moderator::dsl::*;
282     diesel::delete(
283       community_moderator
284         .filter(community_id.eq(community_user_form.community_id))
285         .filter(user_id.eq(community_user_form.user_id)),
286     )
287     .execute(conn)
288   }
289 }
290
291 impl CommunityModerator {
292   pub fn delete_for_community(conn: &PgConnection, for_community_id: i32) -> Result<usize, Error> {
293     use crate::schema::community_moderator::dsl::*;
294     diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
295   }
296
297   pub fn get_user_moderated_communities(
298     conn: &PgConnection,
299     for_user_id: i32,
300   ) -> Result<Vec<i32>, Error> {
301     use crate::schema::community_moderator::dsl::*;
302     community_moderator
303       .filter(user_id.eq(for_user_id))
304       .select(community_id)
305       .load::<i32>(conn)
306   }
307 }
308
309 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
310 #[belongs_to(Community)]
311 #[table_name = "community_user_ban"]
312 pub struct CommunityUserBan {
313   pub id: i32,
314   pub community_id: i32,
315   pub user_id: i32,
316   pub published: chrono::NaiveDateTime,
317 }
318
319 #[derive(Insertable, AsChangeset, Clone)]
320 #[table_name = "community_user_ban"]
321 pub struct CommunityUserBanForm {
322   pub community_id: i32,
323   pub user_id: i32,
324 }
325
326 impl Bannable<CommunityUserBanForm> for CommunityUserBan {
327   fn ban(
328     conn: &PgConnection,
329     community_user_ban_form: &CommunityUserBanForm,
330   ) -> Result<Self, Error> {
331     use crate::schema::community_user_ban::dsl::*;
332     insert_into(community_user_ban)
333       .values(community_user_ban_form)
334       .get_result::<Self>(conn)
335   }
336
337   fn unban(
338     conn: &PgConnection,
339     community_user_ban_form: &CommunityUserBanForm,
340   ) -> Result<usize, Error> {
341     use crate::schema::community_user_ban::dsl::*;
342     diesel::delete(
343       community_user_ban
344         .filter(community_id.eq(community_user_ban_form.community_id))
345         .filter(user_id.eq(community_user_ban_form.user_id)),
346     )
347     .execute(conn)
348   }
349 }
350
351 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
352 #[belongs_to(Community)]
353 #[table_name = "community_follower"]
354 pub struct CommunityFollower {
355   pub id: i32,
356   pub community_id: i32,
357   pub user_id: i32,
358   pub published: chrono::NaiveDateTime,
359   pub pending: Option<bool>,
360 }
361
362 #[derive(Insertable, AsChangeset, Clone)]
363 #[table_name = "community_follower"]
364 pub struct CommunityFollowerForm {
365   pub community_id: i32,
366   pub user_id: i32,
367   pub pending: bool,
368 }
369
370 impl Followable<CommunityFollowerForm> for CommunityFollower {
371   fn follow(
372     conn: &PgConnection,
373     community_follower_form: &CommunityFollowerForm,
374   ) -> Result<Self, Error> {
375     use crate::schema::community_follower::dsl::*;
376     insert_into(community_follower)
377       .values(community_follower_form)
378       .on_conflict((community_id, user_id))
379       .do_update()
380       .set(community_follower_form)
381       .get_result::<Self>(conn)
382   }
383   fn follow_accepted(conn: &PgConnection, community_id_: i32, user_id_: i32) -> Result<Self, Error>
384   where
385     Self: Sized,
386   {
387     use crate::schema::community_follower::dsl::*;
388     diesel::update(
389       community_follower
390         .filter(community_id.eq(community_id_))
391         .filter(user_id.eq(user_id_)),
392     )
393     .set(pending.eq(true))
394     .get_result::<Self>(conn)
395   }
396   fn unfollow(
397     conn: &PgConnection,
398     community_follower_form: &CommunityFollowerForm,
399   ) -> Result<usize, Error> {
400     use crate::schema::community_follower::dsl::*;
401     diesel::delete(
402       community_follower
403         .filter(community_id.eq(&community_follower_form.community_id))
404         .filter(user_id.eq(&community_follower_form.user_id)),
405     )
406     .execute(conn)
407   }
408   // TODO: this function name only makes sense if you call it with a remote community. for a local
409   //       community, it will also return true if only remote followers exist
410   fn has_local_followers(conn: &PgConnection, community_id_: i32) -> Result<bool, Error> {
411     use crate::schema::community_follower::dsl::*;
412     diesel::select(exists(
413       community_follower.filter(community_id.eq(community_id_)),
414     ))
415     .get_result(conn)
416   }
417 }
418
419 #[cfg(test)]
420 mod tests {
421   use crate::{
422     source::{community::*, user::*},
423     tests::establish_unpooled_connection,
424     ListingType,
425     SortType,
426   };
427
428   #[test]
429   fn test_crud() {
430     let conn = establish_unpooled_connection();
431
432     let new_user = UserForm {
433       name: "bobbee".into(),
434       preferred_username: None,
435       password_encrypted: "nope".into(),
436       email: None,
437       matrix_user_id: None,
438       avatar: None,
439       banner: None,
440       admin: false,
441       banned: Some(false),
442       published: None,
443       updated: None,
444       show_nsfw: false,
445       theme: "browser".into(),
446       default_sort_type: SortType::Hot as i16,
447       default_listing_type: ListingType::Subscribed as i16,
448       lang: "browser".into(),
449       show_avatars: true,
450       send_notifications_to_email: false,
451       actor_id: None,
452       bio: None,
453       local: true,
454       private_key: None,
455       public_key: None,
456       last_refreshed_at: None,
457     };
458
459     let inserted_user = User_::create(&conn, &new_user).unwrap();
460
461     let new_community = CommunityForm {
462       name: "TIL".into(),
463       creator_id: inserted_user.id,
464       title: "nada".to_owned(),
465       description: None,
466       category_id: 1,
467       nsfw: false,
468       removed: None,
469       deleted: None,
470       updated: None,
471       actor_id: None,
472       local: true,
473       private_key: None,
474       public_key: None,
475       last_refreshed_at: None,
476       published: None,
477       icon: None,
478       banner: None,
479     };
480
481     let inserted_community = Community::create(&conn, &new_community).unwrap();
482
483     let expected_community = Community {
484       id: inserted_community.id,
485       creator_id: inserted_user.id,
486       name: "TIL".into(),
487       title: "nada".to_owned(),
488       description: None,
489       category_id: 1,
490       nsfw: false,
491       removed: false,
492       deleted: false,
493       published: inserted_community.published,
494       updated: None,
495       actor_id: inserted_community.actor_id.to_owned(),
496       local: true,
497       private_key: None,
498       public_key: None,
499       last_refreshed_at: inserted_community.published,
500       icon: None,
501       banner: None,
502     };
503
504     let community_follower_form = CommunityFollowerForm {
505       community_id: inserted_community.id,
506       user_id: inserted_user.id,
507       pending: false,
508     };
509
510     let inserted_community_follower =
511       CommunityFollower::follow(&conn, &community_follower_form).unwrap();
512
513     let expected_community_follower = CommunityFollower {
514       id: inserted_community_follower.id,
515       community_id: inserted_community.id,
516       user_id: inserted_user.id,
517       pending: Some(false),
518       published: inserted_community_follower.published,
519     };
520
521     let community_user_form = CommunityModeratorForm {
522       community_id: inserted_community.id,
523       user_id: inserted_user.id,
524     };
525
526     let inserted_community_user = CommunityModerator::join(&conn, &community_user_form).unwrap();
527
528     let expected_community_user = CommunityModerator {
529       id: inserted_community_user.id,
530       community_id: inserted_community.id,
531       user_id: inserted_user.id,
532       published: inserted_community_user.published,
533     };
534
535     let community_user_ban_form = CommunityUserBanForm {
536       community_id: inserted_community.id,
537       user_id: inserted_user.id,
538     };
539
540     let inserted_community_user_ban =
541       CommunityUserBan::ban(&conn, &community_user_ban_form).unwrap();
542
543     let expected_community_user_ban = CommunityUserBan {
544       id: inserted_community_user_ban.id,
545       community_id: inserted_community.id,
546       user_id: inserted_user.id,
547       published: inserted_community_user_ban.published,
548     };
549
550     let read_community = Community::read(&conn, inserted_community.id).unwrap();
551     let updated_community =
552       Community::update(&conn, inserted_community.id, &new_community).unwrap();
553     let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
554     let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
555     let unban = CommunityUserBan::unban(&conn, &community_user_ban_form).unwrap();
556     let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
557     User_::delete(&conn, inserted_user.id).unwrap();
558
559     assert_eq!(expected_community, read_community);
560     assert_eq!(expected_community, inserted_community);
561     assert_eq!(expected_community, updated_community);
562     assert_eq!(expected_community_follower, inserted_community_follower);
563     assert_eq!(expected_community_user, inserted_community_user);
564     assert_eq!(expected_community_user_ban, inserted_community_user_ban);
565     assert_eq!(1, ignored_community);
566     assert_eq!(1, left_community);
567     assert_eq!(1, unban);
568     // assert_eq!(2, loaded_count);
569     assert_eq!(1, num_deleted);
570   }
571 }