2 api::{claims::Claims, get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform},
4 captcha_espeak_wav_base64,
6 messages::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage},
11 use actix_web::web::Data;
14 use captcha::{gen, Difficulty};
16 use lemmy_api_structs::{blocking, user::*};
22 diesel_option_overwrite,
25 password_reset_request::*,
29 private_message_view::*,
43 apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
49 generate_random_string,
50 is_valid_preferred_username,
60 use std::str::FromStr;
62 #[async_trait::async_trait(?Send)]
63 impl Perform for Login {
64 type Response = LoginResponse;
68 context: &Data<LemmyContext>,
69 _websocket_id: Option<ConnectionId>,
70 ) -> Result<LoginResponse, LemmyError> {
71 let data: &Login = &self;
73 // Fetch that username / email
74 let username_or_email = data.username_or_email.clone();
75 let user = match blocking(context.pool(), move |conn| {
76 User_::find_by_email_or_username(conn, &username_or_email)
81 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
84 // Verify the password
85 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
87 return Err(APIError::err("password_incorrect").into());
92 jwt: Claims::jwt(user, Settings::get().hostname)?,
97 #[async_trait::async_trait(?Send)]
98 impl Perform for Register {
99 type Response = LoginResponse;
103 context: &Data<LemmyContext>,
104 _websocket_id: Option<ConnectionId>,
105 ) -> Result<LoginResponse, LemmyError> {
106 let data: &Register = &self;
108 // Make sure site has open registration
109 if let Ok(site) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
110 let site: SiteView = site;
111 if !site.open_registration {
112 return Err(APIError::err("registration_closed").into());
116 // Make sure passwords match
117 if data.password != data.password_verify {
118 return Err(APIError::err("passwords_dont_match").into());
121 // If its not the admin, check the captcha
122 if !data.admin && Settings::get().captcha.enabled {
129 .unwrap_or_else(|| "".to_string()),
133 .unwrap_or_else(|| "".to_string()),
137 return Err(APIError::err("captcha_incorrect").into());
141 check_slurs(&data.username)?;
143 // Make sure there are no admins
144 let any_admins = blocking(context.pool(), move |conn| {
145 UserView::admins(conn).map(|a| a.is_empty())
148 if data.admin && !any_admins {
149 return Err(APIError::err("admin_already_created").into());
152 let user_keypair = generate_actor_keypair()?;
153 if !is_valid_username(&data.username) {
154 return Err(APIError::err("invalid_username").into());
157 // Register the new user
158 let user_form = UserForm {
159 name: data.username.to_owned(),
160 email: Some(data.email.to_owned()),
161 matrix_user_id: None,
164 password_encrypted: data.password.to_owned(),
165 preferred_username: None,
169 show_nsfw: data.show_nsfw,
170 theme: "darkly".into(),
171 default_sort_type: SortType::Active as i16,
172 default_listing_type: ListingType::Subscribed as i16,
173 lang: "browser".into(),
175 send_notifications_to_email: false,
176 actor_id: Some(make_apub_endpoint(EndpointType::User, &data.username).to_string()),
179 private_key: Some(user_keypair.private_key),
180 public_key: Some(user_keypair.public_key),
181 last_refreshed_at: None,
185 let inserted_user = match blocking(context.pool(), move |conn| {
186 User_::register(conn, &user_form)
192 let err_type = if e.to_string()
193 == "duplicate key value violates unique constraint \"user__email_key\""
195 "email_already_exists"
197 "user_already_exists"
200 return Err(APIError::err(err_type).into());
204 let main_community_keypair = generate_actor_keypair()?;
206 // Create the main community if it doesn't exist
208 match blocking(context.pool(), move |conn| Community::read(conn, 2)).await? {
211 let default_community_name = "main";
212 let community_form = CommunityForm {
213 name: default_community_name.to_string(),
214 title: "The Default Community".to_string(),
215 description: Some("The Default Community".to_string()),
218 creator_id: inserted_user.id,
223 make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
226 private_key: Some(main_community_keypair.private_key),
227 public_key: Some(main_community_keypair.public_key),
228 last_refreshed_at: None,
233 blocking(context.pool(), move |conn| {
234 Community::create(conn, &community_form)
240 // Sign them up for main community no matter what
241 let community_follower_form = CommunityFollowerForm {
242 community_id: main_community.id,
243 user_id: inserted_user.id,
246 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
247 if blocking(context.pool(), follow).await?.is_err() {
248 return Err(APIError::err("community_follower_already_exists").into());
251 // If its an admin, add them as a mod and follower to main
253 let community_moderator_form = CommunityModeratorForm {
254 community_id: main_community.id,
255 user_id: inserted_user.id,
258 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
259 if blocking(context.pool(), join).await?.is_err() {
260 return Err(APIError::err("community_moderator_already_exists").into());
266 jwt: Claims::jwt(inserted_user, Settings::get().hostname)?,
271 #[async_trait::async_trait(?Send)]
272 impl Perform for GetCaptcha {
273 type Response = GetCaptchaResponse;
277 context: &Data<LemmyContext>,
278 _websocket_id: Option<ConnectionId>,
279 ) -> Result<Self::Response, LemmyError> {
280 let captcha_settings = Settings::get().captcha;
282 if !captcha_settings.enabled {
283 return Ok(GetCaptchaResponse { ok: None });
286 let captcha = match captcha_settings.difficulty.as_str() {
287 "easy" => gen(Difficulty::Easy),
288 "medium" => gen(Difficulty::Medium),
289 "hard" => gen(Difficulty::Hard),
290 _ => gen(Difficulty::Medium),
293 let answer = captcha.chars_as_string();
295 let png_byte_array = captcha.as_png().expect("failed to generate captcha");
297 let png = base64::encode(png_byte_array);
299 let uuid = uuid::Uuid::new_v4().to_string();
301 let wav = captcha_espeak_wav_base64(&answer).ok();
303 let captcha_item = CaptchaItem {
305 uuid: uuid.to_owned(),
306 expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
309 // Stores the captcha item on the queue
310 context.chat_server().do_send(captcha_item);
312 Ok(GetCaptchaResponse {
313 ok: Some(CaptchaResponse { png, uuid, wav }),
318 #[async_trait::async_trait(?Send)]
319 impl Perform for SaveUserSettings {
320 type Response = LoginResponse;
324 context: &Data<LemmyContext>,
325 _websocket_id: Option<ConnectionId>,
326 ) -> Result<LoginResponse, LemmyError> {
327 let data: &SaveUserSettings = &self;
328 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
330 let user_id = user.id;
331 let read_user = blocking(context.pool(), move |conn| User_::read(conn, user_id)).await??;
333 let bio = match &data.bio {
335 if bio.chars().count() <= 300 {
338 return Err(APIError::err("bio_length_overflow").into());
341 None => read_user.bio,
344 let avatar = diesel_option_overwrite(&data.avatar);
345 let banner = diesel_option_overwrite(&data.banner);
346 let email = diesel_option_overwrite(&data.email);
348 // The DB constraint should stop too many characters
349 let preferred_username = match &data.preferred_username {
350 Some(preferred_username) => {
351 if !is_valid_preferred_username(preferred_username.trim()) {
352 return Err(APIError::err("invalid_username").into());
354 Some(preferred_username.trim().to_string())
356 None => read_user.preferred_username,
359 let password_encrypted = match &data.new_password {
360 Some(new_password) => {
361 match &data.new_password_verify {
362 Some(new_password_verify) => {
363 // Make sure passwords match
364 if new_password != new_password_verify {
365 return Err(APIError::err("passwords_dont_match").into());
368 // Check the old password
369 match &data.old_password {
370 Some(old_password) => {
372 verify(old_password, &read_user.password_encrypted).unwrap_or(false);
374 return Err(APIError::err("password_incorrect").into());
376 let new_password = new_password.to_owned();
377 let user = blocking(context.pool(), move |conn| {
378 User_::update_password(conn, user_id, &new_password)
381 user.password_encrypted
383 None => return Err(APIError::err("password_incorrect").into()),
386 None => return Err(APIError::err("passwords_dont_match").into()),
389 None => read_user.password_encrypted,
392 let user_form = UserForm {
393 name: read_user.name,
395 matrix_user_id: data.matrix_user_id.to_owned(),
400 updated: Some(naive_now()),
401 admin: read_user.admin,
402 banned: read_user.banned,
403 show_nsfw: data.show_nsfw,
404 theme: data.theme.to_owned(),
405 default_sort_type: data.default_sort_type,
406 default_listing_type: data.default_listing_type,
407 lang: data.lang.to_owned(),
408 show_avatars: data.show_avatars,
409 send_notifications_to_email: data.send_notifications_to_email,
410 actor_id: Some(read_user.actor_id),
412 local: read_user.local,
413 private_key: read_user.private_key,
414 public_key: read_user.public_key,
415 last_refreshed_at: None,
418 let res = blocking(context.pool(), move |conn| {
419 User_::update(conn, user_id, &user_form)
422 let updated_user: User_ = match res {
425 let err_type = if e.to_string()
426 == "duplicate key value violates unique constraint \"user__email_key\""
428 "email_already_exists"
430 "user_already_exists"
433 return Err(APIError::err(err_type).into());
439 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
444 #[async_trait::async_trait(?Send)]
445 impl Perform for GetUserDetails {
446 type Response = GetUserDetailsResponse;
450 context: &Data<LemmyContext>,
451 _websocket_id: Option<ConnectionId>,
452 ) -> Result<GetUserDetailsResponse, LemmyError> {
453 let data: &GetUserDetails = &self;
454 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
456 let show_nsfw = match &user {
457 Some(user) => user.show_nsfw,
461 let sort = SortType::from_str(&data.sort)?;
466 .unwrap_or_else(|| "admin".to_string());
467 let user_details_id = match data.user_id {
470 let user = blocking(context.pool(), move |conn| {
471 User_::read_from_name(conn, &username)
476 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
481 let user_view = blocking(context.pool(), move |conn| {
482 UserView::get_user_secure(conn, user_details_id)
486 let page = data.page;
487 let limit = data.limit;
488 let saved_only = data.saved_only;
489 let community_id = data.community_id;
490 let user_id = user.map(|u| u.id);
491 let (posts, comments) = blocking(context.pool(), move |conn| {
492 let mut posts_query = PostQueryBuilder::create(conn)
494 .show_nsfw(show_nsfw)
495 .saved_only(saved_only)
496 .for_community_id(community_id)
501 let mut comments_query = CommentQueryBuilder::create(conn)
503 .saved_only(saved_only)
508 // If its saved only, you don't care what creator it was
509 // Or, if its not saved, then you only want it for that specific creator
511 posts_query = posts_query.for_creator_id(user_details_id);
512 comments_query = comments_query.for_creator_id(user_details_id);
515 let posts = posts_query.list()?;
516 let comments = comments_query.list()?;
518 Ok((posts, comments)) as Result<_, LemmyError>
522 let follows = blocking(context.pool(), move |conn| {
523 CommunityFollowerView::for_user(conn, user_details_id)
526 let moderates = blocking(context.pool(), move |conn| {
527 CommunityModeratorView::for_user(conn, user_details_id)
532 Ok(GetUserDetailsResponse {
542 #[async_trait::async_trait(?Send)]
543 impl Perform for AddAdmin {
544 type Response = AddAdminResponse;
548 context: &Data<LemmyContext>,
549 websocket_id: Option<ConnectionId>,
550 ) -> Result<AddAdminResponse, LemmyError> {
551 let data: &AddAdmin = &self;
552 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
554 // Make sure user is an admin
555 is_admin(context.pool(), user.id).await?;
557 let added = data.added;
558 let added_user_id = data.user_id;
559 let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
560 if blocking(context.pool(), add_admin).await?.is_err() {
561 return Err(APIError::err("couldnt_update_user").into());
565 let form = ModAddForm {
566 mod_user_id: user.id,
567 other_user_id: data.user_id,
568 removed: Some(!data.added),
571 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
573 let site_creator_id = blocking(context.pool(), move |conn| {
574 Site::read(conn, 1).map(|s| s.creator_id)
578 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
579 let creator_index = admins
581 .position(|r| r.id == site_creator_id)
582 .context(location_info!())?;
583 let creator_user = admins.remove(creator_index);
584 admins.insert(0, creator_user);
586 let res = AddAdminResponse { admins };
588 context.chat_server().do_send(SendAllMessage {
589 op: UserOperation::AddAdmin,
590 response: res.clone(),
598 #[async_trait::async_trait(?Send)]
599 impl Perform for BanUser {
600 type Response = BanUserResponse;
604 context: &Data<LemmyContext>,
605 websocket_id: Option<ConnectionId>,
606 ) -> Result<BanUserResponse, LemmyError> {
607 let data: &BanUser = &self;
608 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
610 // Make sure user is an admin
611 is_admin(context.pool(), user.id).await?;
614 let banned_user_id = data.user_id;
615 let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
616 if blocking(context.pool(), ban_user).await?.is_err() {
617 return Err(APIError::err("couldnt_update_user").into());
620 // Remove their data if that's desired
621 if let Some(remove_data) = data.remove_data {
623 blocking(context.pool(), move |conn: &'_ _| {
624 Post::update_removed_for_creator(conn, banned_user_id, None, remove_data)
629 blocking(context.pool(), move |conn: &'_ _| {
630 Community::update_removed_for_creator(conn, banned_user_id, remove_data)
635 blocking(context.pool(), move |conn: &'_ _| {
636 Comment::update_removed_for_creator(conn, banned_user_id, remove_data)
642 let expires = match data.expires {
643 Some(time) => Some(naive_from_unix(time)),
647 let form = ModBanForm {
648 mod_user_id: user.id,
649 other_user_id: data.user_id,
650 reason: data.reason.to_owned(),
651 banned: Some(data.ban),
655 blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
657 let user_id = data.user_id;
658 let user_view = blocking(context.pool(), move |conn| {
659 UserView::get_user_secure(conn, user_id)
663 let res = BanUserResponse {
668 context.chat_server().do_send(SendAllMessage {
669 op: UserOperation::BanUser,
670 response: res.clone(),
678 #[async_trait::async_trait(?Send)]
679 impl Perform for GetReplies {
680 type Response = GetRepliesResponse;
684 context: &Data<LemmyContext>,
685 _websocket_id: Option<ConnectionId>,
686 ) -> Result<GetRepliesResponse, LemmyError> {
687 let data: &GetReplies = &self;
688 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
690 let sort = SortType::from_str(&data.sort)?;
692 let page = data.page;
693 let limit = data.limit;
694 let unread_only = data.unread_only;
695 let user_id = user.id;
696 let replies = blocking(context.pool(), move |conn| {
697 ReplyQueryBuilder::create(conn, user_id)
699 .unread_only(unread_only)
706 Ok(GetRepliesResponse { replies })
710 #[async_trait::async_trait(?Send)]
711 impl Perform for GetUserMentions {
712 type Response = GetUserMentionsResponse;
716 context: &Data<LemmyContext>,
717 _websocket_id: Option<ConnectionId>,
718 ) -> Result<GetUserMentionsResponse, LemmyError> {
719 let data: &GetUserMentions = &self;
720 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
722 let sort = SortType::from_str(&data.sort)?;
724 let page = data.page;
725 let limit = data.limit;
726 let unread_only = data.unread_only;
727 let user_id = user.id;
728 let mentions = blocking(context.pool(), move |conn| {
729 UserMentionQueryBuilder::create(conn, user_id)
731 .unread_only(unread_only)
738 Ok(GetUserMentionsResponse { mentions })
742 #[async_trait::async_trait(?Send)]
743 impl Perform for MarkUserMentionAsRead {
744 type Response = UserMentionResponse;
748 context: &Data<LemmyContext>,
749 _websocket_id: Option<ConnectionId>,
750 ) -> Result<UserMentionResponse, LemmyError> {
751 let data: &MarkUserMentionAsRead = &self;
752 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
754 let user_mention_id = data.user_mention_id;
755 let read_user_mention = blocking(context.pool(), move |conn| {
756 UserMention::read(conn, user_mention_id)
760 if user.id != read_user_mention.recipient_id {
761 return Err(APIError::err("couldnt_update_comment").into());
764 let user_mention_id = read_user_mention.id;
765 let read = data.read;
766 let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
767 if blocking(context.pool(), update_mention).await?.is_err() {
768 return Err(APIError::err("couldnt_update_comment").into());
771 let user_mention_id = read_user_mention.id;
772 let user_id = user.id;
773 let user_mention_view = blocking(context.pool(), move |conn| {
774 UserMentionView::read(conn, user_mention_id, user_id)
778 Ok(UserMentionResponse {
779 mention: user_mention_view,
784 #[async_trait::async_trait(?Send)]
785 impl Perform for MarkAllAsRead {
786 type Response = GetRepliesResponse;
790 context: &Data<LemmyContext>,
791 _websocket_id: Option<ConnectionId>,
792 ) -> Result<GetRepliesResponse, LemmyError> {
793 let data: &MarkAllAsRead = &self;
794 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
796 let user_id = user.id;
797 let replies = blocking(context.pool(), move |conn| {
798 ReplyQueryBuilder::create(conn, user_id)
806 // TODO: this should probably be a bulk operation
807 // Not easy to do as a bulk operation,
808 // because recipient_id isn't in the comment table
809 for reply in &replies {
810 let reply_id = reply.id;
811 let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
812 if blocking(context.pool(), mark_as_read).await?.is_err() {
813 return Err(APIError::err("couldnt_update_comment").into());
817 // Mark all user mentions as read
818 let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
819 if blocking(context.pool(), update_user_mentions)
823 return Err(APIError::err("couldnt_update_comment").into());
826 // Mark all private_messages as read
827 let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
828 if blocking(context.pool(), update_pm).await?.is_err() {
829 return Err(APIError::err("couldnt_update_private_message").into());
832 Ok(GetRepliesResponse { replies: vec![] })
836 #[async_trait::async_trait(?Send)]
837 impl Perform for DeleteAccount {
838 type Response = LoginResponse;
842 context: &Data<LemmyContext>,
843 _websocket_id: Option<ConnectionId>,
844 ) -> Result<LoginResponse, LemmyError> {
845 let data: &DeleteAccount = &self;
846 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
848 // Verify the password
849 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
851 return Err(APIError::err("password_incorrect").into());
855 let user_id = user.id;
856 let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
857 if blocking(context.pool(), permadelete).await?.is_err() {
858 return Err(APIError::err("couldnt_update_comment").into());
862 let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
863 if blocking(context.pool(), permadelete).await?.is_err() {
864 return Err(APIError::err("couldnt_update_post").into());
868 jwt: data.auth.to_owned(),
873 #[async_trait::async_trait(?Send)]
874 impl Perform for PasswordReset {
875 type Response = PasswordResetResponse;
879 context: &Data<LemmyContext>,
880 _websocket_id: Option<ConnectionId>,
881 ) -> Result<PasswordResetResponse, LemmyError> {
882 let data: &PasswordReset = &self;
885 let email = data.email.clone();
886 let user = match blocking(context.pool(), move |conn| {
887 User_::find_by_email(conn, &email)
892 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
895 // Generate a random token
896 let token = generate_random_string();
899 let token2 = token.clone();
900 let user_id = user.id;
901 blocking(context.pool(), move |conn| {
902 PasswordResetRequest::create_token(conn, user_id, &token2)
906 // Email the pure token to the user.
907 // TODO no i18n support here.
908 let user_email = &user.email.expect("email");
909 let subject = &format!("Password reset for {}", user.name);
910 let hostname = &format!("https://{}", Settings::get().hostname); //TODO add https for now.
911 let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", user.name, hostname, &token);
912 match send_email(subject, user_email, &user.name, html) {
914 Err(_e) => return Err(APIError::err(&_e).into()),
917 Ok(PasswordResetResponse {})
921 #[async_trait::async_trait(?Send)]
922 impl Perform for PasswordChange {
923 type Response = LoginResponse;
927 context: &Data<LemmyContext>,
928 _websocket_id: Option<ConnectionId>,
929 ) -> Result<LoginResponse, LemmyError> {
930 let data: &PasswordChange = &self;
932 // Fetch the user_id from the token
933 let token = data.token.clone();
934 let user_id = blocking(context.pool(), move |conn| {
935 PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
939 // Make sure passwords match
940 if data.password != data.password_verify {
941 return Err(APIError::err("passwords_dont_match").into());
944 // Update the user with the new password
945 let password = data.password.clone();
946 let updated_user = match blocking(context.pool(), move |conn| {
947 User_::update_password(conn, user_id, &password)
952 Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
957 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
962 #[async_trait::async_trait(?Send)]
963 impl Perform for CreatePrivateMessage {
964 type Response = PrivateMessageResponse;
968 context: &Data<LemmyContext>,
969 websocket_id: Option<ConnectionId>,
970 ) -> Result<PrivateMessageResponse, LemmyError> {
971 let data: &CreatePrivateMessage = &self;
972 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
974 let hostname = &format!("https://{}", Settings::get().hostname);
976 let content_slurs_removed = remove_slurs(&data.content.to_owned());
978 let private_message_form = PrivateMessageForm {
979 content: content_slurs_removed.to_owned(),
981 recipient_id: data.recipient_id,
990 let inserted_private_message = match blocking(context.pool(), move |conn| {
991 PrivateMessage::create(conn, &private_message_form)
995 Ok(private_message) => private_message,
997 return Err(APIError::err("couldnt_create_private_message").into());
1001 let inserted_private_message_id = inserted_private_message.id;
1002 let updated_private_message = match blocking(context.pool(), move |conn| {
1003 let apub_id = make_apub_endpoint(
1004 EndpointType::PrivateMessage,
1005 &inserted_private_message_id.to_string(),
1008 PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
1012 Ok(private_message) => private_message,
1013 Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
1016 updated_private_message.send_create(&user, context).await?;
1018 // Send notifications to the recipient
1019 let recipient_id = data.recipient_id;
1020 let recipient_user =
1021 blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1022 if recipient_user.send_notifications_to_email {
1023 if let Some(email) = recipient_user.email {
1024 let subject = &format!(
1025 "{} - Private Message from {}",
1026 Settings::get().hostname,
1029 let html = &format!(
1030 "<h1>Private Message</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
1031 user.name, &content_slurs_removed, hostname
1033 match send_email(subject, &email, &recipient_user.name, html) {
1035 Err(e) => error!("{}", e),
1040 let message = blocking(context.pool(), move |conn| {
1041 PrivateMessageView::read(conn, inserted_private_message.id)
1045 let res = PrivateMessageResponse { message };
1047 context.chat_server().do_send(SendUserRoomMessage {
1048 op: UserOperation::CreatePrivateMessage,
1049 response: res.clone(),
1058 #[async_trait::async_trait(?Send)]
1059 impl Perform for EditPrivateMessage {
1060 type Response = PrivateMessageResponse;
1064 context: &Data<LemmyContext>,
1065 websocket_id: Option<ConnectionId>,
1066 ) -> Result<PrivateMessageResponse, LemmyError> {
1067 let data: &EditPrivateMessage = &self;
1068 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1070 // Checking permissions
1071 let edit_id = data.edit_id;
1072 let orig_private_message = blocking(context.pool(), move |conn| {
1073 PrivateMessage::read(conn, edit_id)
1076 if user.id != orig_private_message.creator_id {
1077 return Err(APIError::err("no_private_message_edit_allowed").into());
1081 let content_slurs_removed = remove_slurs(&data.content);
1082 let edit_id = data.edit_id;
1083 let updated_private_message = match blocking(context.pool(), move |conn| {
1084 PrivateMessage::update_content(conn, edit_id, &content_slurs_removed)
1088 Ok(private_message) => private_message,
1089 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1092 // Send the apub update
1093 updated_private_message.send_update(&user, context).await?;
1095 let edit_id = data.edit_id;
1096 let message = blocking(context.pool(), move |conn| {
1097 PrivateMessageView::read(conn, edit_id)
1100 let recipient_id = message.recipient_id;
1102 let res = PrivateMessageResponse { message };
1104 context.chat_server().do_send(SendUserRoomMessage {
1105 op: UserOperation::EditPrivateMessage,
1106 response: res.clone(),
1115 #[async_trait::async_trait(?Send)]
1116 impl Perform for DeletePrivateMessage {
1117 type Response = PrivateMessageResponse;
1121 context: &Data<LemmyContext>,
1122 websocket_id: Option<ConnectionId>,
1123 ) -> Result<PrivateMessageResponse, LemmyError> {
1124 let data: &DeletePrivateMessage = &self;
1125 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1127 // Checking permissions
1128 let edit_id = data.edit_id;
1129 let orig_private_message = blocking(context.pool(), move |conn| {
1130 PrivateMessage::read(conn, edit_id)
1133 if user.id != orig_private_message.creator_id {
1134 return Err(APIError::err("no_private_message_edit_allowed").into());
1138 let edit_id = data.edit_id;
1139 let deleted = data.deleted;
1140 let updated_private_message = match blocking(context.pool(), move |conn| {
1141 PrivateMessage::update_deleted(conn, edit_id, deleted)
1145 Ok(private_message) => private_message,
1146 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1149 // Send the apub update
1151 updated_private_message.send_delete(&user, context).await?;
1153 updated_private_message
1154 .send_undo_delete(&user, context)
1158 let edit_id = data.edit_id;
1159 let message = blocking(context.pool(), move |conn| {
1160 PrivateMessageView::read(conn, edit_id)
1163 let recipient_id = message.recipient_id;
1165 let res = PrivateMessageResponse { message };
1167 context.chat_server().do_send(SendUserRoomMessage {
1168 op: UserOperation::DeletePrivateMessage,
1169 response: res.clone(),
1178 #[async_trait::async_trait(?Send)]
1179 impl Perform for MarkPrivateMessageAsRead {
1180 type Response = PrivateMessageResponse;
1184 context: &Data<LemmyContext>,
1185 websocket_id: Option<ConnectionId>,
1186 ) -> Result<PrivateMessageResponse, LemmyError> {
1187 let data: &MarkPrivateMessageAsRead = &self;
1188 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
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)
1196 if user.id != orig_private_message.recipient_id {
1197 return Err(APIError::err("couldnt_update_private_message").into());
1201 let edit_id = data.edit_id;
1202 let read = data.read;
1203 match blocking(context.pool(), move |conn| {
1204 PrivateMessage::update_read(conn, edit_id, read)
1208 Ok(private_message) => private_message,
1209 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1212 // No need to send an apub update
1214 let edit_id = data.edit_id;
1215 let message = blocking(context.pool(), move |conn| {
1216 PrivateMessageView::read(conn, edit_id)
1219 let recipient_id = message.recipient_id;
1221 let res = PrivateMessageResponse { message };
1223 context.chat_server().do_send(SendUserRoomMessage {
1224 op: UserOperation::MarkPrivateMessageAsRead,
1225 response: res.clone(),
1234 #[async_trait::async_trait(?Send)]
1235 impl Perform for GetPrivateMessages {
1236 type Response = PrivateMessagesResponse;
1240 context: &Data<LemmyContext>,
1241 _websocket_id: Option<ConnectionId>,
1242 ) -> Result<PrivateMessagesResponse, LemmyError> {
1243 let data: &GetPrivateMessages = &self;
1244 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1245 let user_id = user.id;
1247 let page = data.page;
1248 let limit = data.limit;
1249 let unread_only = data.unread_only;
1250 let messages = blocking(context.pool(), move |conn| {
1251 PrivateMessageQueryBuilder::create(&conn, user_id)
1254 .unread_only(unread_only)
1259 Ok(PrivateMessagesResponse { messages })
1263 #[async_trait::async_trait(?Send)]
1264 impl Perform for UserJoin {
1265 type Response = UserJoinResponse;
1269 context: &Data<LemmyContext>,
1270 websocket_id: Option<ConnectionId>,
1271 ) -> Result<UserJoinResponse, LemmyError> {
1272 let data: &UserJoin = &self;
1273 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1275 if let Some(ws_id) = websocket_id {
1276 context.chat_server().do_send(JoinUserRoom {
1282 Ok(UserJoinResponse { user_id: user.id })