mod admin; mod admin_storage; mod auth; mod billing; mod compress; mod context; mod downloads; mod envelope; mod health; mod redemption; mod response; mod tasks; mod user; mod webhooks; use crate::error::{AppError, ErrorCode}; use crate::state::AppState; use axum::extract::DefaultBodyLimit; use axum::Router; use std::net::SocketAddr; use tower_http::services::{ServeDir, ServeFile}; use tower_http::trace::TraceLayer; pub async fn run(state: AppState) -> Result<(), AppError> { let addr = format!("{}:{}", state.config.host, state.config.port); crate::services::bootstrap::ensure_schema(&state).await?; crate::services::bootstrap::ensure_admin_user(&state).await?; let static_service = ServeDir::new("static").not_found_service(ServeFile::new("static/index.html")); let v1 = v1_router().layer(DefaultBodyLimit::max(100 * 1024 * 1024)); let app = Router::new() .route("/health", axum::routing::get(health::health)) .nest("/downloads", downloads::router()) .nest("/api/v1", v1) .fallback_service(static_service) .layer(TraceLayer::new_for_http()) .with_state(state); let listener = tokio::net::TcpListener::bind(&addr) .await .map_err(|err| AppError::new(ErrorCode::Internal, "监听端口失败").with_source(err))?; tracing::info!(addr = %addr, "API server listening"); axum::serve( listener, app.into_make_service_with_connect_info::(), ) .await .map_err(|err| AppError::new(ErrorCode::Internal, "HTTP 服务异常退出").with_source(err)) } fn v1_router() -> Router { Router::new() .nest("/auth", auth::router()) .merge(compress::router()) .merge(tasks::router()) .merge(billing::router()) .merge(webhooks::router()) .merge(user::router()) .merge(redemption::router()) .merge(admin::router()) .merge(admin_storage::router()) .fallback(response::not_found) }