use actix_web::{guard, web, Error, HttpResponse, Result}; use lemmy_api::Perform; use lemmy_api_common::{ comment::{ CreateComment, CreateCommentLike, CreateCommentReport, DeleteComment, EditComment, GetComment, GetComments, ListCommentReports, RemoveComment, ResolveCommentReport, SaveComment, }, community::{ AddModToCommunity, BanFromCommunity, BlockCommunity, CreateCommunity, DeleteCommunity, EditCommunity, FollowCommunity, GetCommunity, HideCommunity, ListCommunities, RemoveCommunity, TransferCommunity, }, context::LemmyContext, person::{ AddAdmin, BanPerson, BlockPerson, ChangePassword, DeleteAccount, GetBannedPersons, GetCaptcha, GetPersonDetails, GetPersonMentions, GetReplies, GetReportCount, GetUnreadCount, Login, MarkAllAsRead, MarkCommentReplyAsRead, MarkPersonMentionAsRead, PasswordChangeAfterReset, PasswordReset, Register, SaveUserSettings, VerifyEmail, }, post::{ CreatePost, CreatePostLike, CreatePostReport, DeletePost, EditPost, GetPost, GetPosts, GetSiteMetadata, ListPostReports, LockPost, MarkPostAsRead, RemovePost, ResolvePostReport, SavePost, StickyPost, }, private_message::{ CreatePrivateMessage, CreatePrivateMessageReport, DeletePrivateMessage, EditPrivateMessage, GetPrivateMessages, ListPrivateMessageReports, MarkPrivateMessageAsRead, ResolvePrivateMessageReport, }, site::{ ApproveRegistrationApplication, CreateSite, EditSite, GetModlog, GetSite, GetUnreadRegistrationApplicationCount, LeaveAdmin, ListRegistrationApplications, PurgeComment, PurgeCommunity, PurgePerson, PurgePost, ResolveObject, Search, }, websocket::{ routes::chat_route, serialize_websocket_message, structs::{CommunityJoin, ModJoin, PostJoin, UserJoin}, UserOperation, UserOperationApub, UserOperationCrud, }, }; use lemmy_api_crud::PerformCrud; use lemmy_apub::{api::PerformApub, SendActivity}; use lemmy_utils::{error::LemmyError, rate_limit::RateLimitCell, ConnectionId}; use serde::Deserialize; use std::result; pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { cfg.service( web::scope("/api/v3") // Websocket .service(web::resource("/ws").to(chat_route)) // Site .service( web::scope("/site") .wrap(rate_limit.message()) .route("", web::get().to(route_get_crud::)) // Admin Actions .route("", web::post().to(route_post_crud::)) .route("", web::put().to(route_post_crud::)), ) .service( web::resource("/modlog") .wrap(rate_limit.message()) .route(web::get().to(route_get::)), ) .service( web::resource("/search") .wrap(rate_limit.search()) .route(web::get().to(route_get_apub::)), ) .service( web::resource("/resolve_object") .wrap(rate_limit.message()) .route(web::get().to(route_get_apub::)), ) // Community .service( web::resource("/community") .guard(guard::Post()) .wrap(rate_limit.register()) .route(web::post().to(route_post_crud::)), ) .service( web::scope("/community") .wrap(rate_limit.message()) .route("", web::get().to(route_get_apub::)) .route("", web::put().to(route_post_crud::)) .route("/hide", web::put().to(route_post::)) .route("/list", web::get().to(route_get_crud::)) .route("/follow", web::post().to(route_post::)) .route("/block", web::post().to(route_post::)) .route( "/delete", web::post().to(route_post_crud::), ) // Mod Actions .route( "/remove", web::post().to(route_post_crud::), ) .route("/transfer", web::post().to(route_post::)) .route("/ban_user", web::post().to(route_post::)) .route("/mod", web::post().to(route_post::)) .route("/join", web::post().to(route_post::)) .route("/mod/join", web::post().to(route_post::)), ) // Post .service( // Handle POST to /post separately to add the post() rate limitter web::resource("/post") .guard(guard::Post()) .wrap(rate_limit.post()) .route(web::post().to(route_post_crud::)), ) .service( web::scope("/post") .wrap(rate_limit.message()) .route("", web::get().to(route_get_crud::)) .route("", web::put().to(route_post_crud::)) .route("/delete", web::post().to(route_post_crud::)) .route("/remove", web::post().to(route_post_crud::)) .route( "/mark_as_read", web::post().to(route_post::), ) .route("/lock", web::post().to(route_post::)) .route("/sticky", web::post().to(route_post::)) .route("/list", web::get().to(route_get_apub::)) .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)) .route("/join", web::post().to(route_post::)) .route("/report", web::post().to(route_post::)) .route( "/report/resolve", web::put().to(route_post::), ) .route("/report/list", web::get().to(route_get::)) .route( "/site_metadata", web::get().to(route_get::), ), ) // Comment .service( // Handle POST to /comment separately to add the comment() rate limitter web::resource("/comment") .guard(guard::Post()) .wrap(rate_limit.comment()) .route(web::post().to(route_post_crud::)), ) .service( web::scope("/comment") .wrap(rate_limit.message()) .route("", web::get().to(route_get_crud::)) .route("", web::put().to(route_post_crud::)) .route("/delete", web::post().to(route_post_crud::)) .route("/remove", web::post().to(route_post_crud::)) .route( "/mark_as_read", web::post().to(route_post::), ) .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)) .route("/list", web::get().to(route_get_apub::)) .route("/report", web::post().to(route_post::)) .route( "/report/resolve", web::put().to(route_post::), ) .route( "/report/list", web::get().to(route_get::), ), ) // Private Message .service( web::scope("/private_message") .wrap(rate_limit.message()) .route("/list", web::get().to(route_get_crud::)) .route("", web::post().to(route_post_crud::)) .route("", web::put().to(route_post_crud::)) .route( "/delete", web::post().to(route_post_crud::), ) .route( "/mark_as_read", web::post().to(route_post::), ) .route( "/report", web::post().to(route_post::), ) .route( "/report/resolve", web::put().to(route_post::), ) .route( "/report/list", web::get().to(route_get::), ), ) // User .service( // Account action, I don't like that it's in /user maybe /accounts // Handle /user/register separately to add the register() rate limitter web::resource("/user/register") .guard(guard::Post()) .wrap(rate_limit.register()) .route(web::post().to(route_post_crud::)), ) .service( // Handle captcha separately web::resource("/user/get_captcha") .wrap(rate_limit.post()) .route(web::get().to(route_get::)), ) // User actions .service( web::scope("/user") .wrap(rate_limit.message()) .route("", web::get().to(route_get_apub::)) .route("/mention", web::get().to(route_get::)) .route( "/mention/mark_as_read", web::post().to(route_post::), ) .route("/replies", web::get().to(route_get::)) .route("/join", web::post().to(route_post::)) // Admin action. I don't like that it's in /user .route("/ban", web::post().to(route_post::)) .route("/banned", web::get().to(route_get::)) .route("/block", web::post().to(route_post::)) // Account actions. I don't like that they're in /user maybe /accounts .route("/login", web::post().to(route_post::)) .route( "/delete_account", web::post().to(route_post_crud::), ) .route( "/password_reset", web::post().to(route_post::), ) .route( "/password_change", web::post().to(route_post::), ) // mark_all_as_read feels off being in this section as well .route( "/mark_all_as_read", web::post().to(route_post::), ) .route( "/save_user_settings", web::put().to(route_post::), ) .route( "/change_password", web::put().to(route_post::), ) .route("/report_count", web::get().to(route_get::)) .route("/unread_count", web::get().to(route_get::)) .route("/verify_email", web::post().to(route_post::)) .route("/leave_admin", web::post().to(route_post::)), ) // Admin Actions .service( web::scope("/admin") .wrap(rate_limit.message()) .route("/add", web::post().to(route_post::)) .route( "/registration_application/count", web::get().to(route_get::), ) .route( "/registration_application/list", web::get().to(route_get::), ) .route( "/registration_application/approve", web::put().to(route_post::), ), ) .service( web::scope("/admin/purge") .wrap(rate_limit.message()) .route("/person", web::post().to(route_post::)) .route("/community", web::post().to(route_post::)) .route("/post", web::post().to(route_post::)) .route("/comment", web::post().to(route_post::)), ), ); } async fn perform<'a, Data>( data: Data, context: web::Data, ) -> Result where Data: Perform + SendActivity::Response> + Clone + Deserialize<'a> + Send + 'static, { let res = data.perform(&context, None).await?; SendActivity::send_activity(&data, &res, &context).await?; Ok(HttpResponse::Ok().json(res)) } async fn route_get<'a, Data>( data: web::Query, context: web::Data, ) -> Result where Data: Perform + SendActivity::Response> + Clone + Deserialize<'a> + Send + 'static, { perform::(data.0, context).await } async fn route_get_apub<'a, Data>( data: web::Query, context: web::Data, ) -> Result where Data: PerformApub + SendActivity::Response> + Clone + Deserialize<'a> + Send + 'static, { let res = data.perform(&context, None).await?; SendActivity::send_activity(&data.0, &res, &context).await?; Ok(HttpResponse::Ok().json(res)) } async fn route_post<'a, Data>( data: web::Json, context: web::Data, ) -> Result where Data: Perform + SendActivity::Response> + Clone + Deserialize<'a> + Send + 'static, { perform::(data.0, context).await } async fn perform_crud<'a, Data>( data: Data, context: web::Data, ) -> Result where Data: PerformCrud + SendActivity::Response> + Clone + Deserialize<'a> + Send + 'static, { let res = data.perform(&context, None).await?; SendActivity::send_activity(&data, &res, &context).await?; Ok(HttpResponse::Ok().json(res)) } async fn route_get_crud<'a, Data>( data: web::Query, context: web::Data, ) -> Result where Data: PerformCrud + SendActivity::Response> + Clone + Deserialize<'a> + Send + 'static, { perform_crud::(data.0, context).await } async fn route_post_crud<'a, Data>( data: web::Json, context: web::Data, ) -> Result where Data: PerformCrud + SendActivity::Response> + Clone + Deserialize<'a> + Send + 'static, { perform_crud::(data.0, context).await } pub async fn match_websocket_operation_crud( context: LemmyContext, id: ConnectionId, op: UserOperationCrud, data: &str, ) -> result::Result { match op { // User ops UserOperationCrud::Register => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::DeleteAccount => { do_websocket_operation_crud::(context, id, op, data).await } // Private Message ops UserOperationCrud::CreatePrivateMessage => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::EditPrivateMessage => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::DeletePrivateMessage => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::GetPrivateMessages => { do_websocket_operation_crud::(context, id, op, data).await } // Site ops UserOperationCrud::CreateSite => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::EditSite => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::GetSite => { do_websocket_operation_crud::(context, id, op, data).await } // Community ops UserOperationCrud::ListCommunities => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::CreateCommunity => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::EditCommunity => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::DeleteCommunity => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::RemoveCommunity => { do_websocket_operation_crud::(context, id, op, data).await } // Post ops UserOperationCrud::CreatePost => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::GetPost => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::EditPost => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::DeletePost => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::RemovePost => { do_websocket_operation_crud::(context, id, op, data).await } // Comment ops UserOperationCrud::CreateComment => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::EditComment => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::DeleteComment => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::RemoveComment => { do_websocket_operation_crud::(context, id, op, data).await } UserOperationCrud::GetComment => { do_websocket_operation_crud::(context, id, op, data).await } } } async fn do_websocket_operation_crud<'a, 'b, Data>( context: LemmyContext, id: ConnectionId, op: UserOperationCrud, data: &str, ) -> result::Result where Data: PerformCrud + SendActivity::Response>, for<'de> Data: Deserialize<'de>, { let parsed_data: Data = serde_json::from_str(data)?; let res = parsed_data .perform(&web::Data::new(context.clone()), Some(id)) .await?; SendActivity::send_activity(&parsed_data, &res, &context).await?; serialize_websocket_message(&op, &res) } pub async fn match_websocket_operation_apub( context: LemmyContext, id: ConnectionId, op: UserOperationApub, data: &str, ) -> result::Result { match op { UserOperationApub::GetPersonDetails => { do_websocket_operation_apub::(context, id, op, data).await } UserOperationApub::GetCommunity => { do_websocket_operation_apub::(context, id, op, data).await } UserOperationApub::GetComments => { do_websocket_operation_apub::(context, id, op, data).await } UserOperationApub::GetPosts => { do_websocket_operation_apub::(context, id, op, data).await } UserOperationApub::ResolveObject => { do_websocket_operation_apub::(context, id, op, data).await } UserOperationApub::Search => do_websocket_operation_apub::(context, id, op, data).await, } } async fn do_websocket_operation_apub<'a, 'b, Data>( context: LemmyContext, id: ConnectionId, op: UserOperationApub, data: &str, ) -> result::Result where Data: PerformApub + SendActivity::Response>, for<'de> Data: Deserialize<'de>, { let parsed_data: Data = serde_json::from_str(data)?; let res = parsed_data .perform(&web::Data::new(context.clone()), Some(id)) .await?; SendActivity::send_activity(&parsed_data, &res, &context).await?; serialize_websocket_message(&op, &res) } pub async fn match_websocket_operation( context: LemmyContext, id: ConnectionId, op: UserOperation, data: &str, ) -> result::Result { match op { // User ops UserOperation::Login => do_websocket_operation::(context, id, op, data).await, UserOperation::GetCaptcha => do_websocket_operation::(context, id, op, data).await, UserOperation::GetReplies => do_websocket_operation::(context, id, op, data).await, UserOperation::AddAdmin => do_websocket_operation::(context, id, op, data).await, UserOperation::GetUnreadRegistrationApplicationCount => { do_websocket_operation::(context, id, op, data).await } UserOperation::ListRegistrationApplications => { do_websocket_operation::(context, id, op, data).await } UserOperation::ApproveRegistrationApplication => { do_websocket_operation::(context, id, op, data).await } UserOperation::BanPerson => do_websocket_operation::(context, id, op, data).await, UserOperation::GetBannedPersons => { do_websocket_operation::(context, id, op, data).await } UserOperation::BlockPerson => { do_websocket_operation::(context, id, op, data).await } UserOperation::GetPersonMentions => { do_websocket_operation::(context, id, op, data).await } UserOperation::MarkPersonMentionAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::MarkCommentReplyAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::MarkAllAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::PasswordReset => { do_websocket_operation::(context, id, op, data).await } UserOperation::PasswordChange => { do_websocket_operation::(context, id, op, data).await } UserOperation::UserJoin => do_websocket_operation::(context, id, op, data).await, UserOperation::PostJoin => do_websocket_operation::(context, id, op, data).await, UserOperation::CommunityJoin => { do_websocket_operation::(context, id, op, data).await } UserOperation::ModJoin => do_websocket_operation::(context, id, op, data).await, UserOperation::SaveUserSettings => { do_websocket_operation::(context, id, op, data).await } UserOperation::ChangePassword => { do_websocket_operation::(context, id, op, data).await } UserOperation::GetReportCount => { do_websocket_operation::(context, id, op, data).await } UserOperation::GetUnreadCount => { do_websocket_operation::(context, id, op, data).await } UserOperation::VerifyEmail => { do_websocket_operation::(context, id, op, data).await } // Private Message ops UserOperation::MarkPrivateMessageAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::CreatePrivateMessageReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::ResolvePrivateMessageReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::ListPrivateMessageReports => { do_websocket_operation::(context, id, op, data).await } // Site ops UserOperation::GetModlog => do_websocket_operation::(context, id, op, data).await, UserOperation::PurgePerson => { do_websocket_operation::(context, id, op, data).await } UserOperation::PurgeCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::PurgePost => do_websocket_operation::(context, id, op, data).await, UserOperation::PurgeComment => { do_websocket_operation::(context, id, op, data).await } UserOperation::TransferCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::LeaveAdmin => do_websocket_operation::(context, id, op, data).await, // Community ops UserOperation::FollowCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::BlockCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::BanFromCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::AddModToCommunity => { do_websocket_operation::(context, id, op, data).await } // Post ops UserOperation::LockPost => do_websocket_operation::(context, id, op, data).await, UserOperation::StickyPost => do_websocket_operation::(context, id, op, data).await, UserOperation::CreatePostLike => { do_websocket_operation::(context, id, op, data).await } UserOperation::MarkPostAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::SavePost => do_websocket_operation::(context, id, op, data).await, UserOperation::CreatePostReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::ListPostReports => { do_websocket_operation::(context, id, op, data).await } UserOperation::ResolvePostReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::GetSiteMetadata => { do_websocket_operation::(context, id, op, data).await } // Comment ops UserOperation::SaveComment => { do_websocket_operation::(context, id, op, data).await } UserOperation::CreateCommentLike => { do_websocket_operation::(context, id, op, data).await } UserOperation::CreateCommentReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::ListCommentReports => { do_websocket_operation::(context, id, op, data).await } UserOperation::ResolveCommentReport => { do_websocket_operation::(context, id, op, data).await } } } async fn do_websocket_operation<'a, 'b, Data>( context: LemmyContext, id: ConnectionId, op: UserOperation, data: &str, ) -> result::Result where Data: Perform + SendActivity::Response>, for<'de> Data: Deserialize<'de>, { let parsed_data: Data = serde_json::from_str(data)?; let res = parsed_data .perform(&web::Data::new(context.clone()), Some(id)) .await?; SendActivity::send_activity(&parsed_data, &res, &context).await?; serialize_websocket_message(&op, &res) }