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