]> Untitled Git - lemmy.git/blob - lemmy_db/src/community.rs
Merge branch 'main' into move_views_to_diesel
[lemmy.git] / lemmy_db / src / 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::{community::Community, schema::community::columns::*, 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       .get_result::<Self>(conn)
379   }
380   fn follow_accepted(conn: &PgConnection, community_id_: i32, user_id_: i32) -> Result<Self, Error>
381   where
382     Self: Sized,
383   {
384     use crate::schema::community_follower::dsl::*;
385     diesel::update(
386       community_follower
387         .filter(community_id.eq(community_id_))
388         .filter(user_id.eq(user_id_)),
389     )
390     .set(pending.eq(true))
391     .get_result::<Self>(conn)
392   }
393   fn unfollow(
394     conn: &PgConnection,
395     community_follower_form: &CommunityFollowerForm,
396   ) -> Result<usize, Error> {
397     use crate::schema::community_follower::dsl::*;
398     diesel::delete(
399       community_follower
400         .filter(community_id.eq(&community_follower_form.community_id))
401         .filter(user_id.eq(&community_follower_form.user_id)),
402     )
403     .execute(conn)
404   }
405   // TODO: this function name only makes sense if you call it with a remote community. for a local
406   //       community, it will also return true if only remote followers exist
407   fn has_local_followers(conn: &PgConnection, community_id_: i32) -> Result<bool, Error> {
408     use crate::schema::community_follower::dsl::*;
409     diesel::select(exists(
410       community_follower.filter(community_id.eq(community_id_)),
411     ))
412     .get_result(conn)
413   }
414 }
415
416 #[cfg(test)]
417 mod tests {
418   use crate::{community::*, tests::establish_unpooled_connection, user::*, ListingType, SortType};
419
420   #[test]
421   fn test_crud() {
422     let conn = establish_unpooled_connection();
423
424     let new_user = UserForm {
425       name: "bobbee".into(),
426       preferred_username: None,
427       password_encrypted: "nope".into(),
428       email: None,
429       matrix_user_id: None,
430       avatar: None,
431       banner: None,
432       admin: false,
433       banned: Some(false),
434       published: None,
435       updated: None,
436       show_nsfw: false,
437       theme: "browser".into(),
438       default_sort_type: SortType::Hot as i16,
439       default_listing_type: ListingType::Subscribed as i16,
440       lang: "browser".into(),
441       show_avatars: true,
442       send_notifications_to_email: false,
443       actor_id: None,
444       bio: None,
445       local: true,
446       private_key: None,
447       public_key: None,
448       last_refreshed_at: None,
449     };
450
451     let inserted_user = User_::create(&conn, &new_user).unwrap();
452
453     let new_community = CommunityForm {
454       name: "TIL".into(),
455       creator_id: inserted_user.id,
456       title: "nada".to_owned(),
457       description: None,
458       category_id: 1,
459       nsfw: false,
460       removed: None,
461       deleted: None,
462       updated: None,
463       actor_id: None,
464       local: true,
465       private_key: None,
466       public_key: None,
467       last_refreshed_at: None,
468       published: None,
469       icon: None,
470       banner: None,
471     };
472
473     let inserted_community = Community::create(&conn, &new_community).unwrap();
474
475     let expected_community = Community {
476       id: inserted_community.id,
477       creator_id: inserted_user.id,
478       name: "TIL".into(),
479       title: "nada".to_owned(),
480       description: None,
481       category_id: 1,
482       nsfw: false,
483       removed: false,
484       deleted: false,
485       published: inserted_community.published,
486       updated: None,
487       actor_id: inserted_community.actor_id.to_owned(),
488       local: true,
489       private_key: None,
490       public_key: None,
491       last_refreshed_at: inserted_community.published,
492       icon: None,
493       banner: None,
494     };
495
496     let community_follower_form = CommunityFollowerForm {
497       community_id: inserted_community.id,
498       user_id: inserted_user.id,
499       pending: false,
500     };
501
502     let inserted_community_follower =
503       CommunityFollower::follow(&conn, &community_follower_form).unwrap();
504
505     let expected_community_follower = CommunityFollower {
506       id: inserted_community_follower.id,
507       community_id: inserted_community.id,
508       user_id: inserted_user.id,
509       pending: Some(false),
510       published: inserted_community_follower.published,
511     };
512
513     let community_user_form = CommunityModeratorForm {
514       community_id: inserted_community.id,
515       user_id: inserted_user.id,
516     };
517
518     let inserted_community_user = CommunityModerator::join(&conn, &community_user_form).unwrap();
519
520     let expected_community_user = CommunityModerator {
521       id: inserted_community_user.id,
522       community_id: inserted_community.id,
523       user_id: inserted_user.id,
524       published: inserted_community_user.published,
525     };
526
527     let community_user_ban_form = CommunityUserBanForm {
528       community_id: inserted_community.id,
529       user_id: inserted_user.id,
530     };
531
532     let inserted_community_user_ban =
533       CommunityUserBan::ban(&conn, &community_user_ban_form).unwrap();
534
535     let expected_community_user_ban = CommunityUserBan {
536       id: inserted_community_user_ban.id,
537       community_id: inserted_community.id,
538       user_id: inserted_user.id,
539       published: inserted_community_user_ban.published,
540     };
541
542     let read_community = Community::read(&conn, inserted_community.id).unwrap();
543     let updated_community =
544       Community::update(&conn, inserted_community.id, &new_community).unwrap();
545     let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
546     let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
547     let unban = CommunityUserBan::unban(&conn, &community_user_ban_form).unwrap();
548     let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
549     User_::delete(&conn, inserted_user.id).unwrap();
550
551     assert_eq!(expected_community, read_community);
552     assert_eq!(expected_community, inserted_community);
553     assert_eq!(expected_community, updated_community);
554     assert_eq!(expected_community_follower, inserted_community_follower);
555     assert_eq!(expected_community_user, inserted_community_user);
556     assert_eq!(expected_community_user_ban, inserted_community_user_ban);
557     assert_eq!(1, ignored_community);
558     assert_eq!(1, left_community);
559     assert_eq!(1, unban);
560     // assert_eq!(2, loaded_count);
561     assert_eq!(1, num_deleted);
562   }
563 }