2 captcha_espeak_wav_base64,
9 use actix_web::web::Data;
12 use captcha::{gen, Difficulty};
14 use lemmy_apub::ApubObjectType;
20 diesel_option_overwrite,
23 password_reset_request::*,
27 private_message_view::*,
40 use lemmy_structs::{blocking, user::*};
42 apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
48 generate_random_string,
49 is_valid_preferred_username,
58 use lemmy_websocket::{
59 messages::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage},
64 use std::str::FromStr;
66 #[async_trait::async_trait(?Send)]
67 impl Perform for Login {
68 type Response = LoginResponse;
72 context: &Data<LemmyContext>,
73 _websocket_id: Option<ConnectionId>,
74 ) -> Result<LoginResponse, LemmyError> {
75 let data: &Login = &self;
77 // Fetch that username / email
78 let username_or_email = data.username_or_email.clone();
79 let user = match blocking(context.pool(), move |conn| {
80 User_::find_by_email_or_username(conn, &username_or_email)
85 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
88 // Verify the password
89 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
91 return Err(APIError::err("password_incorrect").into());
96 jwt: Claims::jwt(user, Settings::get().hostname)?,
101 #[async_trait::async_trait(?Send)]
102 impl Perform for Register {
103 type Response = LoginResponse;
107 context: &Data<LemmyContext>,
108 _websocket_id: Option<ConnectionId>,
109 ) -> Result<LoginResponse, LemmyError> {
110 let data: &Register = &self;
112 // Make sure site has open registration
113 if let Ok(site) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
114 let site: SiteView = site;
115 if !site.open_registration {
116 return Err(APIError::err("registration_closed").into());
120 // Make sure passwords match
121 if data.password != data.password_verify {
122 return Err(APIError::err("passwords_dont_match").into());
125 // If its not the admin, check the captcha
126 if !data.admin && Settings::get().captcha.enabled {
133 .unwrap_or_else(|| "".to_string()),
137 .unwrap_or_else(|| "".to_string()),
141 return Err(APIError::err("captcha_incorrect").into());
145 check_slurs(&data.username)?;
147 // Make sure there are no admins
148 let any_admins = blocking(context.pool(), move |conn| {
149 UserView::admins(conn).map(|a| a.is_empty())
152 if data.admin && !any_admins {
153 return Err(APIError::err("admin_already_created").into());
156 let user_keypair = generate_actor_keypair()?;
157 if !is_valid_username(&data.username) {
158 return Err(APIError::err("invalid_username").into());
161 // Register the new user
162 let user_form = UserForm {
163 name: data.username.to_owned(),
164 email: Some(data.email.to_owned()),
165 matrix_user_id: None,
168 password_encrypted: data.password.to_owned(),
169 preferred_username: None,
174 show_nsfw: data.show_nsfw,
175 theme: "darkly".into(),
176 default_sort_type: SortType::Active as i16,
177 default_listing_type: ListingType::Subscribed as i16,
178 lang: "browser".into(),
180 send_notifications_to_email: false,
181 actor_id: Some(make_apub_endpoint(EndpointType::User, &data.username).to_string()),
184 private_key: Some(user_keypair.private_key),
185 public_key: Some(user_keypair.public_key),
186 last_refreshed_at: None,
190 let inserted_user = match blocking(context.pool(), move |conn| {
191 User_::register(conn, &user_form)
197 let err_type = if e.to_string()
198 == "duplicate key value violates unique constraint \"user__email_key\""
200 "email_already_exists"
202 "user_already_exists"
205 return Err(APIError::err(err_type).into());
209 let main_community_keypair = generate_actor_keypair()?;
211 // Create the main community if it doesn't exist
213 match blocking(context.pool(), move |conn| Community::read(conn, 2)).await? {
216 let default_community_name = "main";
217 let community_form = CommunityForm {
218 name: default_community_name.to_string(),
219 title: "The Default Community".to_string(),
220 description: Some("The Default Community".to_string()),
223 creator_id: inserted_user.id,
228 make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
231 private_key: Some(main_community_keypair.private_key),
232 public_key: Some(main_community_keypair.public_key),
233 last_refreshed_at: None,
238 blocking(context.pool(), move |conn| {
239 Community::create(conn, &community_form)
245 // Sign them up for main community no matter what
246 let community_follower_form = CommunityFollowerForm {
247 community_id: main_community.id,
248 user_id: inserted_user.id,
251 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
252 if blocking(context.pool(), follow).await?.is_err() {
253 return Err(APIError::err("community_follower_already_exists").into());
256 // If its an admin, add them as a mod and follower to main
258 let community_moderator_form = CommunityModeratorForm {
259 community_id: main_community.id,
260 user_id: inserted_user.id,
263 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
264 if blocking(context.pool(), join).await?.is_err() {
265 return Err(APIError::err("community_moderator_already_exists").into());
271 jwt: Claims::jwt(inserted_user, Settings::get().hostname)?,
276 #[async_trait::async_trait(?Send)]
277 impl Perform for GetCaptcha {
278 type Response = GetCaptchaResponse;
282 context: &Data<LemmyContext>,
283 _websocket_id: Option<ConnectionId>,
284 ) -> Result<Self::Response, LemmyError> {
285 let captcha_settings = Settings::get().captcha;
287 if !captcha_settings.enabled {
288 return Ok(GetCaptchaResponse { ok: None });
291 let captcha = match captcha_settings.difficulty.as_str() {
292 "easy" => gen(Difficulty::Easy),
293 "medium" => gen(Difficulty::Medium),
294 "hard" => gen(Difficulty::Hard),
295 _ => gen(Difficulty::Medium),
298 let answer = captcha.chars_as_string();
300 let png_byte_array = captcha.as_png().expect("failed to generate captcha");
302 let png = base64::encode(png_byte_array);
304 let uuid = uuid::Uuid::new_v4().to_string();
306 let wav = captcha_espeak_wav_base64(&answer).ok();
308 let captcha_item = CaptchaItem {
310 uuid: uuid.to_owned(),
311 expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
314 // Stores the captcha item on the queue
315 context.chat_server().do_send(captcha_item);
317 Ok(GetCaptchaResponse {
318 ok: Some(CaptchaResponse { png, uuid, wav }),
323 #[async_trait::async_trait(?Send)]
324 impl Perform for SaveUserSettings {
325 type Response = LoginResponse;
329 context: &Data<LemmyContext>,
330 _websocket_id: Option<ConnectionId>,
331 ) -> Result<LoginResponse, LemmyError> {
332 let data: &SaveUserSettings = &self;
333 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
335 let user_id = user.id;
336 let read_user = blocking(context.pool(), move |conn| User_::read(conn, user_id)).await??;
338 let bio = match &data.bio {
340 if bio.chars().count() <= 300 {
343 return Err(APIError::err("bio_length_overflow").into());
346 None => read_user.bio,
349 let avatar = diesel_option_overwrite(&data.avatar);
350 let banner = diesel_option_overwrite(&data.banner);
351 let email = diesel_option_overwrite(&data.email);
353 // The DB constraint should stop too many characters
354 let preferred_username = match &data.preferred_username {
355 Some(preferred_username) => {
356 if !is_valid_preferred_username(preferred_username.trim()) {
357 return Err(APIError::err("invalid_username").into());
359 Some(preferred_username.trim().to_string())
361 None => read_user.preferred_username,
364 let password_encrypted = match &data.new_password {
365 Some(new_password) => {
366 match &data.new_password_verify {
367 Some(new_password_verify) => {
368 // Make sure passwords match
369 if new_password != new_password_verify {
370 return Err(APIError::err("passwords_dont_match").into());
373 // Check the old password
374 match &data.old_password {
375 Some(old_password) => {
377 verify(old_password, &read_user.password_encrypted).unwrap_or(false);
379 return Err(APIError::err("password_incorrect").into());
381 let new_password = new_password.to_owned();
382 let user = blocking(context.pool(), move |conn| {
383 User_::update_password(conn, user_id, &new_password)
386 user.password_encrypted
388 None => return Err(APIError::err("password_incorrect").into()),
391 None => return Err(APIError::err("passwords_dont_match").into()),
394 None => read_user.password_encrypted,
397 let user_form = UserForm {
398 name: read_user.name,
400 matrix_user_id: data.matrix_user_id.to_owned(),
405 published: Some(read_user.published),
406 updated: Some(naive_now()),
407 admin: read_user.admin,
408 banned: read_user.banned,
409 show_nsfw: data.show_nsfw,
410 theme: data.theme.to_owned(),
411 default_sort_type: data.default_sort_type,
412 default_listing_type: data.default_listing_type,
413 lang: data.lang.to_owned(),
414 show_avatars: data.show_avatars,
415 send_notifications_to_email: data.send_notifications_to_email,
416 actor_id: Some(read_user.actor_id),
418 local: read_user.local,
419 private_key: read_user.private_key,
420 public_key: read_user.public_key,
421 last_refreshed_at: None,
424 let res = blocking(context.pool(), move |conn| {
425 User_::update(conn, user_id, &user_form)
428 let updated_user: User_ = match res {
431 let err_type = if e.to_string()
432 == "duplicate key value violates unique constraint \"user__email_key\""
434 "email_already_exists"
436 "user_already_exists"
439 return Err(APIError::err(err_type).into());
445 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
450 #[async_trait::async_trait(?Send)]
451 impl Perform for GetUserDetails {
452 type Response = GetUserDetailsResponse;
456 context: &Data<LemmyContext>,
457 _websocket_id: Option<ConnectionId>,
458 ) -> Result<GetUserDetailsResponse, LemmyError> {
459 let data: &GetUserDetails = &self;
460 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
462 let show_nsfw = match &user {
463 Some(user) => user.show_nsfw,
467 let sort = SortType::from_str(&data.sort)?;
472 .unwrap_or_else(|| "admin".to_string());
473 let user_details_id = match data.user_id {
476 let user = blocking(context.pool(), move |conn| {
477 User_::read_from_name(conn, &username)
482 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
487 let user_view = blocking(context.pool(), move |conn| {
488 UserView::get_user_secure(conn, user_details_id)
492 let page = data.page;
493 let limit = data.limit;
494 let saved_only = data.saved_only;
495 let community_id = data.community_id;
496 let user_id = user.map(|u| u.id);
497 let (posts, comments) = blocking(context.pool(), move |conn| {
498 let mut posts_query = PostQueryBuilder::create(conn)
500 .show_nsfw(show_nsfw)
501 .saved_only(saved_only)
502 .for_community_id(community_id)
507 let mut comments_query = CommentQueryBuilder::create(conn)
509 .saved_only(saved_only)
514 // If its saved only, you don't care what creator it was
515 // Or, if its not saved, then you only want it for that specific creator
517 posts_query = posts_query.for_creator_id(user_details_id);
518 comments_query = comments_query.for_creator_id(user_details_id);
521 let posts = posts_query.list()?;
522 let comments = comments_query.list()?;
524 Ok((posts, comments)) as Result<_, LemmyError>
528 let follows = blocking(context.pool(), move |conn| {
529 CommunityFollowerView::for_user(conn, user_details_id)
532 let moderates = blocking(context.pool(), move |conn| {
533 CommunityModeratorView::for_user(conn, user_details_id)
538 Ok(GetUserDetailsResponse {
548 #[async_trait::async_trait(?Send)]
549 impl Perform for AddAdmin {
550 type Response = AddAdminResponse;
554 context: &Data<LemmyContext>,
555 websocket_id: Option<ConnectionId>,
556 ) -> Result<AddAdminResponse, LemmyError> {
557 let data: &AddAdmin = &self;
558 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
560 // Make sure user is an admin
561 is_admin(context.pool(), user.id).await?;
563 let added = data.added;
564 let added_user_id = data.user_id;
565 let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
566 if blocking(context.pool(), add_admin).await?.is_err() {
567 return Err(APIError::err("couldnt_update_user").into());
571 let form = ModAddForm {
572 mod_user_id: user.id,
573 other_user_id: data.user_id,
574 removed: Some(!data.added),
577 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
579 let site_creator_id = blocking(context.pool(), move |conn| {
580 Site::read(conn, 1).map(|s| s.creator_id)
584 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
585 let creator_index = admins
587 .position(|r| r.id == site_creator_id)
588 .context(location_info!())?;
589 let creator_user = admins.remove(creator_index);
590 admins.insert(0, creator_user);
592 let res = AddAdminResponse { admins };
594 context.chat_server().do_send(SendAllMessage {
595 op: UserOperation::AddAdmin,
596 response: res.clone(),
604 #[async_trait::async_trait(?Send)]
605 impl Perform for BanUser {
606 type Response = BanUserResponse;
610 context: &Data<LemmyContext>,
611 websocket_id: Option<ConnectionId>,
612 ) -> Result<BanUserResponse, LemmyError> {
613 let data: &BanUser = &self;
614 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
616 // Make sure user is an admin
617 is_admin(context.pool(), user.id).await?;
620 let banned_user_id = data.user_id;
621 let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
622 if blocking(context.pool(), ban_user).await?.is_err() {
623 return Err(APIError::err("couldnt_update_user").into());
626 // Remove their data if that's desired
627 if let Some(remove_data) = data.remove_data {
629 blocking(context.pool(), move |conn: &'_ _| {
630 Post::update_removed_for_creator(conn, banned_user_id, None, remove_data)
635 blocking(context.pool(), move |conn: &'_ _| {
636 Community::update_removed_for_creator(conn, banned_user_id, remove_data)
641 blocking(context.pool(), move |conn: &'_ _| {
642 Comment::update_removed_for_creator(conn, banned_user_id, remove_data)
648 let expires = match data.expires {
649 Some(time) => Some(naive_from_unix(time)),
653 let form = ModBanForm {
654 mod_user_id: user.id,
655 other_user_id: data.user_id,
656 reason: data.reason.to_owned(),
657 banned: Some(data.ban),
661 blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
663 let user_id = data.user_id;
664 let user_view = blocking(context.pool(), move |conn| {
665 UserView::get_user_secure(conn, user_id)
669 let res = BanUserResponse {
674 context.chat_server().do_send(SendAllMessage {
675 op: UserOperation::BanUser,
676 response: res.clone(),
684 #[async_trait::async_trait(?Send)]
685 impl Perform for GetReplies {
686 type Response = GetRepliesResponse;
690 context: &Data<LemmyContext>,
691 _websocket_id: Option<ConnectionId>,
692 ) -> Result<GetRepliesResponse, LemmyError> {
693 let data: &GetReplies = &self;
694 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
696 let sort = SortType::from_str(&data.sort)?;
698 let page = data.page;
699 let limit = data.limit;
700 let unread_only = data.unread_only;
701 let user_id = user.id;
702 let replies = blocking(context.pool(), move |conn| {
703 ReplyQueryBuilder::create(conn, user_id)
705 .unread_only(unread_only)
712 Ok(GetRepliesResponse { replies })
716 #[async_trait::async_trait(?Send)]
717 impl Perform for GetUserMentions {
718 type Response = GetUserMentionsResponse;
722 context: &Data<LemmyContext>,
723 _websocket_id: Option<ConnectionId>,
724 ) -> Result<GetUserMentionsResponse, LemmyError> {
725 let data: &GetUserMentions = &self;
726 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
728 let sort = SortType::from_str(&data.sort)?;
730 let page = data.page;
731 let limit = data.limit;
732 let unread_only = data.unread_only;
733 let user_id = user.id;
734 let mentions = blocking(context.pool(), move |conn| {
735 UserMentionQueryBuilder::create(conn, user_id)
737 .unread_only(unread_only)
744 Ok(GetUserMentionsResponse { mentions })
748 #[async_trait::async_trait(?Send)]
749 impl Perform for MarkUserMentionAsRead {
750 type Response = UserMentionResponse;
754 context: &Data<LemmyContext>,
755 _websocket_id: Option<ConnectionId>,
756 ) -> Result<UserMentionResponse, LemmyError> {
757 let data: &MarkUserMentionAsRead = &self;
758 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
760 let user_mention_id = data.user_mention_id;
761 let read_user_mention = blocking(context.pool(), move |conn| {
762 UserMention::read(conn, user_mention_id)
766 if user.id != read_user_mention.recipient_id {
767 return Err(APIError::err("couldnt_update_comment").into());
770 let user_mention_id = read_user_mention.id;
771 let read = data.read;
772 let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
773 if blocking(context.pool(), update_mention).await?.is_err() {
774 return Err(APIError::err("couldnt_update_comment").into());
777 let user_mention_id = read_user_mention.id;
778 let user_id = user.id;
779 let user_mention_view = blocking(context.pool(), move |conn| {
780 UserMentionView::read(conn, user_mention_id, user_id)
784 Ok(UserMentionResponse {
785 mention: user_mention_view,
790 #[async_trait::async_trait(?Send)]
791 impl Perform for MarkAllAsRead {
792 type Response = GetRepliesResponse;
796 context: &Data<LemmyContext>,
797 _websocket_id: Option<ConnectionId>,
798 ) -> Result<GetRepliesResponse, LemmyError> {
799 let data: &MarkAllAsRead = &self;
800 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
802 let user_id = user.id;
803 let replies = blocking(context.pool(), move |conn| {
804 ReplyQueryBuilder::create(conn, user_id)
812 // TODO: this should probably be a bulk operation
813 // Not easy to do as a bulk operation,
814 // because recipient_id isn't in the comment table
815 for reply in &replies {
816 let reply_id = reply.id;
817 let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
818 if blocking(context.pool(), mark_as_read).await?.is_err() {
819 return Err(APIError::err("couldnt_update_comment").into());
823 // Mark all user mentions as read
824 let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
825 if blocking(context.pool(), update_user_mentions)
829 return Err(APIError::err("couldnt_update_comment").into());
832 // Mark all private_messages as read
833 let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
834 if blocking(context.pool(), update_pm).await?.is_err() {
835 return Err(APIError::err("couldnt_update_private_message").into());
838 Ok(GetRepliesResponse { replies: vec![] })
842 #[async_trait::async_trait(?Send)]
843 impl Perform for DeleteAccount {
844 type Response = LoginResponse;
848 context: &Data<LemmyContext>,
849 _websocket_id: Option<ConnectionId>,
850 ) -> Result<LoginResponse, LemmyError> {
851 let data: &DeleteAccount = &self;
852 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
854 // Verify the password
855 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
857 return Err(APIError::err("password_incorrect").into());
861 let user_id = user.id;
862 let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
863 if blocking(context.pool(), permadelete).await?.is_err() {
864 return Err(APIError::err("couldnt_update_comment").into());
868 let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
869 if blocking(context.pool(), permadelete).await?.is_err() {
870 return Err(APIError::err("couldnt_update_post").into());
874 jwt: data.auth.to_owned(),
879 #[async_trait::async_trait(?Send)]
880 impl Perform for PasswordReset {
881 type Response = PasswordResetResponse;
885 context: &Data<LemmyContext>,
886 _websocket_id: Option<ConnectionId>,
887 ) -> Result<PasswordResetResponse, LemmyError> {
888 let data: &PasswordReset = &self;
891 let email = data.email.clone();
892 let user = match blocking(context.pool(), move |conn| {
893 User_::find_by_email(conn, &email)
898 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
901 // Generate a random token
902 let token = generate_random_string();
905 let token2 = token.clone();
906 let user_id = user.id;
907 blocking(context.pool(), move |conn| {
908 PasswordResetRequest::create_token(conn, user_id, &token2)
912 // Email the pure token to the user.
913 // TODO no i18n support here.
914 let user_email = &user.email.expect("email");
915 let subject = &format!("Password reset for {}", user.name);
916 let hostname = &format!("https://{}", Settings::get().hostname); //TODO add https for now.
917 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);
918 match send_email(subject, user_email, &user.name, html) {
920 Err(_e) => return Err(APIError::err(&_e).into()),
923 Ok(PasswordResetResponse {})
927 #[async_trait::async_trait(?Send)]
928 impl Perform for PasswordChange {
929 type Response = LoginResponse;
933 context: &Data<LemmyContext>,
934 _websocket_id: Option<ConnectionId>,
935 ) -> Result<LoginResponse, LemmyError> {
936 let data: &PasswordChange = &self;
938 // Fetch the user_id from the token
939 let token = data.token.clone();
940 let user_id = blocking(context.pool(), move |conn| {
941 PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
945 // Make sure passwords match
946 if data.password != data.password_verify {
947 return Err(APIError::err("passwords_dont_match").into());
950 // Update the user with the new password
951 let password = data.password.clone();
952 let updated_user = match blocking(context.pool(), move |conn| {
953 User_::update_password(conn, user_id, &password)
958 Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
963 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
968 #[async_trait::async_trait(?Send)]
969 impl Perform for CreatePrivateMessage {
970 type Response = PrivateMessageResponse;
974 context: &Data<LemmyContext>,
975 websocket_id: Option<ConnectionId>,
976 ) -> Result<PrivateMessageResponse, LemmyError> {
977 let data: &CreatePrivateMessage = &self;
978 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
980 let hostname = &format!("https://{}", Settings::get().hostname);
982 let content_slurs_removed = remove_slurs(&data.content.to_owned());
984 let private_message_form = PrivateMessageForm {
985 content: content_slurs_removed.to_owned(),
987 recipient_id: data.recipient_id,
996 let inserted_private_message = match blocking(context.pool(), move |conn| {
997 PrivateMessage::create(conn, &private_message_form)
1001 Ok(private_message) => private_message,
1003 return Err(APIError::err("couldnt_create_private_message").into());
1007 let inserted_private_message_id = inserted_private_message.id;
1008 let updated_private_message = match blocking(context.pool(), move |conn| {
1009 let apub_id = make_apub_endpoint(
1010 EndpointType::PrivateMessage,
1011 &inserted_private_message_id.to_string(),
1014 PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
1018 Ok(private_message) => private_message,
1019 Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
1022 updated_private_message.send_create(&user, context).await?;
1024 // Send notifications to the recipient
1025 let recipient_id = data.recipient_id;
1026 let recipient_user =
1027 blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1028 if recipient_user.send_notifications_to_email {
1029 if let Some(email) = recipient_user.email {
1030 let subject = &format!(
1031 "{} - Private Message from {}",
1032 Settings::get().hostname,
1035 let html = &format!(
1036 "<h1>Private Message</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
1037 user.name, &content_slurs_removed, hostname
1039 match send_email(subject, &email, &recipient_user.name, html) {
1041 Err(e) => error!("{}", e),
1046 let message = blocking(context.pool(), move |conn| {
1047 PrivateMessageView::read(conn, inserted_private_message.id)
1051 let res = PrivateMessageResponse { message };
1053 context.chat_server().do_send(SendUserRoomMessage {
1054 op: UserOperation::CreatePrivateMessage,
1055 response: res.clone(),
1064 #[async_trait::async_trait(?Send)]
1065 impl Perform for EditPrivateMessage {
1066 type Response = PrivateMessageResponse;
1070 context: &Data<LemmyContext>,
1071 websocket_id: Option<ConnectionId>,
1072 ) -> Result<PrivateMessageResponse, LemmyError> {
1073 let data: &EditPrivateMessage = &self;
1074 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1076 // Checking permissions
1077 let edit_id = data.edit_id;
1078 let orig_private_message = blocking(context.pool(), move |conn| {
1079 PrivateMessage::read(conn, edit_id)
1082 if user.id != orig_private_message.creator_id {
1083 return Err(APIError::err("no_private_message_edit_allowed").into());
1087 let content_slurs_removed = remove_slurs(&data.content);
1088 let edit_id = data.edit_id;
1089 let updated_private_message = match blocking(context.pool(), move |conn| {
1090 PrivateMessage::update_content(conn, edit_id, &content_slurs_removed)
1094 Ok(private_message) => private_message,
1095 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1098 // Send the apub update
1099 updated_private_message.send_update(&user, context).await?;
1101 let edit_id = data.edit_id;
1102 let message = blocking(context.pool(), move |conn| {
1103 PrivateMessageView::read(conn, edit_id)
1106 let recipient_id = message.recipient_id;
1108 let res = PrivateMessageResponse { message };
1110 context.chat_server().do_send(SendUserRoomMessage {
1111 op: UserOperation::EditPrivateMessage,
1112 response: res.clone(),
1121 #[async_trait::async_trait(?Send)]
1122 impl Perform for DeletePrivateMessage {
1123 type Response = PrivateMessageResponse;
1127 context: &Data<LemmyContext>,
1128 websocket_id: Option<ConnectionId>,
1129 ) -> Result<PrivateMessageResponse, LemmyError> {
1130 let data: &DeletePrivateMessage = &self;
1131 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1133 // Checking permissions
1134 let edit_id = data.edit_id;
1135 let orig_private_message = blocking(context.pool(), move |conn| {
1136 PrivateMessage::read(conn, edit_id)
1139 if user.id != orig_private_message.creator_id {
1140 return Err(APIError::err("no_private_message_edit_allowed").into());
1144 let edit_id = data.edit_id;
1145 let deleted = data.deleted;
1146 let updated_private_message = match blocking(context.pool(), move |conn| {
1147 PrivateMessage::update_deleted(conn, edit_id, deleted)
1151 Ok(private_message) => private_message,
1152 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1155 // Send the apub update
1157 updated_private_message.send_delete(&user, context).await?;
1159 updated_private_message
1160 .send_undo_delete(&user, context)
1164 let edit_id = data.edit_id;
1165 let message = blocking(context.pool(), move |conn| {
1166 PrivateMessageView::read(conn, edit_id)
1169 let recipient_id = message.recipient_id;
1171 let res = PrivateMessageResponse { message };
1173 context.chat_server().do_send(SendUserRoomMessage {
1174 op: UserOperation::DeletePrivateMessage,
1175 response: res.clone(),
1184 #[async_trait::async_trait(?Send)]
1185 impl Perform for MarkPrivateMessageAsRead {
1186 type Response = PrivateMessageResponse;
1190 context: &Data<LemmyContext>,
1191 websocket_id: Option<ConnectionId>,
1192 ) -> Result<PrivateMessageResponse, LemmyError> {
1193 let data: &MarkPrivateMessageAsRead = &self;
1194 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1196 // Checking permissions
1197 let edit_id = data.edit_id;
1198 let orig_private_message = blocking(context.pool(), move |conn| {
1199 PrivateMessage::read(conn, edit_id)
1202 if user.id != orig_private_message.recipient_id {
1203 return Err(APIError::err("couldnt_update_private_message").into());
1207 let edit_id = data.edit_id;
1208 let read = data.read;
1209 match blocking(context.pool(), move |conn| {
1210 PrivateMessage::update_read(conn, edit_id, read)
1214 Ok(private_message) => private_message,
1215 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1218 // No need to send an apub update
1220 let edit_id = data.edit_id;
1221 let message = blocking(context.pool(), move |conn| {
1222 PrivateMessageView::read(conn, edit_id)
1225 let recipient_id = message.recipient_id;
1227 let res = PrivateMessageResponse { message };
1229 context.chat_server().do_send(SendUserRoomMessage {
1230 op: UserOperation::MarkPrivateMessageAsRead,
1231 response: res.clone(),
1240 #[async_trait::async_trait(?Send)]
1241 impl Perform for GetPrivateMessages {
1242 type Response = PrivateMessagesResponse;
1246 context: &Data<LemmyContext>,
1247 _websocket_id: Option<ConnectionId>,
1248 ) -> Result<PrivateMessagesResponse, LemmyError> {
1249 let data: &GetPrivateMessages = &self;
1250 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1251 let user_id = user.id;
1253 let page = data.page;
1254 let limit = data.limit;
1255 let unread_only = data.unread_only;
1256 let messages = blocking(context.pool(), move |conn| {
1257 PrivateMessageQueryBuilder::create(&conn, user_id)
1260 .unread_only(unread_only)
1265 Ok(PrivateMessagesResponse { messages })
1269 #[async_trait::async_trait(?Send)]
1270 impl Perform for UserJoin {
1271 type Response = UserJoinResponse;
1275 context: &Data<LemmyContext>,
1276 websocket_id: Option<ConnectionId>,
1277 ) -> Result<UserJoinResponse, LemmyError> {
1278 let data: &UserJoin = &self;
1279 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1281 if let Some(ws_id) = websocket_id {
1282 context.chat_server().do_send(JoinUserRoom {
1288 Ok(UserJoinResponse { joined: true })