1 use crate::{captcha_as_wav_base64, Perform};
2 use actix_web::web::Data;
5 use captcha::{gen, Difficulty};
7 use lemmy_api_common::{
9 check_registration_application,
10 get_local_user_view_from_jwt,
12 password_length_check,
14 send_email_verification_success,
15 send_password_reset_email,
16 send_verification_email,
18 use lemmy_db_schema::{
19 diesel_option_overwrite,
20 diesel_option_overwrite_to_url,
21 from_opt_str_to_opt_enum,
26 email_verification::EmailVerification,
27 local_user::{LocalUser, LocalUserForm},
29 password_reset_request::*,
31 person_block::{PersonBlock, PersonBlockForm},
34 private_message::PrivateMessage,
37 traits::{Blockable, Crud},
41 comment_report_view::CommentReportView,
42 comment_view::{CommentQueryBuilder, CommentView},
43 local_user_view::LocalUserView,
44 post_report_view::PostReportView,
45 private_message_view::PrivateMessageView,
47 use lemmy_db_views_actor::{
48 community_moderator_view::CommunityModeratorView,
49 person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
50 person_view::PersonViewSafe,
55 utils::{is_valid_display_name, is_valid_matrix_id, naive_from_unix},
59 use lemmy_websocket::{
60 messages::{CaptchaItem, SendAllMessage},
65 #[async_trait::async_trait(?Send)]
66 impl Perform for Login {
67 type Response = LoginResponse;
69 #[tracing::instrument(skip(context, _websocket_id))]
72 context: &Data<LemmyContext>,
73 _websocket_id: Option<ConnectionId>,
74 ) -> Result<LoginResponse, LemmyError> {
75 let data: &Login = self;
77 // Fetch that username / email
78 let username_or_email = data.username_or_email.clone();
79 let local_user_view = blocking(context.pool(), move |conn| {
80 LocalUserView::find_by_email_or_name(conn, &username_or_email)
83 .map_err(LemmyError::from)
84 .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
86 // Verify the password
87 let valid: bool = verify(
89 &local_user_view.local_user.password_encrypted,
93 return Err(LemmyError::from_message("password_incorrect"));
96 let site = blocking(context.pool(), Site::read_simple).await??;
97 if site.require_email_verification && !local_user_view.local_user.email_verified {
98 return Err(LemmyError::from_message("email_not_verified"));
101 check_registration_application(&site, &local_user_view, context.pool()).await?;
107 local_user_view.local_user.id.0,
108 &context.secret().jwt_secret,
109 &context.settings().hostname,
113 verify_email_sent: false,
114 registration_created: false,
119 #[async_trait::async_trait(?Send)]
120 impl Perform for GetCaptcha {
121 type Response = GetCaptchaResponse;
123 #[tracing::instrument(skip(context, _websocket_id))]
126 context: &Data<LemmyContext>,
127 _websocket_id: Option<ConnectionId>,
128 ) -> Result<Self::Response, LemmyError> {
129 let captcha_settings = context.settings().captcha;
131 if !captcha_settings.enabled {
132 return Ok(GetCaptchaResponse { ok: None });
135 let captcha = match captcha_settings.difficulty.as_str() {
136 "easy" => gen(Difficulty::Easy),
137 "medium" => gen(Difficulty::Medium),
138 "hard" => gen(Difficulty::Hard),
139 _ => gen(Difficulty::Medium),
142 let answer = captcha.chars_as_string();
144 let png = captcha.as_base64().expect("failed to generate captcha");
146 let uuid = uuid::Uuid::new_v4().to_string();
148 let wav = captcha_as_wav_base64(&captcha);
150 let captcha_item = CaptchaItem {
152 uuid: uuid.to_owned(),
153 expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
156 // Stores the captcha item on the queue
157 context.chat_server().do_send(captcha_item);
159 Ok(GetCaptchaResponse {
160 ok: Some(CaptchaResponse { png, wav, uuid }),
165 #[async_trait::async_trait(?Send)]
166 impl Perform for SaveUserSettings {
167 type Response = LoginResponse;
169 #[tracing::instrument(skip(context, _websocket_id))]
172 context: &Data<LemmyContext>,
173 _websocket_id: Option<ConnectionId>,
174 ) -> Result<LoginResponse, LemmyError> {
175 let data: &SaveUserSettings = self;
176 let local_user_view =
177 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
179 let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
180 let banner = diesel_option_overwrite_to_url(&data.banner)?;
181 let bio = diesel_option_overwrite(&data.bio);
182 let display_name = diesel_option_overwrite(&data.display_name);
183 let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
184 let bot_account = data.bot_account;
185 let email_deref = data.email.as_deref().map(|e| e.to_owned());
186 let email = diesel_option_overwrite(&email_deref);
188 if let Some(Some(email)) = &email {
189 let previous_email = local_user_view.local_user.email.unwrap_or_default();
190 // Only send the verification email if there was an email change
191 if previous_email.ne(email) {
192 send_verification_email(
193 local_user_view.local_user.id,
195 &local_user_view.person.name,
203 // When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
204 if let Some(email) = &email {
205 let site_fut = blocking(context.pool(), Site::read_simple);
206 if email.is_none() && site_fut.await??.require_email_verification {
207 return Err(LemmyError::from_message("email_required"));
211 if let Some(Some(bio)) = &bio {
212 if bio.chars().count() > 300 {
213 return Err(LemmyError::from_message("bio_length_overflow"));
217 if let Some(Some(display_name)) = &display_name {
218 if !is_valid_display_name(
220 context.settings().actor_name_max_length,
222 return Err(LemmyError::from_message("invalid_username"));
226 if let Some(Some(matrix_user_id)) = &matrix_user_id {
227 if !is_valid_matrix_id(matrix_user_id) {
228 return Err(LemmyError::from_message("invalid_matrix_id"));
232 let local_user_id = local_user_view.local_user.id;
233 let person_id = local_user_view.person.id;
234 let default_listing_type = data.default_listing_type;
235 let default_sort_type = data.default_sort_type;
236 let password_encrypted = local_user_view.local_user.password_encrypted;
237 let public_key = local_user_view.person.public_key;
239 let person_form = PersonForm {
240 name: local_user_view.person.name,
246 updated: Some(naive_now()),
255 last_refreshed_at: None,
256 shared_inbox_url: None,
262 blocking(context.pool(), move |conn| {
263 Person::update(conn, person_id, &person_form)
266 .map_err(LemmyError::from)
267 .map_err(|e| e.with_message("user_already_exists"))?;
269 let local_user_form = LocalUserForm {
270 person_id: Some(person_id),
272 password_encrypted: Some(password_encrypted),
273 show_nsfw: data.show_nsfw,
274 show_bot_accounts: data.show_bot_accounts,
275 show_scores: data.show_scores,
276 theme: data.theme.to_owned(),
278 default_listing_type,
279 lang: data.lang.to_owned(),
280 show_avatars: data.show_avatars,
281 show_read_posts: data.show_read_posts,
282 show_new_post_notifs: data.show_new_post_notifs,
283 send_notifications_to_email: data.send_notifications_to_email,
284 email_verified: None,
285 accepted_application: None,
288 let local_user_res = blocking(context.pool(), move |conn| {
289 LocalUser::update(conn, local_user_id, &local_user_form)
292 let updated_local_user = match local_user_res {
295 let err_type = if e.to_string()
296 == "duplicate key value violates unique constraint \"local_user_email_key\""
298 "email_already_exists"
300 "user_already_exists"
303 return Err(LemmyError::from(e).with_message(err_type));
311 updated_local_user.id.0,
312 &context.secret().jwt_secret,
313 &context.settings().hostname,
317 verify_email_sent: false,
318 registration_created: false,
323 #[async_trait::async_trait(?Send)]
324 impl Perform for ChangePassword {
325 type Response = LoginResponse;
327 #[tracing::instrument(skip(self, context, _websocket_id))]
330 context: &Data<LemmyContext>,
331 _websocket_id: Option<ConnectionId>,
332 ) -> Result<LoginResponse, LemmyError> {
333 let data: &ChangePassword = self;
334 let local_user_view =
335 get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
337 password_length_check(&data.new_password)?;
339 // Make sure passwords match
340 if data.new_password != data.new_password_verify {
341 return Err(LemmyError::from_message("passwords_dont_match"));
344 // Check the old password
345 let valid: bool = verify(
347 &local_user_view.local_user.password_encrypted,
351 return Err(LemmyError::from_message("password_incorrect"));
354 let local_user_id = local_user_view.local_user.id;
355 let new_password = data.new_password.to_owned();
356 let updated_local_user = blocking(context.pool(), move |conn| {
357 LocalUser::update_password(conn, local_user_id, &new_password)
365 updated_local_user.id.0,
366 &context.secret().jwt_secret,
367 &context.settings().hostname,
371 verify_email_sent: false,
372 registration_created: false,
377 #[async_trait::async_trait(?Send)]
378 impl Perform for AddAdmin {
379 type Response = AddAdminResponse;
381 #[tracing::instrument(skip(context, websocket_id))]
384 context: &Data<LemmyContext>,
385 websocket_id: Option<ConnectionId>,
386 ) -> Result<AddAdminResponse, LemmyError> {
387 let data: &AddAdmin = self;
388 let local_user_view =
389 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
391 // Make sure user is an admin
392 is_admin(&local_user_view)?;
394 let added = data.added;
395 let added_person_id = data.person_id;
396 let added_admin = blocking(context.pool(), move |conn| {
397 Person::add_admin(conn, added_person_id, added)
400 .map_err(LemmyError::from)
401 .map_err(|e| e.with_message("couldnt_update_user"))?;
404 let form = ModAddForm {
405 mod_person_id: local_user_view.person.id,
406 other_person_id: added_admin.id,
407 removed: Some(!data.added),
410 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
412 let site_creator_id = blocking(context.pool(), move |conn| {
413 Site::read(conn, 1).map(|s| s.creator_id)
417 let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
418 let creator_index = admins
420 .position(|r| r.person.id == site_creator_id)
421 .context(location_info!())?;
422 let creator_person = admins.remove(creator_index);
423 admins.insert(0, creator_person);
425 let res = AddAdminResponse { admins };
427 context.chat_server().do_send(SendAllMessage {
428 op: UserOperation::AddAdmin,
429 response: res.clone(),
437 #[async_trait::async_trait(?Send)]
438 impl Perform for BanPerson {
439 type Response = BanPersonResponse;
441 #[tracing::instrument(skip(context, websocket_id))]
444 context: &Data<LemmyContext>,
445 websocket_id: Option<ConnectionId>,
446 ) -> Result<BanPersonResponse, LemmyError> {
447 let data: &BanPerson = self;
448 let local_user_view =
449 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
451 // Make sure user is an admin
452 is_admin(&local_user_view)?;
455 let banned_person_id = data.person_id;
456 let expires = data.expires.map(naive_from_unix);
458 let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban, expires);
459 blocking(context.pool(), ban_person)
461 .map_err(LemmyError::from)
462 .map_err(|e| e.with_message("couldnt_update_user"))?;
464 // Remove their data if that's desired
465 if data.remove_data.unwrap_or(false) {
467 blocking(context.pool(), move |conn: &'_ _| {
468 Post::update_removed_for_creator(conn, banned_person_id, None, true)
473 // Remove all communities where they're the top mod
474 // for now, remove the communities manually
475 let first_mod_communities = blocking(context.pool(), move |conn: &'_ _| {
476 CommunityModeratorView::get_community_first_mods(conn)
480 // Filter to only this banned users top communities
481 let banned_user_first_communities: Vec<CommunityModeratorView> = first_mod_communities
483 .filter(|fmc| fmc.moderator.id == banned_person_id)
486 for first_mod_community in banned_user_first_communities {
487 blocking(context.pool(), move |conn: &'_ _| {
488 Community::update_removed(conn, first_mod_community.community.id, true)
494 blocking(context.pool(), move |conn: &'_ _| {
495 Comment::update_removed_for_creator(conn, banned_person_id, true)
501 let form = ModBanForm {
502 mod_person_id: local_user_view.person.id,
503 other_person_id: data.person_id,
504 reason: data.reason.to_owned(),
505 banned: Some(data.ban),
509 blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
511 let person_id = data.person_id;
512 let person_view = blocking(context.pool(), move |conn| {
513 PersonViewSafe::read(conn, person_id)
517 let res = BanPersonResponse {
522 context.chat_server().do_send(SendAllMessage {
523 op: UserOperation::BanPerson,
524 response: res.clone(),
532 #[async_trait::async_trait(?Send)]
533 impl Perform for GetBannedPersons {
534 type Response = BannedPersonsResponse;
538 context: &Data<LemmyContext>,
539 _websocket_id: Option<ConnectionId>,
540 ) -> Result<Self::Response, LemmyError> {
541 let data: &GetBannedPersons = self;
542 let local_user_view =
543 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
545 // Make sure user is an admin
546 is_admin(&local_user_view)?;
548 let banned = blocking(context.pool(), PersonViewSafe::banned).await??;
550 let res = Self::Response { banned };
556 #[async_trait::async_trait(?Send)]
557 impl Perform for BlockPerson {
558 type Response = BlockPersonResponse;
560 #[tracing::instrument(skip(context, _websocket_id))]
563 context: &Data<LemmyContext>,
564 _websocket_id: Option<ConnectionId>,
565 ) -> Result<BlockPersonResponse, LemmyError> {
566 let data: &BlockPerson = self;
567 let local_user_view =
568 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
570 let target_id = data.person_id;
571 let person_id = local_user_view.person.id;
573 // Don't let a person block themselves
574 if target_id == person_id {
575 return Err(LemmyError::from_message("cant_block_yourself"));
578 let person_block_form = PersonBlockForm {
584 let block = move |conn: &'_ _| PersonBlock::block(conn, &person_block_form);
585 blocking(context.pool(), block)
587 .map_err(LemmyError::from)
588 .map_err(|e| e.with_message("person_block_already_exists"))?;
590 let unblock = move |conn: &'_ _| PersonBlock::unblock(conn, &person_block_form);
591 blocking(context.pool(), unblock)
593 .map_err(LemmyError::from)
594 .map_err(|e| e.with_message("person_block_already_exists"))?;
597 // TODO does any federated stuff need to be done here?
599 let person_view = blocking(context.pool(), move |conn| {
600 PersonViewSafe::read(conn, target_id)
604 let res = BlockPersonResponse {
613 #[async_trait::async_trait(?Send)]
614 impl Perform for GetReplies {
615 type Response = GetRepliesResponse;
617 #[tracing::instrument(skip(context, _websocket_id))]
620 context: &Data<LemmyContext>,
621 _websocket_id: Option<ConnectionId>,
622 ) -> Result<GetRepliesResponse, LemmyError> {
623 let data: &GetReplies = self;
624 let local_user_view =
625 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
627 let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
629 let page = data.page;
630 let limit = data.limit;
631 let unread_only = data.unread_only;
632 let person_id = local_user_view.person.id;
633 let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
635 let replies = blocking(context.pool(), move |conn| {
636 CommentQueryBuilder::create(conn)
638 .unread_only(unread_only)
639 .recipient_id(person_id)
640 .show_bot_accounts(show_bot_accounts)
641 .my_person_id(person_id)
648 Ok(GetRepliesResponse { replies })
652 #[async_trait::async_trait(?Send)]
653 impl Perform for GetPersonMentions {
654 type Response = GetPersonMentionsResponse;
656 #[tracing::instrument(skip(context, _websocket_id))]
659 context: &Data<LemmyContext>,
660 _websocket_id: Option<ConnectionId>,
661 ) -> Result<GetPersonMentionsResponse, LemmyError> {
662 let data: &GetPersonMentions = self;
663 let local_user_view =
664 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
666 let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
668 let page = data.page;
669 let limit = data.limit;
670 let unread_only = data.unread_only;
671 let person_id = local_user_view.person.id;
672 let mentions = blocking(context.pool(), move |conn| {
673 PersonMentionQueryBuilder::create(conn)
674 .recipient_id(person_id)
675 .my_person_id(person_id)
677 .unread_only(unread_only)
684 Ok(GetPersonMentionsResponse { mentions })
688 #[async_trait::async_trait(?Send)]
689 impl Perform for MarkPersonMentionAsRead {
690 type Response = PersonMentionResponse;
692 #[tracing::instrument(skip(context, _websocket_id))]
695 context: &Data<LemmyContext>,
696 _websocket_id: Option<ConnectionId>,
697 ) -> Result<PersonMentionResponse, LemmyError> {
698 let data: &MarkPersonMentionAsRead = self;
699 let local_user_view =
700 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
702 let person_mention_id = data.person_mention_id;
703 let read_person_mention = blocking(context.pool(), move |conn| {
704 PersonMention::read(conn, person_mention_id)
708 if local_user_view.person.id != read_person_mention.recipient_id {
709 return Err(LemmyError::from_message("couldnt_update_comment"));
712 let person_mention_id = read_person_mention.id;
713 let read = data.read;
715 move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
716 blocking(context.pool(), update_mention)
718 .map_err(LemmyError::from)
719 .map_err(|e| e.with_message("couldnt_update_comment"))?;
721 let person_mention_id = read_person_mention.id;
722 let person_id = local_user_view.person.id;
723 let person_mention_view = blocking(context.pool(), move |conn| {
724 PersonMentionView::read(conn, person_mention_id, Some(person_id))
728 Ok(PersonMentionResponse {
734 #[async_trait::async_trait(?Send)]
735 impl Perform for MarkAllAsRead {
736 type Response = GetRepliesResponse;
738 #[tracing::instrument(skip(context, _websocket_id))]
741 context: &Data<LemmyContext>,
742 _websocket_id: Option<ConnectionId>,
743 ) -> Result<GetRepliesResponse, LemmyError> {
744 let data: &MarkAllAsRead = self;
745 let local_user_view =
746 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
748 let person_id = local_user_view.person.id;
749 let replies = blocking(context.pool(), move |conn| {
750 CommentQueryBuilder::create(conn)
751 .my_person_id(person_id)
752 .recipient_id(person_id)
760 // TODO: this should probably be a bulk operation
761 // Not easy to do as a bulk operation,
762 // because recipient_id isn't in the comment table
763 for comment_view in &replies {
764 let reply_id = comment_view.comment.id;
765 let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
766 blocking(context.pool(), mark_as_read)
768 .map_err(LemmyError::from)
769 .map_err(|e| e.with_message("couldnt_update_comment"))?;
772 // Mark all user mentions as read
773 let update_person_mentions =
774 move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
775 blocking(context.pool(), update_person_mentions)
777 .map_err(LemmyError::from)
778 .map_err(|e| e.with_message("couldnt_update_comment"))?;
780 // Mark all private_messages as read
781 let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
782 blocking(context.pool(), update_pm)
784 .map_err(LemmyError::from)
785 .map_err(|e| e.with_message("couldnt_update_private_message"))?;
787 Ok(GetRepliesResponse { replies: vec![] })
791 #[async_trait::async_trait(?Send)]
792 impl Perform for PasswordReset {
793 type Response = PasswordResetResponse;
795 #[tracing::instrument(skip(self, context, _websocket_id))]
798 context: &Data<LemmyContext>,
799 _websocket_id: Option<ConnectionId>,
800 ) -> Result<PasswordResetResponse, LemmyError> {
801 let data: &PasswordReset = self;
804 let email = data.email.clone();
805 let local_user_view = blocking(context.pool(), move |conn| {
806 LocalUserView::find_by_email(conn, &email)
809 .map_err(LemmyError::from)
810 .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
812 // Email the pure token to the user.
813 send_password_reset_email(&local_user_view, context.pool(), &context.settings()).await?;
814 Ok(PasswordResetResponse {})
818 #[async_trait::async_trait(?Send)]
819 impl Perform for PasswordChange {
820 type Response = LoginResponse;
822 #[tracing::instrument(skip(self, context, _websocket_id))]
825 context: &Data<LemmyContext>,
826 _websocket_id: Option<ConnectionId>,
827 ) -> Result<LoginResponse, LemmyError> {
828 let data: &PasswordChange = self;
830 // Fetch the user_id from the token
831 let token = data.token.clone();
832 let local_user_id = blocking(context.pool(), move |conn| {
833 PasswordResetRequest::read_from_token(conn, &token).map(|p| p.local_user_id)
837 password_length_check(&data.password)?;
839 // Make sure passwords match
840 if data.password != data.password_verify {
841 return Err(LemmyError::from_message("passwords_dont_match"));
844 // Update the user with the new password
845 let password = data.password.clone();
846 let updated_local_user = blocking(context.pool(), move |conn| {
847 LocalUser::update_password(conn, local_user_id, &password)
850 .map_err(LemmyError::from)
851 .map_err(|e| e.with_message("couldnt_update_user"))?;
857 updated_local_user.id.0,
858 &context.secret().jwt_secret,
859 &context.settings().hostname,
863 verify_email_sent: false,
864 registration_created: false,
869 #[async_trait::async_trait(?Send)]
870 impl Perform for GetReportCount {
871 type Response = GetReportCountResponse;
873 #[tracing::instrument(skip(context, _websocket_id))]
876 context: &Data<LemmyContext>,
877 _websocket_id: Option<ConnectionId>,
878 ) -> Result<GetReportCountResponse, LemmyError> {
879 let data: &GetReportCount = self;
880 let local_user_view =
881 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
883 let person_id = local_user_view.person.id;
884 let admin = local_user_view.person.admin;
885 let community_id = data.community_id;
887 let comment_reports = blocking(context.pool(), move |conn| {
888 CommentReportView::get_report_count(conn, person_id, admin, community_id)
892 let post_reports = blocking(context.pool(), move |conn| {
893 PostReportView::get_report_count(conn, person_id, admin, community_id)
897 let res = GetReportCountResponse {
907 #[async_trait::async_trait(?Send)]
908 impl Perform for GetUnreadCount {
909 type Response = GetUnreadCountResponse;
911 #[tracing::instrument(skip(context, _websocket_id))]
914 context: &Data<LemmyContext>,
915 _websocket_id: Option<ConnectionId>,
916 ) -> Result<Self::Response, LemmyError> {
918 let local_user_view =
919 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
921 let person_id = local_user_view.person.id;
923 let replies = blocking(context.pool(), move |conn| {
924 CommentView::get_unread_replies(conn, person_id)
928 let mentions = blocking(context.pool(), move |conn| {
929 PersonMentionView::get_unread_mentions(conn, person_id)
933 let private_messages = blocking(context.pool(), move |conn| {
934 PrivateMessageView::get_unread_messages(conn, person_id)
938 let res = Self::Response {
948 #[async_trait::async_trait(?Send)]
949 impl Perform for VerifyEmail {
950 type Response = VerifyEmailResponse;
954 context: &Data<LemmyContext>,
955 _websocket_id: Option<usize>,
956 ) -> Result<Self::Response, LemmyError> {
957 let token = self.token.clone();
958 let verification = blocking(context.pool(), move |conn| {
959 EmailVerification::read_for_token(conn, &token)
962 .map_err(LemmyError::from)
963 .map_err(|e| e.with_message("token_not_found"))?;
965 let form = LocalUserForm {
966 // necessary in case this is a new signup
967 email_verified: Some(true),
968 // necessary in case email of an existing user was changed
969 email: Some(Some(verification.email)),
970 ..LocalUserForm::default()
972 let local_user_id = verification.local_user_id;
973 blocking(context.pool(), move |conn| {
974 LocalUser::update(conn, local_user_id, &form)
978 let local_user_view = blocking(context.pool(), move |conn| {
979 LocalUserView::read(conn, local_user_id)
983 send_email_verification_success(&local_user_view, &context.settings())?;
985 blocking(context.pool(), move |conn| {
986 EmailVerification::delete_old_tokens_for_local_user(conn, local_user_id)
990 Ok(VerifyEmailResponse {})