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