132 lines
4.3 KiB
Rust
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")))
|
|
}
|