]> Untitled Git - lemmy.git/blob - crates/api/src/user.rs
Remove categories (fixes #1429)
[lemmy.git] / crates / api / src / user.rs
1 use crate::{
2   captcha_espeak_wav_base64,
3   check_optional_url,
4   collect_moderated_communities,
5   get_user_from_jwt,
6   get_user_from_jwt_opt,
7   is_admin,
8   Perform,
9 };
10 use actix_web::web::Data;
11 use anyhow::Context;
12 use bcrypt::verify;
13 use captcha::{gen, Difficulty};
14 use chrono::Duration;
15 use lemmy_apub::{
16   generate_apub_endpoint,
17   generate_followers_url,
18   generate_inbox_url,
19   generate_shared_inbox_url,
20   ApubObjectType,
21   EndpointType,
22 };
23 use lemmy_db_queries::{
24   diesel_option_overwrite,
25   source::{
26     comment::Comment_,
27     community::Community_,
28     password_reset_request::PasswordResetRequest_,
29     post::Post_,
30     private_message::PrivateMessage_,
31     site::Site_,
32     user::User,
33     user_mention::UserMention_,
34   },
35   Crud,
36   Followable,
37   Joinable,
38   ListingType,
39   SortType,
40 };
41 use lemmy_db_schema::{
42   naive_now,
43   source::{
44     comment::Comment,
45     community::*,
46     moderator::*,
47     password_reset_request::*,
48     post::Post,
49     private_message::*,
50     site::*,
51     user::*,
52     user_mention::*,
53   },
54 };
55 use lemmy_db_views::{
56   comment_report_view::CommentReportView,
57   comment_view::CommentQueryBuilder,
58   post_report_view::PostReportView,
59   post_view::PostQueryBuilder,
60   private_message_view::{PrivateMessageQueryBuilder, PrivateMessageView},
61 };
62 use lemmy_db_views_actor::{
63   community_follower_view::CommunityFollowerView,
64   community_moderator_view::CommunityModeratorView,
65   user_mention_view::{UserMentionQueryBuilder, UserMentionView},
66   user_view::UserViewSafe,
67 };
68 use lemmy_structs::{blocking, send_email_to_user, user::*};
69 use lemmy_utils::{
70   apub::generate_actor_keypair,
71   claims::Claims,
72   email::send_email,
73   location_info,
74   settings::Settings,
75   utils::{
76     check_slurs,
77     generate_random_string,
78     is_valid_preferred_username,
79     is_valid_username,
80     naive_from_unix,
81     remove_slurs,
82   },
83   ApiError,
84   ConnectionId,
85   LemmyError,
86 };
87 use lemmy_websocket::{
88   messages::{CaptchaItem, CheckCaptcha, SendAllMessage, SendUserRoomMessage},
89   LemmyContext,
90   UserOperation,
91 };
92 use std::str::FromStr;
93
94 #[async_trait::async_trait(?Send)]
95 impl Perform for Login {
96   type Response = LoginResponse;
97
98   async fn perform(
99     &self,
100     context: &Data<LemmyContext>,
101     _websocket_id: Option<ConnectionId>,
102   ) -> Result<LoginResponse, LemmyError> {
103     let data: &Login = &self;
104
105     // Fetch that username / email
106     let username_or_email = data.username_or_email.clone();
107     let user = match blocking(context.pool(), move |conn| {
108       User_::find_by_email_or_username(conn, &username_or_email)
109     })
110     .await?
111     {
112       Ok(user) => user,
113       Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
114     };
115
116     // Verify the password
117     let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
118     if !valid {
119       return Err(ApiError::err("password_incorrect").into());
120     }
121
122     // Return the jwt
123     Ok(LoginResponse {
124       jwt: Claims::jwt(user.id, Settings::get().hostname)?,
125     })
126   }
127 }
128
129 #[async_trait::async_trait(?Send)]
130 impl Perform for Register {
131   type Response = LoginResponse;
132
133   async fn perform(
134     &self,
135     context: &Data<LemmyContext>,
136     _websocket_id: Option<ConnectionId>,
137   ) -> Result<LoginResponse, LemmyError> {
138     let data: &Register = &self;
139
140     // Make sure site has open registration
141     if let Ok(site) = blocking(context.pool(), move |conn| Site::read_simple(conn)).await? {
142       if !site.open_registration {
143         return Err(ApiError::err("registration_closed").into());
144       }
145     }
146
147     // Password length check
148     if data.password.len() > 60 {
149       return Err(ApiError::err("invalid_password").into());
150     }
151
152     // Make sure passwords match
153     if data.password != data.password_verify {
154       return Err(ApiError::err("passwords_dont_match").into());
155     }
156
157     // Check if there are admins. False if admins exist
158     let no_admins = blocking(context.pool(), move |conn| {
159       UserViewSafe::admins(conn).map(|a| a.is_empty())
160     })
161     .await??;
162
163     // If its not the admin, check the captcha
164     if !no_admins && Settings::get().captcha.enabled {
165       let check = context
166         .chat_server()
167         .send(CheckCaptcha {
168           uuid: data
169             .captcha_uuid
170             .to_owned()
171             .unwrap_or_else(|| "".to_string()),
172           answer: data
173             .captcha_answer
174             .to_owned()
175             .unwrap_or_else(|| "".to_string()),
176         })
177         .await?;
178       if !check {
179         return Err(ApiError::err("captcha_incorrect").into());
180       }
181     }
182
183     check_slurs(&data.username)?;
184
185     let user_keypair = generate_actor_keypair()?;
186     if !is_valid_username(&data.username) {
187       return Err(ApiError::err("invalid_username").into());
188     }
189     let user_actor_id = generate_apub_endpoint(EndpointType::User, &data.username)?;
190
191     // Register the new user
192     let user_form = UserForm {
193       name: data.username.to_owned(),
194       email: Some(data.email.to_owned()),
195       matrix_user_id: None,
196       avatar: None,
197       banner: None,
198       password_encrypted: data.password.to_owned(),
199       preferred_username: None,
200       published: None,
201       updated: None,
202       admin: no_admins,
203       banned: Some(false),
204       show_nsfw: data.show_nsfw,
205       theme: "browser".into(),
206       default_sort_type: SortType::Active as i16,
207       default_listing_type: ListingType::Subscribed as i16,
208       lang: "browser".into(),
209       show_avatars: true,
210       send_notifications_to_email: false,
211       actor_id: Some(user_actor_id.clone()),
212       bio: None,
213       local: true,
214       private_key: Some(user_keypair.private_key),
215       public_key: Some(user_keypair.public_key),
216       last_refreshed_at: None,
217       inbox_url: Some(generate_inbox_url(&user_actor_id)?),
218       shared_inbox_url: Some(Some(generate_shared_inbox_url(&user_actor_id)?)),
219     };
220
221     // Create the user
222     let inserted_user = match blocking(context.pool(), move |conn| {
223       User_::register(conn, &user_form)
224     })
225     .await?
226     {
227       Ok(user) => user,
228       Err(e) => {
229         let err_type = if e.to_string()
230           == "duplicate key value violates unique constraint \"user__email_key\""
231         {
232           "email_already_exists"
233         } else {
234           "user_already_exists"
235         };
236
237         return Err(ApiError::err(err_type).into());
238       }
239     };
240
241     let main_community_keypair = generate_actor_keypair()?;
242
243     // Create the main community if it doesn't exist
244     let main_community =
245       match blocking(context.pool(), move |conn| Community::read(conn, 2)).await? {
246         Ok(c) => c,
247         Err(_e) => {
248           let default_community_name = "main";
249           let actor_id = generate_apub_endpoint(EndpointType::Community, default_community_name)?;
250           let community_form = CommunityForm {
251             name: default_community_name.to_string(),
252             title: "The Default Community".to_string(),
253             description: Some("The Default Community".to_string()),
254             nsfw: false,
255             creator_id: inserted_user.id,
256             removed: None,
257             deleted: None,
258             updated: None,
259             actor_id: Some(actor_id.to_owned()),
260             local: true,
261             private_key: Some(main_community_keypair.private_key),
262             public_key: Some(main_community_keypair.public_key),
263             last_refreshed_at: None,
264             published: None,
265             icon: None,
266             banner: None,
267             followers_url: Some(generate_followers_url(&actor_id)?),
268             inbox_url: Some(generate_inbox_url(&actor_id)?),
269             shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
270           };
271           blocking(context.pool(), move |conn| {
272             Community::create(conn, &community_form)
273           })
274           .await??
275         }
276       };
277
278     // Sign them up for main community no matter what
279     let community_follower_form = CommunityFollowerForm {
280       community_id: main_community.id,
281       user_id: inserted_user.id,
282       pending: false,
283     };
284
285     let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
286     if blocking(context.pool(), follow).await?.is_err() {
287       return Err(ApiError::err("community_follower_already_exists").into());
288     };
289
290     // If its an admin, add them as a mod and follower to main
291     if no_admins {
292       let community_moderator_form = CommunityModeratorForm {
293         community_id: main_community.id,
294         user_id: inserted_user.id,
295       };
296
297       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
298       if blocking(context.pool(), join).await?.is_err() {
299         return Err(ApiError::err("community_moderator_already_exists").into());
300       }
301     }
302
303     // Return the jwt
304     Ok(LoginResponse {
305       jwt: Claims::jwt(inserted_user.id, Settings::get().hostname)?,
306     })
307   }
308 }
309
310 #[async_trait::async_trait(?Send)]
311 impl Perform for GetCaptcha {
312   type Response = GetCaptchaResponse;
313
314   async fn perform(
315     &self,
316     context: &Data<LemmyContext>,
317     _websocket_id: Option<ConnectionId>,
318   ) -> Result<Self::Response, LemmyError> {
319     let captcha_settings = Settings::get().captcha;
320
321     if !captcha_settings.enabled {
322       return Ok(GetCaptchaResponse { ok: None });
323     }
324
325     let captcha = match captcha_settings.difficulty.as_str() {
326       "easy" => gen(Difficulty::Easy),
327       "medium" => gen(Difficulty::Medium),
328       "hard" => gen(Difficulty::Hard),
329       _ => gen(Difficulty::Medium),
330     };
331
332     let answer = captcha.chars_as_string();
333
334     let png_byte_array = captcha.as_png().expect("failed to generate captcha");
335
336     let png = base64::encode(png_byte_array);
337
338     let uuid = uuid::Uuid::new_v4().to_string();
339
340     let wav = captcha_espeak_wav_base64(&answer).ok();
341
342     let captcha_item = CaptchaItem {
343       answer,
344       uuid: uuid.to_owned(),
345       expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
346     };
347
348     // Stores the captcha item on the queue
349     context.chat_server().do_send(captcha_item);
350
351     Ok(GetCaptchaResponse {
352       ok: Some(CaptchaResponse { png, uuid, wav }),
353     })
354   }
355 }
356
357 #[async_trait::async_trait(?Send)]
358 impl Perform for SaveUserSettings {
359   type Response = LoginResponse;
360
361   async fn perform(
362     &self,
363     context: &Data<LemmyContext>,
364     _websocket_id: Option<ConnectionId>,
365   ) -> Result<LoginResponse, LemmyError> {
366     let data: &SaveUserSettings = &self;
367     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
368
369     let avatar = diesel_option_overwrite(&data.avatar);
370     let banner = diesel_option_overwrite(&data.banner);
371     let email = diesel_option_overwrite(&data.email);
372     let bio = diesel_option_overwrite(&data.bio);
373     let preferred_username = diesel_option_overwrite(&data.preferred_username);
374     let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
375
376     // Check to make sure the avatar and banners are urls
377     check_optional_url(&avatar)?;
378     check_optional_url(&banner)?;
379
380     if let Some(Some(bio)) = &bio {
381       if bio.chars().count() > 300 {
382         return Err(ApiError::err("bio_length_overflow").into());
383       }
384     }
385
386     if let Some(Some(preferred_username)) = &preferred_username {
387       if !is_valid_preferred_username(preferred_username.trim()) {
388         return Err(ApiError::err("invalid_username").into());
389       }
390     }
391
392     let user_id = user.id;
393     let password_encrypted = match &data.new_password {
394       Some(new_password) => {
395         match &data.new_password_verify {
396           Some(new_password_verify) => {
397             // Make sure passwords match
398             if new_password != new_password_verify {
399               return Err(ApiError::err("passwords_dont_match").into());
400             }
401
402             // Check the old password
403             match &data.old_password {
404               Some(old_password) => {
405                 let valid: bool = verify(old_password, &user.password_encrypted).unwrap_or(false);
406                 if !valid {
407                   return Err(ApiError::err("password_incorrect").into());
408                 }
409                 let new_password = new_password.to_owned();
410                 let user = blocking(context.pool(), move |conn| {
411                   User_::update_password(conn, user_id, &new_password)
412                 })
413                 .await??;
414                 user.password_encrypted
415               }
416               None => return Err(ApiError::err("password_incorrect").into()),
417             }
418           }
419           None => return Err(ApiError::err("passwords_dont_match").into()),
420         }
421       }
422       None => user.password_encrypted,
423     };
424
425     let default_listing_type = data.default_listing_type;
426     let default_sort_type = data.default_sort_type;
427
428     let user_form = UserForm {
429       name: user.name,
430       email,
431       matrix_user_id,
432       avatar,
433       banner,
434       inbox_url: None,
435       password_encrypted,
436       preferred_username,
437       published: Some(user.published),
438       updated: Some(naive_now()),
439       admin: user.admin,
440       banned: Some(user.banned),
441       show_nsfw: data.show_nsfw,
442       theme: data.theme.to_owned(),
443       default_sort_type,
444       default_listing_type,
445       lang: data.lang.to_owned(),
446       show_avatars: data.show_avatars,
447       send_notifications_to_email: data.send_notifications_to_email,
448       actor_id: Some(user.actor_id),
449       bio,
450       local: user.local,
451       private_key: user.private_key,
452       public_key: user.public_key,
453       last_refreshed_at: None,
454       shared_inbox_url: None,
455     };
456
457     let res = blocking(context.pool(), move |conn| {
458       User_::update(conn, user_id, &user_form)
459     })
460     .await?;
461     let updated_user: User_ = match res {
462       Ok(user) => user,
463       Err(e) => {
464         let err_type = if e.to_string()
465           == "duplicate key value violates unique constraint \"user__email_key\""
466         {
467           "email_already_exists"
468         } else {
469           "user_already_exists"
470         };
471
472         return Err(ApiError::err(err_type).into());
473       }
474     };
475
476     // Return the jwt
477     Ok(LoginResponse {
478       jwt: Claims::jwt(updated_user.id, Settings::get().hostname)?,
479     })
480   }
481 }
482
483 #[async_trait::async_trait(?Send)]
484 impl Perform for GetUserDetails {
485   type Response = GetUserDetailsResponse;
486
487   async fn perform(
488     &self,
489     context: &Data<LemmyContext>,
490     _websocket_id: Option<ConnectionId>,
491   ) -> Result<GetUserDetailsResponse, LemmyError> {
492     let data: &GetUserDetails = &self;
493     let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
494
495     let show_nsfw = match &user {
496       Some(user) => user.show_nsfw,
497       None => false,
498     };
499
500     let sort = SortType::from_str(&data.sort)?;
501
502     let username = data
503       .username
504       .to_owned()
505       .unwrap_or_else(|| "admin".to_string());
506     let user_details_id = match data.user_id {
507       Some(id) => id,
508       None => {
509         let user = blocking(context.pool(), move |conn| {
510           User_::read_from_name(conn, &username)
511         })
512         .await?;
513         match user {
514           Ok(user) => user.id,
515           Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
516         }
517       }
518     };
519
520     let user_id = user.map(|u| u.id);
521
522     // You don't need to return settings for the user, since this comes back with GetSite
523     // `my_user`
524     let user_view = blocking(context.pool(), move |conn| {
525       UserViewSafe::read(conn, user_details_id)
526     })
527     .await??;
528
529     let page = data.page;
530     let limit = data.limit;
531     let saved_only = data.saved_only;
532     let community_id = data.community_id;
533
534     let (posts, comments) = blocking(context.pool(), move |conn| {
535       let mut posts_query = PostQueryBuilder::create(conn)
536         .sort(&sort)
537         .show_nsfw(show_nsfw)
538         .saved_only(saved_only)
539         .community_id(community_id)
540         .my_user_id(user_id)
541         .page(page)
542         .limit(limit);
543
544       let mut comments_query = CommentQueryBuilder::create(conn)
545         .my_user_id(user_id)
546         .sort(&sort)
547         .saved_only(saved_only)
548         .page(page)
549         .limit(limit);
550
551       // If its saved only, you don't care what creator it was
552       // Or, if its not saved, then you only want it for that specific creator
553       if !saved_only {
554         posts_query = posts_query.creator_id(user_details_id);
555         comments_query = comments_query.creator_id(user_details_id);
556       }
557
558       let posts = posts_query.list()?;
559       let comments = comments_query.list()?;
560
561       Ok((posts, comments)) as Result<_, LemmyError>
562     })
563     .await??;
564
565     let mut follows = vec![];
566     if let Some(uid) = user_id {
567       if uid == user_details_id {
568         follows = blocking(context.pool(), move |conn| {
569           CommunityFollowerView::for_user(conn, user_details_id)
570         })
571         .await??;
572       }
573     };
574     let moderates = blocking(context.pool(), move |conn| {
575       CommunityModeratorView::for_user(conn, user_details_id)
576     })
577     .await??;
578
579     // Return the jwt
580     Ok(GetUserDetailsResponse {
581       user_view,
582       follows,
583       moderates,
584       comments,
585       posts,
586     })
587   }
588 }
589
590 #[async_trait::async_trait(?Send)]
591 impl Perform for AddAdmin {
592   type Response = AddAdminResponse;
593
594   async fn perform(
595     &self,
596     context: &Data<LemmyContext>,
597     websocket_id: Option<ConnectionId>,
598   ) -> Result<AddAdminResponse, LemmyError> {
599     let data: &AddAdmin = &self;
600     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
601
602     // Make sure user is an admin
603     is_admin(context.pool(), user.id).await?;
604
605     let added = data.added;
606     let added_user_id = data.user_id;
607     let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
608     if blocking(context.pool(), add_admin).await?.is_err() {
609       return Err(ApiError::err("couldnt_update_user").into());
610     }
611
612     // Mod tables
613     let form = ModAddForm {
614       mod_user_id: user.id,
615       other_user_id: data.user_id,
616       removed: Some(!data.added),
617     };
618
619     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
620
621     let site_creator_id = blocking(context.pool(), move |conn| {
622       Site::read(conn, 1).map(|s| s.creator_id)
623     })
624     .await??;
625
626     let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
627     let creator_index = admins
628       .iter()
629       .position(|r| r.user.id == site_creator_id)
630       .context(location_info!())?;
631     let creator_user = admins.remove(creator_index);
632     admins.insert(0, creator_user);
633
634     let res = AddAdminResponse { admins };
635
636     context.chat_server().do_send(SendAllMessage {
637       op: UserOperation::AddAdmin,
638       response: res.clone(),
639       websocket_id,
640     });
641
642     Ok(res)
643   }
644 }
645
646 #[async_trait::async_trait(?Send)]
647 impl Perform for BanUser {
648   type Response = BanUserResponse;
649
650   async fn perform(
651     &self,
652     context: &Data<LemmyContext>,
653     websocket_id: Option<ConnectionId>,
654   ) -> Result<BanUserResponse, LemmyError> {
655     let data: &BanUser = &self;
656     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
657
658     // Make sure user is an admin
659     is_admin(context.pool(), user.id).await?;
660
661     let ban = data.ban;
662     let banned_user_id = data.user_id;
663     let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
664     if blocking(context.pool(), ban_user).await?.is_err() {
665       return Err(ApiError::err("couldnt_update_user").into());
666     }
667
668     // Remove their data if that's desired
669     if data.remove_data {
670       // Posts
671       blocking(context.pool(), move |conn: &'_ _| {
672         Post::update_removed_for_creator(conn, banned_user_id, None, true)
673       })
674       .await??;
675
676       // Communities
677       blocking(context.pool(), move |conn: &'_ _| {
678         Community::update_removed_for_creator(conn, banned_user_id, true)
679       })
680       .await??;
681
682       // Comments
683       blocking(context.pool(), move |conn: &'_ _| {
684         Comment::update_removed_for_creator(conn, banned_user_id, true)
685       })
686       .await??;
687     }
688
689     // Mod tables
690     let expires = match data.expires {
691       Some(time) => Some(naive_from_unix(time)),
692       None => None,
693     };
694
695     let form = ModBanForm {
696       mod_user_id: user.id,
697       other_user_id: data.user_id,
698       reason: data.reason.to_owned(),
699       banned: Some(data.ban),
700       expires,
701     };
702
703     blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
704
705     let user_id = data.user_id;
706     let user_view = blocking(context.pool(), move |conn| {
707       UserViewSafe::read(conn, user_id)
708     })
709     .await??;
710
711     let res = BanUserResponse {
712       user_view,
713       banned: data.ban,
714     };
715
716     context.chat_server().do_send(SendAllMessage {
717       op: UserOperation::BanUser,
718       response: res.clone(),
719       websocket_id,
720     });
721
722     Ok(res)
723   }
724 }
725
726 #[async_trait::async_trait(?Send)]
727 impl Perform for GetReplies {
728   type Response = GetRepliesResponse;
729
730   async fn perform(
731     &self,
732     context: &Data<LemmyContext>,
733     _websocket_id: Option<ConnectionId>,
734   ) -> Result<GetRepliesResponse, LemmyError> {
735     let data: &GetReplies = &self;
736     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
737
738     let sort = SortType::from_str(&data.sort)?;
739
740     let page = data.page;
741     let limit = data.limit;
742     let unread_only = data.unread_only;
743     let user_id = user.id;
744     let replies = blocking(context.pool(), move |conn| {
745       CommentQueryBuilder::create(conn)
746         .sort(&sort)
747         .unread_only(unread_only)
748         .recipient_id(user_id)
749         .my_user_id(user_id)
750         .page(page)
751         .limit(limit)
752         .list()
753     })
754     .await??;
755
756     Ok(GetRepliesResponse { replies })
757   }
758 }
759
760 #[async_trait::async_trait(?Send)]
761 impl Perform for GetUserMentions {
762   type Response = GetUserMentionsResponse;
763
764   async fn perform(
765     &self,
766     context: &Data<LemmyContext>,
767     _websocket_id: Option<ConnectionId>,
768   ) -> Result<GetUserMentionsResponse, LemmyError> {
769     let data: &GetUserMentions = &self;
770     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
771
772     let sort = SortType::from_str(&data.sort)?;
773
774     let page = data.page;
775     let limit = data.limit;
776     let unread_only = data.unread_only;
777     let user_id = user.id;
778     let mentions = blocking(context.pool(), move |conn| {
779       UserMentionQueryBuilder::create(conn)
780         .recipient_id(user_id)
781         .my_user_id(user_id)
782         .sort(&sort)
783         .unread_only(unread_only)
784         .page(page)
785         .limit(limit)
786         .list()
787     })
788     .await??;
789
790     Ok(GetUserMentionsResponse { mentions })
791   }
792 }
793
794 #[async_trait::async_trait(?Send)]
795 impl Perform for MarkUserMentionAsRead {
796   type Response = UserMentionResponse;
797
798   async fn perform(
799     &self,
800     context: &Data<LemmyContext>,
801     _websocket_id: Option<ConnectionId>,
802   ) -> Result<UserMentionResponse, LemmyError> {
803     let data: &MarkUserMentionAsRead = &self;
804     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
805
806     let user_mention_id = data.user_mention_id;
807     let read_user_mention = blocking(context.pool(), move |conn| {
808       UserMention::read(conn, user_mention_id)
809     })
810     .await??;
811
812     if user.id != read_user_mention.recipient_id {
813       return Err(ApiError::err("couldnt_update_comment").into());
814     }
815
816     let user_mention_id = read_user_mention.id;
817     let read = data.read;
818     let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
819     if blocking(context.pool(), update_mention).await?.is_err() {
820       return Err(ApiError::err("couldnt_update_comment").into());
821     };
822
823     let user_mention_id = read_user_mention.id;
824     let user_id = user.id;
825     let user_mention_view = blocking(context.pool(), move |conn| {
826       UserMentionView::read(conn, user_mention_id, Some(user_id))
827     })
828     .await??;
829
830     Ok(UserMentionResponse { user_mention_view })
831   }
832 }
833
834 #[async_trait::async_trait(?Send)]
835 impl Perform for MarkAllAsRead {
836   type Response = GetRepliesResponse;
837
838   async fn perform(
839     &self,
840     context: &Data<LemmyContext>,
841     _websocket_id: Option<ConnectionId>,
842   ) -> Result<GetRepliesResponse, LemmyError> {
843     let data: &MarkAllAsRead = &self;
844     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
845
846     let user_id = user.id;
847     let replies = blocking(context.pool(), move |conn| {
848       CommentQueryBuilder::create(conn)
849         .my_user_id(user_id)
850         .recipient_id(user_id)
851         .unread_only(true)
852         .page(1)
853         .limit(999)
854         .list()
855     })
856     .await??;
857
858     // TODO: this should probably be a bulk operation
859     // Not easy to do as a bulk operation,
860     // because recipient_id isn't in the comment table
861     for comment_view in &replies {
862       let reply_id = comment_view.comment.id;
863       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
864       if blocking(context.pool(), mark_as_read).await?.is_err() {
865         return Err(ApiError::err("couldnt_update_comment").into());
866       }
867     }
868
869     // Mark all user mentions as read
870     let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
871     if blocking(context.pool(), update_user_mentions)
872       .await?
873       .is_err()
874     {
875       return Err(ApiError::err("couldnt_update_comment").into());
876     }
877
878     // Mark all private_messages as read
879     let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
880     if blocking(context.pool(), update_pm).await?.is_err() {
881       return Err(ApiError::err("couldnt_update_private_message").into());
882     }
883
884     Ok(GetRepliesResponse { replies: vec![] })
885   }
886 }
887
888 #[async_trait::async_trait(?Send)]
889 impl Perform for DeleteAccount {
890   type Response = LoginResponse;
891
892   async fn perform(
893     &self,
894     context: &Data<LemmyContext>,
895     _websocket_id: Option<ConnectionId>,
896   ) -> Result<LoginResponse, LemmyError> {
897     let data: &DeleteAccount = &self;
898     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
899
900     // Verify the password
901     let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
902     if !valid {
903       return Err(ApiError::err("password_incorrect").into());
904     }
905
906     // Comments
907     let user_id = user.id;
908     let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
909     if blocking(context.pool(), permadelete).await?.is_err() {
910       return Err(ApiError::err("couldnt_update_comment").into());
911     }
912
913     // Posts
914     let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
915     if blocking(context.pool(), permadelete).await?.is_err() {
916       return Err(ApiError::err("couldnt_update_post").into());
917     }
918
919     blocking(context.pool(), move |conn| {
920       User_::delete_account(conn, user_id)
921     })
922     .await??;
923
924     Ok(LoginResponse {
925       jwt: data.auth.to_owned(),
926     })
927   }
928 }
929
930 #[async_trait::async_trait(?Send)]
931 impl Perform for PasswordReset {
932   type Response = PasswordResetResponse;
933
934   async fn perform(
935     &self,
936     context: &Data<LemmyContext>,
937     _websocket_id: Option<ConnectionId>,
938   ) -> Result<PasswordResetResponse, LemmyError> {
939     let data: &PasswordReset = &self;
940
941     // Fetch that email
942     let email = data.email.clone();
943     let user = match blocking(context.pool(), move |conn| {
944       User_::find_by_email(conn, &email)
945     })
946     .await?
947     {
948       Ok(user) => user,
949       Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
950     };
951
952     // Generate a random token
953     let token = generate_random_string();
954
955     // Insert the row
956     let token2 = token.clone();
957     let user_id = user.id;
958     blocking(context.pool(), move |conn| {
959       PasswordResetRequest::create_token(conn, user_id, &token2)
960     })
961     .await??;
962
963     // Email the pure token to the user.
964     // TODO no i18n support here.
965     let user_email = &user.email.expect("email");
966     let subject = &format!("Password reset for {}", user.name);
967     let hostname = &Settings::get().get_protocol_and_hostname();
968     let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", user.name, hostname, &token);
969     match send_email(subject, user_email, &user.name, html) {
970       Ok(_o) => _o,
971       Err(_e) => return Err(ApiError::err(&_e).into()),
972     };
973
974     Ok(PasswordResetResponse {})
975   }
976 }
977
978 #[async_trait::async_trait(?Send)]
979 impl Perform for PasswordChange {
980   type Response = LoginResponse;
981
982   async fn perform(
983     &self,
984     context: &Data<LemmyContext>,
985     _websocket_id: Option<ConnectionId>,
986   ) -> Result<LoginResponse, LemmyError> {
987     let data: &PasswordChange = &self;
988
989     // Fetch the user_id from the token
990     let token = data.token.clone();
991     let user_id = blocking(context.pool(), move |conn| {
992       PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
993     })
994     .await??;
995
996     // Make sure passwords match
997     if data.password != data.password_verify {
998       return Err(ApiError::err("passwords_dont_match").into());
999     }
1000
1001     // Update the user with the new password
1002     let password = data.password.clone();
1003     let updated_user = match blocking(context.pool(), move |conn| {
1004       User_::update_password(conn, user_id, &password)
1005     })
1006     .await?
1007     {
1008       Ok(user) => user,
1009       Err(_e) => return Err(ApiError::err("couldnt_update_user").into()),
1010     };
1011
1012     // Return the jwt
1013     Ok(LoginResponse {
1014       jwt: Claims::jwt(updated_user.id, Settings::get().hostname)?,
1015     })
1016   }
1017 }
1018
1019 #[async_trait::async_trait(?Send)]
1020 impl Perform for CreatePrivateMessage {
1021   type Response = PrivateMessageResponse;
1022
1023   async fn perform(
1024     &self,
1025     context: &Data<LemmyContext>,
1026     websocket_id: Option<ConnectionId>,
1027   ) -> Result<PrivateMessageResponse, LemmyError> {
1028     let data: &CreatePrivateMessage = &self;
1029     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1030
1031     let content_slurs_removed = remove_slurs(&data.content.to_owned());
1032
1033     let private_message_form = PrivateMessageForm {
1034       content: content_slurs_removed.to_owned(),
1035       creator_id: user.id,
1036       recipient_id: data.recipient_id,
1037       deleted: None,
1038       read: None,
1039       updated: None,
1040       ap_id: None,
1041       local: true,
1042       published: None,
1043     };
1044
1045     let inserted_private_message = match blocking(context.pool(), move |conn| {
1046       PrivateMessage::create(conn, &private_message_form)
1047     })
1048     .await?
1049     {
1050       Ok(private_message) => private_message,
1051       Err(_e) => {
1052         return Err(ApiError::err("couldnt_create_private_message").into());
1053       }
1054     };
1055
1056     let inserted_private_message_id = inserted_private_message.id;
1057     let updated_private_message = match blocking(
1058       context.pool(),
1059       move |conn| -> Result<PrivateMessage, LemmyError> {
1060         let apub_id = generate_apub_endpoint(
1061           EndpointType::PrivateMessage,
1062           &inserted_private_message_id.to_string(),
1063         )?;
1064         Ok(PrivateMessage::update_ap_id(
1065           &conn,
1066           inserted_private_message_id,
1067           apub_id,
1068         )?)
1069       },
1070     )
1071     .await?
1072     {
1073       Ok(private_message) => private_message,
1074       Err(_e) => return Err(ApiError::err("couldnt_create_private_message").into()),
1075     };
1076
1077     updated_private_message.send_create(&user, context).await?;
1078
1079     // Send notifications to the recipient
1080     let recipient_id = data.recipient_id;
1081     let recipient_user =
1082       blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1083     if recipient_user.send_notifications_to_email {
1084       send_email_to_user(
1085         recipient_user,
1086         "Private Message from",
1087         "Private Message",
1088         &content_slurs_removed,
1089       );
1090     }
1091
1092     let message = blocking(context.pool(), move |conn| {
1093       PrivateMessageView::read(conn, inserted_private_message.id)
1094     })
1095     .await??;
1096
1097     let res = PrivateMessageResponse {
1098       private_message_view: message,
1099     };
1100
1101     context.chat_server().do_send(SendUserRoomMessage {
1102       op: UserOperation::CreatePrivateMessage,
1103       response: res.clone(),
1104       recipient_id,
1105       websocket_id,
1106     });
1107
1108     Ok(res)
1109   }
1110 }
1111
1112 #[async_trait::async_trait(?Send)]
1113 impl Perform for EditPrivateMessage {
1114   type Response = PrivateMessageResponse;
1115
1116   async fn perform(
1117     &self,
1118     context: &Data<LemmyContext>,
1119     websocket_id: Option<ConnectionId>,
1120   ) -> Result<PrivateMessageResponse, LemmyError> {
1121     let data: &EditPrivateMessage = &self;
1122     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1123
1124     // Checking permissions
1125     let private_message_id = data.private_message_id;
1126     let orig_private_message = blocking(context.pool(), move |conn| {
1127       PrivateMessage::read(conn, private_message_id)
1128     })
1129     .await??;
1130     if user.id != orig_private_message.creator_id {
1131       return Err(ApiError::err("no_private_message_edit_allowed").into());
1132     }
1133
1134     // Doing the update
1135     let content_slurs_removed = remove_slurs(&data.content);
1136     let private_message_id = data.private_message_id;
1137     let updated_private_message = match blocking(context.pool(), move |conn| {
1138       PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
1139     })
1140     .await?
1141     {
1142       Ok(private_message) => private_message,
1143       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1144     };
1145
1146     // Send the apub update
1147     updated_private_message.send_update(&user, context).await?;
1148
1149     let private_message_id = data.private_message_id;
1150     let message = blocking(context.pool(), move |conn| {
1151       PrivateMessageView::read(conn, private_message_id)
1152     })
1153     .await??;
1154     let recipient_id = message.recipient.id;
1155
1156     let res = PrivateMessageResponse {
1157       private_message_view: message,
1158     };
1159
1160     context.chat_server().do_send(SendUserRoomMessage {
1161       op: UserOperation::EditPrivateMessage,
1162       response: res.clone(),
1163       recipient_id,
1164       websocket_id,
1165     });
1166
1167     Ok(res)
1168   }
1169 }
1170
1171 #[async_trait::async_trait(?Send)]
1172 impl Perform for DeletePrivateMessage {
1173   type Response = PrivateMessageResponse;
1174
1175   async fn perform(
1176     &self,
1177     context: &Data<LemmyContext>,
1178     websocket_id: Option<ConnectionId>,
1179   ) -> Result<PrivateMessageResponse, LemmyError> {
1180     let data: &DeletePrivateMessage = &self;
1181     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1182
1183     // Checking permissions
1184     let private_message_id = data.private_message_id;
1185     let orig_private_message = blocking(context.pool(), move |conn| {
1186       PrivateMessage::read(conn, private_message_id)
1187     })
1188     .await??;
1189     if user.id != orig_private_message.creator_id {
1190       return Err(ApiError::err("no_private_message_edit_allowed").into());
1191     }
1192
1193     // Doing the update
1194     let private_message_id = data.private_message_id;
1195     let deleted = data.deleted;
1196     let updated_private_message = match blocking(context.pool(), move |conn| {
1197       PrivateMessage::update_deleted(conn, private_message_id, deleted)
1198     })
1199     .await?
1200     {
1201       Ok(private_message) => private_message,
1202       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1203     };
1204
1205     // Send the apub update
1206     if data.deleted {
1207       updated_private_message.send_delete(&user, context).await?;
1208     } else {
1209       updated_private_message
1210         .send_undo_delete(&user, context)
1211         .await?;
1212     }
1213
1214     let private_message_id = data.private_message_id;
1215     let message = blocking(context.pool(), move |conn| {
1216       PrivateMessageView::read(conn, private_message_id)
1217     })
1218     .await??;
1219     let recipient_id = message.recipient.id;
1220
1221     let res = PrivateMessageResponse {
1222       private_message_view: message,
1223     };
1224
1225     context.chat_server().do_send(SendUserRoomMessage {
1226       op: UserOperation::DeletePrivateMessage,
1227       response: res.clone(),
1228       recipient_id,
1229       websocket_id,
1230     });
1231
1232     Ok(res)
1233   }
1234 }
1235
1236 #[async_trait::async_trait(?Send)]
1237 impl Perform for MarkPrivateMessageAsRead {
1238   type Response = PrivateMessageResponse;
1239
1240   async fn perform(
1241     &self,
1242     context: &Data<LemmyContext>,
1243     websocket_id: Option<ConnectionId>,
1244   ) -> Result<PrivateMessageResponse, LemmyError> {
1245     let data: &MarkPrivateMessageAsRead = &self;
1246     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1247
1248     // Checking permissions
1249     let private_message_id = data.private_message_id;
1250     let orig_private_message = blocking(context.pool(), move |conn| {
1251       PrivateMessage::read(conn, private_message_id)
1252     })
1253     .await??;
1254     if user.id != orig_private_message.recipient_id {
1255       return Err(ApiError::err("couldnt_update_private_message").into());
1256     }
1257
1258     // Doing the update
1259     let private_message_id = data.private_message_id;
1260     let read = data.read;
1261     match blocking(context.pool(), move |conn| {
1262       PrivateMessage::update_read(conn, private_message_id, read)
1263     })
1264     .await?
1265     {
1266       Ok(private_message) => private_message,
1267       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1268     };
1269
1270     // No need to send an apub update
1271
1272     let private_message_id = data.private_message_id;
1273     let message = blocking(context.pool(), move |conn| {
1274       PrivateMessageView::read(conn, private_message_id)
1275     })
1276     .await??;
1277     let recipient_id = message.recipient.id;
1278
1279     let res = PrivateMessageResponse {
1280       private_message_view: message,
1281     };
1282
1283     context.chat_server().do_send(SendUserRoomMessage {
1284       op: UserOperation::MarkPrivateMessageAsRead,
1285       response: res.clone(),
1286       recipient_id,
1287       websocket_id,
1288     });
1289
1290     Ok(res)
1291   }
1292 }
1293
1294 #[async_trait::async_trait(?Send)]
1295 impl Perform for GetPrivateMessages {
1296   type Response = PrivateMessagesResponse;
1297
1298   async fn perform(
1299     &self,
1300     context: &Data<LemmyContext>,
1301     _websocket_id: Option<ConnectionId>,
1302   ) -> Result<PrivateMessagesResponse, LemmyError> {
1303     let data: &GetPrivateMessages = &self;
1304     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1305     let user_id = user.id;
1306
1307     let page = data.page;
1308     let limit = data.limit;
1309     let unread_only = data.unread_only;
1310     let messages = blocking(context.pool(), move |conn| {
1311       PrivateMessageQueryBuilder::create(&conn, user_id)
1312         .page(page)
1313         .limit(limit)
1314         .unread_only(unread_only)
1315         .list()
1316     })
1317     .await??;
1318
1319     Ok(PrivateMessagesResponse {
1320       private_messages: messages,
1321     })
1322   }
1323 }
1324
1325 #[async_trait::async_trait(?Send)]
1326 impl Perform for GetReportCount {
1327   type Response = GetReportCountResponse;
1328
1329   async fn perform(
1330     &self,
1331     context: &Data<LemmyContext>,
1332     websocket_id: Option<ConnectionId>,
1333   ) -> Result<GetReportCountResponse, LemmyError> {
1334     let data: &GetReportCount = &self;
1335     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1336
1337     let user_id = user.id;
1338     let community_id = data.community;
1339     let community_ids =
1340       collect_moderated_communities(user_id, community_id, context.pool()).await?;
1341
1342     let res = {
1343       if community_ids.is_empty() {
1344         GetReportCountResponse {
1345           community: None,
1346           comment_reports: 0,
1347           post_reports: 0,
1348         }
1349       } else {
1350         let ids = community_ids.clone();
1351         let comment_reports = blocking(context.pool(), move |conn| {
1352           CommentReportView::get_report_count(conn, &ids)
1353         })
1354         .await??;
1355
1356         let ids = community_ids.clone();
1357         let post_reports = blocking(context.pool(), move |conn| {
1358           PostReportView::get_report_count(conn, &ids)
1359         })
1360         .await??;
1361
1362         GetReportCountResponse {
1363           community: data.community,
1364           comment_reports,
1365           post_reports,
1366         }
1367       }
1368     };
1369
1370     context.chat_server().do_send(SendUserRoomMessage {
1371       op: UserOperation::GetReportCount,
1372       response: res.clone(),
1373       recipient_id: user.id,
1374       websocket_id,
1375     });
1376
1377     Ok(res)
1378   }
1379 }