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