]> Untitled Git - lemmy.git/blob - crates/api/src/user.rs
Merge pull request 'Order outbox by published, not id' (#171) from outbox-order-publi...
[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 mut follows = vec![];
567     if let Some(uid) = user_id {
568       if uid == user_details_id {
569         follows = blocking(context.pool(), move |conn| {
570           CommunityFollowerView::for_user(conn, user_details_id)
571         })
572         .await??;
573       }
574     };
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       user_view,
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.id, 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(
1059       context.pool(),
1060       move |conn| -> Result<PrivateMessage, LemmyError> {
1061         let apub_id = generate_apub_endpoint(
1062           EndpointType::PrivateMessage,
1063           &inserted_private_message_id.to_string(),
1064         )?;
1065         Ok(PrivateMessage::update_ap_id(
1066           &conn,
1067           inserted_private_message_id,
1068           apub_id,
1069         )?)
1070       },
1071     )
1072     .await?
1073     {
1074       Ok(private_message) => private_message,
1075       Err(_e) => return Err(ApiError::err("couldnt_create_private_message").into()),
1076     };
1077
1078     updated_private_message.send_create(&user, context).await?;
1079
1080     // Send notifications to the recipient
1081     let recipient_id = data.recipient_id;
1082     let recipient_user =
1083       blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1084     if recipient_user.send_notifications_to_email {
1085       send_email_to_user(
1086         recipient_user,
1087         "Private Message from",
1088         "Private Message",
1089         &content_slurs_removed,
1090       );
1091     }
1092
1093     let message = blocking(context.pool(), move |conn| {
1094       PrivateMessageView::read(conn, inserted_private_message.id)
1095     })
1096     .await??;
1097
1098     let res = PrivateMessageResponse {
1099       private_message_view: message,
1100     };
1101
1102     context.chat_server().do_send(SendUserRoomMessage {
1103       op: UserOperation::CreatePrivateMessage,
1104       response: res.clone(),
1105       recipient_id,
1106       websocket_id,
1107     });
1108
1109     Ok(res)
1110   }
1111 }
1112
1113 #[async_trait::async_trait(?Send)]
1114 impl Perform for EditPrivateMessage {
1115   type Response = PrivateMessageResponse;
1116
1117   async fn perform(
1118     &self,
1119     context: &Data<LemmyContext>,
1120     websocket_id: Option<ConnectionId>,
1121   ) -> Result<PrivateMessageResponse, LemmyError> {
1122     let data: &EditPrivateMessage = &self;
1123     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1124
1125     // Checking permissions
1126     let private_message_id = data.private_message_id;
1127     let orig_private_message = blocking(context.pool(), move |conn| {
1128       PrivateMessage::read(conn, private_message_id)
1129     })
1130     .await??;
1131     if user.id != orig_private_message.creator_id {
1132       return Err(ApiError::err("no_private_message_edit_allowed").into());
1133     }
1134
1135     // Doing the update
1136     let content_slurs_removed = remove_slurs(&data.content);
1137     let private_message_id = data.private_message_id;
1138     let updated_private_message = match blocking(context.pool(), move |conn| {
1139       PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
1140     })
1141     .await?
1142     {
1143       Ok(private_message) => private_message,
1144       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1145     };
1146
1147     // Send the apub update
1148     updated_private_message.send_update(&user, context).await?;
1149
1150     let private_message_id = data.private_message_id;
1151     let message = blocking(context.pool(), move |conn| {
1152       PrivateMessageView::read(conn, private_message_id)
1153     })
1154     .await??;
1155     let recipient_id = message.recipient.id;
1156
1157     let res = PrivateMessageResponse {
1158       private_message_view: message,
1159     };
1160
1161     context.chat_server().do_send(SendUserRoomMessage {
1162       op: UserOperation::EditPrivateMessage,
1163       response: res.clone(),
1164       recipient_id,
1165       websocket_id,
1166     });
1167
1168     Ok(res)
1169   }
1170 }
1171
1172 #[async_trait::async_trait(?Send)]
1173 impl Perform for DeletePrivateMessage {
1174   type Response = PrivateMessageResponse;
1175
1176   async fn perform(
1177     &self,
1178     context: &Data<LemmyContext>,
1179     websocket_id: Option<ConnectionId>,
1180   ) -> Result<PrivateMessageResponse, LemmyError> {
1181     let data: &DeletePrivateMessage = &self;
1182     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1183
1184     // Checking permissions
1185     let private_message_id = data.private_message_id;
1186     let orig_private_message = blocking(context.pool(), move |conn| {
1187       PrivateMessage::read(conn, private_message_id)
1188     })
1189     .await??;
1190     if user.id != orig_private_message.creator_id {
1191       return Err(ApiError::err("no_private_message_edit_allowed").into());
1192     }
1193
1194     // Doing the update
1195     let private_message_id = data.private_message_id;
1196     let deleted = data.deleted;
1197     let updated_private_message = match blocking(context.pool(), move |conn| {
1198       PrivateMessage::update_deleted(conn, private_message_id, deleted)
1199     })
1200     .await?
1201     {
1202       Ok(private_message) => private_message,
1203       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1204     };
1205
1206     // Send the apub update
1207     if data.deleted {
1208       updated_private_message.send_delete(&user, context).await?;
1209     } else {
1210       updated_private_message
1211         .send_undo_delete(&user, context)
1212         .await?;
1213     }
1214
1215     let private_message_id = data.private_message_id;
1216     let message = blocking(context.pool(), move |conn| {
1217       PrivateMessageView::read(conn, private_message_id)
1218     })
1219     .await??;
1220     let recipient_id = message.recipient.id;
1221
1222     let res = PrivateMessageResponse {
1223       private_message_view: message,
1224     };
1225
1226     context.chat_server().do_send(SendUserRoomMessage {
1227       op: UserOperation::DeletePrivateMessage,
1228       response: res.clone(),
1229       recipient_id,
1230       websocket_id,
1231     });
1232
1233     Ok(res)
1234   }
1235 }
1236
1237 #[async_trait::async_trait(?Send)]
1238 impl Perform for MarkPrivateMessageAsRead {
1239   type Response = PrivateMessageResponse;
1240
1241   async fn perform(
1242     &self,
1243     context: &Data<LemmyContext>,
1244     websocket_id: Option<ConnectionId>,
1245   ) -> Result<PrivateMessageResponse, LemmyError> {
1246     let data: &MarkPrivateMessageAsRead = &self;
1247     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1248
1249     // Checking permissions
1250     let private_message_id = data.private_message_id;
1251     let orig_private_message = blocking(context.pool(), move |conn| {
1252       PrivateMessage::read(conn, private_message_id)
1253     })
1254     .await??;
1255     if user.id != orig_private_message.recipient_id {
1256       return Err(ApiError::err("couldnt_update_private_message").into());
1257     }
1258
1259     // Doing the update
1260     let private_message_id = data.private_message_id;
1261     let read = data.read;
1262     match blocking(context.pool(), move |conn| {
1263       PrivateMessage::update_read(conn, private_message_id, read)
1264     })
1265     .await?
1266     {
1267       Ok(private_message) => private_message,
1268       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1269     };
1270
1271     // No need to send an apub update
1272
1273     let private_message_id = data.private_message_id;
1274     let message = blocking(context.pool(), move |conn| {
1275       PrivateMessageView::read(conn, private_message_id)
1276     })
1277     .await??;
1278     let recipient_id = message.recipient.id;
1279
1280     let res = PrivateMessageResponse {
1281       private_message_view: message,
1282     };
1283
1284     context.chat_server().do_send(SendUserRoomMessage {
1285       op: UserOperation::MarkPrivateMessageAsRead,
1286       response: res.clone(),
1287       recipient_id,
1288       websocket_id,
1289     });
1290
1291     Ok(res)
1292   }
1293 }
1294
1295 #[async_trait::async_trait(?Send)]
1296 impl Perform for GetPrivateMessages {
1297   type Response = PrivateMessagesResponse;
1298
1299   async fn perform(
1300     &self,
1301     context: &Data<LemmyContext>,
1302     _websocket_id: Option<ConnectionId>,
1303   ) -> Result<PrivateMessagesResponse, LemmyError> {
1304     let data: &GetPrivateMessages = &self;
1305     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1306     let user_id = user.id;
1307
1308     let page = data.page;
1309     let limit = data.limit;
1310     let unread_only = data.unread_only;
1311     let messages = blocking(context.pool(), move |conn| {
1312       PrivateMessageQueryBuilder::create(&conn, user_id)
1313         .page(page)
1314         .limit(limit)
1315         .unread_only(unread_only)
1316         .list()
1317     })
1318     .await??;
1319
1320     Ok(PrivateMessagesResponse {
1321       private_messages: messages,
1322     })
1323   }
1324 }
1325
1326 #[async_trait::async_trait(?Send)]
1327 impl Perform for GetReportCount {
1328   type Response = GetReportCountResponse;
1329
1330   async fn perform(
1331     &self,
1332     context: &Data<LemmyContext>,
1333     websocket_id: Option<ConnectionId>,
1334   ) -> Result<GetReportCountResponse, LemmyError> {
1335     let data: &GetReportCount = &self;
1336     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1337
1338     let user_id = user.id;
1339     let community_id = data.community;
1340     let community_ids =
1341       collect_moderated_communities(user_id, community_id, context.pool()).await?;
1342
1343     let res = {
1344       if community_ids.is_empty() {
1345         GetReportCountResponse {
1346           community: None,
1347           comment_reports: 0,
1348           post_reports: 0,
1349         }
1350       } else {
1351         let ids = community_ids.clone();
1352         let comment_reports = blocking(context.pool(), move |conn| {
1353           CommentReportView::get_report_count(conn, &ids)
1354         })
1355         .await??;
1356
1357         let ids = community_ids.clone();
1358         let post_reports = blocking(context.pool(), move |conn| {
1359           PostReportView::get_report_count(conn, &ids)
1360         })
1361         .await??;
1362
1363         GetReportCountResponse {
1364           community: data.community,
1365           comment_reports,
1366           post_reports,
1367         }
1368       }
1369     };
1370
1371     context.chat_server().do_send(SendUserRoomMessage {
1372       op: UserOperation::GetReportCount,
1373       response: res.clone(),
1374       recipient_id: user.id,
1375       websocket_id,
1376     });
1377
1378     Ok(res)
1379   }
1380 }