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