]> Untitled Git - lemmy.git/blob - server/src/api/community.rs
Merge branch 'federation_add_fed_columns' of https://yerbamate.dev/dessalines/lemmy...
[lemmy.git] / server / src / api / community.rs
1 use super::*;
2 use crate::apub::puller::{get_all_communities, get_remote_community};
3 use crate::apub::{gen_keypair_str, make_apub_endpoint, EndpointType};
4 use crate::settings::Settings;
5 use diesel::PgConnection;
6 use std::str::FromStr;
7
8 #[derive(Serialize, Deserialize)]
9 pub struct GetCommunity {
10   id: Option<i32>,
11   pub name: Option<String>,
12   auth: Option<String>,
13 }
14
15 #[derive(Serialize, Deserialize)]
16 pub struct GetCommunityResponse {
17   pub community: CommunityView,
18   pub moderators: Vec<CommunityModeratorView>,
19   pub admins: Vec<UserView>,
20   pub online: usize,
21 }
22
23 #[derive(Serialize, Deserialize)]
24 pub struct CreateCommunity {
25   name: String,
26   title: String,
27   description: Option<String>,
28   category_id: i32,
29   nsfw: bool,
30   auth: String,
31 }
32
33 #[derive(Serialize, Deserialize, Clone)]
34 pub struct CommunityResponse {
35   pub community: CommunityView,
36 }
37
38 #[derive(Serialize, Deserialize, Debug)]
39 pub struct ListCommunities {
40   pub sort: String,
41   pub page: Option<i64>,
42   pub limit: Option<i64>,
43   pub auth: Option<String>,
44   pub local_only: Option<bool>,
45 }
46
47 #[derive(Serialize, Deserialize, Debug)]
48 pub struct ListCommunitiesResponse {
49   pub communities: Vec<CommunityView>,
50 }
51
52 #[derive(Serialize, Deserialize, Clone)]
53 pub struct BanFromCommunity {
54   pub community_id: i32,
55   user_id: i32,
56   ban: bool,
57   reason: Option<String>,
58   expires: Option<i64>,
59   auth: String,
60 }
61
62 #[derive(Serialize, Deserialize)]
63 pub struct BanFromCommunityResponse {
64   user: UserView,
65   banned: bool,
66 }
67
68 #[derive(Serialize, Deserialize)]
69 pub struct AddModToCommunity {
70   pub community_id: i32,
71   user_id: i32,
72   added: bool,
73   auth: String,
74 }
75
76 #[derive(Serialize, Deserialize)]
77 pub struct AddModToCommunityResponse {
78   moderators: Vec<CommunityModeratorView>,
79 }
80
81 #[derive(Serialize, Deserialize)]
82 pub struct EditCommunity {
83   pub edit_id: i32,
84   name: String,
85   title: String,
86   description: Option<String>,
87   category_id: i32,
88   removed: Option<bool>,
89   deleted: Option<bool>,
90   nsfw: bool,
91   reason: Option<String>,
92   expires: Option<i64>,
93   auth: String,
94 }
95
96 #[derive(Serialize, Deserialize)]
97 pub struct FollowCommunity {
98   community_id: i32,
99   follow: bool,
100   auth: String,
101 }
102
103 #[derive(Serialize, Deserialize)]
104 pub struct GetFollowedCommunities {
105   auth: String,
106 }
107
108 #[derive(Serialize, Deserialize)]
109 pub struct GetFollowedCommunitiesResponse {
110   communities: Vec<CommunityFollowerView>,
111 }
112
113 #[derive(Serialize, Deserialize)]
114 pub struct TransferCommunity {
115   community_id: i32,
116   user_id: i32,
117   auth: String,
118 }
119
120 impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
121   fn perform(&self, conn: &PgConnection) -> Result<GetCommunityResponse, Error> {
122     let data: &GetCommunity = &self.data;
123
124     if data.name.is_some()
125       && Settings::get().federation.enabled
126       && data.name.as_ref().unwrap().contains('@')
127     {
128       return get_remote_community(data.name.as_ref().unwrap());
129     }
130
131     let user_id: Option<i32> = match &data.auth {
132       Some(auth) => match Claims::decode(&auth) {
133         Ok(claims) => {
134           let user_id = claims.claims.id;
135           Some(user_id)
136         }
137         Err(_e) => None,
138       },
139       None => None,
140     };
141
142     let community_id = match data.id {
143       Some(id) => id,
144       None => {
145         match Community::read_from_name(
146           &conn,
147           data.name.to_owned().unwrap_or_else(|| "main".to_string()),
148         ) {
149           Ok(community) => community.id,
150           Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
151         }
152       }
153     };
154
155     let community_view = match CommunityView::read(&conn, community_id, user_id) {
156       Ok(community) => community,
157       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
158     };
159
160     let moderators = match CommunityModeratorView::for_community(&conn, community_id) {
161       Ok(moderators) => moderators,
162       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
163     };
164
165     let site_creator_id = Site::read(&conn, 1)?.creator_id;
166     let mut admins = UserView::admins(&conn)?;
167     let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
168     let creator_user = admins.remove(creator_index);
169     admins.insert(0, creator_user);
170
171     // Return the jwt
172     Ok(GetCommunityResponse {
173       community: community_view,
174       moderators,
175       admins,
176       online: 0,
177     })
178   }
179 }
180
181 impl Perform<CommunityResponse> for Oper<CreateCommunity> {
182   fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
183     let data: &CreateCommunity = &self.data;
184
185     let claims = match Claims::decode(&data.auth) {
186       Ok(claims) => claims.claims,
187       Err(_e) => return Err(APIError::err("not_logged_in").into()),
188     };
189
190     if let Err(slurs) = slur_check(&data.name) {
191       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
192     }
193
194     if let Err(slurs) = slur_check(&data.title) {
195       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
196     }
197
198     if let Some(description) = &data.description {
199       if let Err(slurs) = slur_check(description) {
200         return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
201       }
202     }
203
204     let user_id = claims.id;
205
206     // Check for a site ban
207     if UserView::read(&conn, user_id)?.banned {
208       return Err(APIError::err("site_ban").into());
209     }
210
211     // When you create a community, make sure the user becomes a moderator and a follower
212     let (community_public_key, community_private_key) = gen_keypair_str();
213
214     let community_form = CommunityForm {
215       name: data.name.to_owned(),
216       title: data.title.to_owned(),
217       description: data.description.to_owned(),
218       category_id: data.category_id,
219       creator_id: user_id,
220       removed: None,
221       deleted: None,
222       nsfw: data.nsfw,
223       updated: None,
224       actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
225       local: true,
226       private_key: Some(community_private_key),
227       public_key: Some(community_public_key),
228       last_refreshed_at: None,
229     };
230
231     let inserted_community = match Community::create(&conn, &community_form) {
232       Ok(community) => community,
233       Err(_e) => return Err(APIError::err("community_already_exists").into()),
234     };
235
236     let community_moderator_form = CommunityModeratorForm {
237       community_id: inserted_community.id,
238       user_id,
239     };
240
241     let _inserted_community_moderator =
242       match CommunityModerator::join(&conn, &community_moderator_form) {
243         Ok(user) => user,
244         Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
245       };
246
247     let community_follower_form = CommunityFollowerForm {
248       community_id: inserted_community.id,
249       user_id,
250     };
251
252     let _inserted_community_follower =
253       match CommunityFollower::follow(&conn, &community_follower_form) {
254         Ok(user) => user,
255         Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
256       };
257
258     let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?;
259
260     Ok(CommunityResponse {
261       community: community_view,
262     })
263   }
264 }
265
266 impl Perform<CommunityResponse> for Oper<EditCommunity> {
267   fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
268     let data: &EditCommunity = &self.data;
269
270     if let Err(slurs) = slur_check(&data.name) {
271       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
272     }
273
274     if let Err(slurs) = slur_check(&data.title) {
275       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
276     }
277
278     if let Some(description) = &data.description {
279       if let Err(slurs) = slur_check(description) {
280         return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
281       }
282     }
283
284     let claims = match Claims::decode(&data.auth) {
285       Ok(claims) => claims.claims,
286       Err(_e) => return Err(APIError::err("not_logged_in").into()),
287     };
288
289     let user_id = claims.id;
290
291     // Check for a site ban
292     if UserView::read(&conn, user_id)?.banned {
293       return Err(APIError::err("site_ban").into());
294     }
295
296     // Verify its a mod
297     let mut editors: Vec<i32> = Vec::new();
298     editors.append(
299       &mut CommunityModeratorView::for_community(&conn, data.edit_id)?
300         .into_iter()
301         .map(|m| m.user_id)
302         .collect(),
303     );
304     editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
305     if !editors.contains(&user_id) {
306       return Err(APIError::err("no_community_edit_allowed").into());
307     }
308
309     let read_community = Community::read(&conn, data.edit_id)?;
310
311     let community_form = CommunityForm {
312       name: data.name.to_owned(),
313       title: data.title.to_owned(),
314       description: data.description.to_owned(),
315       category_id: data.category_id.to_owned(),
316       creator_id: user_id,
317       removed: data.removed.to_owned(),
318       deleted: data.deleted.to_owned(),
319       nsfw: data.nsfw,
320       updated: Some(naive_now()),
321       actor_id: read_community.actor_id,
322       local: read_community.local,
323       private_key: read_community.private_key,
324       public_key: read_community.public_key,
325       last_refreshed_at: None,
326     };
327
328     let _updated_community = match Community::update(&conn, data.edit_id, &community_form) {
329       Ok(community) => community,
330       Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
331     };
332
333     // Mod tables
334     if let Some(removed) = data.removed.to_owned() {
335       let expires = match data.expires {
336         Some(time) => Some(naive_from_unix(time)),
337         None => None,
338       };
339       let form = ModRemoveCommunityForm {
340         mod_user_id: user_id,
341         community_id: data.edit_id,
342         removed: Some(removed),
343         reason: data.reason.to_owned(),
344         expires,
345       };
346       ModRemoveCommunity::create(&conn, &form)?;
347     }
348
349     let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
350
351     Ok(CommunityResponse {
352       community: community_view,
353     })
354   }
355 }
356
357 impl Perform<ListCommunitiesResponse> for Oper<ListCommunities> {
358   fn perform(&self, conn: &PgConnection) -> Result<ListCommunitiesResponse, Error> {
359     let data: &ListCommunities = &self.data;
360
361     let local_only = data.local_only.unwrap_or(false);
362     if Settings::get().federation.enabled && !local_only {
363       return Ok(ListCommunitiesResponse {
364         communities: get_all_communities()?,
365       });
366     }
367
368     let user_claims: Option<Claims> = match &data.auth {
369       Some(auth) => match Claims::decode(&auth) {
370         Ok(claims) => Some(claims.claims),
371         Err(_e) => None,
372       },
373       None => None,
374     };
375
376     let user_id = match &user_claims {
377       Some(claims) => Some(claims.id),
378       None => None,
379     };
380
381     let show_nsfw = match &user_claims {
382       Some(claims) => claims.show_nsfw,
383       None => false,
384     };
385
386     let sort = SortType::from_str(&data.sort)?;
387
388     let communities = CommunityQueryBuilder::create(&conn)
389       .sort(&sort)
390       .for_user(user_id)
391       .show_nsfw(show_nsfw)
392       .page(data.page)
393       .limit(data.limit)
394       .list()?;
395
396     // Return the jwt
397     Ok(ListCommunitiesResponse { communities })
398   }
399 }
400
401 impl Perform<CommunityResponse> for Oper<FollowCommunity> {
402   fn perform(&self, conn: &PgConnection) -> Result<CommunityResponse, Error> {
403     let data: &FollowCommunity = &self.data;
404
405     let claims = match Claims::decode(&data.auth) {
406       Ok(claims) => claims.claims,
407       Err(_e) => return Err(APIError::err("not_logged_in").into()),
408     };
409
410     let user_id = claims.id;
411
412     let community_follower_form = CommunityFollowerForm {
413       community_id: data.community_id,
414       user_id,
415     };
416
417     if data.follow {
418       match CommunityFollower::follow(&conn, &community_follower_form) {
419         Ok(user) => user,
420         Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
421       };
422     } else {
423       match CommunityFollower::ignore(&conn, &community_follower_form) {
424         Ok(user) => user,
425         Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
426       };
427     }
428
429     let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
430
431     Ok(CommunityResponse {
432       community: community_view,
433     })
434   }
435 }
436
437 impl Perform<GetFollowedCommunitiesResponse> for Oper<GetFollowedCommunities> {
438   fn perform(&self, conn: &PgConnection) -> Result<GetFollowedCommunitiesResponse, Error> {
439     let data: &GetFollowedCommunities = &self.data;
440
441     let claims = match Claims::decode(&data.auth) {
442       Ok(claims) => claims.claims,
443       Err(_e) => return Err(APIError::err("not_logged_in").into()),
444     };
445
446     let user_id = claims.id;
447
448     let communities: Vec<CommunityFollowerView> =
449       match CommunityFollowerView::for_user(&conn, user_id) {
450         Ok(communities) => communities,
451         Err(_e) => return Err(APIError::err("system_err_login").into()),
452       };
453
454     // Return the jwt
455     Ok(GetFollowedCommunitiesResponse { communities })
456   }
457 }
458
459 impl Perform<BanFromCommunityResponse> for Oper<BanFromCommunity> {
460   fn perform(&self, conn: &PgConnection) -> Result<BanFromCommunityResponse, Error> {
461     let data: &BanFromCommunity = &self.data;
462
463     let claims = match Claims::decode(&data.auth) {
464       Ok(claims) => claims.claims,
465       Err(_e) => return Err(APIError::err("not_logged_in").into()),
466     };
467
468     let user_id = claims.id;
469
470     let community_user_ban_form = CommunityUserBanForm {
471       community_id: data.community_id,
472       user_id: data.user_id,
473     };
474
475     if data.ban {
476       match CommunityUserBan::ban(&conn, &community_user_ban_form) {
477         Ok(user) => user,
478         Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
479       };
480     } else {
481       match CommunityUserBan::unban(&conn, &community_user_ban_form) {
482         Ok(user) => user,
483         Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
484       };
485     }
486
487     // Mod tables
488     let expires = match data.expires {
489       Some(time) => Some(naive_from_unix(time)),
490       None => None,
491     };
492
493     let form = ModBanFromCommunityForm {
494       mod_user_id: user_id,
495       other_user_id: data.user_id,
496       community_id: data.community_id,
497       reason: data.reason.to_owned(),
498       banned: Some(data.ban),
499       expires,
500     };
501     ModBanFromCommunity::create(&conn, &form)?;
502
503     let user_view = UserView::read(&conn, data.user_id)?;
504
505     Ok(BanFromCommunityResponse {
506       user: user_view,
507       banned: data.ban,
508     })
509   }
510 }
511
512 impl Perform<AddModToCommunityResponse> for Oper<AddModToCommunity> {
513   fn perform(&self, conn: &PgConnection) -> Result<AddModToCommunityResponse, Error> {
514     let data: &AddModToCommunity = &self.data;
515
516     let claims = match Claims::decode(&data.auth) {
517       Ok(claims) => claims.claims,
518       Err(_e) => return Err(APIError::err("not_logged_in").into()),
519     };
520
521     let user_id = claims.id;
522
523     let community_moderator_form = CommunityModeratorForm {
524       community_id: data.community_id,
525       user_id: data.user_id,
526     };
527
528     if data.added {
529       match CommunityModerator::join(&conn, &community_moderator_form) {
530         Ok(user) => user,
531         Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
532       };
533     } else {
534       match CommunityModerator::leave(&conn, &community_moderator_form) {
535         Ok(user) => user,
536         Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
537       };
538     }
539
540     // Mod tables
541     let form = ModAddCommunityForm {
542       mod_user_id: user_id,
543       other_user_id: data.user_id,
544       community_id: data.community_id,
545       removed: Some(!data.added),
546     };
547     ModAddCommunity::create(&conn, &form)?;
548
549     let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?;
550
551     Ok(AddModToCommunityResponse { moderators })
552   }
553 }
554
555 impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
556   fn perform(&self, conn: &PgConnection) -> Result<GetCommunityResponse, Error> {
557     let data: &TransferCommunity = &self.data;
558
559     let claims = match Claims::decode(&data.auth) {
560       Ok(claims) => claims.claims,
561       Err(_e) => return Err(APIError::err("not_logged_in").into()),
562     };
563
564     let user_id = claims.id;
565
566     let read_community = Community::read(&conn, data.community_id)?;
567
568     let site_creator_id = Site::read(&conn, 1)?.creator_id;
569     let mut admins = UserView::admins(&conn)?;
570     let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
571     let creator_user = admins.remove(creator_index);
572     admins.insert(0, creator_user);
573
574     // Make sure user is the creator, or an admin
575     if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) {
576       return Err(APIError::err("not_an_admin").into());
577     }
578
579     let community_form = CommunityForm {
580       name: read_community.name,
581       title: read_community.title,
582       description: read_community.description,
583       category_id: read_community.category_id,
584       creator_id: data.user_id,
585       removed: None,
586       deleted: None,
587       nsfw: read_community.nsfw,
588       updated: Some(naive_now()),
589       actor_id: read_community.actor_id,
590       local: read_community.local,
591       private_key: read_community.private_key,
592       public_key: read_community.public_key,
593       last_refreshed_at: None,
594     };
595
596     let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
597       Ok(community) => community,
598       Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
599     };
600
601     // You also have to re-do the community_moderator table, reordering it.
602     let mut community_mods = CommunityModeratorView::for_community(&conn, data.community_id)?;
603     let creator_index = community_mods
604       .iter()
605       .position(|r| r.user_id == data.user_id)
606       .unwrap();
607     let creator_user = community_mods.remove(creator_index);
608     community_mods.insert(0, creator_user);
609
610     CommunityModerator::delete_for_community(&conn, data.community_id)?;
611
612     for cmod in &community_mods {
613       let community_moderator_form = CommunityModeratorForm {
614         community_id: cmod.community_id,
615         user_id: cmod.user_id,
616       };
617
618       let _inserted_community_moderator =
619         match CommunityModerator::join(&conn, &community_moderator_form) {
620           Ok(user) => user,
621           Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
622         };
623     }
624
625     // Mod tables
626     let form = ModAddCommunityForm {
627       mod_user_id: user_id,
628       other_user_id: data.user_id,
629       community_id: data.community_id,
630       removed: Some(false),
631     };
632     ModAddCommunity::create(&conn, &form)?;
633
634     let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) {
635       Ok(community) => community,
636       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
637     };
638
639     let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) {
640       Ok(moderators) => moderators,
641       Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
642     };
643
644     // Return the jwt
645     Ok(GetCommunityResponse {
646       community: community_view,
647       moderators,
648       admins,
649       online: 0,
650     })
651   }
652 }