use std::env; use anyhow::Context; use async_sqlx_session::PostgresSessionStore; use axum::extract::FromRef; use axum::http::{HeaderValue, Method}; use axum::Router; use como_infrastructure::register::ServiceRegister; use oauth2::basic::BasicClient; use tower::ServiceBuilder; use tower_http::{cors::CorsLayer, trace::TraceLayer}; use crate::controllers::auth::AuthController; use crate::controllers::graphql::GraphQLController; use crate::zitadel::client::oauth_client; use crate::zitadel::{IntrospectionState, IntrospectionStateBuilder}; pub struct Api; impl Api { pub async fn new( port: u32, cors_origin: &str, service_register: ServiceRegister, ) -> anyhow::Result<()> { let client_id = env::var("CLIENT_ID").expect("Missing CLIENT_ID!"); let client_secret = env::var("CLIENT_SECRET").expect("Missing CLIENT_SECRET!"); let zitadel_url = env::var("ZITADEL_URL").expect("missing ZITADEL_URL"); let is = IntrospectionStateBuilder::new(&zitadel_url) .with_basic_auth(&client_id, &client_secret) .build() .await?; let oauth_client = oauth_client(); let app_state = AppState { oauth_client, store: service_register.session_store.clone(), introspection_state: is, }; let router = Router::new() .nest( "/auth", AuthController::new_router(service_register.clone(), app_state.clone()).await?, ) .nest( "/graphql", GraphQLController::new_router(service_register.clone(), app_state.clone()), ) .layer( ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer( CorsLayer::new() .allow_origin( cors_origin .parse::() .context("could not parse cors origin as header")?, ) .allow_headers([axum::http::header::CONTENT_TYPE]) .allow_methods([Method::GET, Method::POST, Method::OPTIONS]), ), ); let host = env::var("HOST").unwrap_or("0.0.0.0".to_string()); tracing::info!("running on: {host}:{}", port); axum::Server::bind(&format!("{host}:{}", port).parse().unwrap()) .serve(router.into_make_service()) .await .context("error while starting API")?; Ok(()) } } #[derive(Clone)] pub struct AppState { oauth_client: BasicClient, introspection_state: IntrospectionState, store: PostgresSessionStore, } impl FromRef for BasicClient { fn from_ref(state: &AppState) -> Self { state.oauth_client.clone() } } impl FromRef for PostgresSessionStore { fn from_ref(state: &AppState) -> Self { state.store.clone() } } impl FromRef for IntrospectionState { fn from_ref(input: &AppState) -> Self { input.introspection_state.clone() } }