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