use crate::error::{Error, Result};
use crate::manifest::MirrorManifest;
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Response},
routing::get,
Router,
};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tower_http::services::ServeDir;
use tracing::{info, warn};
#[derive(Debug, Clone)]
pub struct ServeConfig {
pub mirror_path: PathBuf,
pub port: u16,
pub bind: String,
pub log_access: bool,
}
impl Default for ServeConfig {
fn default() -> Self {
Self {
mirror_path: PathBuf::from("./mirror"),
port: 8080,
bind: "0.0.0.0".to_string(),
log_access: false,
}
}
}
struct ServerState {
manifest: MirrorManifest,
mirror_path: PathBuf,
}
pub async fn serve_mirror(config: ServeConfig) -> Result<()> {
let manifest_path = config.mirror_path.join("manifest.json");
if !manifest_path.exists() {
return Err(Error::InvalidMirror);
}
let manifest = MirrorManifest::load(&manifest_path)?;
info!(
"Serving mirror with {} formulas, {} casks",
manifest.formulas.count, manifest.casks.count
);
let state = Arc::new(ServerState {
manifest,
mirror_path: config.mirror_path.clone(),
});
let app = Router::new()
.route("/api/v1/manifest", get(get_manifest))
.route("/api/v1/health", get(health_check))
.nest_service("/", ServeDir::new(&config.mirror_path))
.with_state(state);
let addr: SocketAddr = format!("{}:{}", config.bind, config.port)
.parse()
.map_err(|e| Error::Server(format!("Invalid bind address: {}", e)))?;
info!("Starting mirror server at http://{}", addr);
info!(" Mirror path: {:?}", config.mirror_path);
info!(" Press Ctrl+C to stop");
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(|e| Error::Server(format!("Failed to bind: {}", e)))?;
axum::serve(listener, app)
.await
.map_err(|e| Error::Server(format!("Server error: {}", e)))?;
Ok(())
}
async fn health_check() -> impl IntoResponse {
"OK"
}
async fn get_manifest(State(state): State<Arc<ServerState>>) -> Response {
match serde_json::to_string_pretty(&state.manifest) {
Ok(json) => (
StatusCode::OK,
[("content-type", "application/json")],
json,
)
.into_response(),
Err(e) => {
warn!("Failed to serialize manifest: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response()
}
}
}