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