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};
29 diesel_option_overwrite,
32 password_reset_request::*,
36 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,
176 show_nsfw: data.show_nsfw,
177 theme: "darkly".into(),
178 default_sort_type: SortType::Active as i16,
179 default_listing_type: ListingType::Subscribed as i16,
180 lang: "browser".into(),
182 send_notifications_to_email: false,
183 actor_id: Some(make_apub_endpoint(EndpointType::User, &data.username).to_string()),
186 private_key: Some(user_keypair.private_key),
187 public_key: Some(user_keypair.public_key),
188 last_refreshed_at: None,
192 let inserted_user = match blocking(context.pool(), move |conn| {
193 User_::register(conn, &user_form)
199 let err_type = if e.to_string()
200 == "duplicate key value violates unique constraint \"user__email_key\""
202 "email_already_exists"
204 "user_already_exists"
207 return Err(APIError::err(err_type).into());
211 let main_community_keypair = generate_actor_keypair()?;
213 // Create the main community if it doesn't exist
215 match blocking(context.pool(), move |conn| Community::read(conn, 2)).await? {
218 let default_community_name = "main";
219 let community_form = CommunityForm {
220 name: default_community_name.to_string(),
221 title: "The Default Community".to_string(),
222 description: Some("The Default Community".to_string()),
225 creator_id: inserted_user.id,
230 make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
233 private_key: Some(main_community_keypair.private_key),
234 public_key: Some(main_community_keypair.public_key),
235 last_refreshed_at: None,
240 blocking(context.pool(), move |conn| {
241 Community::create(conn, &community_form)
247 // Sign them up for main community no matter what
248 let community_follower_form = CommunityFollowerForm {
249 community_id: main_community.id,
250 user_id: inserted_user.id,
253 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
254 if blocking(context.pool(), follow).await?.is_err() {
255 return Err(APIError::err("community_follower_already_exists").into());
258 // If its an admin, add them as a mod and follower to main
260 let community_moderator_form = CommunityModeratorForm {
261 community_id: main_community.id,
262 user_id: inserted_user.id,
265 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
266 if blocking(context.pool(), join).await?.is_err() {
267 return Err(APIError::err("community_moderator_already_exists").into());
273 jwt: Claims::jwt(inserted_user, Settings::get().hostname)?,
278 #[async_trait::async_trait(?Send)]
279 impl Perform for GetCaptcha {
280 type Response = GetCaptchaResponse;
284 context: &Data<LemmyContext>,
285 _websocket_id: Option<ConnectionId>,
286 ) -> Result<Self::Response, LemmyError> {
287 let captcha_settings = Settings::get().captcha;
289 if !captcha_settings.enabled {
290 return Ok(GetCaptchaResponse { ok: None });
293 let captcha = match captcha_settings.difficulty.as_str() {
294 "easy" => gen(Difficulty::Easy),
295 "medium" => gen(Difficulty::Medium),
296 "hard" => gen(Difficulty::Hard),
297 _ => gen(Difficulty::Medium),
300 let answer = captcha.chars_as_string();
302 let png_byte_array = captcha.as_png().expect("failed to generate captcha");
304 let png = base64::encode(png_byte_array);
306 let uuid = uuid::Uuid::new_v4().to_string();
308 let wav = captcha_espeak_wav_base64(&answer).ok();
310 let captcha_item = CaptchaItem {
312 uuid: uuid.to_owned(),
313 expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
316 // Stores the captcha item on the queue
317 context.chat_server().do_send(captcha_item);
319 Ok(GetCaptchaResponse {
320 ok: Some(CaptchaResponse { png, uuid, wav }),
325 #[async_trait::async_trait(?Send)]
326 impl Perform for SaveUserSettings {
327 type Response = LoginResponse;
331 context: &Data<LemmyContext>,
332 _websocket_id: Option<ConnectionId>,
333 ) -> Result<LoginResponse, LemmyError> {
334 let data: &SaveUserSettings = &self;
335 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
337 let user_id = user.id;
338 let read_user = blocking(context.pool(), move |conn| User_::read(conn, user_id)).await??;
340 let bio = match &data.bio {
342 if bio.chars().count() <= 300 {
345 return Err(APIError::err("bio_length_overflow").into());
348 None => read_user.bio,
351 let avatar = diesel_option_overwrite(&data.avatar);
352 let banner = diesel_option_overwrite(&data.banner);
353 let email = diesel_option_overwrite(&data.email);
355 // The DB constraint should stop too many characters
356 let preferred_username = match &data.preferred_username {
357 Some(preferred_username) => {
358 if !is_valid_preferred_username(preferred_username.trim()) {
359 return Err(APIError::err("invalid_username").into());
361 Some(preferred_username.trim().to_string())
363 None => read_user.preferred_username,
366 let password_encrypted = match &data.new_password {
367 Some(new_password) => {
368 match &data.new_password_verify {
369 Some(new_password_verify) => {
370 // Make sure passwords match
371 if new_password != new_password_verify {
372 return Err(APIError::err("passwords_dont_match").into());
375 // Check the old password
376 match &data.old_password {
377 Some(old_password) => {
379 verify(old_password, &read_user.password_encrypted).unwrap_or(false);
381 return Err(APIError::err("password_incorrect").into());
383 let new_password = new_password.to_owned();
384 let user = blocking(context.pool(), move |conn| {
385 User_::update_password(conn, user_id, &new_password)
388 user.password_encrypted
390 None => return Err(APIError::err("password_incorrect").into()),
393 None => return Err(APIError::err("passwords_dont_match").into()),
396 None => read_user.password_encrypted,
399 let user_form = UserForm {
400 name: read_user.name,
402 matrix_user_id: data.matrix_user_id.to_owned(),
407 updated: Some(naive_now()),
408 admin: read_user.admin,
409 banned: read_user.banned,
410 show_nsfw: data.show_nsfw,
411 theme: data.theme.to_owned(),
412 default_sort_type: data.default_sort_type,
413 default_listing_type: data.default_listing_type,
414 lang: data.lang.to_owned(),
415 show_avatars: data.show_avatars,
416 send_notifications_to_email: data.send_notifications_to_email,
417 actor_id: Some(read_user.actor_id),
419 local: read_user.local,
420 private_key: read_user.private_key,
421 public_key: read_user.public_key,
422 last_refreshed_at: None,
425 let res = blocking(context.pool(), move |conn| {
426 User_::update(conn, user_id, &user_form)
429 let updated_user: User_ = match res {
432 let err_type = if e.to_string()
433 == "duplicate key value violates unique constraint \"user__email_key\""
435 "email_already_exists"
437 "user_already_exists"
440 return Err(APIError::err(err_type).into());
446 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
451 #[async_trait::async_trait(?Send)]
452 impl Perform for GetUserDetails {
453 type Response = GetUserDetailsResponse;
457 context: &Data<LemmyContext>,
458 _websocket_id: Option<ConnectionId>,
459 ) -> Result<GetUserDetailsResponse, LemmyError> {
460 let data: &GetUserDetails = &self;
461 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
463 let show_nsfw = match &user {
464 Some(user) => user.show_nsfw,
468 let sort = SortType::from_str(&data.sort)?;
473 .unwrap_or_else(|| "admin".to_string());
474 let user_details_id = match data.user_id {
477 let user = blocking(context.pool(), move |conn| {
478 User_::read_from_name(conn, &username)
483 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
488 let user_view = blocking(context.pool(), move |conn| {
489 UserView::get_user_secure(conn, user_details_id)
493 let page = data.page;
494 let limit = data.limit;
495 let saved_only = data.saved_only;
496 let community_id = data.community_id;
497 let user_id = user.map(|u| u.id);
498 let (posts, comments) = blocking(context.pool(), move |conn| {
499 let mut posts_query = PostQueryBuilder::create(conn)
501 .show_nsfw(show_nsfw)
502 .saved_only(saved_only)
503 .for_community_id(community_id)
508 let mut comments_query = CommentQueryBuilder::create(conn)
510 .saved_only(saved_only)
515 // If its saved only, you don't care what creator it was
516 // Or, if its not saved, then you only want it for that specific creator
518 posts_query = posts_query.for_creator_id(user_details_id);
519 comments_query = comments_query.for_creator_id(user_details_id);
522 let posts = posts_query.list()?;
523 let comments = comments_query.list()?;
525 Ok((posts, comments)) as Result<_, LemmyError>
529 let follows = blocking(context.pool(), move |conn| {
530 CommunityFollowerView::for_user(conn, user_details_id)
533 let moderates = blocking(context.pool(), move |conn| {
534 CommunityModeratorView::for_user(conn, user_details_id)
539 Ok(GetUserDetailsResponse {
549 #[async_trait::async_trait(?Send)]
550 impl Perform for AddAdmin {
551 type Response = AddAdminResponse;
555 context: &Data<LemmyContext>,
556 websocket_id: Option<ConnectionId>,
557 ) -> Result<AddAdminResponse, LemmyError> {
558 let data: &AddAdmin = &self;
559 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
561 // Make sure user is an admin
562 is_admin(context.pool(), user.id).await?;
564 let added = data.added;
565 let added_user_id = data.user_id;
566 let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
567 if blocking(context.pool(), add_admin).await?.is_err() {
568 return Err(APIError::err("couldnt_update_user").into());
572 let form = ModAddForm {
573 mod_user_id: user.id,
574 other_user_id: data.user_id,
575 removed: Some(!data.added),
578 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
580 let site_creator_id = blocking(context.pool(), move |conn| {
581 Site::read(conn, 1).map(|s| s.creator_id)
585 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
586 let creator_index = admins
588 .position(|r| r.id == site_creator_id)
589 .context(location_info!())?;
590 let creator_user = admins.remove(creator_index);
591 admins.insert(0, creator_user);
593 let res = AddAdminResponse { admins };
595 context.chat_server().do_send(SendAllMessage {
596 op: UserOperation::AddAdmin,
597 response: res.clone(),
605 #[async_trait::async_trait(?Send)]
606 impl Perform for BanUser {
607 type Response = BanUserResponse;
611 context: &Data<LemmyContext>,
612 websocket_id: Option<ConnectionId>,
613 ) -> Result<BanUserResponse, LemmyError> {
614 let data: &BanUser = &self;
615 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
617 // Make sure user is an admin
618 is_admin(context.pool(), user.id).await?;
621 let banned_user_id = data.user_id;
622 let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
623 if blocking(context.pool(), ban_user).await?.is_err() {
624 return Err(APIError::err("couldnt_update_user").into());
627 // Remove their data if that's desired
628 if let Some(remove_data) = data.remove_data {
630 blocking(context.pool(), move |conn: &'_ _| {
631 Post::update_removed_for_creator(conn, banned_user_id, None, remove_data)
636 blocking(context.pool(), move |conn: &'_ _| {
637 Community::update_removed_for_creator(conn, banned_user_id, remove_data)
642 blocking(context.pool(), move |conn: &'_ _| {
643 Comment::update_removed_for_creator(conn, banned_user_id, remove_data)
649 let expires = match data.expires {
650 Some(time) => Some(naive_from_unix(time)),
654 let form = ModBanForm {
655 mod_user_id: user.id,
656 other_user_id: data.user_id,
657 reason: data.reason.to_owned(),
658 banned: Some(data.ban),
662 blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
664 let user_id = data.user_id;
665 let user_view = blocking(context.pool(), move |conn| {
666 UserView::get_user_secure(conn, user_id)
670 let res = BanUserResponse {
675 context.chat_server().do_send(SendAllMessage {
676 op: UserOperation::BanUser,
677 response: res.clone(),
685 #[async_trait::async_trait(?Send)]
686 impl Perform for GetReplies {
687 type Response = GetRepliesResponse;
691 context: &Data<LemmyContext>,
692 _websocket_id: Option<ConnectionId>,
693 ) -> Result<GetRepliesResponse, LemmyError> {
694 let data: &GetReplies = &self;
695 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
697 let sort = SortType::from_str(&data.sort)?;
699 let page = data.page;
700 let limit = data.limit;
701 let unread_only = data.unread_only;
702 let user_id = user.id;
703 let replies = blocking(context.pool(), move |conn| {
704 ReplyQueryBuilder::create(conn, user_id)
706 .unread_only(unread_only)
713 Ok(GetRepliesResponse { replies })
717 #[async_trait::async_trait(?Send)]
718 impl Perform for GetUserMentions {
719 type Response = GetUserMentionsResponse;
723 context: &Data<LemmyContext>,
724 _websocket_id: Option<ConnectionId>,
725 ) -> Result<GetUserMentionsResponse, LemmyError> {
726 let data: &GetUserMentions = &self;
727 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
729 let sort = SortType::from_str(&data.sort)?;
731 let page = data.page;
732 let limit = data.limit;
733 let unread_only = data.unread_only;
734 let user_id = user.id;
735 let mentions = blocking(context.pool(), move |conn| {
736 UserMentionQueryBuilder::create(conn, user_id)
738 .unread_only(unread_only)
745 Ok(GetUserMentionsResponse { mentions })
749 #[async_trait::async_trait(?Send)]
750 impl Perform for MarkUserMentionAsRead {
751 type Response = UserMentionResponse;
755 context: &Data<LemmyContext>,
756 _websocket_id: Option<ConnectionId>,
757 ) -> Result<UserMentionResponse, LemmyError> {
758 let data: &MarkUserMentionAsRead = &self;
759 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
761 let user_mention_id = data.user_mention_id;
762 let read_user_mention = blocking(context.pool(), move |conn| {
763 UserMention::read(conn, user_mention_id)
767 if user.id != read_user_mention.recipient_id {
768 return Err(APIError::err("couldnt_update_comment").into());
771 let user_mention_id = read_user_mention.id;
772 let read = data.read;
773 let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
774 if blocking(context.pool(), update_mention).await?.is_err() {
775 return Err(APIError::err("couldnt_update_comment").into());
778 let user_mention_id = read_user_mention.id;
779 let user_id = user.id;
780 let user_mention_view = blocking(context.pool(), move |conn| {
781 UserMentionView::read(conn, user_mention_id, user_id)
785 Ok(UserMentionResponse {
786 mention: user_mention_view,
791 #[async_trait::async_trait(?Send)]
792 impl Perform for MarkAllAsRead {
793 type Response = GetRepliesResponse;
797 context: &Data<LemmyContext>,
798 _websocket_id: Option<ConnectionId>,
799 ) -> Result<GetRepliesResponse, LemmyError> {
800 let data: &MarkAllAsRead = &self;
801 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
803 let user_id = user.id;
804 let replies = blocking(context.pool(), move |conn| {
805 ReplyQueryBuilder::create(conn, user_id)
813 // TODO: this should probably be a bulk operation
814 // Not easy to do as a bulk operation,
815 // because recipient_id isn't in the comment table
816 for reply in &replies {
817 let reply_id = reply.id;
818 let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
819 if blocking(context.pool(), mark_as_read).await?.is_err() {
820 return Err(APIError::err("couldnt_update_comment").into());
824 // Mark all user mentions as read
825 let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
826 if blocking(context.pool(), update_user_mentions)
830 return Err(APIError::err("couldnt_update_comment").into());
833 // Mark all private_messages as read
834 let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
835 if blocking(context.pool(), update_pm).await?.is_err() {
836 return Err(APIError::err("couldnt_update_private_message").into());
839 Ok(GetRepliesResponse { replies: vec![] })
843 #[async_trait::async_trait(?Send)]
844 impl Perform for DeleteAccount {
845 type Response = LoginResponse;
849 context: &Data<LemmyContext>,
850 _websocket_id: Option<ConnectionId>,
851 ) -> Result<LoginResponse, LemmyError> {
852 let data: &DeleteAccount = &self;
853 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
855 // Verify the password
856 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
858 return Err(APIError::err("password_incorrect").into());
862 let user_id = user.id;
863 let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
864 if blocking(context.pool(), permadelete).await?.is_err() {
865 return Err(APIError::err("couldnt_update_comment").into());
869 let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
870 if blocking(context.pool(), permadelete).await?.is_err() {
871 return Err(APIError::err("couldnt_update_post").into());
875 jwt: data.auth.to_owned(),
880 #[async_trait::async_trait(?Send)]
881 impl Perform for PasswordReset {
882 type Response = PasswordResetResponse;
886 context: &Data<LemmyContext>,
887 _websocket_id: Option<ConnectionId>,
888 ) -> Result<PasswordResetResponse, LemmyError> {
889 let data: &PasswordReset = &self;
892 let email = data.email.clone();
893 let user = match blocking(context.pool(), move |conn| {
894 User_::find_by_email(conn, &email)
899 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
902 // Generate a random token
903 let token = generate_random_string();
906 let token2 = token.clone();
907 let user_id = user.id;
908 blocking(context.pool(), move |conn| {
909 PasswordResetRequest::create_token(conn, user_id, &token2)
913 // Email the pure token to the user.
914 // TODO no i18n support here.
915 let user_email = &user.email.expect("email");
916 let subject = &format!("Password reset for {}", user.name);
917 let hostname = &format!("https://{}", Settings::get().hostname); //TODO add https for now.
918 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);
919 match send_email(subject, user_email, &user.name, html) {
921 Err(_e) => return Err(APIError::err(&_e).into()),
924 Ok(PasswordResetResponse {})
928 #[async_trait::async_trait(?Send)]
929 impl Perform for PasswordChange {
930 type Response = LoginResponse;
934 context: &Data<LemmyContext>,
935 _websocket_id: Option<ConnectionId>,
936 ) -> Result<LoginResponse, LemmyError> {
937 let data: &PasswordChange = &self;
939 // Fetch the user_id from the token
940 let token = data.token.clone();
941 let user_id = blocking(context.pool(), move |conn| {
942 PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
946 // Make sure passwords match
947 if data.password != data.password_verify {
948 return Err(APIError::err("passwords_dont_match").into());
951 // Update the user with the new password
952 let password = data.password.clone();
953 let updated_user = match blocking(context.pool(), move |conn| {
954 User_::update_password(conn, user_id, &password)
959 Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
964 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
969 #[async_trait::async_trait(?Send)]
970 impl Perform for CreatePrivateMessage {
971 type Response = PrivateMessageResponse;
975 context: &Data<LemmyContext>,
976 websocket_id: Option<ConnectionId>,
977 ) -> Result<PrivateMessageResponse, LemmyError> {
978 let data: &CreatePrivateMessage = &self;
979 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
981 let hostname = &format!("https://{}", Settings::get().hostname);
983 let content_slurs_removed = remove_slurs(&data.content.to_owned());
985 let private_message_form = PrivateMessageForm {
986 content: content_slurs_removed.to_owned(),
988 recipient_id: data.recipient_id,
997 let inserted_private_message = match blocking(context.pool(), move |conn| {
998 PrivateMessage::create(conn, &private_message_form)
1002 Ok(private_message) => private_message,
1004 return Err(APIError::err("couldnt_create_private_message").into());
1008 let inserted_private_message_id = inserted_private_message.id;
1009 let updated_private_message = match blocking(context.pool(), move |conn| {
1010 let apub_id = make_apub_endpoint(
1011 EndpointType::PrivateMessage,
1012 &inserted_private_message_id.to_string(),
1015 PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
1019 Ok(private_message) => private_message,
1020 Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
1023 updated_private_message.send_create(&user, context).await?;
1025 // Send notifications to the recipient
1026 let recipient_id = data.recipient_id;
1027 let recipient_user =
1028 blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1029 if recipient_user.send_notifications_to_email {
1030 if let Some(email) = recipient_user.email {
1031 let subject = &format!(
1032 "{} - Private Message from {}",
1033 Settings::get().hostname,
1036 let html = &format!(
1037 "<h1>Private Message</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
1038 user.name, &content_slurs_removed, hostname
1040 match send_email(subject, &email, &recipient_user.name, html) {
1042 Err(e) => error!("{}", e),
1047 let message = blocking(context.pool(), move |conn| {
1048 PrivateMessageView::read(conn, inserted_private_message.id)
1052 let res = PrivateMessageResponse { message };
1054 context.chat_server().do_send(SendUserRoomMessage {
1055 op: UserOperation::CreatePrivateMessage,
1056 response: res.clone(),
1065 #[async_trait::async_trait(?Send)]
1066 impl Perform for EditPrivateMessage {
1067 type Response = PrivateMessageResponse;
1071 context: &Data<LemmyContext>,
1072 websocket_id: Option<ConnectionId>,
1073 ) -> Result<PrivateMessageResponse, LemmyError> {
1074 let data: &EditPrivateMessage = &self;
1075 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1077 // Checking permissions
1078 let edit_id = data.edit_id;
1079 let orig_private_message = blocking(context.pool(), move |conn| {
1080 PrivateMessage::read(conn, edit_id)
1083 if user.id != orig_private_message.creator_id {
1084 return Err(APIError::err("no_private_message_edit_allowed").into());
1088 let content_slurs_removed = remove_slurs(&data.content);
1089 let edit_id = data.edit_id;
1090 let updated_private_message = match blocking(context.pool(), move |conn| {
1091 PrivateMessage::update_content(conn, edit_id, &content_slurs_removed)
1095 Ok(private_message) => private_message,
1096 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1099 // Send the apub update
1100 updated_private_message.send_update(&user, context).await?;
1102 let edit_id = data.edit_id;
1103 let message = blocking(context.pool(), move |conn| {
1104 PrivateMessageView::read(conn, edit_id)
1107 let recipient_id = message.recipient_id;
1109 let res = PrivateMessageResponse { message };
1111 context.chat_server().do_send(SendUserRoomMessage {
1112 op: UserOperation::EditPrivateMessage,
1113 response: res.clone(),
1122 #[async_trait::async_trait(?Send)]
1123 impl Perform for DeletePrivateMessage {
1124 type Response = PrivateMessageResponse;
1128 context: &Data<LemmyContext>,
1129 websocket_id: Option<ConnectionId>,
1130 ) -> Result<PrivateMessageResponse, LemmyError> {
1131 let data: &DeletePrivateMessage = &self;
1132 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1134 // Checking permissions
1135 let edit_id = data.edit_id;
1136 let orig_private_message = blocking(context.pool(), move |conn| {
1137 PrivateMessage::read(conn, edit_id)
1140 if user.id != orig_private_message.creator_id {
1141 return Err(APIError::err("no_private_message_edit_allowed").into());
1145 let edit_id = data.edit_id;
1146 let deleted = data.deleted;
1147 let updated_private_message = match blocking(context.pool(), move |conn| {
1148 PrivateMessage::update_deleted(conn, edit_id, deleted)
1152 Ok(private_message) => private_message,
1153 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1156 // Send the apub update
1158 updated_private_message.send_delete(&user, context).await?;
1160 updated_private_message
1161 .send_undo_delete(&user, context)
1165 let edit_id = data.edit_id;
1166 let message = blocking(context.pool(), move |conn| {
1167 PrivateMessageView::read(conn, edit_id)
1170 let recipient_id = message.recipient_id;
1172 let res = PrivateMessageResponse { message };
1174 context.chat_server().do_send(SendUserRoomMessage {
1175 op: UserOperation::DeletePrivateMessage,
1176 response: res.clone(),
1185 #[async_trait::async_trait(?Send)]
1186 impl Perform for MarkPrivateMessageAsRead {
1187 type Response = PrivateMessageResponse;
1191 context: &Data<LemmyContext>,
1192 websocket_id: Option<ConnectionId>,
1193 ) -> Result<PrivateMessageResponse, LemmyError> {
1194 let data: &MarkPrivateMessageAsRead = &self;
1195 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1197 // Checking permissions
1198 let edit_id = data.edit_id;
1199 let orig_private_message = blocking(context.pool(), move |conn| {
1200 PrivateMessage::read(conn, edit_id)
1203 if user.id != orig_private_message.recipient_id {
1204 return Err(APIError::err("couldnt_update_private_message").into());
1208 let edit_id = data.edit_id;
1209 let read = data.read;
1210 match blocking(context.pool(), move |conn| {
1211 PrivateMessage::update_read(conn, edit_id, read)
1215 Ok(private_message) => private_message,
1216 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1219 // No need to send an apub update
1221 let edit_id = data.edit_id;
1222 let message = blocking(context.pool(), move |conn| {
1223 PrivateMessageView::read(conn, edit_id)
1226 let recipient_id = message.recipient_id;
1228 let res = PrivateMessageResponse { message };
1230 context.chat_server().do_send(SendUserRoomMessage {
1231 op: UserOperation::MarkPrivateMessageAsRead,
1232 response: res.clone(),
1241 #[async_trait::async_trait(?Send)]
1242 impl Perform for GetPrivateMessages {
1243 type Response = PrivateMessagesResponse;
1247 context: &Data<LemmyContext>,
1248 _websocket_id: Option<ConnectionId>,
1249 ) -> Result<PrivateMessagesResponse, LemmyError> {
1250 let data: &GetPrivateMessages = &self;
1251 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1252 let user_id = user.id;
1254 let page = data.page;
1255 let limit = data.limit;
1256 let unread_only = data.unread_only;
1257 let messages = blocking(context.pool(), move |conn| {
1258 PrivateMessageQueryBuilder::create(&conn, user_id)
1261 .unread_only(unread_only)
1266 Ok(PrivateMessagesResponse { messages })
1270 #[async_trait::async_trait(?Send)]
1271 impl Perform for UserJoin {
1272 type Response = UserJoinResponse;
1276 context: &Data<LemmyContext>,
1277 websocket_id: Option<ConnectionId>,
1278 ) -> Result<UserJoinResponse, LemmyError> {
1279 let data: &UserJoin = &self;
1280 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1282 if let Some(ws_id) = websocket_id {
1283 context.chat_server().do_send(JoinUserRoom {
1289 Ok(UserJoinResponse { joined: true })