2 api::{claims::Claims, get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform},
4 captcha_espeak_wav_base64,
7 use actix_web::web::Data;
10 use captcha::{gen, Difficulty};
17 diesel_option_overwrite,
20 password_reset_request::*,
24 private_message_view::*,
50 apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
56 generate_random_string,
57 is_valid_preferred_username,
67 use std::str::FromStr;
69 #[async_trait::async_trait(?Send)]
70 impl Perform for Login {
71 type Response = LoginResponse;
75 context: &Data<LemmyContext>,
76 _websocket_id: Option<ConnectionId>,
77 ) -> Result<LoginResponse, LemmyError> {
78 let data: &Login = &self;
80 // Fetch that username / email
81 let username_or_email = data.username_or_email.clone();
82 let user = match blocking(context.pool(), move |conn| {
83 User_::find_by_email_or_username(conn, &username_or_email)
88 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
91 // Verify the password
92 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
94 return Err(APIError::err("password_incorrect").into());
99 jwt: Claims::jwt(user, Settings::get().hostname)?,
104 #[async_trait::async_trait(?Send)]
105 impl Perform for Register {
106 type Response = LoginResponse;
110 context: &Data<LemmyContext>,
111 _websocket_id: Option<ConnectionId>,
112 ) -> Result<LoginResponse, LemmyError> {
113 let data: &Register = &self;
115 // Make sure site has open registration
116 if let Ok(site) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
117 let site: SiteView = site;
118 if !site.open_registration {
119 return Err(APIError::err("registration_closed").into());
123 // Make sure passwords match
124 if data.password != data.password_verify {
125 return Err(APIError::err("passwords_dont_match").into());
128 // If its not the admin, check the captcha
129 if !data.admin && Settings::get().captcha.enabled {
136 .unwrap_or_else(|| "".to_string()),
140 .unwrap_or_else(|| "".to_string()),
144 return Err(APIError::err("captcha_incorrect").into());
148 check_slurs(&data.username)?;
150 // Make sure there are no admins
151 let any_admins = blocking(context.pool(), move |conn| {
152 UserView::admins(conn).map(|a| a.is_empty())
155 if data.admin && !any_admins {
156 return Err(APIError::err("admin_already_created").into());
159 let user_keypair = generate_actor_keypair()?;
160 if !is_valid_username(&data.username) {
161 return Err(APIError::err("invalid_username").into());
164 // Register the new user
165 let user_form = UserForm {
166 name: data.username.to_owned(),
167 email: Some(data.email.to_owned()),
168 matrix_user_id: None,
171 password_encrypted: data.password.to_owned(),
172 preferred_username: None,
177 show_nsfw: data.show_nsfw,
178 theme: "darkly".into(),
179 default_sort_type: SortType::Active as i16,
180 default_listing_type: ListingType::Subscribed as i16,
181 lang: "browser".into(),
183 send_notifications_to_email: false,
184 actor_id: Some(make_apub_endpoint(EndpointType::User, &data.username).to_string()),
187 private_key: Some(user_keypair.private_key),
188 public_key: Some(user_keypair.public_key),
189 last_refreshed_at: None,
193 let inserted_user = match blocking(context.pool(), move |conn| {
194 User_::register(conn, &user_form)
200 let err_type = if e.to_string()
201 == "duplicate key value violates unique constraint \"user__email_key\""
203 "email_already_exists"
205 "user_already_exists"
208 return Err(APIError::err(err_type).into());
212 let main_community_keypair = generate_actor_keypair()?;
214 // Create the main community if it doesn't exist
216 match blocking(context.pool(), move |conn| Community::read(conn, 2)).await? {
219 let default_community_name = "main";
220 let community_form = CommunityForm {
221 name: default_community_name.to_string(),
222 title: "The Default Community".to_string(),
223 description: Some("The Default Community".to_string()),
226 creator_id: inserted_user.id,
231 make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
234 private_key: Some(main_community_keypair.private_key),
235 public_key: Some(main_community_keypair.public_key),
236 last_refreshed_at: None,
241 blocking(context.pool(), move |conn| {
242 Community::create(conn, &community_form)
248 // Sign them up for main community no matter what
249 let community_follower_form = CommunityFollowerForm {
250 community_id: main_community.id,
251 user_id: inserted_user.id,
254 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
255 if blocking(context.pool(), follow).await?.is_err() {
256 return Err(APIError::err("community_follower_already_exists").into());
259 // If its an admin, add them as a mod and follower to main
261 let community_moderator_form = CommunityModeratorForm {
262 community_id: main_community.id,
263 user_id: inserted_user.id,
266 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
267 if blocking(context.pool(), join).await?.is_err() {
268 return Err(APIError::err("community_moderator_already_exists").into());
274 jwt: Claims::jwt(inserted_user, Settings::get().hostname)?,
279 #[async_trait::async_trait(?Send)]
280 impl Perform for GetCaptcha {
281 type Response = GetCaptchaResponse;
285 context: &Data<LemmyContext>,
286 _websocket_id: Option<ConnectionId>,
287 ) -> Result<Self::Response, LemmyError> {
288 let captcha_settings = Settings::get().captcha;
290 if !captcha_settings.enabled {
291 return Ok(GetCaptchaResponse { ok: None });
294 let captcha = match captcha_settings.difficulty.as_str() {
295 "easy" => gen(Difficulty::Easy),
296 "medium" => gen(Difficulty::Medium),
297 "hard" => gen(Difficulty::Hard),
298 _ => gen(Difficulty::Medium),
301 let answer = captcha.chars_as_string();
303 let png_byte_array = captcha.as_png().expect("failed to generate captcha");
305 let png = base64::encode(png_byte_array);
307 let uuid = uuid::Uuid::new_v4().to_string();
309 let wav = captcha_espeak_wav_base64(&answer).ok();
311 let captcha_item = CaptchaItem {
313 uuid: uuid.to_owned(),
314 expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
317 // Stores the captcha item on the queue
318 context.chat_server().do_send(captcha_item);
320 Ok(GetCaptchaResponse {
321 ok: Some(CaptchaResponse { png, uuid, wav }),
326 #[async_trait::async_trait(?Send)]
327 impl Perform for SaveUserSettings {
328 type Response = LoginResponse;
332 context: &Data<LemmyContext>,
333 _websocket_id: Option<ConnectionId>,
334 ) -> Result<LoginResponse, LemmyError> {
335 let data: &SaveUserSettings = &self;
336 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
338 let user_id = user.id;
339 let read_user = blocking(context.pool(), move |conn| User_::read(conn, user_id)).await??;
341 let bio = match &data.bio {
343 if bio.chars().count() <= 300 {
346 return Err(APIError::err("bio_length_overflow").into());
349 None => read_user.bio,
352 let avatar = diesel_option_overwrite(&data.avatar);
353 let banner = diesel_option_overwrite(&data.banner);
354 let email = diesel_option_overwrite(&data.email);
356 // The DB constraint should stop too many characters
357 let preferred_username = match &data.preferred_username {
358 Some(preferred_username) => {
359 if !is_valid_preferred_username(preferred_username.trim()) {
360 return Err(APIError::err("invalid_username").into());
362 Some(preferred_username.trim().to_string())
364 None => read_user.preferred_username,
367 let password_encrypted = match &data.new_password {
368 Some(new_password) => {
369 match &data.new_password_verify {
370 Some(new_password_verify) => {
371 // Make sure passwords match
372 if new_password != new_password_verify {
373 return Err(APIError::err("passwords_dont_match").into());
376 // Check the old password
377 match &data.old_password {
378 Some(old_password) => {
380 verify(old_password, &read_user.password_encrypted).unwrap_or(false);
382 return Err(APIError::err("password_incorrect").into());
384 let new_password = new_password.to_owned();
385 let user = blocking(context.pool(), move |conn| {
386 User_::update_password(conn, user_id, &new_password)
389 user.password_encrypted
391 None => return Err(APIError::err("password_incorrect").into()),
394 None => return Err(APIError::err("passwords_dont_match").into()),
397 None => read_user.password_encrypted,
400 let user_form = UserForm {
401 name: read_user.name,
403 matrix_user_id: data.matrix_user_id.to_owned(),
408 published: Some(read_user.published),
409 updated: Some(naive_now()),
410 admin: read_user.admin,
411 banned: read_user.banned,
412 show_nsfw: data.show_nsfw,
413 theme: data.theme.to_owned(),
414 default_sort_type: data.default_sort_type,
415 default_listing_type: data.default_listing_type,
416 lang: data.lang.to_owned(),
417 show_avatars: data.show_avatars,
418 send_notifications_to_email: data.send_notifications_to_email,
419 actor_id: Some(read_user.actor_id),
421 local: read_user.local,
422 private_key: read_user.private_key,
423 public_key: read_user.public_key,
424 last_refreshed_at: None,
427 let res = blocking(context.pool(), move |conn| {
428 User_::update(conn, user_id, &user_form)
431 let updated_user: User_ = match res {
434 let err_type = if e.to_string()
435 == "duplicate key value violates unique constraint \"user__email_key\""
437 "email_already_exists"
439 "user_already_exists"
442 return Err(APIError::err(err_type).into());
448 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
453 #[async_trait::async_trait(?Send)]
454 impl Perform for GetUserDetails {
455 type Response = GetUserDetailsResponse;
459 context: &Data<LemmyContext>,
460 _websocket_id: Option<ConnectionId>,
461 ) -> Result<GetUserDetailsResponse, LemmyError> {
462 let data: &GetUserDetails = &self;
463 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
465 let show_nsfw = match &user {
466 Some(user) => user.show_nsfw,
470 let sort = SortType::from_str(&data.sort)?;
475 .unwrap_or_else(|| "admin".to_string());
476 let user_details_id = match data.user_id {
479 let user = blocking(context.pool(), move |conn| {
480 User_::read_from_name(conn, &username)
485 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
490 let user_view = blocking(context.pool(), move |conn| {
491 UserView::get_user_secure(conn, user_details_id)
495 let page = data.page;
496 let limit = data.limit;
497 let saved_only = data.saved_only;
498 let community_id = data.community_id;
499 let user_id = user.map(|u| u.id);
500 let (posts, comments) = blocking(context.pool(), move |conn| {
501 let mut posts_query = PostQueryBuilder::create(conn)
503 .show_nsfw(show_nsfw)
504 .saved_only(saved_only)
505 .for_community_id(community_id)
510 let mut comments_query = CommentQueryBuilder::create(conn)
512 .saved_only(saved_only)
517 // If its saved only, you don't care what creator it was
518 // Or, if its not saved, then you only want it for that specific creator
520 posts_query = posts_query.for_creator_id(user_details_id);
521 comments_query = comments_query.for_creator_id(user_details_id);
524 let posts = posts_query.list()?;
525 let comments = comments_query.list()?;
527 Ok((posts, comments)) as Result<_, LemmyError>
531 let follows = blocking(context.pool(), move |conn| {
532 CommunityFollowerView::for_user(conn, user_details_id)
535 let moderates = blocking(context.pool(), move |conn| {
536 CommunityModeratorView::for_user(conn, user_details_id)
541 Ok(GetUserDetailsResponse {
551 #[async_trait::async_trait(?Send)]
552 impl Perform for AddAdmin {
553 type Response = AddAdminResponse;
557 context: &Data<LemmyContext>,
558 websocket_id: Option<ConnectionId>,
559 ) -> Result<AddAdminResponse, LemmyError> {
560 let data: &AddAdmin = &self;
561 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
563 // Make sure user is an admin
564 is_admin(context.pool(), user.id).await?;
566 let added = data.added;
567 let added_user_id = data.user_id;
568 let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
569 if blocking(context.pool(), add_admin).await?.is_err() {
570 return Err(APIError::err("couldnt_update_user").into());
574 let form = ModAddForm {
575 mod_user_id: user.id,
576 other_user_id: data.user_id,
577 removed: Some(!data.added),
580 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
582 let site_creator_id = blocking(context.pool(), move |conn| {
583 Site::read(conn, 1).map(|s| s.creator_id)
587 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
588 let creator_index = admins
590 .position(|r| r.id == site_creator_id)
591 .context(location_info!())?;
592 let creator_user = admins.remove(creator_index);
593 admins.insert(0, creator_user);
595 let res = AddAdminResponse { admins };
597 context.chat_server().do_send(SendAllMessage {
598 op: UserOperation::AddAdmin,
599 response: res.clone(),
607 #[async_trait::async_trait(?Send)]
608 impl Perform for BanUser {
609 type Response = BanUserResponse;
613 context: &Data<LemmyContext>,
614 websocket_id: Option<ConnectionId>,
615 ) -> Result<BanUserResponse, LemmyError> {
616 let data: &BanUser = &self;
617 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
619 // Make sure user is an admin
620 is_admin(context.pool(), user.id).await?;
623 let banned_user_id = data.user_id;
624 let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
625 if blocking(context.pool(), ban_user).await?.is_err() {
626 return Err(APIError::err("couldnt_update_user").into());
629 // Remove their data if that's desired
630 if let Some(remove_data) = data.remove_data {
632 blocking(context.pool(), move |conn: &'_ _| {
633 Post::update_removed_for_creator(conn, banned_user_id, None, remove_data)
638 blocking(context.pool(), move |conn: &'_ _| {
639 Community::update_removed_for_creator(conn, banned_user_id, remove_data)
644 blocking(context.pool(), move |conn: &'_ _| {
645 Comment::update_removed_for_creator(conn, banned_user_id, remove_data)
651 let expires = match data.expires {
652 Some(time) => Some(naive_from_unix(time)),
656 let form = ModBanForm {
657 mod_user_id: user.id,
658 other_user_id: data.user_id,
659 reason: data.reason.to_owned(),
660 banned: Some(data.ban),
664 blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
666 let user_id = data.user_id;
667 let user_view = blocking(context.pool(), move |conn| {
668 UserView::get_user_secure(conn, user_id)
672 let res = BanUserResponse {
677 context.chat_server().do_send(SendAllMessage {
678 op: UserOperation::BanUser,
679 response: res.clone(),
687 #[async_trait::async_trait(?Send)]
688 impl Perform for GetReplies {
689 type Response = GetRepliesResponse;
693 context: &Data<LemmyContext>,
694 _websocket_id: Option<ConnectionId>,
695 ) -> Result<GetRepliesResponse, LemmyError> {
696 let data: &GetReplies = &self;
697 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
699 let sort = SortType::from_str(&data.sort)?;
701 let page = data.page;
702 let limit = data.limit;
703 let unread_only = data.unread_only;
704 let user_id = user.id;
705 let replies = blocking(context.pool(), move |conn| {
706 ReplyQueryBuilder::create(conn, user_id)
708 .unread_only(unread_only)
715 Ok(GetRepliesResponse { replies })
719 #[async_trait::async_trait(?Send)]
720 impl Perform for GetUserMentions {
721 type Response = GetUserMentionsResponse;
725 context: &Data<LemmyContext>,
726 _websocket_id: Option<ConnectionId>,
727 ) -> Result<GetUserMentionsResponse, LemmyError> {
728 let data: &GetUserMentions = &self;
729 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
731 let sort = SortType::from_str(&data.sort)?;
733 let page = data.page;
734 let limit = data.limit;
735 let unread_only = data.unread_only;
736 let user_id = user.id;
737 let mentions = blocking(context.pool(), move |conn| {
738 UserMentionQueryBuilder::create(conn, user_id)
740 .unread_only(unread_only)
747 Ok(GetUserMentionsResponse { mentions })
751 #[async_trait::async_trait(?Send)]
752 impl Perform for MarkUserMentionAsRead {
753 type Response = UserMentionResponse;
757 context: &Data<LemmyContext>,
758 _websocket_id: Option<ConnectionId>,
759 ) -> Result<UserMentionResponse, LemmyError> {
760 let data: &MarkUserMentionAsRead = &self;
761 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
763 let user_mention_id = data.user_mention_id;
764 let read_user_mention = blocking(context.pool(), move |conn| {
765 UserMention::read(conn, user_mention_id)
769 if user.id != read_user_mention.recipient_id {
770 return Err(APIError::err("couldnt_update_comment").into());
773 let user_mention_id = read_user_mention.id;
774 let read = data.read;
775 let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
776 if blocking(context.pool(), update_mention).await?.is_err() {
777 return Err(APIError::err("couldnt_update_comment").into());
780 let user_mention_id = read_user_mention.id;
781 let user_id = user.id;
782 let user_mention_view = blocking(context.pool(), move |conn| {
783 UserMentionView::read(conn, user_mention_id, user_id)
787 Ok(UserMentionResponse {
788 mention: user_mention_view,
793 #[async_trait::async_trait(?Send)]
794 impl Perform for MarkAllAsRead {
795 type Response = GetRepliesResponse;
799 context: &Data<LemmyContext>,
800 _websocket_id: Option<ConnectionId>,
801 ) -> Result<GetRepliesResponse, LemmyError> {
802 let data: &MarkAllAsRead = &self;
803 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
805 let user_id = user.id;
806 let replies = blocking(context.pool(), move |conn| {
807 ReplyQueryBuilder::create(conn, user_id)
815 // TODO: this should probably be a bulk operation
816 // Not easy to do as a bulk operation,
817 // because recipient_id isn't in the comment table
818 for reply in &replies {
819 let reply_id = reply.id;
820 let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
821 if blocking(context.pool(), mark_as_read).await?.is_err() {
822 return Err(APIError::err("couldnt_update_comment").into());
826 // Mark all user mentions as read
827 let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
828 if blocking(context.pool(), update_user_mentions)
832 return Err(APIError::err("couldnt_update_comment").into());
835 // Mark all private_messages as read
836 let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
837 if blocking(context.pool(), update_pm).await?.is_err() {
838 return Err(APIError::err("couldnt_update_private_message").into());
841 Ok(GetRepliesResponse { replies: vec![] })
845 #[async_trait::async_trait(?Send)]
846 impl Perform for DeleteAccount {
847 type Response = LoginResponse;
851 context: &Data<LemmyContext>,
852 _websocket_id: Option<ConnectionId>,
853 ) -> Result<LoginResponse, LemmyError> {
854 let data: &DeleteAccount = &self;
855 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
857 // Verify the password
858 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
860 return Err(APIError::err("password_incorrect").into());
864 let user_id = user.id;
865 let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
866 if blocking(context.pool(), permadelete).await?.is_err() {
867 return Err(APIError::err("couldnt_update_comment").into());
871 let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
872 if blocking(context.pool(), permadelete).await?.is_err() {
873 return Err(APIError::err("couldnt_update_post").into());
877 jwt: data.auth.to_owned(),
882 #[async_trait::async_trait(?Send)]
883 impl Perform for PasswordReset {
884 type Response = PasswordResetResponse;
888 context: &Data<LemmyContext>,
889 _websocket_id: Option<ConnectionId>,
890 ) -> Result<PasswordResetResponse, LemmyError> {
891 let data: &PasswordReset = &self;
894 let email = data.email.clone();
895 let user = match blocking(context.pool(), move |conn| {
896 User_::find_by_email(conn, &email)
901 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
904 // Generate a random token
905 let token = generate_random_string();
908 let token2 = token.clone();
909 let user_id = user.id;
910 blocking(context.pool(), move |conn| {
911 PasswordResetRequest::create_token(conn, user_id, &token2)
915 // Email the pure token to the user.
916 // TODO no i18n support here.
917 let user_email = &user.email.expect("email");
918 let subject = &format!("Password reset for {}", user.name);
919 let hostname = &format!("https://{}", Settings::get().hostname); //TODO add https for now.
920 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);
921 match send_email(subject, user_email, &user.name, html) {
923 Err(_e) => return Err(APIError::err(&_e).into()),
926 Ok(PasswordResetResponse {})
930 #[async_trait::async_trait(?Send)]
931 impl Perform for PasswordChange {
932 type Response = LoginResponse;
936 context: &Data<LemmyContext>,
937 _websocket_id: Option<ConnectionId>,
938 ) -> Result<LoginResponse, LemmyError> {
939 let data: &PasswordChange = &self;
941 // Fetch the user_id from the token
942 let token = data.token.clone();
943 let user_id = blocking(context.pool(), move |conn| {
944 PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
948 // Make sure passwords match
949 if data.password != data.password_verify {
950 return Err(APIError::err("passwords_dont_match").into());
953 // Update the user with the new password
954 let password = data.password.clone();
955 let updated_user = match blocking(context.pool(), move |conn| {
956 User_::update_password(conn, user_id, &password)
961 Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
966 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
971 #[async_trait::async_trait(?Send)]
972 impl Perform for CreatePrivateMessage {
973 type Response = PrivateMessageResponse;
977 context: &Data<LemmyContext>,
978 websocket_id: Option<ConnectionId>,
979 ) -> Result<PrivateMessageResponse, LemmyError> {
980 let data: &CreatePrivateMessage = &self;
981 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
983 let hostname = &format!("https://{}", Settings::get().hostname);
985 let content_slurs_removed = remove_slurs(&data.content.to_owned());
987 let private_message_form = PrivateMessageForm {
988 content: content_slurs_removed.to_owned(),
990 recipient_id: data.recipient_id,
999 let inserted_private_message = match blocking(context.pool(), move |conn| {
1000 PrivateMessage::create(conn, &private_message_form)
1004 Ok(private_message) => private_message,
1006 return Err(APIError::err("couldnt_create_private_message").into());
1010 let inserted_private_message_id = inserted_private_message.id;
1011 let updated_private_message = match blocking(context.pool(), move |conn| {
1012 let apub_id = make_apub_endpoint(
1013 EndpointType::PrivateMessage,
1014 &inserted_private_message_id.to_string(),
1017 PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
1021 Ok(private_message) => private_message,
1022 Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
1025 updated_private_message.send_create(&user, context).await?;
1027 // Send notifications to the recipient
1028 let recipient_id = data.recipient_id;
1029 let recipient_user =
1030 blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1031 if recipient_user.send_notifications_to_email {
1032 if let Some(email) = recipient_user.email {
1033 let subject = &format!(
1034 "{} - Private Message from {}",
1035 Settings::get().hostname,
1038 let html = &format!(
1039 "<h1>Private Message</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
1040 user.name, &content_slurs_removed, hostname
1042 match send_email(subject, &email, &recipient_user.name, html) {
1044 Err(e) => error!("{}", e),
1049 let message = blocking(context.pool(), move |conn| {
1050 PrivateMessageView::read(conn, inserted_private_message.id)
1054 let res = PrivateMessageResponse { message };
1056 context.chat_server().do_send(SendUserRoomMessage {
1057 op: UserOperation::CreatePrivateMessage,
1058 response: res.clone(),
1067 #[async_trait::async_trait(?Send)]
1068 impl Perform for EditPrivateMessage {
1069 type Response = PrivateMessageResponse;
1073 context: &Data<LemmyContext>,
1074 websocket_id: Option<ConnectionId>,
1075 ) -> Result<PrivateMessageResponse, LemmyError> {
1076 let data: &EditPrivateMessage = &self;
1077 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1079 // Checking permissions
1080 let edit_id = data.edit_id;
1081 let orig_private_message = blocking(context.pool(), move |conn| {
1082 PrivateMessage::read(conn, edit_id)
1085 if user.id != orig_private_message.creator_id {
1086 return Err(APIError::err("no_private_message_edit_allowed").into());
1090 let content_slurs_removed = remove_slurs(&data.content);
1091 let edit_id = data.edit_id;
1092 let updated_private_message = match blocking(context.pool(), move |conn| {
1093 PrivateMessage::update_content(conn, edit_id, &content_slurs_removed)
1097 Ok(private_message) => private_message,
1098 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1101 // Send the apub update
1102 updated_private_message.send_update(&user, context).await?;
1104 let edit_id = data.edit_id;
1105 let message = blocking(context.pool(), move |conn| {
1106 PrivateMessageView::read(conn, edit_id)
1109 let recipient_id = message.recipient_id;
1111 let res = PrivateMessageResponse { message };
1113 context.chat_server().do_send(SendUserRoomMessage {
1114 op: UserOperation::EditPrivateMessage,
1115 response: res.clone(),
1124 #[async_trait::async_trait(?Send)]
1125 impl Perform for DeletePrivateMessage {
1126 type Response = PrivateMessageResponse;
1130 context: &Data<LemmyContext>,
1131 websocket_id: Option<ConnectionId>,
1132 ) -> Result<PrivateMessageResponse, LemmyError> {
1133 let data: &DeletePrivateMessage = &self;
1134 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1136 // Checking permissions
1137 let edit_id = data.edit_id;
1138 let orig_private_message = blocking(context.pool(), move |conn| {
1139 PrivateMessage::read(conn, edit_id)
1142 if user.id != orig_private_message.creator_id {
1143 return Err(APIError::err("no_private_message_edit_allowed").into());
1147 let edit_id = data.edit_id;
1148 let deleted = data.deleted;
1149 let updated_private_message = match blocking(context.pool(), move |conn| {
1150 PrivateMessage::update_deleted(conn, edit_id, deleted)
1154 Ok(private_message) => private_message,
1155 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1158 // Send the apub update
1160 updated_private_message.send_delete(&user, context).await?;
1162 updated_private_message
1163 .send_undo_delete(&user, context)
1167 let edit_id = data.edit_id;
1168 let message = blocking(context.pool(), move |conn| {
1169 PrivateMessageView::read(conn, edit_id)
1172 let recipient_id = message.recipient_id;
1174 let res = PrivateMessageResponse { message };
1176 context.chat_server().do_send(SendUserRoomMessage {
1177 op: UserOperation::DeletePrivateMessage,
1178 response: res.clone(),
1187 #[async_trait::async_trait(?Send)]
1188 impl Perform for MarkPrivateMessageAsRead {
1189 type Response = PrivateMessageResponse;
1193 context: &Data<LemmyContext>,
1194 websocket_id: Option<ConnectionId>,
1195 ) -> Result<PrivateMessageResponse, LemmyError> {
1196 let data: &MarkPrivateMessageAsRead = &self;
1197 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1199 // Checking permissions
1200 let edit_id = data.edit_id;
1201 let orig_private_message = blocking(context.pool(), move |conn| {
1202 PrivateMessage::read(conn, edit_id)
1205 if user.id != orig_private_message.recipient_id {
1206 return Err(APIError::err("couldnt_update_private_message").into());
1210 let edit_id = data.edit_id;
1211 let read = data.read;
1212 match blocking(context.pool(), move |conn| {
1213 PrivateMessage::update_read(conn, edit_id, read)
1217 Ok(private_message) => private_message,
1218 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1221 // No need to send an apub update
1223 let edit_id = data.edit_id;
1224 let message = blocking(context.pool(), move |conn| {
1225 PrivateMessageView::read(conn, edit_id)
1228 let recipient_id = message.recipient_id;
1230 let res = PrivateMessageResponse { message };
1232 context.chat_server().do_send(SendUserRoomMessage {
1233 op: UserOperation::MarkPrivateMessageAsRead,
1234 response: res.clone(),
1243 #[async_trait::async_trait(?Send)]
1244 impl Perform for GetPrivateMessages {
1245 type Response = PrivateMessagesResponse;
1249 context: &Data<LemmyContext>,
1250 _websocket_id: Option<ConnectionId>,
1251 ) -> Result<PrivateMessagesResponse, LemmyError> {
1252 let data: &GetPrivateMessages = &self;
1253 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1254 let user_id = user.id;
1256 let page = data.page;
1257 let limit = data.limit;
1258 let unread_only = data.unread_only;
1259 let messages = blocking(context.pool(), move |conn| {
1260 PrivateMessageQueryBuilder::create(&conn, user_id)
1263 .unread_only(unread_only)
1268 Ok(PrivateMessagesResponse { messages })
1272 #[async_trait::async_trait(?Send)]
1273 impl Perform for UserJoin {
1274 type Response = UserJoinResponse;
1278 context: &Data<LemmyContext>,
1279 websocket_id: Option<ConnectionId>,
1280 ) -> Result<UserJoinResponse, LemmyError> {
1281 let data: &UserJoin = &self;
1282 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1284 if let Some(ws_id) = websocket_id {
1285 context.chat_server().do_send(JoinUserRoom {
1291 Ok(UserJoinResponse { joined: true })