PluralKit/crates/api/src/main.rs

186 lines
6.3 KiB
Rust
Raw Normal View History

2025-04-26 12:03:00 +00:00
#![feature(let_chains)]
2025-05-17 20:39:29 +00:00
use auth::{AuthState, INTERNAL_APPID_HEADER, INTERNAL_SYSTEMID_HEADER};
2023-02-15 19:27:36 -05:00
use axum::{
body::Body,
extract::{Request as ExtractRequest, State},
http::Uri,
response::{IntoResponse, Response},
2023-02-15 19:27:36 -05:00
routing::{delete, get, patch, post},
2025-05-17 20:39:29 +00:00
Extension, Router,
2023-02-15 19:27:36 -05:00
};
use hyper_util::{
client::legacy::{connect::HttpConnector, Client},
rt::TokioExecutor,
};
use tracing::info;
use pk_macros::api_endpoint;
2023-02-15 19:27:36 -05:00
2025-05-17 20:39:29 +00:00
mod auth;
mod endpoints;
mod error;
2023-02-15 19:27:36 -05:00
mod middleware;
mod util;
#[derive(Clone)]
pub struct ApiContext {
pub db: sqlx::postgres::PgPool,
2024-11-01 06:32:40 +09:00
pub redis: fred::clients::RedisPool,
rproxy_uri: String,
rproxy_client: Client<HttpConnector, Body>,
}
#[api_endpoint]
async fn rproxy(
2025-05-17 20:39:29 +00:00
Extension(auth): Extension<AuthState>,
State(ctx): State<ApiContext>,
mut req: ExtractRequest<Body>,
) -> Response {
let path = req.uri().path();
let path_query = req
.uri()
.path_and_query()
.map(|v| v.as_str())
.unwrap_or(path);
let uri = format!("{}{}", ctx.rproxy_uri, path_query);
*req.uri_mut() = Uri::try_from(uri).unwrap();
2025-05-17 20:39:29 +00:00
let headers = req.headers_mut();
headers.remove(INTERNAL_SYSTEMID_HEADER);
headers.remove(INTERNAL_APPID_HEADER);
if let Some(sid) = auth.system_id() {
headers.append(INTERNAL_SYSTEMID_HEADER, sid.into());
}
if let Some(aid) = auth.app_id() {
headers.append(INTERNAL_APPID_HEADER, aid.into());
}
Ok(ctx.rproxy_client.request(req).await?.into_response())
}
// this function is manually formatted for easier legibility of route_services
2023-02-15 19:27:36 -05:00
#[rustfmt::skip]
fn router(ctx: ApiContext) -> Router {
2023-02-15 19:27:36 -05:00
// processed upside down (???) so we have to put middleware at the end
Router::new()
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}", get(rproxy))
.route("/v2/systems/{system_id}", patch(rproxy))
.route("/v2/systems/{system_id}/settings", get(endpoints::system::get_system_settings))
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}/settings", patch(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}/members", get(rproxy))
.route("/v2/members", post(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/members/{member_id}", get(rproxy))
.route("/v2/members/{member_id}", patch(rproxy))
.route("/v2/members/{member_id}", delete(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}/groups", get(rproxy))
.route("/v2/groups", post(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/groups/{group_id}", get(rproxy))
.route("/v2/groups/{group_id}", patch(rproxy))
.route("/v2/groups/{group_id}", delete(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/groups/{group_id}/members", get(rproxy))
.route("/v2/groups/{group_id}/members/add", post(rproxy))
.route("/v2/groups/{group_id}/members/remove", post(rproxy))
.route("/v2/groups/{group_id}/members/overwrite", post(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/members/{member_id}/groups", get(rproxy))
.route("/v2/members/{member_id}/groups/add", post(rproxy))
.route("/v2/members/{member_id}/groups/remove", post(rproxy))
.route("/v2/members/{member_id}/groups/overwrite", post(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}/switches", get(rproxy))
.route("/v2/systems/{system_id}/switches", post(rproxy))
.route("/v2/systems/{system_id}/fronters", get(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}/switches/{switch_id}", get(rproxy))
.route("/v2/systems/{system_id}/switches/{switch_id}", patch(rproxy))
.route("/v2/systems/{system_id}/switches/{switch_id}/members", patch(rproxy))
.route("/v2/systems/{system_id}/switches/{switch_id}", delete(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}/guilds/{guild_id}", get(rproxy))
.route("/v2/systems/{system_id}/guilds/{guild_id}", patch(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/members/{member_id}/guilds/{guild_id}", get(rproxy))
.route("/v2/members/{member_id}/guilds/{guild_id}", patch(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}/autoproxy", get(rproxy))
.route("/v2/systems/{system_id}/autoproxy", patch(rproxy))
2025-05-17 18:57:48 +00:00
.route("/v2/messages/{message_id}", get(rproxy))
.route("/private/bulk_privacy/member", post(rproxy))
.route("/private/bulk_privacy/group", post(rproxy))
.route("/private/discord/callback", post(rproxy))
.route("/private/discord/callback2", post(endpoints::private::discord_callback))
2024-12-23 22:31:20 +00:00
.route("/private/discord/shard_state", get(endpoints::private::discord_state))
.route("/private/stats", get(endpoints::private::meta))
2025-05-17 18:57:48 +00:00
.route("/v2/systems/{system_id}/oembed.json", get(rproxy))
.route("/v2/members/{member_id}/oembed.json", get(rproxy))
.route("/v2/groups/{group_id}/oembed.json", get(rproxy))
2023-02-15 19:27:36 -05:00
2023-03-18 23:06:55 -04:00
.layer(middleware::ratelimit::ratelimiter(middleware::ratelimit::do_request_ratelimited)) // this sucks
2023-02-15 19:27:36 -05:00
.layer(axum::middleware::from_fn(middleware::ignore_invalid_routes::ignore_invalid_routes))
.layer(axum::middleware::from_fn(middleware::logger::logger))
.layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::params::params))
2025-05-17 20:39:29 +00:00
.layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::auth::auth))
2025-06-08 22:38:19 +00:00
.layer(axum::middleware::from_fn(middleware::cors::cors))
.layer(tower_http::catch_panic::CatchPanicLayer::custom(util::handle_panic))
.with_state(ctx)
.route("/", get(|| async { axum::response::Redirect::to("https://pluralkit.me/api") }))
}
#[libpk::main]
async fn main() -> anyhow::Result<()> {
let db = libpk::db::init_data_db().await?;
let redis = libpk::db::init_redis().await?;
let rproxy_uri = Uri::from_static(
&libpk::config
.api
.as_ref()
.expect("missing api config")
.remote_url,
)
.to_string();
let rproxy_client = hyper_util::client::legacy::Client::<(), ()>::builder(TokioExecutor::new())
.build(HttpConnector::new());
let ctx = ApiContext {
db,
redis,
rproxy_uri: rproxy_uri[..rproxy_uri.len() - 1].to_string(),
rproxy_client,
};
let app = router(ctx);
let addr: &str = libpk::config
.api
.as_ref()
.expect("missing api config")
.addr
.as_ref();
2023-02-15 19:27:36 -05:00
let listener = tokio::net::TcpListener::bind(addr).await?;
2024-09-14 12:19:47 +09:00
info!("listening on {}", addr);
axum::serve(listener, app).await?;
2023-02-15 19:27:36 -05:00
Ok(())
}