1 use actix_web::{guard, web, Error, HttpResponse, Result};
2 use lemmy_api::Perform;
3 use lemmy_api_common::{
31 context::LemmyContext,
47 MarkCommentReplyAsRead,
48 MarkPersonMentionAsRead,
49 PasswordChangeAfterReset,
74 CreatePrivateMessageReport,
78 ListPrivateMessageReports,
79 MarkPrivateMessageAsRead,
80 ResolvePrivateMessageReport,
83 ApproveRegistrationApplication,
88 GetUnreadRegistrationApplicationCount,
90 ListRegistrationApplications,
100 serialize_websocket_message,
101 structs::{CommunityJoin, ModJoin, PostJoin, UserJoin},
107 use lemmy_api_crud::PerformCrud;
108 use lemmy_apub::{api::PerformApub, SendActivity};
109 use lemmy_utils::{error::LemmyError, rate_limit::RateLimitCell, ConnectionId};
110 use serde::Deserialize;
113 pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
115 web::scope("/api/v3")
117 .service(web::resource("/ws").to(chat_route))
121 .wrap(rate_limit.message())
122 .route("", web::get().to(route_get_crud::<GetSite>))
124 .route("", web::post().to(route_post_crud::<CreateSite>))
125 .route("", web::put().to(route_post_crud::<EditSite>)),
128 web::resource("/modlog")
129 .wrap(rate_limit.message())
130 .route(web::get().to(route_get::<GetModlog>)),
133 web::resource("/search")
134 .wrap(rate_limit.search())
135 .route(web::get().to(route_get_apub::<Search>)),
138 web::resource("/resolve_object")
139 .wrap(rate_limit.message())
140 .route(web::get().to(route_get_apub::<ResolveObject>)),
144 web::resource("/community")
145 .guard(guard::Post())
146 .wrap(rate_limit.register())
147 .route(web::post().to(route_post_crud::<CreateCommunity>)),
150 web::scope("/community")
151 .wrap(rate_limit.message())
152 .route("", web::get().to(route_get_apub::<GetCommunity>))
153 .route("", web::put().to(route_post_crud::<EditCommunity>))
154 .route("/hide", web::put().to(route_post::<HideCommunity>))
155 .route("/list", web::get().to(route_get_crud::<ListCommunities>))
156 .route("/follow", web::post().to(route_post::<FollowCommunity>))
157 .route("/block", web::post().to(route_post::<BlockCommunity>))
160 web::post().to(route_post_crud::<DeleteCommunity>),
165 web::post().to(route_post_crud::<RemoveCommunity>),
167 .route("/transfer", web::post().to(route_post::<TransferCommunity>))
168 .route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
169 .route("/mod", web::post().to(route_post::<AddModToCommunity>))
170 .route("/join", web::post().to(route_post::<CommunityJoin>))
171 .route("/mod/join", web::post().to(route_post::<ModJoin>)),
175 // Handle POST to /post separately to add the post() rate limitter
176 web::resource("/post")
177 .guard(guard::Post())
178 .wrap(rate_limit.post())
179 .route(web::post().to(route_post_crud::<CreatePost>)),
183 .wrap(rate_limit.message())
184 .route("", web::get().to(route_get_crud::<GetPost>))
185 .route("", web::put().to(route_post_crud::<EditPost>))
186 .route("/delete", web::post().to(route_post_crud::<DeletePost>))
187 .route("/remove", web::post().to(route_post_crud::<RemovePost>))
190 web::post().to(route_post::<MarkPostAsRead>),
192 .route("/lock", web::post().to(route_post::<LockPost>))
193 .route("/sticky", web::post().to(route_post::<StickyPost>))
194 .route("/list", web::get().to(route_get_apub::<GetPosts>))
195 .route("/like", web::post().to(route_post::<CreatePostLike>))
196 .route("/save", web::put().to(route_post::<SavePost>))
197 .route("/join", web::post().to(route_post::<PostJoin>))
198 .route("/report", web::post().to(route_post::<CreatePostReport>))
201 web::put().to(route_post::<ResolvePostReport>),
203 .route("/report/list", web::get().to(route_get::<ListPostReports>))
206 web::get().to(route_get::<GetSiteMetadata>),
211 // Handle POST to /comment separately to add the comment() rate limitter
212 web::resource("/comment")
213 .guard(guard::Post())
214 .wrap(rate_limit.comment())
215 .route(web::post().to(route_post_crud::<CreateComment>)),
218 web::scope("/comment")
219 .wrap(rate_limit.message())
220 .route("", web::get().to(route_get_crud::<GetComment>))
221 .route("", web::put().to(route_post_crud::<EditComment>))
222 .route("/delete", web::post().to(route_post_crud::<DeleteComment>))
223 .route("/remove", web::post().to(route_post_crud::<RemoveComment>))
226 web::post().to(route_post::<MarkCommentReplyAsRead>),
228 .route("/like", web::post().to(route_post::<CreateCommentLike>))
229 .route("/save", web::put().to(route_post::<SaveComment>))
230 .route("/list", web::get().to(route_get_apub::<GetComments>))
231 .route("/report", web::post().to(route_post::<CreateCommentReport>))
234 web::put().to(route_post::<ResolveCommentReport>),
238 web::get().to(route_get::<ListCommentReports>),
243 web::scope("/private_message")
244 .wrap(rate_limit.message())
245 .route("/list", web::get().to(route_get_crud::<GetPrivateMessages>))
246 .route("", web::post().to(route_post_crud::<CreatePrivateMessage>))
247 .route("", web::put().to(route_post_crud::<EditPrivateMessage>))
250 web::post().to(route_post_crud::<DeletePrivateMessage>),
254 web::post().to(route_post::<MarkPrivateMessageAsRead>),
258 web::post().to(route_post::<CreatePrivateMessageReport>),
262 web::put().to(route_post::<ResolvePrivateMessageReport>),
266 web::get().to(route_get::<ListPrivateMessageReports>),
271 // Account action, I don't like that it's in /user maybe /accounts
272 // Handle /user/register separately to add the register() rate limitter
273 web::resource("/user/register")
274 .guard(guard::Post())
275 .wrap(rate_limit.register())
276 .route(web::post().to(route_post_crud::<Register>)),
279 // Handle captcha separately
280 web::resource("/user/get_captcha")
281 .wrap(rate_limit.post())
282 .route(web::get().to(route_get::<GetCaptcha>)),
287 .wrap(rate_limit.message())
288 .route("", web::get().to(route_get_apub::<GetPersonDetails>))
289 .route("/mention", web::get().to(route_get::<GetPersonMentions>))
291 "/mention/mark_as_read",
292 web::post().to(route_post::<MarkPersonMentionAsRead>),
294 .route("/replies", web::get().to(route_get::<GetReplies>))
295 .route("/join", web::post().to(route_post::<UserJoin>))
296 // Admin action. I don't like that it's in /user
297 .route("/ban", web::post().to(route_post::<BanPerson>))
298 .route("/banned", web::get().to(route_get::<GetBannedPersons>))
299 .route("/block", web::post().to(route_post::<BlockPerson>))
300 // Account actions. I don't like that they're in /user maybe /accounts
301 .route("/login", web::post().to(route_post::<Login>))
304 web::post().to(route_post_crud::<DeleteAccount>),
308 web::post().to(route_post::<PasswordReset>),
312 web::post().to(route_post::<PasswordChangeAfterReset>),
314 // mark_all_as_read feels off being in this section as well
317 web::post().to(route_post::<MarkAllAsRead>),
320 "/save_user_settings",
321 web::put().to(route_post::<SaveUserSettings>),
325 web::put().to(route_post::<ChangePassword>),
327 .route("/report_count", web::get().to(route_get::<GetReportCount>))
328 .route("/unread_count", web::get().to(route_get::<GetUnreadCount>))
329 .route("/verify_email", web::post().to(route_post::<VerifyEmail>))
330 .route("/leave_admin", web::post().to(route_post::<LeaveAdmin>)),
335 .wrap(rate_limit.message())
336 .route("/add", web::post().to(route_post::<AddAdmin>))
338 "/registration_application/count",
339 web::get().to(route_get::<GetUnreadRegistrationApplicationCount>),
342 "/registration_application/list",
343 web::get().to(route_get::<ListRegistrationApplications>),
346 "/registration_application/approve",
347 web::put().to(route_post::<ApproveRegistrationApplication>),
351 web::scope("/admin/purge")
352 .wrap(rate_limit.message())
353 .route("/person", web::post().to(route_post::<PurgePerson>))
354 .route("/community", web::post().to(route_post::<PurgeCommunity>))
355 .route("/post", web::post().to(route_post::<PurgePost>))
356 .route("/comment", web::post().to(route_post::<PurgeComment>)),
361 async fn perform<'a, Data>(
363 context: web::Data<LemmyContext>,
364 ) -> Result<HttpResponse, Error>
367 + SendActivity<Response = <Data as Perform>::Response>
373 let res = data.perform(&context, None).await?;
374 SendActivity::send_activity(&data, &res, &context).await?;
375 Ok(HttpResponse::Ok().json(res))
378 async fn route_get<'a, Data>(
379 data: web::Query<Data>,
380 context: web::Data<LemmyContext>,
381 ) -> Result<HttpResponse, Error>
384 + SendActivity<Response = <Data as Perform>::Response>
390 perform::<Data>(data.0, context).await
393 async fn route_get_apub<'a, Data>(
394 data: web::Query<Data>,
395 context: web::Data<LemmyContext>,
396 ) -> Result<HttpResponse, Error>
399 + SendActivity<Response = <Data as PerformApub>::Response>
405 let res = data.perform(&context, None).await?;
406 SendActivity::send_activity(&data.0, &res, &context).await?;
407 Ok(HttpResponse::Ok().json(res))
410 async fn route_post<'a, Data>(
411 data: web::Json<Data>,
412 context: web::Data<LemmyContext>,
413 ) -> Result<HttpResponse, Error>
416 + SendActivity<Response = <Data as Perform>::Response>
422 perform::<Data>(data.0, context).await
425 async fn perform_crud<'a, Data>(
427 context: web::Data<LemmyContext>,
428 ) -> Result<HttpResponse, Error>
431 + SendActivity<Response = <Data as PerformCrud>::Response>
437 let res = data.perform(&context, None).await?;
438 SendActivity::send_activity(&data, &res, &context).await?;
439 Ok(HttpResponse::Ok().json(res))
442 async fn route_get_crud<'a, Data>(
443 data: web::Query<Data>,
444 context: web::Data<LemmyContext>,
445 ) -> Result<HttpResponse, Error>
448 + SendActivity<Response = <Data as PerformCrud>::Response>
454 perform_crud::<Data>(data.0, context).await
457 async fn route_post_crud<'a, Data>(
458 data: web::Json<Data>,
459 context: web::Data<LemmyContext>,
460 ) -> Result<HttpResponse, Error>
463 + SendActivity<Response = <Data as PerformCrud>::Response>
469 perform_crud::<Data>(data.0, context).await
472 pub async fn match_websocket_operation_crud(
473 context: LemmyContext,
475 op: UserOperationCrud,
477 ) -> result::Result<String, LemmyError> {
480 UserOperationCrud::Register => {
481 do_websocket_operation_crud::<Register>(context, id, op, data).await
483 UserOperationCrud::DeleteAccount => {
484 do_websocket_operation_crud::<DeleteAccount>(context, id, op, data).await
487 // Private Message ops
488 UserOperationCrud::CreatePrivateMessage => {
489 do_websocket_operation_crud::<CreatePrivateMessage>(context, id, op, data).await
491 UserOperationCrud::EditPrivateMessage => {
492 do_websocket_operation_crud::<EditPrivateMessage>(context, id, op, data).await
494 UserOperationCrud::DeletePrivateMessage => {
495 do_websocket_operation_crud::<DeletePrivateMessage>(context, id, op, data).await
497 UserOperationCrud::GetPrivateMessages => {
498 do_websocket_operation_crud::<GetPrivateMessages>(context, id, op, data).await
502 UserOperationCrud::CreateSite => {
503 do_websocket_operation_crud::<CreateSite>(context, id, op, data).await
505 UserOperationCrud::EditSite => {
506 do_websocket_operation_crud::<EditSite>(context, id, op, data).await
508 UserOperationCrud::GetSite => {
509 do_websocket_operation_crud::<GetSite>(context, id, op, data).await
513 UserOperationCrud::ListCommunities => {
514 do_websocket_operation_crud::<ListCommunities>(context, id, op, data).await
516 UserOperationCrud::CreateCommunity => {
517 do_websocket_operation_crud::<CreateCommunity>(context, id, op, data).await
519 UserOperationCrud::EditCommunity => {
520 do_websocket_operation_crud::<EditCommunity>(context, id, op, data).await
522 UserOperationCrud::DeleteCommunity => {
523 do_websocket_operation_crud::<DeleteCommunity>(context, id, op, data).await
525 UserOperationCrud::RemoveCommunity => {
526 do_websocket_operation_crud::<RemoveCommunity>(context, id, op, data).await
530 UserOperationCrud::CreatePost => {
531 do_websocket_operation_crud::<CreatePost>(context, id, op, data).await
533 UserOperationCrud::GetPost => {
534 do_websocket_operation_crud::<GetPost>(context, id, op, data).await
536 UserOperationCrud::EditPost => {
537 do_websocket_operation_crud::<EditPost>(context, id, op, data).await
539 UserOperationCrud::DeletePost => {
540 do_websocket_operation_crud::<DeletePost>(context, id, op, data).await
542 UserOperationCrud::RemovePost => {
543 do_websocket_operation_crud::<RemovePost>(context, id, op, data).await
547 UserOperationCrud::CreateComment => {
548 do_websocket_operation_crud::<CreateComment>(context, id, op, data).await
550 UserOperationCrud::EditComment => {
551 do_websocket_operation_crud::<EditComment>(context, id, op, data).await
553 UserOperationCrud::DeleteComment => {
554 do_websocket_operation_crud::<DeleteComment>(context, id, op, data).await
556 UserOperationCrud::RemoveComment => {
557 do_websocket_operation_crud::<RemoveComment>(context, id, op, data).await
559 UserOperationCrud::GetComment => {
560 do_websocket_operation_crud::<GetComment>(context, id, op, data).await
565 async fn do_websocket_operation_crud<'a, 'b, Data>(
566 context: LemmyContext,
568 op: UserOperationCrud,
570 ) -> result::Result<String, LemmyError>
572 Data: PerformCrud + SendActivity<Response = <Data as PerformCrud>::Response>,
573 for<'de> Data: Deserialize<'de>,
575 let parsed_data: Data = serde_json::from_str(data)?;
576 let res = parsed_data
577 .perform(&web::Data::new(context.clone()), Some(id))
579 SendActivity::send_activity(&parsed_data, &res, &context).await?;
580 serialize_websocket_message(&op, &res)
583 pub async fn match_websocket_operation_apub(
584 context: LemmyContext,
586 op: UserOperationApub,
588 ) -> result::Result<String, LemmyError> {
590 UserOperationApub::GetPersonDetails => {
591 do_websocket_operation_apub::<GetPersonDetails>(context, id, op, data).await
593 UserOperationApub::GetCommunity => {
594 do_websocket_operation_apub::<GetCommunity>(context, id, op, data).await
596 UserOperationApub::GetComments => {
597 do_websocket_operation_apub::<GetComments>(context, id, op, data).await
599 UserOperationApub::GetPosts => {
600 do_websocket_operation_apub::<GetPosts>(context, id, op, data).await
602 UserOperationApub::ResolveObject => {
603 do_websocket_operation_apub::<ResolveObject>(context, id, op, data).await
605 UserOperationApub::Search => do_websocket_operation_apub::<Search>(context, id, op, data).await,
609 async fn do_websocket_operation_apub<'a, 'b, Data>(
610 context: LemmyContext,
612 op: UserOperationApub,
614 ) -> result::Result<String, LemmyError>
616 Data: PerformApub + SendActivity<Response = <Data as PerformApub>::Response>,
617 for<'de> Data: Deserialize<'de>,
619 let parsed_data: Data = serde_json::from_str(data)?;
620 let res = parsed_data
621 .perform(&web::Data::new(context.clone()), Some(id))
623 SendActivity::send_activity(&parsed_data, &res, &context).await?;
624 serialize_websocket_message(&op, &res)
627 pub async fn match_websocket_operation(
628 context: LemmyContext,
632 ) -> result::Result<String, LemmyError> {
635 UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
636 UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
637 UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
638 UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
639 UserOperation::GetUnreadRegistrationApplicationCount => {
640 do_websocket_operation::<GetUnreadRegistrationApplicationCount>(context, id, op, data).await
642 UserOperation::ListRegistrationApplications => {
643 do_websocket_operation::<ListRegistrationApplications>(context, id, op, data).await
645 UserOperation::ApproveRegistrationApplication => {
646 do_websocket_operation::<ApproveRegistrationApplication>(context, id, op, data).await
648 UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
649 UserOperation::GetBannedPersons => {
650 do_websocket_operation::<GetBannedPersons>(context, id, op, data).await
652 UserOperation::BlockPerson => {
653 do_websocket_operation::<BlockPerson>(context, id, op, data).await
655 UserOperation::GetPersonMentions => {
656 do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
658 UserOperation::MarkPersonMentionAsRead => {
659 do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
661 UserOperation::MarkCommentReplyAsRead => {
662 do_websocket_operation::<MarkCommentReplyAsRead>(context, id, op, data).await
664 UserOperation::MarkAllAsRead => {
665 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
667 UserOperation::PasswordReset => {
668 do_websocket_operation::<PasswordReset>(context, id, op, data).await
670 UserOperation::PasswordChange => {
671 do_websocket_operation::<PasswordChangeAfterReset>(context, id, op, data).await
673 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
674 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
675 UserOperation::CommunityJoin => {
676 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
678 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
679 UserOperation::SaveUserSettings => {
680 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
682 UserOperation::ChangePassword => {
683 do_websocket_operation::<ChangePassword>(context, id, op, data).await
685 UserOperation::GetReportCount => {
686 do_websocket_operation::<GetReportCount>(context, id, op, data).await
688 UserOperation::GetUnreadCount => {
689 do_websocket_operation::<GetUnreadCount>(context, id, op, data).await
691 UserOperation::VerifyEmail => {
692 do_websocket_operation::<VerifyEmail>(context, id, op, data).await
695 // Private Message ops
696 UserOperation::MarkPrivateMessageAsRead => {
697 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
699 UserOperation::CreatePrivateMessageReport => {
700 do_websocket_operation::<CreatePrivateMessageReport>(context, id, op, data).await
702 UserOperation::ResolvePrivateMessageReport => {
703 do_websocket_operation::<ResolvePrivateMessageReport>(context, id, op, data).await
705 UserOperation::ListPrivateMessageReports => {
706 do_websocket_operation::<ListPrivateMessageReports>(context, id, op, data).await
710 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
711 UserOperation::PurgePerson => {
712 do_websocket_operation::<PurgePerson>(context, id, op, data).await
714 UserOperation::PurgeCommunity => {
715 do_websocket_operation::<PurgeCommunity>(context, id, op, data).await
717 UserOperation::PurgePost => do_websocket_operation::<PurgePost>(context, id, op, data).await,
718 UserOperation::PurgeComment => {
719 do_websocket_operation::<PurgeComment>(context, id, op, data).await
721 UserOperation::TransferCommunity => {
722 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
724 UserOperation::LeaveAdmin => do_websocket_operation::<LeaveAdmin>(context, id, op, data).await,
727 UserOperation::FollowCommunity => {
728 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
730 UserOperation::BlockCommunity => {
731 do_websocket_operation::<BlockCommunity>(context, id, op, data).await
733 UserOperation::BanFromCommunity => {
734 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
736 UserOperation::AddModToCommunity => {
737 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
741 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
742 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
743 UserOperation::CreatePostLike => {
744 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
746 UserOperation::MarkPostAsRead => {
747 do_websocket_operation::<MarkPostAsRead>(context, id, op, data).await
749 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
750 UserOperation::CreatePostReport => {
751 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
753 UserOperation::ListPostReports => {
754 do_websocket_operation::<ListPostReports>(context, id, op, data).await
756 UserOperation::ResolvePostReport => {
757 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
759 UserOperation::GetSiteMetadata => {
760 do_websocket_operation::<GetSiteMetadata>(context, id, op, data).await
764 UserOperation::SaveComment => {
765 do_websocket_operation::<SaveComment>(context, id, op, data).await
767 UserOperation::CreateCommentLike => {
768 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
770 UserOperation::CreateCommentReport => {
771 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
773 UserOperation::ListCommentReports => {
774 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
776 UserOperation::ResolveCommentReport => {
777 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
782 async fn do_websocket_operation<'a, 'b, Data>(
783 context: LemmyContext,
787 ) -> result::Result<String, LemmyError>
789 Data: Perform + SendActivity<Response = <Data as Perform>::Response>,
790 for<'de> Data: Deserialize<'de>,
792 let parsed_data: Data = serde_json::from_str(data)?;
793 let res = parsed_data
794 .perform(&web::Data::new(context.clone()), Some(id))
796 SendActivity::send_activity(&parsed_data, &res, &context).await?;
797 serialize_websocket_message(&op, &res)