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