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