mmap/src/app.rs
tsuki fb3706ff38
Some checks are pending
Docker Build and Push / build (push) Waiting to run
rate limit
2025-08-13 22:00:56 +08:00

132 lines
4.3 KiB
Rust

use axum_gcra::{gcra::Quota, real_ip::RealIp, RateLimitLayer};
use axum_reverse_proxy::ReverseProxy;
use std::{num::NonZero, sync::Arc, time::Duration};
use tokio::sync::broadcast;
use async_graphql::{
http::{playground_source, GraphQLPlaygroundConfig},
Schema,
};
use async_graphql_axum::{GraphQLRequest, GraphQLResponse, GraphQLSubscription};
use axum::{
extract::{FromRef, State},
http::Method,
response::{Html, IntoResponse},
routing::get,
Router,
};
use jsonwebtoken::DecodingKey;
use sqlx::PgPool;
use tower_http::cors::CorsLayer;
use crate::{
auth::{AuthUserState, Claims as MyClaims},
config::Config,
graphql::{subscription::StatusUpdate, MutationRoot, QueryRoot, SubscriptionRoot},
services::{
blog_service::BlogService, casbin_service::CasbinService,
invite_code_service::InviteCodeService, mosaic_service::MosaicService,
page_block_service::PageBlockService, settings_service::SettingsService,
system_config_service::SystemConfigService, user_service::UserService,
},
};
use axum_jwt_auth::{JwtDecoderState, LocalDecoder};
use jsonwebtoken::Validation;
pub type AppSchema = Schema<QueryRoot, MutationRoot, SubscriptionRoot>;
#[derive(Clone, FromRef)]
pub struct AppState {
pub schema: AppSchema,
pub decoder: JwtDecoderState<MyClaims>,
pub status_sender: Option<broadcast::Sender<StatusUpdate>>,
}
pub async fn create_router(
pool: PgPool,
config: Config,
status_sender: Option<broadcast::Sender<StatusUpdate>>,
) -> Router {
let user_service = UserService::new(pool.clone(), config.jwt_secret.clone());
let invite_code_service = InviteCodeService::new(pool.clone());
let system_config_service = SystemConfigService::new(pool.clone());
let mosaic_service = MosaicService::new(pool.clone());
let settings_service = SettingsService::new(pool.clone());
let page_block_service = PageBlockService::new(pool.clone());
let blog_service = BlogService::new(pool.clone());
let casbin_service = CasbinService::new(config.database_url.clone())
.await
.expect("Failed to initialize CasbinService");
let schema = Schema::build(QueryRoot, MutationRoot, SubscriptionRoot)
.data(pool)
.data(user_service)
.data(invite_code_service)
.data(system_config_service)
.data(mosaic_service)
.data(settings_service)
.data(page_block_service)
.data(blog_service)
.data(casbin_service)
.data(config.clone())
.data(status_sender.clone())
.finish();
let keys = vec![DecodingKey::from_secret(config.jwt_secret.as_bytes())];
let validation = Validation::default();
let decoder = LocalDecoder::builder()
.keys(keys)
.validation(validation)
.build()
.unwrap();
let app_state = AppState {
schema: schema.clone(),
decoder: JwtDecoderState {
decoder: Arc::new(decoder),
},
status_sender,
};
let router = ReverseProxy::new("/api", &config.tile_server_url.as_str());
Router::new()
.route("/", get(graphql_playground))
.route("/graphql", get(graphql_playground).post(graphql_handler))
.route_layer(
RateLimitLayer::<RealIp>::builder()
.with_route(
(Method::GET, "/graphql"),
Quota::new(Duration::from_millis(100), NonZero::new(10).unwrap()),
)
.with_route(
(Method::POST, "/graphql"),
Quota::new(Duration::from_millis(100), NonZero::new(10).unwrap()),
)
.with_gc_interval(1000)
.default_handle_error(),
)
.route_service("/ws", GraphQLSubscription::new(schema))
.layer(CorsLayer::permissive())
.merge(router)
.with_state(app_state)
}
#[axum::debug_handler]
async fn graphql_handler(
AuthUserState(user): AuthUserState,
State(state): State<AppState>,
req: GraphQLRequest,
) -> GraphQLResponse {
let mut request = req.into_inner();
if let Some(user) = user {
request = request.data(user);
}
state.schema.execute(request).await.into()
}
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/graphql")))
}