]> Untitled Git - lemmy.git/blob - crates/api/src/local_user.rs
Creating default DB forms. Fixes #1511
[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       ..PersonForm::default()
208     };
209
210     // insert the person
211     let inserted_person = match blocking(context.pool(), move |conn| {
212       Person::create(conn, &person_form)
213     })
214     .await?
215     {
216       Ok(u) => u,
217       Err(_) => {
218         return Err(ApiError::err("user_already_exists").into());
219       }
220     };
221
222     // Create the local user
223     let local_user_form = LocalUserForm {
224       person_id: inserted_person.id,
225       email: Some(data.email.to_owned()),
226       password_encrypted: data.password.to_owned(),
227       admin: Some(no_admins),
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       private_key: None,
459       public_key: None,
460       last_refreshed_at: None,
461       shared_inbox_url: None,
462       matrix_user_id,
463     };
464
465     let person_res = blocking(context.pool(), move |conn| {
466       Person::update(conn, person_id, &person_form)
467     })
468     .await?;
469     let _updated_person: Person = match person_res {
470       Ok(p) => p,
471       Err(_) => {
472         return Err(ApiError::err("user_already_exists").into());
473       }
474     };
475
476     let local_user_form = LocalUserForm {
477       person_id,
478       email,
479       password_encrypted,
480       admin: None,
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         .page(page)
582         .limit(limit);
583
584       // If its saved only, you don't care what creator it was
585       // Or, if its not saved, then you only want it for that specific creator
586       if !saved_only {
587         posts_query = posts_query.creator_id(person_details_id);
588         comments_query = comments_query.creator_id(person_details_id);
589       }
590
591       let posts = posts_query.list()?;
592       let comments = comments_query.list()?;
593
594       Ok((posts, comments)) as Result<_, LemmyError>
595     })
596     .await??;
597
598     let mut follows = vec![];
599     if let Some(pid) = person_id {
600       if pid == person_details_id {
601         follows = blocking(context.pool(), move |conn| {
602           CommunityFollowerView::for_person(conn, person_details_id)
603         })
604         .await??;
605       }
606     };
607     let moderates = blocking(context.pool(), move |conn| {
608       CommunityModeratorView::for_person(conn, person_details_id)
609     })
610     .await??;
611
612     // Return the jwt
613     Ok(GetPersonDetailsResponse {
614       person_view,
615       follows,
616       moderates,
617       comments,
618       posts,
619     })
620   }
621 }
622
623 #[async_trait::async_trait(?Send)]
624 impl Perform for AddAdmin {
625   type Response = AddAdminResponse;
626
627   async fn perform(
628     &self,
629     context: &Data<LemmyContext>,
630     websocket_id: Option<ConnectionId>,
631   ) -> Result<AddAdminResponse, LemmyError> {
632     let data: &AddAdmin = &self;
633     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
634
635     // Make sure user is an admin
636     is_admin(&local_user_view)?;
637
638     let added = data.added;
639     let added_person_id = data.person_id;
640     let added_admin = match blocking(context.pool(), move |conn| {
641       LocalUser::add_admin(conn, added_person_id, added)
642     })
643     .await?
644     {
645       Ok(a) => a,
646       Err(_) => {
647         return Err(ApiError::err("couldnt_update_user").into());
648       }
649     };
650
651     // Mod tables
652     let form = ModAddForm {
653       mod_person_id: local_user_view.person.id,
654       other_person_id: added_admin.person_id,
655       removed: Some(!data.added),
656     };
657
658     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
659
660     let site_creator_id = blocking(context.pool(), move |conn| {
661       Site::read(conn, 1).map(|s| s.creator_id)
662     })
663     .await??;
664
665     let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
666     let creator_index = admins
667       .iter()
668       .position(|r| r.person.id == site_creator_id)
669       .context(location_info!())?;
670     let creator_person = admins.remove(creator_index);
671     admins.insert(0, creator_person);
672
673     let res = AddAdminResponse { admins };
674
675     context.chat_server().do_send(SendAllMessage {
676       op: UserOperation::AddAdmin,
677       response: res.clone(),
678       websocket_id,
679     });
680
681     Ok(res)
682   }
683 }
684
685 #[async_trait::async_trait(?Send)]
686 impl Perform for BanPerson {
687   type Response = BanPersonResponse;
688
689   async fn perform(
690     &self,
691     context: &Data<LemmyContext>,
692     websocket_id: Option<ConnectionId>,
693   ) -> Result<BanPersonResponse, LemmyError> {
694     let data: &BanPerson = &self;
695     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
696
697     // Make sure user is an admin
698     is_admin(&local_user_view)?;
699
700     let ban = data.ban;
701     let banned_person_id = data.person_id;
702     let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban);
703     if blocking(context.pool(), ban_person).await?.is_err() {
704       return Err(ApiError::err("couldnt_update_user").into());
705     }
706
707     // Remove their data if that's desired
708     if data.remove_data {
709       // Posts
710       blocking(context.pool(), move |conn: &'_ _| {
711         Post::update_removed_for_creator(conn, banned_person_id, None, true)
712       })
713       .await??;
714
715       // Communities
716       blocking(context.pool(), move |conn: &'_ _| {
717         Community::update_removed_for_creator(conn, banned_person_id, true)
718       })
719       .await??;
720
721       // Comments
722       blocking(context.pool(), move |conn: &'_ _| {
723         Comment::update_removed_for_creator(conn, banned_person_id, true)
724       })
725       .await??;
726     }
727
728     // Mod tables
729     let expires = match data.expires {
730       Some(time) => Some(naive_from_unix(time)),
731       None => None,
732     };
733
734     let form = ModBanForm {
735       mod_person_id: local_user_view.person.id,
736       other_person_id: data.person_id,
737       reason: data.reason.to_owned(),
738       banned: Some(data.ban),
739       expires,
740     };
741
742     blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
743
744     let person_id = data.person_id;
745     let person_view = blocking(context.pool(), move |conn| {
746       PersonViewSafe::read(conn, person_id)
747     })
748     .await??;
749
750     let res = BanPersonResponse {
751       person_view,
752       banned: data.ban,
753     };
754
755     context.chat_server().do_send(SendAllMessage {
756       op: UserOperation::BanPerson,
757       response: res.clone(),
758       websocket_id,
759     });
760
761     Ok(res)
762   }
763 }
764
765 #[async_trait::async_trait(?Send)]
766 impl Perform for GetReplies {
767   type Response = GetRepliesResponse;
768
769   async fn perform(
770     &self,
771     context: &Data<LemmyContext>,
772     _websocket_id: Option<ConnectionId>,
773   ) -> Result<GetRepliesResponse, LemmyError> {
774     let data: &GetReplies = &self;
775     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
776
777     let sort = SortType::from_str(&data.sort)?;
778
779     let page = data.page;
780     let limit = data.limit;
781     let unread_only = data.unread_only;
782     let person_id = local_user_view.person.id;
783     let replies = blocking(context.pool(), move |conn| {
784       CommentQueryBuilder::create(conn)
785         .sort(&sort)
786         .unread_only(unread_only)
787         .recipient_id(person_id)
788         .my_person_id(person_id)
789         .page(page)
790         .limit(limit)
791         .list()
792     })
793     .await??;
794
795     Ok(GetRepliesResponse { replies })
796   }
797 }
798
799 #[async_trait::async_trait(?Send)]
800 impl Perform for GetPersonMentions {
801   type Response = GetPersonMentionsResponse;
802
803   async fn perform(
804     &self,
805     context: &Data<LemmyContext>,
806     _websocket_id: Option<ConnectionId>,
807   ) -> Result<GetPersonMentionsResponse, LemmyError> {
808     let data: &GetPersonMentions = &self;
809     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
810
811     let sort = SortType::from_str(&data.sort)?;
812
813     let page = data.page;
814     let limit = data.limit;
815     let unread_only = data.unread_only;
816     let person_id = local_user_view.person.id;
817     let mentions = blocking(context.pool(), move |conn| {
818       PersonMentionQueryBuilder::create(conn)
819         .recipient_id(person_id)
820         .my_person_id(person_id)
821         .sort(&sort)
822         .unread_only(unread_only)
823         .page(page)
824         .limit(limit)
825         .list()
826     })
827     .await??;
828
829     Ok(GetPersonMentionsResponse { mentions })
830   }
831 }
832
833 #[async_trait::async_trait(?Send)]
834 impl Perform for MarkPersonMentionAsRead {
835   type Response = PersonMentionResponse;
836
837   async fn perform(
838     &self,
839     context: &Data<LemmyContext>,
840     _websocket_id: Option<ConnectionId>,
841   ) -> Result<PersonMentionResponse, LemmyError> {
842     let data: &MarkPersonMentionAsRead = &self;
843     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
844
845     let person_mention_id = data.person_mention_id;
846     let read_person_mention = blocking(context.pool(), move |conn| {
847       PersonMention::read(conn, person_mention_id)
848     })
849     .await??;
850
851     if local_user_view.person.id != read_person_mention.recipient_id {
852       return Err(ApiError::err("couldnt_update_comment").into());
853     }
854
855     let person_mention_id = read_person_mention.id;
856     let read = data.read;
857     let update_mention =
858       move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
859     if blocking(context.pool(), update_mention).await?.is_err() {
860       return Err(ApiError::err("couldnt_update_comment").into());
861     };
862
863     let person_mention_id = read_person_mention.id;
864     let person_id = local_user_view.person.id;
865     let person_mention_view = blocking(context.pool(), move |conn| {
866       PersonMentionView::read(conn, person_mention_id, Some(person_id))
867     })
868     .await??;
869
870     Ok(PersonMentionResponse {
871       person_mention_view,
872     })
873   }
874 }
875
876 #[async_trait::async_trait(?Send)]
877 impl Perform for MarkAllAsRead {
878   type Response = GetRepliesResponse;
879
880   async fn perform(
881     &self,
882     context: &Data<LemmyContext>,
883     _websocket_id: Option<ConnectionId>,
884   ) -> Result<GetRepliesResponse, LemmyError> {
885     let data: &MarkAllAsRead = &self;
886     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
887
888     let person_id = local_user_view.person.id;
889     let replies = blocking(context.pool(), move |conn| {
890       CommentQueryBuilder::create(conn)
891         .my_person_id(person_id)
892         .recipient_id(person_id)
893         .unread_only(true)
894         .page(1)
895         .limit(999)
896         .list()
897     })
898     .await??;
899
900     // TODO: this should probably be a bulk operation
901     // Not easy to do as a bulk operation,
902     // because recipient_id isn't in the comment table
903     for comment_view in &replies {
904       let reply_id = comment_view.comment.id;
905       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
906       if blocking(context.pool(), mark_as_read).await?.is_err() {
907         return Err(ApiError::err("couldnt_update_comment").into());
908       }
909     }
910
911     // Mark all user mentions as read
912     let update_person_mentions =
913       move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
914     if blocking(context.pool(), update_person_mentions)
915       .await?
916       .is_err()
917     {
918       return Err(ApiError::err("couldnt_update_comment").into());
919     }
920
921     // Mark all private_messages as read
922     let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
923     if blocking(context.pool(), update_pm).await?.is_err() {
924       return Err(ApiError::err("couldnt_update_private_message").into());
925     }
926
927     Ok(GetRepliesResponse { replies: vec![] })
928   }
929 }
930
931 #[async_trait::async_trait(?Send)]
932 impl Perform for DeleteAccount {
933   type Response = LoginResponse;
934
935   async fn perform(
936     &self,
937     context: &Data<LemmyContext>,
938     _websocket_id: Option<ConnectionId>,
939   ) -> Result<LoginResponse, LemmyError> {
940     let data: &DeleteAccount = &self;
941     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
942
943     // Verify the password
944     let valid: bool = verify(
945       &data.password,
946       &local_user_view.local_user.password_encrypted,
947     )
948     .unwrap_or(false);
949     if !valid {
950       return Err(ApiError::err("password_incorrect").into());
951     }
952
953     // Comments
954     let person_id = local_user_view.person.id;
955     let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
956     if blocking(context.pool(), permadelete).await?.is_err() {
957       return Err(ApiError::err("couldnt_update_comment").into());
958     }
959
960     // Posts
961     let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, person_id);
962     if blocking(context.pool(), permadelete).await?.is_err() {
963       return Err(ApiError::err("couldnt_update_post").into());
964     }
965
966     blocking(context.pool(), move |conn| {
967       Person::delete_account(conn, person_id)
968     })
969     .await??;
970
971     Ok(LoginResponse {
972       jwt: data.auth.to_owned(),
973     })
974   }
975 }
976
977 #[async_trait::async_trait(?Send)]
978 impl Perform for PasswordReset {
979   type Response = PasswordResetResponse;
980
981   async fn perform(
982     &self,
983     context: &Data<LemmyContext>,
984     _websocket_id: Option<ConnectionId>,
985   ) -> Result<PasswordResetResponse, LemmyError> {
986     let data: &PasswordReset = &self;
987
988     // Fetch that email
989     let email = data.email.clone();
990     let local_user_view = match blocking(context.pool(), move |conn| {
991       LocalUserView::find_by_email(conn, &email)
992     })
993     .await?
994     {
995       Ok(lu) => lu,
996       Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
997     };
998
999     // Generate a random token
1000     let token = generate_random_string();
1001
1002     // Insert the row
1003     let token2 = token.clone();
1004     let local_user_id = local_user_view.local_user.id;
1005     blocking(context.pool(), move |conn| {
1006       PasswordResetRequest::create_token(conn, local_user_id, &token2)
1007     })
1008     .await??;
1009
1010     // Email the pure token to the user.
1011     // TODO no i18n support here.
1012     let email = &local_user_view.local_user.email.expect("email");
1013     let subject = &format!("Password reset for {}", local_user_view.person.name);
1014     let hostname = &Settings::get().get_protocol_and_hostname();
1015     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);
1016     match send_email(subject, email, &local_user_view.person.name, html) {
1017       Ok(_o) => _o,
1018       Err(_e) => return Err(ApiError::err(&_e).into()),
1019     };
1020
1021     Ok(PasswordResetResponse {})
1022   }
1023 }
1024
1025 #[async_trait::async_trait(?Send)]
1026 impl Perform for PasswordChange {
1027   type Response = LoginResponse;
1028
1029   async fn perform(
1030     &self,
1031     context: &Data<LemmyContext>,
1032     _websocket_id: Option<ConnectionId>,
1033   ) -> Result<LoginResponse, LemmyError> {
1034     let data: &PasswordChange = &self;
1035
1036     // Fetch the user_id from the token
1037     let token = data.token.clone();
1038     let local_user_id = blocking(context.pool(), move |conn| {
1039       PasswordResetRequest::read_from_token(conn, &token).map(|p| p.local_user_id)
1040     })
1041     .await??;
1042
1043     password_length_check(&data.password)?;
1044
1045     // Make sure passwords match
1046     if data.password != data.password_verify {
1047       return Err(ApiError::err("passwords_dont_match").into());
1048     }
1049
1050     // Update the user with the new password
1051     let password = data.password.clone();
1052     let updated_local_user = match blocking(context.pool(), move |conn| {
1053       LocalUser::update_password(conn, local_user_id, &password)
1054     })
1055     .await?
1056     {
1057       Ok(u) => u,
1058       Err(_e) => return Err(ApiError::err("couldnt_update_user").into()),
1059     };
1060
1061     // Return the jwt
1062     Ok(LoginResponse {
1063       jwt: Claims::jwt(updated_local_user.id.0)?,
1064     })
1065   }
1066 }
1067
1068 #[async_trait::async_trait(?Send)]
1069 impl Perform for CreatePrivateMessage {
1070   type Response = PrivateMessageResponse;
1071
1072   async fn perform(
1073     &self,
1074     context: &Data<LemmyContext>,
1075     websocket_id: Option<ConnectionId>,
1076   ) -> Result<PrivateMessageResponse, LemmyError> {
1077     let data: &CreatePrivateMessage = &self;
1078     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1079
1080     let content_slurs_removed = remove_slurs(&data.content.to_owned());
1081
1082     let private_message_form = PrivateMessageForm {
1083       content: content_slurs_removed.to_owned(),
1084       creator_id: local_user_view.person.id,
1085       recipient_id: data.recipient_id,
1086       ..PrivateMessageForm::default()
1087     };
1088
1089     let inserted_private_message = match blocking(context.pool(), move |conn| {
1090       PrivateMessage::create(conn, &private_message_form)
1091     })
1092     .await?
1093     {
1094       Ok(private_message) => private_message,
1095       Err(_e) => {
1096         return Err(ApiError::err("couldnt_create_private_message").into());
1097       }
1098     };
1099
1100     let inserted_private_message_id = inserted_private_message.id;
1101     let updated_private_message = match blocking(
1102       context.pool(),
1103       move |conn| -> Result<PrivateMessage, LemmyError> {
1104         let apub_id = generate_apub_endpoint(
1105           EndpointType::PrivateMessage,
1106           &inserted_private_message_id.to_string(),
1107         )?;
1108         Ok(PrivateMessage::update_ap_id(
1109           &conn,
1110           inserted_private_message_id,
1111           apub_id,
1112         )?)
1113       },
1114     )
1115     .await?
1116     {
1117       Ok(private_message) => private_message,
1118       Err(_e) => return Err(ApiError::err("couldnt_create_private_message").into()),
1119     };
1120
1121     updated_private_message
1122       .send_create(&local_user_view.person, context)
1123       .await?;
1124
1125     let private_message_view = blocking(context.pool(), move |conn| {
1126       PrivateMessageView::read(conn, inserted_private_message.id)
1127     })
1128     .await??;
1129
1130     let res = PrivateMessageResponse {
1131       private_message_view,
1132     };
1133
1134     // Send notifications to the local recipient, if one exists
1135     let recipient_id = data.recipient_id;
1136     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
1137       LocalUserView::read_person(conn, recipient_id)
1138     })
1139     .await?
1140     {
1141       send_email_to_user(
1142         &local_recipient,
1143         "Private Message from",
1144         "Private Message",
1145         &content_slurs_removed,
1146       );
1147
1148       let local_recipient_id = local_recipient.local_user.id;
1149       context.chat_server().do_send(SendUserRoomMessage {
1150         op: UserOperation::CreatePrivateMessage,
1151         response: res.clone(),
1152         local_recipient_id,
1153         websocket_id,
1154       });
1155     }
1156
1157     Ok(res)
1158   }
1159 }
1160
1161 #[async_trait::async_trait(?Send)]
1162 impl Perform for EditPrivateMessage {
1163   type Response = PrivateMessageResponse;
1164
1165   async fn perform(
1166     &self,
1167     context: &Data<LemmyContext>,
1168     websocket_id: Option<ConnectionId>,
1169   ) -> Result<PrivateMessageResponse, LemmyError> {
1170     let data: &EditPrivateMessage = &self;
1171     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1172
1173     // Checking permissions
1174     let private_message_id = data.private_message_id;
1175     let orig_private_message = blocking(context.pool(), move |conn| {
1176       PrivateMessage::read(conn, private_message_id)
1177     })
1178     .await??;
1179     if local_user_view.person.id != orig_private_message.creator_id {
1180       return Err(ApiError::err("no_private_message_edit_allowed").into());
1181     }
1182
1183     // Doing the update
1184     let content_slurs_removed = remove_slurs(&data.content);
1185     let private_message_id = data.private_message_id;
1186     let updated_private_message = match blocking(context.pool(), move |conn| {
1187       PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
1188     })
1189     .await?
1190     {
1191       Ok(private_message) => private_message,
1192       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1193     };
1194
1195     // Send the apub update
1196     updated_private_message
1197       .send_update(&local_user_view.person, context)
1198       .await?;
1199
1200     let private_message_id = data.private_message_id;
1201     let private_message_view = blocking(context.pool(), move |conn| {
1202       PrivateMessageView::read(conn, private_message_id)
1203     })
1204     .await??;
1205
1206     let res = PrivateMessageResponse {
1207       private_message_view,
1208     };
1209
1210     // Send notifications to the local recipient, if one exists
1211     let recipient_id = orig_private_message.recipient_id;
1212     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
1213       LocalUserView::read_person(conn, recipient_id)
1214     })
1215     .await?
1216     {
1217       let local_recipient_id = local_recipient.local_user.id;
1218       context.chat_server().do_send(SendUserRoomMessage {
1219         op: UserOperation::EditPrivateMessage,
1220         response: res.clone(),
1221         local_recipient_id,
1222         websocket_id,
1223       });
1224     }
1225
1226     Ok(res)
1227   }
1228 }
1229
1230 #[async_trait::async_trait(?Send)]
1231 impl Perform for DeletePrivateMessage {
1232   type Response = PrivateMessageResponse;
1233
1234   async fn perform(
1235     &self,
1236     context: &Data<LemmyContext>,
1237     websocket_id: Option<ConnectionId>,
1238   ) -> Result<PrivateMessageResponse, LemmyError> {
1239     let data: &DeletePrivateMessage = &self;
1240     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1241
1242     // Checking permissions
1243     let private_message_id = data.private_message_id;
1244     let orig_private_message = blocking(context.pool(), move |conn| {
1245       PrivateMessage::read(conn, private_message_id)
1246     })
1247     .await??;
1248     if local_user_view.person.id != orig_private_message.creator_id {
1249       return Err(ApiError::err("no_private_message_edit_allowed").into());
1250     }
1251
1252     // Doing the update
1253     let private_message_id = data.private_message_id;
1254     let deleted = data.deleted;
1255     let updated_private_message = match blocking(context.pool(), move |conn| {
1256       PrivateMessage::update_deleted(conn, private_message_id, deleted)
1257     })
1258     .await?
1259     {
1260       Ok(private_message) => private_message,
1261       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1262     };
1263
1264     // Send the apub update
1265     if data.deleted {
1266       updated_private_message
1267         .send_delete(&local_user_view.person, context)
1268         .await?;
1269     } else {
1270       updated_private_message
1271         .send_undo_delete(&local_user_view.person, context)
1272         .await?;
1273     }
1274
1275     let private_message_id = data.private_message_id;
1276     let private_message_view = blocking(context.pool(), move |conn| {
1277       PrivateMessageView::read(conn, private_message_id)
1278     })
1279     .await??;
1280
1281     let res = PrivateMessageResponse {
1282       private_message_view,
1283     };
1284
1285     // Send notifications to the local recipient, if one exists
1286     let recipient_id = orig_private_message.recipient_id;
1287     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
1288       LocalUserView::read_person(conn, recipient_id)
1289     })
1290     .await?
1291     {
1292       let local_recipient_id = local_recipient.local_user.id;
1293       context.chat_server().do_send(SendUserRoomMessage {
1294         op: UserOperation::DeletePrivateMessage,
1295         response: res.clone(),
1296         local_recipient_id,
1297         websocket_id,
1298       });
1299     }
1300
1301     Ok(res)
1302   }
1303 }
1304
1305 #[async_trait::async_trait(?Send)]
1306 impl Perform for MarkPrivateMessageAsRead {
1307   type Response = PrivateMessageResponse;
1308
1309   async fn perform(
1310     &self,
1311     context: &Data<LemmyContext>,
1312     websocket_id: Option<ConnectionId>,
1313   ) -> Result<PrivateMessageResponse, LemmyError> {
1314     let data: &MarkPrivateMessageAsRead = &self;
1315     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1316
1317     // Checking permissions
1318     let private_message_id = data.private_message_id;
1319     let orig_private_message = blocking(context.pool(), move |conn| {
1320       PrivateMessage::read(conn, private_message_id)
1321     })
1322     .await??;
1323     if local_user_view.person.id != orig_private_message.recipient_id {
1324       return Err(ApiError::err("couldnt_update_private_message").into());
1325     }
1326
1327     // Doing the update
1328     let private_message_id = data.private_message_id;
1329     let read = data.read;
1330     match blocking(context.pool(), move |conn| {
1331       PrivateMessage::update_read(conn, private_message_id, read)
1332     })
1333     .await?
1334     {
1335       Ok(private_message) => private_message,
1336       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1337     };
1338
1339     // No need to send an apub update
1340     let private_message_id = data.private_message_id;
1341     let private_message_view = blocking(context.pool(), move |conn| {
1342       PrivateMessageView::read(conn, private_message_id)
1343     })
1344     .await??;
1345
1346     let res = PrivateMessageResponse {
1347       private_message_view,
1348     };
1349
1350     // Send notifications to the local recipient, if one exists
1351     let recipient_id = orig_private_message.recipient_id;
1352     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
1353       LocalUserView::read_person(conn, recipient_id)
1354     })
1355     .await?
1356     {
1357       let local_recipient_id = local_recipient.local_user.id;
1358       context.chat_server().do_send(SendUserRoomMessage {
1359         op: UserOperation::MarkPrivateMessageAsRead,
1360         response: res.clone(),
1361         local_recipient_id,
1362         websocket_id,
1363       });
1364     }
1365
1366     Ok(res)
1367   }
1368 }
1369
1370 #[async_trait::async_trait(?Send)]
1371 impl Perform for GetPrivateMessages {
1372   type Response = PrivateMessagesResponse;
1373
1374   async fn perform(
1375     &self,
1376     context: &Data<LemmyContext>,
1377     _websocket_id: Option<ConnectionId>,
1378   ) -> Result<PrivateMessagesResponse, LemmyError> {
1379     let data: &GetPrivateMessages = &self;
1380     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1381     let person_id = local_user_view.person.id;
1382
1383     let page = data.page;
1384     let limit = data.limit;
1385     let unread_only = data.unread_only;
1386     let messages = blocking(context.pool(), move |conn| {
1387       PrivateMessageQueryBuilder::create(&conn, person_id)
1388         .page(page)
1389         .limit(limit)
1390         .unread_only(unread_only)
1391         .list()
1392     })
1393     .await??;
1394
1395     Ok(PrivateMessagesResponse {
1396       private_messages: messages,
1397     })
1398   }
1399 }
1400
1401 #[async_trait::async_trait(?Send)]
1402 impl Perform for GetReportCount {
1403   type Response = GetReportCountResponse;
1404
1405   async fn perform(
1406     &self,
1407     context: &Data<LemmyContext>,
1408     websocket_id: Option<ConnectionId>,
1409   ) -> Result<GetReportCountResponse, LemmyError> {
1410     let data: &GetReportCount = &self;
1411     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1412
1413     let person_id = local_user_view.person.id;
1414     let community_id = data.community;
1415     let community_ids =
1416       collect_moderated_communities(person_id, community_id, context.pool()).await?;
1417
1418     let res = {
1419       if community_ids.is_empty() {
1420         GetReportCountResponse {
1421           community: None,
1422           comment_reports: 0,
1423           post_reports: 0,
1424         }
1425       } else {
1426         let ids = community_ids.clone();
1427         let comment_reports = blocking(context.pool(), move |conn| {
1428           CommentReportView::get_report_count(conn, &ids)
1429         })
1430         .await??;
1431
1432         let ids = community_ids.clone();
1433         let post_reports = blocking(context.pool(), move |conn| {
1434           PostReportView::get_report_count(conn, &ids)
1435         })
1436         .await??;
1437
1438         GetReportCountResponse {
1439           community: data.community,
1440           comment_reports,
1441           post_reports,
1442         }
1443       }
1444     };
1445
1446     context.chat_server().do_send(SendUserRoomMessage {
1447       op: UserOperation::GetReportCount,
1448       response: res.clone(),
1449       local_recipient_id: local_user_view.local_user.id,
1450       websocket_id,
1451     });
1452
1453     Ok(res)
1454   }
1455 }