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;
17 use lemmy_db_queries::{
18 diesel_option_overwrite,
21 community::Community_,
22 password_reset_request::PasswordResetRequest_,
24 private_message::PrivateMessage_,
27 user_mention::UserMention_,
35 use lemmy_db_schema::{
41 password_reset_request::*,
50 comment_report_view::CommentReportView,
51 comment_view::CommentQueryBuilder,
52 post_report_view::PostReportView,
53 post_view::PostQueryBuilder,
54 private_message_view::{PrivateMessageQueryBuilder, PrivateMessageView},
56 use lemmy_db_views_actor::{
57 community_follower_view::CommunityFollowerView,
58 community_moderator_view::CommunityModeratorView,
59 user_mention_view::{UserMentionQueryBuilder, UserMentionView},
60 user_view::UserViewSafe,
62 use lemmy_structs::{blocking, send_email_to_user, user::*};
64 apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
70 generate_random_string,
71 is_valid_preferred_username,
80 use lemmy_websocket::{
81 messages::{CaptchaItem, CheckCaptcha, SendAllMessage, SendUserRoomMessage},
85 use std::str::FromStr;
87 #[async_trait::async_trait(?Send)]
88 impl Perform for Login {
89 type Response = LoginResponse;
93 context: &Data<LemmyContext>,
94 _websocket_id: Option<ConnectionId>,
95 ) -> Result<LoginResponse, LemmyError> {
96 let data: &Login = &self;
98 // Fetch that username / email
99 let username_or_email = data.username_or_email.clone();
100 let user = match blocking(context.pool(), move |conn| {
101 User_::find_by_email_or_username(conn, &username_or_email)
106 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
109 // Verify the password
110 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
112 return Err(APIError::err("password_incorrect").into());
117 jwt: Claims::jwt(user, Settings::get().hostname)?,
122 #[async_trait::async_trait(?Send)]
123 impl Perform for Register {
124 type Response = LoginResponse;
128 context: &Data<LemmyContext>,
129 _websocket_id: Option<ConnectionId>,
130 ) -> Result<LoginResponse, LemmyError> {
131 let data: &Register = &self;
133 // Make sure site has open registration
134 if let Ok(site) = blocking(context.pool(), move |conn| Site::read_simple(conn)).await? {
135 if !site.open_registration {
136 return Err(APIError::err("registration_closed").into());
140 // Password length check
141 if data.password.len() > 60 {
142 return Err(APIError::err("invalid_password").into());
145 // Make sure passwords match
146 if data.password != data.password_verify {
147 return Err(APIError::err("passwords_dont_match").into());
150 // Check if there are admins. False if admins exist
151 let no_admins = blocking(context.pool(), move |conn| {
152 UserViewSafe::admins(conn).map(|a| a.is_empty())
156 // If its not the admin, check the captcha
157 if !no_admins && Settings::get().captcha.enabled {
164 .unwrap_or_else(|| "".to_string()),
168 .unwrap_or_else(|| "".to_string()),
172 return Err(APIError::err("captcha_incorrect").into());
176 check_slurs(&data.username)?;
178 let user_keypair = generate_actor_keypair()?;
179 if !is_valid_username(&data.username) {
180 return Err(APIError::err("invalid_username").into());
183 // Register the new user
184 let user_form = UserForm {
185 name: data.username.to_owned(),
186 email: Some(data.email.to_owned()),
187 matrix_user_id: None,
190 password_encrypted: data.password.to_owned(),
191 preferred_username: None,
196 show_nsfw: data.show_nsfw,
197 theme: "browser".into(),
198 default_sort_type: SortType::Active as i16,
199 default_listing_type: ListingType::Subscribed as i16,
200 lang: "browser".into(),
202 send_notifications_to_email: false,
203 actor_id: Some(make_apub_endpoint(EndpointType::User, &data.username).to_string()),
206 private_key: Some(user_keypair.private_key),
207 public_key: Some(user_keypair.public_key),
208 last_refreshed_at: None,
212 let inserted_user = match blocking(context.pool(), move |conn| {
213 User_::register(conn, &user_form)
219 let err_type = if e.to_string()
220 == "duplicate key value violates unique constraint \"user__email_key\""
222 "email_already_exists"
224 "user_already_exists"
227 return Err(APIError::err(err_type).into());
231 let main_community_keypair = generate_actor_keypair()?;
233 // Create the main community if it doesn't exist
235 match blocking(context.pool(), move |conn| Community::read(conn, 2)).await? {
238 let default_community_name = "main";
239 let community_form = CommunityForm {
240 name: default_community_name.to_string(),
241 title: "The Default Community".to_string(),
242 description: Some("The Default Community".to_string()),
245 creator_id: inserted_user.id,
250 make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
253 private_key: Some(main_community_keypair.private_key),
254 public_key: Some(main_community_keypair.public_key),
255 last_refreshed_at: None,
260 blocking(context.pool(), move |conn| {
261 Community::create(conn, &community_form)
267 // Sign them up for main community no matter what
268 let community_follower_form = CommunityFollowerForm {
269 community_id: main_community.id,
270 user_id: inserted_user.id,
274 let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
275 if blocking(context.pool(), follow).await?.is_err() {
276 return Err(APIError::err("community_follower_already_exists").into());
279 // If its an admin, add them as a mod and follower to main
281 let community_moderator_form = CommunityModeratorForm {
282 community_id: main_community.id,
283 user_id: inserted_user.id,
286 let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
287 if blocking(context.pool(), join).await?.is_err() {
288 return Err(APIError::err("community_moderator_already_exists").into());
294 jwt: Claims::jwt(inserted_user, Settings::get().hostname)?,
299 #[async_trait::async_trait(?Send)]
300 impl Perform for GetCaptcha {
301 type Response = GetCaptchaResponse;
305 context: &Data<LemmyContext>,
306 _websocket_id: Option<ConnectionId>,
307 ) -> Result<Self::Response, LemmyError> {
308 let captcha_settings = Settings::get().captcha;
310 if !captcha_settings.enabled {
311 return Ok(GetCaptchaResponse { ok: None });
314 let captcha = match captcha_settings.difficulty.as_str() {
315 "easy" => gen(Difficulty::Easy),
316 "medium" => gen(Difficulty::Medium),
317 "hard" => gen(Difficulty::Hard),
318 _ => gen(Difficulty::Medium),
321 let answer = captcha.chars_as_string();
323 let png_byte_array = captcha.as_png().expect("failed to generate captcha");
325 let png = base64::encode(png_byte_array);
327 let uuid = uuid::Uuid::new_v4().to_string();
329 let wav = captcha_espeak_wav_base64(&answer).ok();
331 let captcha_item = CaptchaItem {
333 uuid: uuid.to_owned(),
334 expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
337 // Stores the captcha item on the queue
338 context.chat_server().do_send(captcha_item);
340 Ok(GetCaptchaResponse {
341 ok: Some(CaptchaResponse { png, uuid, wav }),
346 #[async_trait::async_trait(?Send)]
347 impl Perform for SaveUserSettings {
348 type Response = LoginResponse;
352 context: &Data<LemmyContext>,
353 _websocket_id: Option<ConnectionId>,
354 ) -> Result<LoginResponse, LemmyError> {
355 let data: &SaveUserSettings = &self;
356 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
358 let avatar = diesel_option_overwrite(&data.avatar);
359 let banner = diesel_option_overwrite(&data.banner);
360 let email = diesel_option_overwrite(&data.email);
361 let bio = diesel_option_overwrite(&data.bio);
362 let preferred_username = diesel_option_overwrite(&data.preferred_username);
363 let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
365 // Check to make sure the avatar and banners are urls
366 check_optional_url(&avatar)?;
367 check_optional_url(&banner)?;
369 if let Some(Some(bio)) = &bio {
370 if bio.chars().count() > 300 {
371 return Err(APIError::err("bio_length_overflow").into());
375 if let Some(Some(preferred_username)) = &preferred_username {
376 if !is_valid_preferred_username(preferred_username.trim()) {
377 return Err(APIError::err("invalid_username").into());
381 let user_id = user.id;
382 let password_encrypted = match &data.new_password {
383 Some(new_password) => {
384 match &data.new_password_verify {
385 Some(new_password_verify) => {
386 // Make sure passwords match
387 if new_password != new_password_verify {
388 return Err(APIError::err("passwords_dont_match").into());
391 // Check the old password
392 match &data.old_password {
393 Some(old_password) => {
394 let valid: bool = verify(old_password, &user.password_encrypted).unwrap_or(false);
396 return Err(APIError::err("password_incorrect").into());
398 let new_password = new_password.to_owned();
399 let user = blocking(context.pool(), move |conn| {
400 User_::update_password(conn, user_id, &new_password)
403 user.password_encrypted
405 None => return Err(APIError::err("password_incorrect").into()),
408 None => return Err(APIError::err("passwords_dont_match").into()),
411 None => user.password_encrypted,
414 let default_listing_type = data.default_listing_type;
415 let default_sort_type = data.default_sort_type;
417 let user_form = UserForm {
425 published: Some(user.published),
426 updated: Some(naive_now()),
428 banned: Some(user.banned),
429 show_nsfw: data.show_nsfw,
430 theme: data.theme.to_owned(),
432 default_listing_type,
433 lang: data.lang.to_owned(),
434 show_avatars: data.show_avatars,
435 send_notifications_to_email: data.send_notifications_to_email,
436 actor_id: Some(user.actor_id),
439 private_key: user.private_key,
440 public_key: user.public_key,
441 last_refreshed_at: None,
444 let res = blocking(context.pool(), move |conn| {
445 User_::update(conn, user_id, &user_form)
448 let updated_user: User_ = match res {
451 let err_type = if e.to_string()
452 == "duplicate key value violates unique constraint \"user__email_key\""
454 "email_already_exists"
456 "user_already_exists"
459 return Err(APIError::err(err_type).into());
465 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
470 #[async_trait::async_trait(?Send)]
471 impl Perform for GetUserDetails {
472 type Response = GetUserDetailsResponse;
476 context: &Data<LemmyContext>,
477 _websocket_id: Option<ConnectionId>,
478 ) -> Result<GetUserDetailsResponse, LemmyError> {
479 let data: &GetUserDetails = &self;
480 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
482 let show_nsfw = match &user {
483 Some(user) => user.show_nsfw,
487 let sort = SortType::from_str(&data.sort)?;
492 .unwrap_or_else(|| "admin".to_string());
493 let user_details_id = match data.user_id {
496 let user = blocking(context.pool(), move |conn| {
497 User_::read_from_name(conn, &username)
502 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
507 let user_id = user.map(|u| u.id);
509 // You don't need to return settings for the user, since this comes back with GetSite
511 let user_view = blocking(context.pool(), move |conn| {
512 UserViewSafe::read(conn, user_details_id)
516 let page = data.page;
517 let limit = data.limit;
518 let saved_only = data.saved_only;
519 let community_id = data.community_id;
521 let (posts, comments) = blocking(context.pool(), move |conn| {
522 let mut posts_query = PostQueryBuilder::create(conn)
524 .show_nsfw(show_nsfw)
525 .saved_only(saved_only)
526 .community_id(community_id)
531 let mut comments_query = CommentQueryBuilder::create(conn)
534 .saved_only(saved_only)
538 // If its saved only, you don't care what creator it was
539 // Or, if its not saved, then you only want it for that specific creator
541 posts_query = posts_query.creator_id(user_details_id);
542 comments_query = comments_query.creator_id(user_details_id);
545 let posts = posts_query.list()?;
546 let comments = comments_query.list()?;
548 Ok((posts, comments)) as Result<_, LemmyError>
552 let follows = blocking(context.pool(), move |conn| {
553 CommunityFollowerView::for_user(conn, user_details_id)
556 let moderates = blocking(context.pool(), move |conn| {
557 CommunityModeratorView::for_user(conn, user_details_id)
562 Ok(GetUserDetailsResponse {
572 #[async_trait::async_trait(?Send)]
573 impl Perform for AddAdmin {
574 type Response = AddAdminResponse;
578 context: &Data<LemmyContext>,
579 websocket_id: Option<ConnectionId>,
580 ) -> Result<AddAdminResponse, LemmyError> {
581 let data: &AddAdmin = &self;
582 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
584 // Make sure user is an admin
585 is_admin(context.pool(), user.id).await?;
587 let added = data.added;
588 let added_user_id = data.user_id;
589 let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
590 if blocking(context.pool(), add_admin).await?.is_err() {
591 return Err(APIError::err("couldnt_update_user").into());
595 let form = ModAddForm {
596 mod_user_id: user.id,
597 other_user_id: data.user_id,
598 removed: Some(!data.added),
601 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
603 let site_creator_id = blocking(context.pool(), move |conn| {
604 Site::read(conn, 1).map(|s| s.creator_id)
608 let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
609 let creator_index = admins
611 .position(|r| r.user.id == site_creator_id)
612 .context(location_info!())?;
613 let creator_user = admins.remove(creator_index);
614 admins.insert(0, creator_user);
616 let res = AddAdminResponse { admins };
618 context.chat_server().do_send(SendAllMessage {
619 op: UserOperation::AddAdmin,
620 response: res.clone(),
628 #[async_trait::async_trait(?Send)]
629 impl Perform for BanUser {
630 type Response = BanUserResponse;
634 context: &Data<LemmyContext>,
635 websocket_id: Option<ConnectionId>,
636 ) -> Result<BanUserResponse, LemmyError> {
637 let data: &BanUser = &self;
638 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
640 // Make sure user is an admin
641 is_admin(context.pool(), user.id).await?;
644 let banned_user_id = data.user_id;
645 let ban_user = move |conn: &'_ _| User_::ban_user(conn, banned_user_id, ban);
646 if blocking(context.pool(), ban_user).await?.is_err() {
647 return Err(APIError::err("couldnt_update_user").into());
650 // Remove their data if that's desired
651 if data.remove_data {
653 blocking(context.pool(), move |conn: &'_ _| {
654 Post::update_removed_for_creator(conn, banned_user_id, None, true)
659 blocking(context.pool(), move |conn: &'_ _| {
660 Community::update_removed_for_creator(conn, banned_user_id, true)
665 blocking(context.pool(), move |conn: &'_ _| {
666 Comment::update_removed_for_creator(conn, banned_user_id, true)
672 let expires = match data.expires {
673 Some(time) => Some(naive_from_unix(time)),
677 let form = ModBanForm {
678 mod_user_id: user.id,
679 other_user_id: data.user_id,
680 reason: data.reason.to_owned(),
681 banned: Some(data.ban),
685 blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
687 let user_id = data.user_id;
688 let user_view = blocking(context.pool(), move |conn| {
689 UserViewSafe::read(conn, user_id)
693 let res = BanUserResponse {
698 context.chat_server().do_send(SendAllMessage {
699 op: UserOperation::BanUser,
700 response: res.clone(),
708 #[async_trait::async_trait(?Send)]
709 impl Perform for GetReplies {
710 type Response = GetRepliesResponse;
714 context: &Data<LemmyContext>,
715 _websocket_id: Option<ConnectionId>,
716 ) -> Result<GetRepliesResponse, LemmyError> {
717 let data: &GetReplies = &self;
718 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
720 let sort = SortType::from_str(&data.sort)?;
722 let page = data.page;
723 let limit = data.limit;
724 let unread_only = data.unread_only;
725 let user_id = user.id;
726 let replies = blocking(context.pool(), move |conn| {
727 CommentQueryBuilder::create(conn)
729 .unread_only(unread_only)
730 .recipient_id(user_id)
738 Ok(GetRepliesResponse { replies })
742 #[async_trait::async_trait(?Send)]
743 impl Perform for GetUserMentions {
744 type Response = GetUserMentionsResponse;
748 context: &Data<LemmyContext>,
749 _websocket_id: Option<ConnectionId>,
750 ) -> Result<GetUserMentionsResponse, LemmyError> {
751 let data: &GetUserMentions = &self;
752 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
754 let sort = SortType::from_str(&data.sort)?;
756 let page = data.page;
757 let limit = data.limit;
758 let unread_only = data.unread_only;
759 let user_id = user.id;
760 let mentions = blocking(context.pool(), move |conn| {
761 UserMentionQueryBuilder::create(conn)
762 .recipient_id(user_id)
765 .unread_only(unread_only)
772 Ok(GetUserMentionsResponse { mentions })
776 #[async_trait::async_trait(?Send)]
777 impl Perform for MarkUserMentionAsRead {
778 type Response = UserMentionResponse;
782 context: &Data<LemmyContext>,
783 _websocket_id: Option<ConnectionId>,
784 ) -> Result<UserMentionResponse, LemmyError> {
785 let data: &MarkUserMentionAsRead = &self;
786 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
788 let user_mention_id = data.user_mention_id;
789 let read_user_mention = blocking(context.pool(), move |conn| {
790 UserMention::read(conn, user_mention_id)
794 if user.id != read_user_mention.recipient_id {
795 return Err(APIError::err("couldnt_update_comment").into());
798 let user_mention_id = read_user_mention.id;
799 let read = data.read;
800 let update_mention = move |conn: &'_ _| UserMention::update_read(conn, user_mention_id, read);
801 if blocking(context.pool(), update_mention).await?.is_err() {
802 return Err(APIError::err("couldnt_update_comment").into());
805 let user_mention_id = read_user_mention.id;
806 let user_id = user.id;
807 let user_mention_view = blocking(context.pool(), move |conn| {
808 UserMentionView::read(conn, user_mention_id, Some(user_id))
812 Ok(UserMentionResponse { user_mention_view })
816 #[async_trait::async_trait(?Send)]
817 impl Perform for MarkAllAsRead {
818 type Response = GetRepliesResponse;
822 context: &Data<LemmyContext>,
823 _websocket_id: Option<ConnectionId>,
824 ) -> Result<GetRepliesResponse, LemmyError> {
825 let data: &MarkAllAsRead = &self;
826 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
828 let user_id = user.id;
829 let replies = blocking(context.pool(), move |conn| {
830 CommentQueryBuilder::create(conn)
832 .recipient_id(user_id)
840 // TODO: this should probably be a bulk operation
841 // Not easy to do as a bulk operation,
842 // because recipient_id isn't in the comment table
843 for comment_view in &replies {
844 let reply_id = comment_view.comment.id;
845 let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
846 if blocking(context.pool(), mark_as_read).await?.is_err() {
847 return Err(APIError::err("couldnt_update_comment").into());
851 // Mark all user mentions as read
852 let update_user_mentions = move |conn: &'_ _| UserMention::mark_all_as_read(conn, user_id);
853 if blocking(context.pool(), update_user_mentions)
857 return Err(APIError::err("couldnt_update_comment").into());
860 // Mark all private_messages as read
861 let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, user_id);
862 if blocking(context.pool(), update_pm).await?.is_err() {
863 return Err(APIError::err("couldnt_update_private_message").into());
866 Ok(GetRepliesResponse { replies: vec![] })
870 #[async_trait::async_trait(?Send)]
871 impl Perform for DeleteAccount {
872 type Response = LoginResponse;
876 context: &Data<LemmyContext>,
877 _websocket_id: Option<ConnectionId>,
878 ) -> Result<LoginResponse, LemmyError> {
879 let data: &DeleteAccount = &self;
880 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
882 // Verify the password
883 let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
885 return Err(APIError::err("password_incorrect").into());
889 let user_id = user.id;
890 let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
891 if blocking(context.pool(), permadelete).await?.is_err() {
892 return Err(APIError::err("couldnt_update_comment").into());
896 let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
897 if blocking(context.pool(), permadelete).await?.is_err() {
898 return Err(APIError::err("couldnt_update_post").into());
901 blocking(context.pool(), move |conn| {
902 User_::delete_account(conn, user_id)
907 jwt: data.auth.to_owned(),
912 #[async_trait::async_trait(?Send)]
913 impl Perform for PasswordReset {
914 type Response = PasswordResetResponse;
918 context: &Data<LemmyContext>,
919 _websocket_id: Option<ConnectionId>,
920 ) -> Result<PasswordResetResponse, LemmyError> {
921 let data: &PasswordReset = &self;
924 let email = data.email.clone();
925 let user = match blocking(context.pool(), move |conn| {
926 User_::find_by_email(conn, &email)
931 Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()),
934 // Generate a random token
935 let token = generate_random_string();
938 let token2 = token.clone();
939 let user_id = user.id;
940 blocking(context.pool(), move |conn| {
941 PasswordResetRequest::create_token(conn, user_id, &token2)
945 // Email the pure token to the user.
946 // TODO no i18n support here.
947 let user_email = &user.email.expect("email");
948 let subject = &format!("Password reset for {}", user.name);
949 let hostname = &Settings::get().get_protocol_and_hostname();
950 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);
951 match send_email(subject, user_email, &user.name, html) {
953 Err(_e) => return Err(APIError::err(&_e).into()),
956 Ok(PasswordResetResponse {})
960 #[async_trait::async_trait(?Send)]
961 impl Perform for PasswordChange {
962 type Response = LoginResponse;
966 context: &Data<LemmyContext>,
967 _websocket_id: Option<ConnectionId>,
968 ) -> Result<LoginResponse, LemmyError> {
969 let data: &PasswordChange = &self;
971 // Fetch the user_id from the token
972 let token = data.token.clone();
973 let user_id = blocking(context.pool(), move |conn| {
974 PasswordResetRequest::read_from_token(conn, &token).map(|p| p.user_id)
978 // Make sure passwords match
979 if data.password != data.password_verify {
980 return Err(APIError::err("passwords_dont_match").into());
983 // Update the user with the new password
984 let password = data.password.clone();
985 let updated_user = match blocking(context.pool(), move |conn| {
986 User_::update_password(conn, user_id, &password)
991 Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
996 jwt: Claims::jwt(updated_user, Settings::get().hostname)?,
1001 #[async_trait::async_trait(?Send)]
1002 impl Perform for CreatePrivateMessage {
1003 type Response = PrivateMessageResponse;
1007 context: &Data<LemmyContext>,
1008 websocket_id: Option<ConnectionId>,
1009 ) -> Result<PrivateMessageResponse, LemmyError> {
1010 let data: &CreatePrivateMessage = &self;
1011 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1013 let content_slurs_removed = remove_slurs(&data.content.to_owned());
1015 let private_message_form = PrivateMessageForm {
1016 content: content_slurs_removed.to_owned(),
1017 creator_id: user.id,
1018 recipient_id: data.recipient_id,
1027 let inserted_private_message = match blocking(context.pool(), move |conn| {
1028 PrivateMessage::create(conn, &private_message_form)
1032 Ok(private_message) => private_message,
1034 return Err(APIError::err("couldnt_create_private_message").into());
1038 let inserted_private_message_id = inserted_private_message.id;
1039 let updated_private_message = match blocking(context.pool(), move |conn| {
1040 let apub_id = make_apub_endpoint(
1041 EndpointType::PrivateMessage,
1042 &inserted_private_message_id.to_string(),
1045 PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
1049 Ok(private_message) => private_message,
1050 Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
1053 updated_private_message.send_create(&user, context).await?;
1055 // Send notifications to the recipient
1056 let recipient_id = data.recipient_id;
1057 let recipient_user =
1058 blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
1059 if recipient_user.send_notifications_to_email {
1062 "Private Message from",
1064 &content_slurs_removed,
1068 let message = blocking(context.pool(), move |conn| {
1069 PrivateMessageView::read(conn, inserted_private_message.id)
1073 let res = PrivateMessageResponse {
1074 private_message_view: message,
1077 context.chat_server().do_send(SendUserRoomMessage {
1078 op: UserOperation::CreatePrivateMessage,
1079 response: res.clone(),
1088 #[async_trait::async_trait(?Send)]
1089 impl Perform for EditPrivateMessage {
1090 type Response = PrivateMessageResponse;
1094 context: &Data<LemmyContext>,
1095 websocket_id: Option<ConnectionId>,
1096 ) -> Result<PrivateMessageResponse, LemmyError> {
1097 let data: &EditPrivateMessage = &self;
1098 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1100 // Checking permissions
1101 let private_message_id = data.private_message_id;
1102 let orig_private_message = blocking(context.pool(), move |conn| {
1103 PrivateMessage::read(conn, private_message_id)
1106 if user.id != orig_private_message.creator_id {
1107 return Err(APIError::err("no_private_message_edit_allowed").into());
1111 let content_slurs_removed = remove_slurs(&data.content);
1112 let private_message_id = data.private_message_id;
1113 let updated_private_message = match blocking(context.pool(), move |conn| {
1114 PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
1118 Ok(private_message) => private_message,
1119 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1122 // Send the apub update
1123 updated_private_message.send_update(&user, context).await?;
1125 let private_message_id = data.private_message_id;
1126 let message = blocking(context.pool(), move |conn| {
1127 PrivateMessageView::read(conn, private_message_id)
1130 let recipient_id = message.recipient.id;
1132 let res = PrivateMessageResponse {
1133 private_message_view: message,
1136 context.chat_server().do_send(SendUserRoomMessage {
1137 op: UserOperation::EditPrivateMessage,
1138 response: res.clone(),
1147 #[async_trait::async_trait(?Send)]
1148 impl Perform for DeletePrivateMessage {
1149 type Response = PrivateMessageResponse;
1153 context: &Data<LemmyContext>,
1154 websocket_id: Option<ConnectionId>,
1155 ) -> Result<PrivateMessageResponse, LemmyError> {
1156 let data: &DeletePrivateMessage = &self;
1157 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1159 // Checking permissions
1160 let private_message_id = data.private_message_id;
1161 let orig_private_message = blocking(context.pool(), move |conn| {
1162 PrivateMessage::read(conn, private_message_id)
1165 if user.id != orig_private_message.creator_id {
1166 return Err(APIError::err("no_private_message_edit_allowed").into());
1170 let private_message_id = data.private_message_id;
1171 let deleted = data.deleted;
1172 let updated_private_message = match blocking(context.pool(), move |conn| {
1173 PrivateMessage::update_deleted(conn, private_message_id, deleted)
1177 Ok(private_message) => private_message,
1178 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1181 // Send the apub update
1183 updated_private_message.send_delete(&user, context).await?;
1185 updated_private_message
1186 .send_undo_delete(&user, context)
1190 let private_message_id = data.private_message_id;
1191 let message = blocking(context.pool(), move |conn| {
1192 PrivateMessageView::read(conn, private_message_id)
1195 let recipient_id = message.recipient.id;
1197 let res = PrivateMessageResponse {
1198 private_message_view: message,
1201 context.chat_server().do_send(SendUserRoomMessage {
1202 op: UserOperation::DeletePrivateMessage,
1203 response: res.clone(),
1212 #[async_trait::async_trait(?Send)]
1213 impl Perform for MarkPrivateMessageAsRead {
1214 type Response = PrivateMessageResponse;
1218 context: &Data<LemmyContext>,
1219 websocket_id: Option<ConnectionId>,
1220 ) -> Result<PrivateMessageResponse, LemmyError> {
1221 let data: &MarkPrivateMessageAsRead = &self;
1222 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1224 // Checking permissions
1225 let private_message_id = data.private_message_id;
1226 let orig_private_message = blocking(context.pool(), move |conn| {
1227 PrivateMessage::read(conn, private_message_id)
1230 if user.id != orig_private_message.recipient_id {
1231 return Err(APIError::err("couldnt_update_private_message").into());
1235 let private_message_id = data.private_message_id;
1236 let read = data.read;
1237 match blocking(context.pool(), move |conn| {
1238 PrivateMessage::update_read(conn, private_message_id, read)
1242 Ok(private_message) => private_message,
1243 Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
1246 // No need to send an apub update
1248 let private_message_id = data.private_message_id;
1249 let message = blocking(context.pool(), move |conn| {
1250 PrivateMessageView::read(conn, private_message_id)
1253 let recipient_id = message.recipient.id;
1255 let res = PrivateMessageResponse {
1256 private_message_view: message,
1259 context.chat_server().do_send(SendUserRoomMessage {
1260 op: UserOperation::MarkPrivateMessageAsRead,
1261 response: res.clone(),
1270 #[async_trait::async_trait(?Send)]
1271 impl Perform for GetPrivateMessages {
1272 type Response = PrivateMessagesResponse;
1276 context: &Data<LemmyContext>,
1277 _websocket_id: Option<ConnectionId>,
1278 ) -> Result<PrivateMessagesResponse, LemmyError> {
1279 let data: &GetPrivateMessages = &self;
1280 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1281 let user_id = user.id;
1283 let page = data.page;
1284 let limit = data.limit;
1285 let unread_only = data.unread_only;
1286 let messages = blocking(context.pool(), move |conn| {
1287 PrivateMessageQueryBuilder::create(&conn, user_id)
1290 .unread_only(unread_only)
1295 Ok(PrivateMessagesResponse {
1296 private_messages: messages,
1301 #[async_trait::async_trait(?Send)]
1302 impl Perform for GetReportCount {
1303 type Response = GetReportCountResponse;
1307 context: &Data<LemmyContext>,
1308 websocket_id: Option<ConnectionId>,
1309 ) -> Result<GetReportCountResponse, LemmyError> {
1310 let data: &GetReportCount = &self;
1311 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
1313 let user_id = user.id;
1314 let community_id = data.community;
1316 collect_moderated_communities(user_id, community_id, context.pool()).await?;
1319 if community_ids.is_empty() {
1320 GetReportCountResponse {
1326 let ids = community_ids.clone();
1327 let comment_reports = blocking(context.pool(), move |conn| {
1328 CommentReportView::get_report_count(conn, &ids)
1332 let ids = community_ids.clone();
1333 let post_reports = blocking(context.pool(), move |conn| {
1334 PostReportView::get_report_count(conn, &ids)
1338 GetReportCountResponse {
1339 community: data.community,
1346 context.chat_server().do_send(SendUserRoomMessage {
1347 op: UserOperation::GetReportCount,
1348 response: res.clone(),
1349 recipient_id: user.id,