mod archive; mod manager; pub mod package; pub use manager::DistroMgr; use axum::{ body::Body, extract::{Path, State}, http::{Request, StatusCode}, response::IntoResponse, routing::{delete, post}, Router, }; use futures::TryStreamExt; use tokio_util::io::StreamReader; use tower::util::ServiceExt; use tower_http::{services::ServeFile, validate_request::ValidateRequestHeaderLayer}; pub fn router(api_key: &str) -> Router { Router::new() .route( "/:distro/:repo", post(post_package_archive) .delete(delete_repo) .route_layer(ValidateRequestHeaderLayer::bearer(api_key)), ) .route( "/:distro/:repo/:arch", delete(delete_arch_repo).route_layer(ValidateRequestHeaderLayer::bearer(api_key)), ) // Routes added after the layer do not get that layer applied, so the GET requests will not // be authorized .route( "/:distro/:repo/:arch/:filename", delete(delete_package) .route_layer(ValidateRequestHeaderLayer::bearer(api_key)) .get(get_file), ) } /// Serve the package archive files and database archives. If files are requested for an /// architecture that does not have any explicit packages, a repository containing only "any" files /// is returned. async fn get_file( State(global): State, Path((distro, repo, arch, file_name)): Path<(String, String, String, String)>, req: Request, ) -> crate::Result { let repo_dir = global .config .data_dir .join("distros") .join(&distro) .join(&repo); let file_name = if file_name == format!("{}.db", repo) || file_name == format!("{}.db.tar.gz", repo) { format!("{}.db.tar.gz", arch) } else if file_name == format!("{}.files", repo) || file_name == format!("{}.files.tar.gz", repo) { format!("{}.files.tar.gz", arch) } else { file_name }; Ok(ServeFile::new(repo_dir.join(file_name)).oneshot(req).await) } async fn post_package_archive( State(global): State, Path((distro, repo)): Path<(String, String)>, body: Body, ) -> crate::Result<()> { let mut body = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other)); let mgr = global.mgr.get_or_create_mgr(&distro).await?; let (name, version, arch) = mgr.add_pkg_from_reader(&mut body, &repo).await?; tracing::info!( "Added '{}-{}' to repository '{}' ({})", name, version, repo, arch ); Ok(()) } async fn delete_repo( State(global): State, Path((distro, repo)): Path<(String, String)>, ) -> crate::Result { if let Some(mgr) = global.mgr.get_mgr(&distro).await { let repo_removed = mgr.remove_repo(&repo).await?; if repo_removed { tracing::info!("Removed repository '{}'", repo); Ok(StatusCode::OK) } else { Ok(StatusCode::NOT_FOUND) } } else { Ok(StatusCode::NOT_FOUND) } } async fn delete_arch_repo( State(global): State, Path((distro, repo, arch)): Path<(String, String, String)>, ) -> crate::Result { if let Some(mgr) = global.mgr.get_mgr(&distro).await { let repo_removed = mgr.remove_repo_arch(&repo, &arch).await?; if repo_removed { tracing::info!("Removed arch '{}' from repository '{}'", arch, repo); Ok(StatusCode::OK) } else { Ok(StatusCode::NOT_FOUND) } } else { Ok(StatusCode::NOT_FOUND) } } async fn delete_package( State(global): State, Path((distro, repo, arch, pkg_name)): Path<(String, String, String, String)>, ) -> crate::Result { if let Some(mgr) = global.mgr.get_mgr(&distro).await { let pkg_removed = mgr.remove_pkg(&repo, &arch, &pkg_name).await?; if pkg_removed { tracing::info!( "Removed package '{}' ({}) from repository '{}'", pkg_name, arch, repo ); Ok(StatusCode::OK) } else { Ok(StatusCode::NOT_FOUND) } } else { Ok(StatusCode::NOT_FOUND) } }