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