2 captcha_espeak_wav_base64,
5 collect_moderated_communities,
11 use actix_web::web::Data;
14 use captcha::{gen, Difficulty};
16 use lemmy_apub::ApubObjectType;
18 diesel_option_overwrite,
24 password_reset_request::*,
32 comment_report_view::CommentReportView,
33 comment_view::CommentQueryBuilder,
35 community_follower_view::CommunityFollowerView,
36 community_moderator_view::CommunityModeratorView,
38 post_report_view::PostReportView,
39 post_view::PostQueryBuilder,
40 private_message_view::{PrivateMessageQueryBuilder, PrivateMessageView},
42 user_mention_view::{UserMentionQueryBuilder, UserMentionView},
43 user_view::{UserViewDangerous, UserViewSafe},
51 use lemmy_structs::{blocking, send_email_to_user, user::*};
53 apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
59 generate_random_string,
60 is_valid_preferred_username,
69 use lemmy_websocket::{
70 messages::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage},
74 use std::str::FromStr;
76 #[async_trait::async_trait(?Send)]
77 impl Perform for Login {
78 type Response = LoginResponse;
82 context: &Data<LemmyContext>,
83 _websocket_id: Option<ConnectionId>,
84 ) -> Result<LoginResponse, LemmyError> {
85 let data: &Login = &self;
87 // Fetch that username / email
88 let username_or_email = data.username_or_email.clone();
89 let user = match blocking(context.pool(), move |conn| {
90 User_::find_by_email_or_username(conn, &username_or_email)
95 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
98 // Verify the password
99 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
101 return Err(APIError::err("password_incorrect").into());
106 jwt: Claims::jwt(user, Settings::get().hostname)?,
111 #[async_trait::async_trait(?Send)]
112 impl Perform for Register {
113 type Response = LoginResponse;
117 context: &Data<LemmyContext>,
118 _websocket_id: Option<ConnectionId>,
119 ) -> Result<LoginResponse, LemmyError> {
120 let data: &Register = &self;
122 // Make sure site has open registration
123 if let Ok(site_view) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
124 if !site_view.site.open_registration {
125 return Err(APIError::err("registration_closed").into());
129 // Password length check
130 if data.password.len() > 60 {
131 return Err(APIError::err("invalid_password").into());
134 // Make sure passwords match
135 if data.password != data.password_verify {
136 return Err(APIError::err("passwords_dont_match").into());
139 // If its not the admin, check the captcha
140 if !data.admin && Settings::get().captcha.enabled {
147 .unwrap_or_else(|| "".to_string()),
151 .unwrap_or_else(|| "".to_string()),
155 return Err(APIError::err("captcha_incorrect").into());
159 check_slurs(&data.username)?;
161 // Make sure there are no admins
162 let any_admins = blocking(context.pool(), move |conn| {
163 UserViewSafe::admins(conn).map(|a| a.is_empty())
166 if data.admin && !any_admins {
167 return Err(APIError::err("admin_already_created").into());
170 let user_keypair = generate_actor_keypair()?;
171 if !is_valid_username(&data.username) {
172 return Err(APIError::err("invalid_username").into());
175 // Register the new user
176 let user_form = UserForm {
177 name: data.username.to_owned(),
178 email: Some(data.email.to_owned()),
179 matrix_user_id: None,
182 password_encrypted: data.password.to_owned(),
183 preferred_username: None,
188 show_nsfw: data.show_nsfw,
189 theme: "browser".into(),
190 default_sort_type: SortType::Active as i16,
191 default_listing_type: ListingType::Subscribed as i16,
192 lang: "browser".into(),
194 send_notifications_to_email: false,
195 actor_id: Some(make_apub_endpoint(EndpointType::User, &data.username).to_string()),
198 private_key: Some(user_keypair.private_key),
199 public_key: Some(user_keypair.public_key),
200 last_refreshed_at: None,
204 let inserted_user = match blocking(context.pool(), move |conn| {
205 User_::register(conn, &user_form)
211 let err_type = if e.to_string()
212 == "duplicate key value violates unique constraint \"user__email_key\""
214 "email_already_exists"
216 "user_already_exists"
219 return Err(APIError::err(err_type).into());
223 let main_community_keypair = generate_actor_keypair()?;
225 // Create the main community if it doesn't exist
227 match blocking(context.pool(), move |conn| Community::read(conn, 2)).await? {
230 let default_community_name = "main";
231 let community_form = CommunityForm {
232 name: default_community_name.to_string(),
233 title: "The Default Community".to_string(),
234 description: Some("The Default Community".to_string()),
237 creator_id: inserted_user.id,
242 make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
245 private_key: Some(main_community_keypair.private_key),
246 public_key: Some(main_community_keypair.public_key),
247 last_refreshed_at: None,
252 blocking(context.pool(), move |conn| {
253 Community::create(conn, &community_form)
259 // Sign them up for main community no matter what
260 let community_follower_form = CommunityFollowerForm {
261 community_id: main_community.id,
262 user_id: inserted_user.id,
266 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
267 if blocking(context.pool(), follow).await?.is_err() {
268 return Err(APIError::err("community_follower_already_exists").into());
271 // If its an admin, add them as a mod and follower to main
273 let community_moderator_form = CommunityModeratorForm {
274 community_id: main_community.id,
275 user_id: inserted_user.id,
278 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
279 if blocking(context.pool(), join).await?.is_err() {
280 return Err(APIError::err("community_moderator_already_exists").into());
286 jwt: Claims::jwt(inserted_user, Settings::get().hostname)?,
291 #[async_trait::async_trait(?Send)]
292 impl Perform for GetCaptcha {
293 type Response = GetCaptchaResponse;
297 context: &Data<LemmyContext>,
298 _websocket_id: Option<ConnectionId>,
299 ) -> Result<Self::Response, LemmyError> {
300 let captcha_settings = Settings::get().captcha;
302 if !captcha_settings.enabled {
303 return Ok(GetCaptchaResponse { ok: None });
306 let captcha = match captcha_settings.difficulty.as_str() {
307 "easy" => gen(Difficulty::Easy),
308 "medium" => gen(Difficulty::Medium),
309 "hard" => gen(Difficulty::Hard),
310 _ => gen(Difficulty::Medium),
313 let answer = captcha.chars_as_string();
315 let png_byte_array = captcha.as_png().expect("failed to generate captcha");
317 let png = base64::encode(png_byte_array);
319 let uuid = uuid::Uuid::new_v4().to_string();
321 let wav = captcha_espeak_wav_base64(&answer).ok();
323 let captcha_item = CaptchaItem {
325 uuid: uuid.to_owned(),
326 expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
329 // Stores the captcha item on the queue
330 context.chat_server().do_send(captcha_item);
332 Ok(GetCaptchaResponse {
333 ok: Some(CaptchaResponse { png, uuid, wav }),
338 #[async_trait::async_trait(?Send)]
339 impl Perform for SaveUserSettings {
340 type Response = LoginResponse;
344 context: &Data<LemmyContext>,
345 _websocket_id: Option<ConnectionId>,
346 ) -> Result<LoginResponse, LemmyError> {
347 let data: &SaveUserSettings = &self;
348 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
350 let user_id = user.id;
351 let read_user = blocking(context.pool(), move |conn| User_::read(conn, user_id)).await??;
353 let avatar = diesel_option_overwrite(&data.avatar);
354 let banner = diesel_option_overwrite(&data.banner);
355 let email = diesel_option_overwrite(&data.email);
356 let bio = diesel_option_overwrite(&data.bio);
357 let preferred_username = diesel_option_overwrite(&data.preferred_username);
358 let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
360 // Check to make sure the avatar and banners are urls
361 check_optional_url(&avatar)?;
362 check_optional_url(&banner)?;
364 if let Some(Some(bio)) = &bio {
365 if bio.chars().count() > 300 {
366 return Err(APIError::err("bio_length_overflow").into());
370 if let Some(Some(preferred_username)) = &preferred_username {
371 if !is_valid_preferred_username(preferred_username.trim()) {
372 return Err(APIError::err("invalid_username").into());
376 let password_encrypted = match &data.new_password {
377 Some(new_password) => {
378 match &data.new_password_verify {
379 Some(new_password_verify) => {
380 // Make sure passwords match
381 if new_password != new_password_verify {
382 return Err(APIError::err("passwords_dont_match").into());
385 // Check the old password
386 match &data.old_password {
387 Some(old_password) => {
389 verify(old_password, &read_user.password_encrypted).unwrap_or(false);
391 return Err(APIError::err("password_incorrect").into());
393 let new_password = new_password.to_owned();
394 let user = blocking(context.pool(), move |conn| {
395 User_::update_password(conn, user_id, &new_password)
398 user.password_encrypted
400 None => return Err(APIError::err("password_incorrect").into()),
403 None => return Err(APIError::err("passwords_dont_match").into()),
406 None => read_user.password_encrypted,
409 let user_form = UserForm {
410 name: read_user.name,
417 published: Some(read_user.published),
418 updated: Some(naive_now()),
419 admin: read_user.admin,
420 banned: Some(read_user.banned),
421 show_nsfw: data.show_nsfw,
422 theme: data.theme.to_owned(),
423 default_sort_type: data.default_sort_type,
424 default_listing_type: data.default_listing_type,
425 lang: data.lang.to_owned(),
426 show_avatars: data.show_avatars,
427 send_notifications_to_email: data.send_notifications_to_email,
428 actor_id: Some(read_user.actor_id),
430 local: read_user.local,
431 private_key: read_user.private_key,
432 public_key: read_user.public_key,
433 last_refreshed_at: None,
436 let res = blocking(context.pool(), move |conn| {
437 User_::update(conn, user_id, &user_form)
440 let updated_user: User_ = match res {
443 let err_type = if e.to_string()
444 == "duplicate key value violates unique constraint \"user__email_key\""
446 "email_already_exists"
448 "user_already_exists"
451 return Err(APIError::err(err_type).into());
457 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
462 #[async_trait::async_trait(?Send)]
463 impl Perform for GetUserDetails {
464 type Response = GetUserDetailsResponse;
468 context: &Data<LemmyContext>,
469 _websocket_id: Option<ConnectionId>,
470 ) -> Result<GetUserDetailsResponse, LemmyError> {
471 let data: &GetUserDetails = &self;
472 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
474 let show_nsfw = match &user {
475 Some(user) => user.show_nsfw,
479 let sort = SortType::from_str(&data.sort)?;
484 .unwrap_or_else(|| "admin".to_string());
485 let user_details_id = match data.user_id {
488 let user = blocking(context.pool(), move |conn| {
489 User_::read_from_name(conn, &username)
494 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
499 let user_id = user.map(|u| u.id);
501 let (user_view, user_dangerous) = if let Some(auth_user_id) = user_id {
502 if user_details_id == auth_user_id {
506 blocking(context.pool(), move |conn| {
507 UserViewDangerous::read(conn, auth_user_id)
515 blocking(context.pool(), move |conn| {
516 UserViewSafe::read(conn, user_details_id)
526 blocking(context.pool(), move |conn| {
527 UserViewSafe::read(conn, user_details_id)
535 let page = data.page;
536 let limit = data.limit;
537 let saved_only = data.saved_only;
538 let community_id = data.community_id;
540 let (posts, comments) = blocking(context.pool(), move |conn| {
541 let mut posts_query = PostQueryBuilder::create(conn)
543 .show_nsfw(show_nsfw)
544 .saved_only(saved_only)
545 .community_id(community_id)
550 let mut comments_query = CommentQueryBuilder::create(conn)
553 .saved_only(saved_only)
557 // If its saved only, you don't care what creator it was
558 // Or, if its not saved, then you only want it for that specific creator
560 posts_query = posts_query.creator_id(user_details_id);
561 comments_query = comments_query.creator_id(user_details_id);
564 let posts = posts_query.list()?;
565 let comments = comments_query.list()?;
567 Ok((posts, comments)) as Result<_, LemmyError>
571 let follows = blocking(context.pool(), move |conn| {
572 CommunityFollowerView::for_user(conn, user_details_id)
575 let moderates = blocking(context.pool(), move |conn| {
576 CommunityModeratorView::for_user(conn, user_details_id)
581 Ok(GetUserDetailsResponse {
582 // TODO need to figure out dangerous user view here
593 #[async_trait::async_trait(?Send)]
594 impl Perform for AddAdmin {
595 type Response = AddAdminResponse;
599 context: &Data<LemmyContext>,
600 websocket_id: Option<ConnectionId>,
601 ) -> Result<AddAdminResponse, LemmyError> {
602 let data: &AddAdmin = &self;
603 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
605 // Make sure user is an admin
606 is_admin(context.pool(), user.id).await?;
608 let added = data.added;
609 let added_user_id = data.user_id;
610 let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
611 if blocking(context.pool(), add_admin).await?.is_err() {
612 return Err(APIError::err("couldnt_update_user").into());
616 let form = ModAddForm {
617 mod_user_id: user.id,
618 other_user_id: data.user_id,
619 removed: Some(!data.added),
622 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
624 let site_creator_id = blocking(context.pool(), move |conn| {
625 Site::read(conn, 1).map(|s| s.creator_id)
629 let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
630 let creator_index = admins
632 .position(|r| r.user.id == site_creator_id)
633 .context(location_info!())?;
634 let creator_user = admins.remove(creator_index);
635 admins.insert(0, creator_user);
637 let res = AddAdminResponse { admins };
639 context.chat_server().do_send(SendAllMessage {
640 op: UserOperation::AddAdmin,
641 response: res.clone(),
649 #[async_trait::async_trait(?Send)]
650 impl Perform for BanUser {
651 type Response = BanUserResponse;
655 context: &Data<LemmyContext>,
656 websocket_id: Option<ConnectionId>,
657 ) -> Result<BanUserResponse, LemmyError> {
658 let data: &BanUser = &self;
659 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
661 // Make sure user is an admin
662 is_admin(context.pool(), user.id).await?;
665 let banned_user_id = data.user_id;
666 let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
667 if blocking(context.pool(), ban_user).await?.is_err() {
668 return Err(APIError::err("couldnt_update_user").into());
671 // Remove their data if that's desired
672 if let Some(remove_data) = data.remove_data {
674 blocking(context.pool(), move |conn: &'_ _| {
675 Post::update_removed_for_creator(conn, banned_user_id, None, remove_data)
680 blocking(context.pool(), move |conn: &'_ _| {
681 Community::update_removed_for_creator(conn, banned_user_id, remove_data)
686 blocking(context.pool(), move |conn: &'_ _| {
687 Comment::update_removed_for_creator(conn, banned_user_id, remove_data)
693 let expires = match data.expires {
694 Some(time) => Some(naive_from_unix(time)),
698 let form = ModBanForm {
699 mod_user_id: user.id,
700 other_user_id: data.user_id,
701 reason: data.reason.to_owned(),
702 banned: Some(data.ban),
706 blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
708 let user_id = data.user_id;
709 let user_view = blocking(context.pool(), move |conn| {
710 UserViewSafe::read(conn, user_id)
714 let res = BanUserResponse {
719 context.chat_server().do_send(SendAllMessage {
720 op: UserOperation::BanUser,
721 response: res.clone(),
729 #[async_trait::async_trait(?Send)]
730 impl Perform for GetReplies {
731 type Response = GetRepliesResponse;
735 context: &Data<LemmyContext>,
736 _websocket_id: Option<ConnectionId>,
737 ) -> Result<GetRepliesResponse, LemmyError> {
738 let data: &GetReplies = &self;
739 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
741 let sort = SortType::from_str(&data.sort)?;
743 let page = data.page;
744 let limit = data.limit;
745 let unread_only = data.unread_only;
746 let user_id = user.id;
747 let replies = blocking(context.pool(), move |conn| {
748 CommentQueryBuilder::create(conn)
750 .unread_only(unread_only)
751 .recipient_id(user_id)
759 Ok(GetRepliesResponse { replies })
763 #[async_trait::async_trait(?Send)]
764 impl Perform for GetUserMentions {
765 type Response = GetUserMentionsResponse;
769 context: &Data<LemmyContext>,
770 _websocket_id: Option<ConnectionId>,
771 ) -> Result<GetUserMentionsResponse, LemmyError> {
772 let data: &GetUserMentions = &self;
773 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
775 let sort = SortType::from_str(&data.sort)?;
777 let page = data.page;
778 let limit = data.limit;
779 let unread_only = data.unread_only;
780 let user_id = user.id;
781 let mentions = blocking(context.pool(), move |conn| {
782 UserMentionQueryBuilder::create(conn)
783 .recipient_id(user_id)
786 .unread_only(unread_only)
793 Ok(GetUserMentionsResponse { mentions })
797 #[async_trait::async_trait(?Send)]
798 impl Perform for MarkUserMentionAsRead {
799 type Response = UserMentionResponse;
803 context: &Data<LemmyContext>,
804 _websocket_id: Option<ConnectionId>,
805 ) -> Result<UserMentionResponse, LemmyError> {
806 let data: &MarkUserMentionAsRead = &self;
807 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
809 let user_mention_id = data.user_mention_id;
810 let read_user_mention = blocking(context.pool(), move |conn| {
811 UserMention::read(conn, user_mention_id)
815 if user.id != read_user_mention.recipient_id {
816 return Err(APIError::err("couldnt_update_comment").into());
819 let user_mention_id = read_user_mention.id;
820 let read = data.read;
821 let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
822 if blocking(context.pool(), update_mention).await?.is_err() {
823 return Err(APIError::err("couldnt_update_comment").into());
826 let user_mention_id = read_user_mention.id;
827 let user_id = user.id;
828 let user_mention_view = blocking(context.pool(), move |conn| {
829 UserMentionView::read(conn, user_mention_id, Some(user_id))
833 Ok(UserMentionResponse { user_mention_view })
837 #[async_trait::async_trait(?Send)]
838 impl Perform for MarkAllAsRead {
839 type Response = GetRepliesResponse;
843 context: &Data<LemmyContext>,
844 _websocket_id: Option<ConnectionId>,
845 ) -> Result<GetRepliesResponse, LemmyError> {
846 let data: &MarkAllAsRead = &self;
847 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
849 let user_id = user.id;
850 let replies = blocking(context.pool(), move |conn| {
851 CommentQueryBuilder::create(conn)
853 .recipient_id(user_id)
861 // TODO: this should probably be a bulk operation
862 // Not easy to do as a bulk operation,
863 // because recipient_id isn't in the comment table
864 for comment_view in &replies {
865 let reply_id = comment_view.comment.id;
866 let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
867 if blocking(context.pool(), mark_as_read).await?.is_err() {
868 return Err(APIError::err("couldnt_update_comment").into());
872 // Mark all user mentions as read
873 let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
874 if blocking(context.pool(), update_user_mentions)
878 return Err(APIError::err("couldnt_update_comment").into());
881 // Mark all private_messages as read
882 let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
883 if blocking(context.pool(), update_pm).await?.is_err() {
884 return Err(APIError::err("couldnt_update_private_message").into());
887 Ok(GetRepliesResponse { replies: vec![] })
891 #[async_trait::async_trait(?Send)]
892 impl Perform for DeleteAccount {
893 type Response = LoginResponse;
897 context: &Data<LemmyContext>,
898 _websocket_id: Option<ConnectionId>,
899 ) -> Result<LoginResponse, LemmyError> {
900 let data: &DeleteAccount = &self;
901 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
903 // Verify the password
904 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
906 return Err(APIError::err("password_incorrect").into());
910 let user_id = user.id;
911 let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
912 if blocking(context.pool(), permadelete).await?.is_err() {
913 return Err(APIError::err("couldnt_update_comment").into());
917 let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
918 if blocking(context.pool(), permadelete).await?.is_err() {
919 return Err(APIError::err("couldnt_update_post").into());
922 blocking(context.pool(), move |conn| {
923 User_::delete_account(conn, user_id)
928 jwt: data.auth.to_owned(),
933 #[async_trait::async_trait(?Send)]
934 impl Perform for PasswordReset {
935 type Response = PasswordResetResponse;
939 context: &Data<LemmyContext>,
940 _websocket_id: Option<ConnectionId>,
941 ) -> Result<PasswordResetResponse, LemmyError> {
942 let data: &PasswordReset = &self;
945 let email = data.email.clone();
946 let user = match blocking(context.pool(), move |conn| {
947 User_::find_by_email(conn, &email)
952 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
955 // Generate a random token
956 let token = generate_random_string();
959 let token2 = token.clone();
960 let user_id = user.id;
961 blocking(context.pool(), move |conn| {
962 PasswordResetRequest::create_token(conn, user_id, &token2)
966 // Email the pure token to the user.
967 // TODO no i18n support here.
968 let user_email = &user.email.expect("email");
969 let subject = &format!("Password reset for {}", user.name);
970 let hostname = &Settings::get().get_protocol_and_hostname();
971 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);
972 match send_email(subject, user_email, &user.name, html) {
974 Err(_e) => return Err(APIError::err(&_e).into()),
977 Ok(PasswordResetResponse {})
981 #[async_trait::async_trait(?Send)]
982 impl Perform for PasswordChange {
983 type Response = LoginResponse;
987 context: &Data<LemmyContext>,
988 _websocket_id: Option<ConnectionId>,
989 ) -> Result<LoginResponse, LemmyError> {
990 let data: &PasswordChange = &self;
992 // Fetch the user_id from the token
993 let token = data.token.clone();
994 let user_id = blocking(context.pool(), move |conn| {
995 PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
999 // Make sure passwords match
1000 if data.password != data.password_verify {
1001 return Err(APIError::err("passwords_dont_match").into());
1004 // Update the user with the new password
1005 let password = data.password.clone();
1006 let updated_user = match blocking(context.pool(), move |conn| {
1007 User_::update_password(conn, user_id, &password)
1012 Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
1017 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
1022 #[async_trait::async_trait(?Send)]
1023 impl Perform for CreatePrivateMessage {
1024 type Response = PrivateMessageResponse;
1028 context: &Data<LemmyContext>,
1029 websocket_id: Option<ConnectionId>,
1030 ) -> Result<PrivateMessageResponse, LemmyError> {
1031 let data: &CreatePrivateMessage = &self;
1032 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1034 let content_slurs_removed = remove_slurs(&data.content.to_owned());
1036 let private_message_form = PrivateMessageForm {
1037 content: content_slurs_removed.to_owned(),
1038 creator_id: user.id,
1039 recipient_id: data.recipient_id,
1048 let inserted_private_message = match blocking(context.pool(), move |conn| {
1049 PrivateMessage::create(conn, &private_message_form)
1053 Ok(private_message) => private_message,
1055 return Err(APIError::err("couldnt_create_private_message").into());
1059 let inserted_private_message_id = inserted_private_message.id;
1060 let updated_private_message = match blocking(context.pool(), move |conn| {
1061 let apub_id = make_apub_endpoint(
1062 EndpointType::PrivateMessage,
1063 &inserted_private_message_id.to_string(),
1066 PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
1070 Ok(private_message) => private_message,
1071 Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
1074 updated_private_message.send_create(&user, context).await?;
1076 // Send notifications to the recipient
1077 let recipient_id = data.recipient_id;
1078 let recipient_user =
1079 blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1080 if recipient_user.send_notifications_to_email {
1083 "Private Message from",
1085 &content_slurs_removed,
1089 let message = blocking(context.pool(), move |conn| {
1090 PrivateMessageView::read(conn, inserted_private_message.id)
1094 let res = PrivateMessageResponse { message };
1096 context.chat_server().do_send(SendUserRoomMessage {
1097 op: UserOperation::CreatePrivateMessage,
1098 response: res.clone(),
1107 #[async_trait::async_trait(?Send)]
1108 impl Perform for EditPrivateMessage {
1109 type Response = PrivateMessageResponse;
1113 context: &Data<LemmyContext>,
1114 websocket_id: Option<ConnectionId>,
1115 ) -> Result<PrivateMessageResponse, LemmyError> {
1116 let data: &EditPrivateMessage = &self;
1117 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1119 // Checking permissions
1120 let edit_id = data.edit_id;
1121 let orig_private_message = blocking(context.pool(), move |conn| {
1122 PrivateMessage::read(conn, edit_id)
1125 if user.id != orig_private_message.creator_id {
1126 return Err(APIError::err("no_private_message_edit_allowed").into());
1130 let content_slurs_removed = remove_slurs(&data.content);
1131 let edit_id = data.edit_id;
1132 let updated_private_message = match blocking(context.pool(), move |conn| {
1133 PrivateMessage::update_content(conn, edit_id, &content_slurs_removed)
1137 Ok(private_message) => private_message,
1138 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1141 // Send the apub update
1142 updated_private_message.send_update(&user, context).await?;
1144 let edit_id = data.edit_id;
1145 let message = blocking(context.pool(), move |conn| {
1146 PrivateMessageView::read(conn, edit_id)
1149 let recipient_id = message.recipient.id;
1151 let res = PrivateMessageResponse { message };
1153 context.chat_server().do_send(SendUserRoomMessage {
1154 op: UserOperation::EditPrivateMessage,
1155 response: res.clone(),
1164 #[async_trait::async_trait(?Send)]
1165 impl Perform for DeletePrivateMessage {
1166 type Response = PrivateMessageResponse;
1170 context: &Data<LemmyContext>,
1171 websocket_id: Option<ConnectionId>,
1172 ) -> Result<PrivateMessageResponse, LemmyError> {
1173 let data: &DeletePrivateMessage = &self;
1174 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1176 // Checking permissions
1177 let edit_id = data.edit_id;
1178 let orig_private_message = blocking(context.pool(), move |conn| {
1179 PrivateMessage::read(conn, edit_id)
1182 if user.id != orig_private_message.creator_id {
1183 return Err(APIError::err("no_private_message_edit_allowed").into());
1187 let edit_id = data.edit_id;
1188 let deleted = data.deleted;
1189 let updated_private_message = match blocking(context.pool(), move |conn| {
1190 PrivateMessage::update_deleted(conn, edit_id, deleted)
1194 Ok(private_message) => private_message,
1195 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1198 // Send the apub update
1200 updated_private_message.send_delete(&user, context).await?;
1202 updated_private_message
1203 .send_undo_delete(&user, context)
1207 let edit_id = data.edit_id;
1208 let message = blocking(context.pool(), move |conn| {
1209 PrivateMessageView::read(conn, edit_id)
1212 let recipient_id = message.recipient.id;
1214 let res = PrivateMessageResponse { message };
1216 context.chat_server().do_send(SendUserRoomMessage {
1217 op: UserOperation::DeletePrivateMessage,
1218 response: res.clone(),
1227 #[async_trait::async_trait(?Send)]
1228 impl Perform for MarkPrivateMessageAsRead {
1229 type Response = PrivateMessageResponse;
1233 context: &Data<LemmyContext>,
1234 websocket_id: Option<ConnectionId>,
1235 ) -> Result<PrivateMessageResponse, LemmyError> {
1236 let data: &MarkPrivateMessageAsRead = &self;
1237 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1239 // Checking permissions
1240 let edit_id = data.edit_id;
1241 let orig_private_message = blocking(context.pool(), move |conn| {
1242 PrivateMessage::read(conn, edit_id)
1245 if user.id != orig_private_message.recipient_id {
1246 return Err(APIError::err("couldnt_update_private_message").into());
1250 let edit_id = data.edit_id;
1251 let read = data.read;
1252 match blocking(context.pool(), move |conn| {
1253 PrivateMessage::update_read(conn, edit_id, read)
1257 Ok(private_message) => private_message,
1258 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1261 // No need to send an apub update
1263 let edit_id = data.edit_id;
1264 let message = blocking(context.pool(), move |conn| {
1265 PrivateMessageView::read(conn, edit_id)
1268 let recipient_id = message.recipient.id;
1270 let res = PrivateMessageResponse { message };
1272 context.chat_server().do_send(SendUserRoomMessage {
1273 op: UserOperation::MarkPrivateMessageAsRead,
1274 response: res.clone(),
1283 #[async_trait::async_trait(?Send)]
1284 impl Perform for GetPrivateMessages {
1285 type Response = PrivateMessagesResponse;
1289 context: &Data<LemmyContext>,
1290 _websocket_id: Option<ConnectionId>,
1291 ) -> Result<PrivateMessagesResponse, LemmyError> {
1292 let data: &GetPrivateMessages = &self;
1293 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1294 let user_id = user.id;
1296 let page = data.page;
1297 let limit = data.limit;
1298 let unread_only = data.unread_only;
1299 let messages = blocking(context.pool(), move |conn| {
1300 PrivateMessageQueryBuilder::create(&conn, user_id)
1303 .unread_only(unread_only)
1308 Ok(PrivateMessagesResponse { messages })
1312 #[async_trait::async_trait(?Send)]
1313 impl Perform for UserJoin {
1314 type Response = UserJoinResponse;
1318 context: &Data<LemmyContext>,
1319 websocket_id: Option<ConnectionId>,
1320 ) -> Result<UserJoinResponse, LemmyError> {
1321 let data: &UserJoin = &self;
1322 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1324 if let Some(ws_id) = websocket_id {
1325 context.chat_server().do_send(JoinUserRoom {
1331 Ok(UserJoinResponse { joined: true })
1335 #[async_trait::async_trait(?Send)]
1336 impl Perform for GetReportCount {
1337 type Response = GetReportCountResponse;
1341 context: &Data<LemmyContext>,
1342 websocket_id: Option<ConnectionId>,
1343 ) -> Result<GetReportCountResponse, LemmyError> {
1344 let data: &GetReportCount = &self;
1345 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1347 let user_id = user.id;
1348 let community_id = data.community;
1350 collect_moderated_communities(user_id, community_id, context.pool()).await?;
1353 if community_ids.is_empty() {
1354 GetReportCountResponse {
1360 let ids = community_ids.clone();
1361 let comment_reports = blocking(context.pool(), move |conn| {
1362 CommentReportView::get_report_count(conn, &ids)
1366 let ids = community_ids.clone();
1367 let post_reports = blocking(context.pool(), move |conn| {
1368 PostReportView::get_report_count(conn, &ids)
1372 GetReportCountResponse {
1373 community: data.community,
1380 context.chat_server().do_send(SendUserRoomMessage {
1381 op: UserOperation::GetReportCount,
1382 response: res.clone(),
1383 recipient_id: user.id,