mod auth_guard; mod ergo_admin; mod flash; mod irccloud; mod k8s; mod page; mod routes; mod state; mod stripe_util; use std::sync::Arc; use axum::{Router, routing::{get, post}}; use tower_http::services::ServeDir; use openid::DiscoveredClient; use state::AppState; use tower_sessions::{MemoryStore, SessionManagerLayer, cookie::SameSite}; use irc_now_common::auth::OidcConfig; use irc_now_common::db::DbConfig; async fn health() -> &'static str { "ok" } #[tokio::main] async fn main() { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); let db = DbConfig::from_env("ACCOUNTS").connect().await.unwrap(); let oidc = OidcConfig::from_env(); let issuer_url = url::Url::parse(&oidc.issuer_url).expect("invalid OIDC issuer URL"); tracing::info!("discovering OIDC provider at {issuer_url}"); let oidc_client = DiscoveredClient::discover( oidc.client_id.clone(), oidc.client_secret.clone(), Some(oidc.redirect_url.clone()), issuer_url, ) .await .expect("OIDC discovery failed"); tracing::info!("OIDC provider discovered"); let stripe_webhook_secret = std::env::var("STRIPE_WEBHOOK_SECRET").unwrap_or_default(); let stripe_secret_key = std::env::var("STRIPE_SECRET_KEY").unwrap_or_default(); let stripe_price_id = std::env::var("STRIPE_PRICE_ID").unwrap_or_default(); let stripe_client = stripe::Client::new(&stripe_secret_key); let kube_client = kube::Client::try_default() .await .expect("kube client init failed"); let namespace = std::env::var("NAMESPACE").unwrap_or_else(|_| "irc-josie-cloud".to_string()); let http_client = reqwest::Client::new(); let announcement = std::env::var("ANNOUNCEMENT").ok().filter(|s| !s.is_empty()); let state = AppState { db, oidc, oidc_client: Arc::new(oidc_client), stripe_webhook_secret, stripe_client, stripe_price_id, kube: kube_client, namespace, http_client, announcement, }; let session_store = MemoryStore::default(); let session_layer = SessionManagerLayer::new(session_store).with_same_site(SameSite::Lax); let app = Router::new() .route("/health", get(health)) .route("/auth/login", get(routes::auth::login)) .route("/auth/callback", get(routes::auth::callback)) .route("/auth/logout", get(routes::auth::logout)) .route("/dashboard", get(routes::dashboard::index)) .route("/bouncers", get(routes::bouncer::list)) .route("/bouncers/create", post(routes::bouncer::create)) .route("/billing", get(routes::billing::index)) .route("/billing/checkout", post(routes::billing::checkout)) .route("/billing/portal", get(routes::billing::portal)) .route("/billing/webhook", post(routes::billing::webhook)) .route("/migrate", get(routes::migrate::form)) .route("/migrate", post(routes::migrate::run_migration)) .route("/profile", get(routes::profile::index)) .route("/profile/update", post(routes::profile::update)) .nest_service("/static", ServeDir::new("static")) .layer(session_layer) .with_state(state); let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); tracing::info!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app).await.unwrap(); }