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