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