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