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