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},
41 user_mention_view::{UserMentionQueryBuilder, UserMentionView},
42 user_view::{UserViewDangerous, UserViewSafe},
50 use lemmy_structs::{blocking, send_email_to_user, user::*};
52 apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
58 generate_random_string,
59 is_valid_preferred_username,
68 use lemmy_websocket::{
69 messages::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage},
73 use std::str::FromStr;
75 #[async_trait::async_trait(?Send)]
76 impl Perform for Login {
77 type Response = LoginResponse;
81 context: &Data<LemmyContext>,
82 _websocket_id: Option<ConnectionId>,
83 ) -> Result<LoginResponse, LemmyError> {
84 let data: &Login = &self;
86 // Fetch that username / email
87 let username_or_email = data.username_or_email.clone();
88 let user = match blocking(context.pool(), move |conn| {
89 User_::find_by_email_or_username(conn, &username_or_email)
94 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
97 // Verify the password
98 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
100 return Err(APIError::err("password_incorrect").into());
105 jwt: Claims::jwt(user, Settings::get().hostname)?,
110 #[async_trait::async_trait(?Send)]
111 impl Perform for Register {
112 type Response = LoginResponse;
116 context: &Data<LemmyContext>,
117 _websocket_id: Option<ConnectionId>,
118 ) -> Result<LoginResponse, LemmyError> {
119 let data: &Register = &self;
121 // Make sure site has open registration
122 if let Ok(site) = blocking(context.pool(), move |conn| Site::read_simple(conn)).await? {
123 if !site.open_registration {
124 return Err(APIError::err("registration_closed").into());
128 // Password length check
129 if data.password.len() > 60 {
130 return Err(APIError::err("invalid_password").into());
133 // Make sure passwords match
134 if data.password != data.password_verify {
135 return Err(APIError::err("passwords_dont_match").into());
138 // If its not the admin, check the captcha
139 if !data.admin && Settings::get().captcha.enabled {
146 .unwrap_or_else(|| "".to_string()),
150 .unwrap_or_else(|| "".to_string()),
154 return Err(APIError::err("captcha_incorrect").into());
158 check_slurs(&data.username)?;
160 // Make sure there are no admins
161 let any_admins = blocking(context.pool(), move |conn| {
162 UserViewSafe::admins(conn).map(|a| a.is_empty())
165 if data.admin && !any_admins {
166 return Err(APIError::err("admin_already_created").into());
169 let user_keypair = generate_actor_keypair()?;
170 if !is_valid_username(&data.username) {
171 return Err(APIError::err("invalid_username").into());
174 // Register the new user
175 let user_form = UserForm {
176 name: data.username.to_owned(),
177 email: Some(data.email.to_owned()),
178 matrix_user_id: None,
181 password_encrypted: data.password.to_owned(),
182 preferred_username: None,
187 show_nsfw: data.show_nsfw,
188 theme: "browser".into(),
189 default_sort_type: SortType::Active as i16,
190 default_listing_type: ListingType::Subscribed as i16,
191 lang: "browser".into(),
193 send_notifications_to_email: false,
194 actor_id: Some(make_apub_endpoint(EndpointType::User, &data.username).to_string()),
197 private_key: Some(user_keypair.private_key),
198 public_key: Some(user_keypair.public_key),
199 last_refreshed_at: None,
203 let inserted_user = match blocking(context.pool(), move |conn| {
204 User_::register(conn, &user_form)
210 let err_type = if e.to_string()
211 == "duplicate key value violates unique constraint \"user__email_key\""
213 "email_already_exists"
215 "user_already_exists"
218 return Err(APIError::err(err_type).into());
222 let main_community_keypair = generate_actor_keypair()?;
224 // Create the main community if it doesn't exist
226 match blocking(context.pool(), move |conn| Community::read(conn, 2)).await? {
229 let default_community_name = "main";
230 let community_form = CommunityForm {
231 name: default_community_name.to_string(),
232 title: "The Default Community".to_string(),
233 description: Some("The Default Community".to_string()),
236 creator_id: inserted_user.id,
241 make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
244 private_key: Some(main_community_keypair.private_key),
245 public_key: Some(main_community_keypair.public_key),
246 last_refreshed_at: None,
251 blocking(context.pool(), move |conn| {
252 Community::create(conn, &community_form)
258 // Sign them up for main community no matter what
259 let community_follower_form = CommunityFollowerForm {
260 community_id: main_community.id,
261 user_id: inserted_user.id,
265 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
266 if blocking(context.pool(), follow).await?.is_err() {
267 return Err(APIError::err("community_follower_already_exists").into());
270 // If its an admin, add them as a mod and follower to main
272 let community_moderator_form = CommunityModeratorForm {
273 community_id: main_community.id,
274 user_id: inserted_user.id,
277 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
278 if blocking(context.pool(), join).await?.is_err() {
279 return Err(APIError::err("community_moderator_already_exists").into());
285 jwt: Claims::jwt(inserted_user, Settings::get().hostname)?,
290 #[async_trait::async_trait(?Send)]
291 impl Perform for GetCaptcha {
292 type Response = GetCaptchaResponse;
296 context: &Data<LemmyContext>,
297 _websocket_id: Option<ConnectionId>,
298 ) -> Result<Self::Response, LemmyError> {
299 let captcha_settings = Settings::get().captcha;
301 if !captcha_settings.enabled {
302 return Ok(GetCaptchaResponse { ok: None });
305 let captcha = match captcha_settings.difficulty.as_str() {
306 "easy" => gen(Difficulty::Easy),
307 "medium" => gen(Difficulty::Medium),
308 "hard" => gen(Difficulty::Hard),
309 _ => gen(Difficulty::Medium),
312 let answer = captcha.chars_as_string();
314 let png_byte_array = captcha.as_png().expect("failed to generate captcha");
316 let png = base64::encode(png_byte_array);
318 let uuid = uuid::Uuid::new_v4().to_string();
320 let wav = captcha_espeak_wav_base64(&answer).ok();
322 let captcha_item = CaptchaItem {
324 uuid: uuid.to_owned(),
325 expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
328 // Stores the captcha item on the queue
329 context.chat_server().do_send(captcha_item);
331 Ok(GetCaptchaResponse {
332 ok: Some(CaptchaResponse { png, uuid, wav }),
337 #[async_trait::async_trait(?Send)]
338 impl Perform for SaveUserSettings {
339 type Response = LoginResponse;
343 context: &Data<LemmyContext>,
344 _websocket_id: Option<ConnectionId>,
345 ) -> Result<LoginResponse, LemmyError> {
346 let data: &SaveUserSettings = &self;
347 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
349 let avatar = diesel_option_overwrite(&data.avatar);
350 let banner = diesel_option_overwrite(&data.banner);
351 let email = diesel_option_overwrite(&data.email);
352 let bio = diesel_option_overwrite(&data.bio);
353 let preferred_username = diesel_option_overwrite(&data.preferred_username);
354 let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
356 // Check to make sure the avatar and banners are urls
357 check_optional_url(&avatar)?;
358 check_optional_url(&banner)?;
360 if let Some(Some(bio)) = &bio {
361 if bio.chars().count() > 300 {
362 return Err(APIError::err("bio_length_overflow").into());
366 if let Some(Some(preferred_username)) = &preferred_username {
367 if !is_valid_preferred_username(preferred_username.trim()) {
368 return Err(APIError::err("invalid_username").into());
372 let user_id = user.id;
373 let password_encrypted = match &data.new_password {
374 Some(new_password) => {
375 match &data.new_password_verify {
376 Some(new_password_verify) => {
377 // Make sure passwords match
378 if new_password != new_password_verify {
379 return Err(APIError::err("passwords_dont_match").into());
382 // Check the old password
383 match &data.old_password {
384 Some(old_password) => {
385 let valid: bool = verify(old_password, &user.password_encrypted).unwrap_or(false);
387 return Err(APIError::err("password_incorrect").into());
389 let new_password = new_password.to_owned();
390 let user = blocking(context.pool(), move |conn| {
391 User_::update_password(conn, user_id, &new_password)
394 user.password_encrypted
396 None => return Err(APIError::err("password_incorrect").into()),
399 None => return Err(APIError::err("passwords_dont_match").into()),
402 None => user.password_encrypted,
405 let default_listing_type = ListingType::from_str(&data.default_listing_type)? as i16;
406 let default_sort_type = SortType::from_str(&data.default_sort_type)? as i16;
408 let user_form = UserForm {
416 published: Some(user.published),
417 updated: Some(naive_now()),
419 banned: Some(user.banned),
420 show_nsfw: data.show_nsfw,
421 theme: data.theme.to_owned(),
423 default_listing_type,
424 lang: data.lang.to_owned(),
425 show_avatars: data.show_avatars,
426 send_notifications_to_email: data.send_notifications_to_email,
427 actor_id: Some(user.actor_id),
430 private_key: user.private_key,
431 public_key: user.public_key,
432 last_refreshed_at: None,
435 let res = blocking(context.pool(), move |conn| {
436 User_::update(conn, user_id, &user_form)
439 let updated_user: User_ = match res {
442 let err_type = if e.to_string()
443 == "duplicate key value violates unique constraint \"user__email_key\""
445 "email_already_exists"
447 "user_already_exists"
450 return Err(APIError::err(err_type).into());
456 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
461 #[async_trait::async_trait(?Send)]
462 impl Perform for GetUserDetails {
463 type Response = GetUserDetailsResponse;
467 context: &Data<LemmyContext>,
468 _websocket_id: Option<ConnectionId>,
469 ) -> Result<GetUserDetailsResponse, LemmyError> {
470 let data: &GetUserDetails = &self;
471 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
473 let show_nsfw = match &user {
474 Some(user) => user.show_nsfw,
478 let sort = SortType::from_str(&data.sort)?;
483 .unwrap_or_else(|| "admin".to_string());
484 let user_details_id = match data.user_id {
487 let user = blocking(context.pool(), move |conn| {
488 User_::read_from_name(conn, &username)
493 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
498 let user_id = user.map(|u| u.id);
500 let (user_view, user_dangerous) = if let Some(auth_user_id) = user_id {
501 if user_details_id == auth_user_id {
505 blocking(context.pool(), move |conn| {
506 UserViewDangerous::read(conn, auth_user_id)
514 blocking(context.pool(), move |conn| {
515 UserViewSafe::read(conn, user_details_id)
525 blocking(context.pool(), move |conn| {
526 UserViewSafe::read(conn, user_details_id)
534 let page = data.page;
535 let limit = data.limit;
536 let saved_only = data.saved_only;
537 let community_id = data.community_id;
539 let (posts, comments) = blocking(context.pool(), move |conn| {
540 let mut posts_query = PostQueryBuilder::create(conn)
542 .show_nsfw(show_nsfw)
543 .saved_only(saved_only)
544 .community_id(community_id)
549 let mut comments_query = CommentQueryBuilder::create(conn)
552 .saved_only(saved_only)
556 // If its saved only, you don't care what creator it was
557 // Or, if its not saved, then you only want it for that specific creator
559 posts_query = posts_query.creator_id(user_details_id);
560 comments_query = comments_query.creator_id(user_details_id);
563 let posts = posts_query.list()?;
564 let comments = comments_query.list()?;
566 Ok((posts, comments)) as Result<_, LemmyError>
570 let follows = blocking(context.pool(), move |conn| {
571 CommunityFollowerView::for_user(conn, user_details_id)
574 let moderates = blocking(context.pool(), move |conn| {
575 CommunityModeratorView::for_user(conn, user_details_id)
580 Ok(GetUserDetailsResponse {
582 user_view_dangerous: user_dangerous,
591 #[async_trait::async_trait(?Send)]
592 impl Perform for AddAdmin {
593 type Response = AddAdminResponse;
597 context: &Data<LemmyContext>,
598 websocket_id: Option<ConnectionId>,
599 ) -> Result<AddAdminResponse, LemmyError> {
600 let data: &AddAdmin = &self;
601 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
603 // Make sure user is an admin
604 is_admin(context.pool(), user.id).await?;
606 let added = data.added;
607 let added_user_id = data.user_id;
608 let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
609 if blocking(context.pool(), add_admin).await?.is_err() {
610 return Err(APIError::err("couldnt_update_user").into());
614 let form = ModAddForm {
615 mod_user_id: user.id,
616 other_user_id: data.user_id,
617 removed: Some(!data.added),
620 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
622 let site_creator_id = blocking(context.pool(), move |conn| {
623 Site::read(conn, 1).map(|s| s.creator_id)
627 let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
628 let creator_index = admins
630 .position(|r| r.user.id == site_creator_id)
631 .context(location_info!())?;
632 let creator_user = admins.remove(creator_index);
633 admins.insert(0, creator_user);
635 let res = AddAdminResponse { admins };
637 context.chat_server().do_send(SendAllMessage {
638 op: UserOperation::AddAdmin,
639 response: res.clone(),
647 #[async_trait::async_trait(?Send)]
648 impl Perform for BanUser {
649 type Response = BanUserResponse;
653 context: &Data<LemmyContext>,
654 websocket_id: Option<ConnectionId>,
655 ) -> Result<BanUserResponse, LemmyError> {
656 let data: &BanUser = &self;
657 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
659 // Make sure user is an admin
660 is_admin(context.pool(), user.id).await?;
663 let banned_user_id = data.user_id;
664 let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
665 if blocking(context.pool(), ban_user).await?.is_err() {
666 return Err(APIError::err("couldnt_update_user").into());
669 // Remove their data if that's desired
670 if data.remove_data {
672 blocking(context.pool(), move |conn: &'_ _| {
673 Post::update_removed_for_creator(conn, banned_user_id, None, true)
678 blocking(context.pool(), move |conn: &'_ _| {
679 Community::update_removed_for_creator(conn, banned_user_id, true)
684 blocking(context.pool(), move |conn: &'_ _| {
685 Comment::update_removed_for_creator(conn, banned_user_id, true)
691 let expires = match data.expires {
692 Some(time) => Some(naive_from_unix(time)),
696 let form = ModBanForm {
697 mod_user_id: user.id,
698 other_user_id: data.user_id,
699 reason: data.reason.to_owned(),
700 banned: Some(data.ban),
704 blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
706 let user_id = data.user_id;
707 let user_view = blocking(context.pool(), move |conn| {
708 UserViewSafe::read(conn, user_id)
712 let res = BanUserResponse {
717 context.chat_server().do_send(SendAllMessage {
718 op: UserOperation::BanUser,
719 response: res.clone(),
727 #[async_trait::async_trait(?Send)]
728 impl Perform for GetReplies {
729 type Response = GetRepliesResponse;
733 context: &Data<LemmyContext>,
734 _websocket_id: Option<ConnectionId>,
735 ) -> Result<GetRepliesResponse, LemmyError> {
736 let data: &GetReplies = &self;
737 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
739 let sort = SortType::from_str(&data.sort)?;
741 let page = data.page;
742 let limit = data.limit;
743 let unread_only = data.unread_only;
744 let user_id = user.id;
745 let replies = blocking(context.pool(), move |conn| {
746 CommentQueryBuilder::create(conn)
748 .unread_only(unread_only)
749 .recipient_id(user_id)
757 Ok(GetRepliesResponse { replies })
761 #[async_trait::async_trait(?Send)]
762 impl Perform for GetUserMentions {
763 type Response = GetUserMentionsResponse;
767 context: &Data<LemmyContext>,
768 _websocket_id: Option<ConnectionId>,
769 ) -> Result<GetUserMentionsResponse, LemmyError> {
770 let data: &GetUserMentions = &self;
771 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
773 let sort = SortType::from_str(&data.sort)?;
775 let page = data.page;
776 let limit = data.limit;
777 let unread_only = data.unread_only;
778 let user_id = user.id;
779 let mentions = blocking(context.pool(), move |conn| {
780 UserMentionQueryBuilder::create(conn)
781 .recipient_id(user_id)
784 .unread_only(unread_only)
791 Ok(GetUserMentionsResponse { mentions })
795 #[async_trait::async_trait(?Send)]
796 impl Perform for MarkUserMentionAsRead {
797 type Response = UserMentionResponse;
801 context: &Data<LemmyContext>,
802 _websocket_id: Option<ConnectionId>,
803 ) -> Result<UserMentionResponse, LemmyError> {
804 let data: &MarkUserMentionAsRead = &self;
805 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
807 let user_mention_id = data.user_mention_id;
808 let read_user_mention = blocking(context.pool(), move |conn| {
809 UserMention::read(conn, user_mention_id)
813 if user.id != read_user_mention.recipient_id {
814 return Err(APIError::err("couldnt_update_comment").into());
817 let user_mention_id = read_user_mention.id;
818 let read = data.read;
819 let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
820 if blocking(context.pool(), update_mention).await?.is_err() {
821 return Err(APIError::err("couldnt_update_comment").into());
824 let user_mention_id = read_user_mention.id;
825 let user_id = user.id;
826 let user_mention_view = blocking(context.pool(), move |conn| {
827 UserMentionView::read(conn, user_mention_id, Some(user_id))
831 Ok(UserMentionResponse { user_mention_view })
835 #[async_trait::async_trait(?Send)]
836 impl Perform for MarkAllAsRead {
837 type Response = GetRepliesResponse;
841 context: &Data<LemmyContext>,
842 _websocket_id: Option<ConnectionId>,
843 ) -> Result<GetRepliesResponse, LemmyError> {
844 let data: &MarkAllAsRead = &self;
845 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
847 let user_id = user.id;
848 let replies = blocking(context.pool(), move |conn| {
849 CommentQueryBuilder::create(conn)
851 .recipient_id(user_id)
859 // TODO: this should probably be a bulk operation
860 // Not easy to do as a bulk operation,
861 // because recipient_id isn't in the comment table
862 for comment_view in &replies {
863 let reply_id = comment_view.comment.id;
864 let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
865 if blocking(context.pool(), mark_as_read).await?.is_err() {
866 return Err(APIError::err("couldnt_update_comment").into());
870 // Mark all user mentions as read
871 let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
872 if blocking(context.pool(), update_user_mentions)
876 return Err(APIError::err("couldnt_update_comment").into());
879 // Mark all private_messages as read
880 let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
881 if blocking(context.pool(), update_pm).await?.is_err() {
882 return Err(APIError::err("couldnt_update_private_message").into());
885 Ok(GetRepliesResponse { replies: vec![] })
889 #[async_trait::async_trait(?Send)]
890 impl Perform for DeleteAccount {
891 type Response = LoginResponse;
895 context: &Data<LemmyContext>,
896 _websocket_id: Option<ConnectionId>,
897 ) -> Result<LoginResponse, LemmyError> {
898 let data: &DeleteAccount = &self;
899 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
901 // Verify the password
902 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
904 return Err(APIError::err("password_incorrect").into());
908 let user_id = user.id;
909 let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
910 if blocking(context.pool(), permadelete).await?.is_err() {
911 return Err(APIError::err("couldnt_update_comment").into());
915 let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
916 if blocking(context.pool(), permadelete).await?.is_err() {
917 return Err(APIError::err("couldnt_update_post").into());
920 blocking(context.pool(), move |conn| {
921 User_::delete_account(conn, user_id)
926 jwt: data.auth.to_owned(),
931 #[async_trait::async_trait(?Send)]
932 impl Perform for PasswordReset {
933 type Response = PasswordResetResponse;
937 context: &Data<LemmyContext>,
938 _websocket_id: Option<ConnectionId>,
939 ) -> Result<PasswordResetResponse, LemmyError> {
940 let data: &PasswordReset = &self;
943 let email = data.email.clone();
944 let user = match blocking(context.pool(), move |conn| {
945 User_::find_by_email(conn, &email)
950 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
953 // Generate a random token
954 let token = generate_random_string();
957 let token2 = token.clone();
958 let user_id = user.id;
959 blocking(context.pool(), move |conn| {
960 PasswordResetRequest::create_token(conn, user_id, &token2)
964 // Email the pure token to the user.
965 // TODO no i18n support here.
966 let user_email = &user.email.expect("email");
967 let subject = &format!("Password reset for {}", user.name);
968 let hostname = &Settings::get().get_protocol_and_hostname();
969 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);
970 match send_email(subject, user_email, &user.name, html) {
972 Err(_e) => return Err(APIError::err(&_e).into()),
975 Ok(PasswordResetResponse {})
979 #[async_trait::async_trait(?Send)]
980 impl Perform for PasswordChange {
981 type Response = LoginResponse;
985 context: &Data<LemmyContext>,
986 _websocket_id: Option<ConnectionId>,
987 ) -> Result<LoginResponse, LemmyError> {
988 let data: &PasswordChange = &self;
990 // Fetch the user_id from the token
991 let token = data.token.clone();
992 let user_id = blocking(context.pool(), move |conn| {
993 PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
997 // Make sure passwords match
998 if data.password != data.password_verify {
999 return Err(APIError::err("passwords_dont_match").into());
1002 // Update the user with the new password
1003 let password = data.password.clone();
1004 let updated_user = match blocking(context.pool(), move |conn| {
1005 User_::update_password(conn, user_id, &password)
1010 Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
1015 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
1020 #[async_trait::async_trait(?Send)]
1021 impl Perform for CreatePrivateMessage {
1022 type Response = PrivateMessageResponse;
1026 context: &Data<LemmyContext>,
1027 websocket_id: Option<ConnectionId>,
1028 ) -> Result<PrivateMessageResponse, LemmyError> {
1029 let data: &CreatePrivateMessage = &self;
1030 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1032 let content_slurs_removed = remove_slurs(&data.content.to_owned());
1034 let private_message_form = PrivateMessageForm {
1035 content: content_slurs_removed.to_owned(),
1036 creator_id: user.id,
1037 recipient_id: data.recipient_id,
1046 let inserted_private_message = match blocking(context.pool(), move |conn| {
1047 PrivateMessage::create(conn, &private_message_form)
1051 Ok(private_message) => private_message,
1053 return Err(APIError::err("couldnt_create_private_message").into());
1057 let inserted_private_message_id = inserted_private_message.id;
1058 let updated_private_message = match blocking(context.pool(), move |conn| {
1059 let apub_id = make_apub_endpoint(
1060 EndpointType::PrivateMessage,
1061 &inserted_private_message_id.to_string(),
1064 PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
1068 Ok(private_message) => private_message,
1069 Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
1072 updated_private_message.send_create(&user, context).await?;
1074 // Send notifications to the recipient
1075 let recipient_id = data.recipient_id;
1076 let recipient_user =
1077 blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1078 if recipient_user.send_notifications_to_email {
1081 "Private Message from",
1083 &content_slurs_removed,
1087 let message = blocking(context.pool(), move |conn| {
1088 PrivateMessageView::read(conn, inserted_private_message.id)
1092 let res = PrivateMessageResponse {
1093 private_message_view: 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 {
1152 private_message_view: message,
1155 context.chat_server().do_send(SendUserRoomMessage {
1156 op: UserOperation::EditPrivateMessage,
1157 response: res.clone(),
1166 #[async_trait::async_trait(?Send)]
1167 impl Perform for DeletePrivateMessage {
1168 type Response = PrivateMessageResponse;
1172 context: &Data<LemmyContext>,
1173 websocket_id: Option<ConnectionId>,
1174 ) -> Result<PrivateMessageResponse, LemmyError> {
1175 let data: &DeletePrivateMessage = &self;
1176 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1178 // Checking permissions
1179 let edit_id = data.edit_id;
1180 let orig_private_message = blocking(context.pool(), move |conn| {
1181 PrivateMessage::read(conn, edit_id)
1184 if user.id != orig_private_message.creator_id {
1185 return Err(APIError::err("no_private_message_edit_allowed").into());
1189 let edit_id = data.edit_id;
1190 let deleted = data.deleted;
1191 let updated_private_message = match blocking(context.pool(), move |conn| {
1192 PrivateMessage::update_deleted(conn, edit_id, deleted)
1196 Ok(private_message) => private_message,
1197 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1200 // Send the apub update
1202 updated_private_message.send_delete(&user, context).await?;
1204 updated_private_message
1205 .send_undo_delete(&user, context)
1209 let edit_id = data.edit_id;
1210 let message = blocking(context.pool(), move |conn| {
1211 PrivateMessageView::read(conn, edit_id)
1214 let recipient_id = message.recipient.id;
1216 let res = PrivateMessageResponse {
1217 private_message_view: message,
1220 context.chat_server().do_send(SendUserRoomMessage {
1221 op: UserOperation::DeletePrivateMessage,
1222 response: res.clone(),
1231 #[async_trait::async_trait(?Send)]
1232 impl Perform for MarkPrivateMessageAsRead {
1233 type Response = PrivateMessageResponse;
1237 context: &Data<LemmyContext>,
1238 websocket_id: Option<ConnectionId>,
1239 ) -> Result<PrivateMessageResponse, LemmyError> {
1240 let data: &MarkPrivateMessageAsRead = &self;
1241 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1243 // Checking permissions
1244 let edit_id = data.edit_id;
1245 let orig_private_message = blocking(context.pool(), move |conn| {
1246 PrivateMessage::read(conn, edit_id)
1249 if user.id != orig_private_message.recipient_id {
1250 return Err(APIError::err("couldnt_update_private_message").into());
1254 let edit_id = data.edit_id;
1255 let read = data.read;
1256 match blocking(context.pool(), move |conn| {
1257 PrivateMessage::update_read(conn, edit_id, read)
1261 Ok(private_message) => private_message,
1262 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1265 // No need to send an apub update
1267 let edit_id = data.edit_id;
1268 let message = blocking(context.pool(), move |conn| {
1269 PrivateMessageView::read(conn, edit_id)
1272 let recipient_id = message.recipient.id;
1274 let res = PrivateMessageResponse {
1275 private_message_view: message,
1278 context.chat_server().do_send(SendUserRoomMessage {
1279 op: UserOperation::MarkPrivateMessageAsRead,
1280 response: res.clone(),
1289 #[async_trait::async_trait(?Send)]
1290 impl Perform for GetPrivateMessages {
1291 type Response = PrivateMessagesResponse;
1295 context: &Data<LemmyContext>,
1296 _websocket_id: Option<ConnectionId>,
1297 ) -> Result<PrivateMessagesResponse, LemmyError> {
1298 let data: &GetPrivateMessages = &self;
1299 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1300 let user_id = user.id;
1302 let page = data.page;
1303 let limit = data.limit;
1304 let unread_only = data.unread_only;
1305 let messages = blocking(context.pool(), move |conn| {
1306 PrivateMessageQueryBuilder::create(&conn, user_id)
1309 .unread_only(unread_only)
1314 Ok(PrivateMessagesResponse {
1315 private_messages: messages,
1320 #[async_trait::async_trait(?Send)]
1321 impl Perform for UserJoin {
1322 type Response = UserJoinResponse;
1326 context: &Data<LemmyContext>,
1327 websocket_id: Option<ConnectionId>,
1328 ) -> Result<UserJoinResponse, LemmyError> {
1329 let data: &UserJoin = &self;
1330 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1332 if let Some(ws_id) = websocket_id {
1333 context.chat_server().do_send(JoinUserRoom {
1339 Ok(UserJoinResponse { joined: true })
1343 #[async_trait::async_trait(?Send)]
1344 impl Perform for GetReportCount {
1345 type Response = GetReportCountResponse;
1349 context: &Data<LemmyContext>,
1350 websocket_id: Option<ConnectionId>,
1351 ) -> Result<GetReportCountResponse, LemmyError> {
1352 let data: &GetReportCount = &self;
1353 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1355 let user_id = user.id;
1356 let community_id = data.community;
1358 collect_moderated_communities(user_id, community_id, context.pool()).await?;
1361 if community_ids.is_empty() {
1362 GetReportCountResponse {
1368 let ids = community_ids.clone();
1369 let comment_reports = blocking(context.pool(), move |conn| {
1370 CommentReportView::get_report_count(conn, &ids)
1374 let ids = community_ids.clone();
1375 let post_reports = blocking(context.pool(), move |conn| {
1376 PostReportView::get_report_count(conn, &ids)
1380 GetReportCountResponse {
1381 community: data.community,
1388 context.chat_server().do_send(SendUserRoomMessage {
1389 op: UserOperation::GetReportCount,
1390 response: res.clone(),
1391 recipient_id: user.id,