//! ZCLAW SaaS 服务入口 use tracing::info; use zclaw_saas::{config::SaaSConfig, db::init_db, state::AppState}; #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "zclaw_saas=debug,tower_http=debug".into()), ) .init(); let config = SaaSConfig::load()?; info!("SaaS config loaded: {}:{}", config.server.host, config.server.port); let db = init_db(&config.database.url).await?; info!("Database initialized"); let state = AppState::new(db, config.clone())?; let app = build_router(state); let listener = tokio::net::TcpListener::bind(format!("{}:{}", config.server.host, config.server.port)) .await?; info!("SaaS server listening on {}:{}", config.server.host, config.server.port); axum::serve(listener, app).await?; Ok(()) } fn build_router(state: AppState) -> axum::Router { use axum::middleware; use tower_http::cors::{Any, CorsLayer}; use tower_http::trace::TraceLayer; use axum::http::HeaderValue; let cors = { let config = state.config.blocking_read(); if config.server.cors_origins.is_empty() { CorsLayer::new() .allow_origin(Any) .allow_methods(Any) .allow_headers(Any) } else { let origins: Vec = config.server.cors_origins.iter() .filter_map(|o: &String| o.parse::().ok()) .collect(); CorsLayer::new() .allow_origin(origins) .allow_methods(Any) .allow_headers(Any) } }; let public_routes = zclaw_saas::auth::routes(); let protected_routes = zclaw_saas::auth::protected_routes() .merge(zclaw_saas::account::routes()) .merge(zclaw_saas::model_config::routes()) .merge(zclaw_saas::relay::routes()) .merge(zclaw_saas::migration::routes()) .layer(middleware::from_fn_with_state( state.clone(), zclaw_saas::auth::auth_middleware, )); axum::Router::new() .merge(public_routes) .merge(protected_routes) .layer(TraceLayer::new_for_http()) .layer(cors) .with_state(state) }