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; #[derive(Clone, FromRef)] pub struct AppState { pub schema: AppSchema, pub decoder: JwtDecoderState, pub status_sender: Option>, } pub async fn create_router( pool: PgPool, config: Config, status_sender: Option>, ) -> 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::::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, 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"))) }