use crate::FsConfig; 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 { if let Some(repo_id) = global.repo.get_repo(&distro, &repo).await? { match global.config.fs { FsConfig::Local { data_dir } => { let repo_dir = data_dir.join("repos").join(repo_id.to_string()); 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 }; let path = repo_dir.join(file_name); Ok(ServeFile::new(path).oneshot(req).await) } } } else { Err(StatusCode::NOT_FOUND.into()) } } async fn post_package_archive( State(global): State, Path((distro, repo)): Path<(String, String)>, body: Body, ) -> crate::Result { let repo_id = global.repo.get_or_create_repo(&distro, &repo).await?; let [tmp_path] = global.repo.random_file_paths(); let mut tmp_file = tokio::fs::File::create(&tmp_path).await?; let mut body = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other)); tokio::io::copy(&mut body, &mut tmp_file).await?; global.repo.queue_pkg(repo_id, tmp_path).await; Ok(StatusCode::ACCEPTED) } async fn delete_repo( State(global): State, Path((distro, repo)): Path<(String, String)>, ) -> crate::Result { if let Some(repo) = global.repo.get_repo(&distro, &repo).await? { global.repo.remove_repo(repo).await?; tracing::info!("Removed repository {repo}"); Ok(StatusCode::OK) } 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(repo) = global.repo.get_repo(&distro, &repo).await? { global.repo.remove_repo_arch(repo, &arch).await?; tracing::info!("Removed architecture '{arch}' from repository {repo}"); Ok(StatusCode::OK) } 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 { Ok(StatusCode::NOT_FOUND) //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) //} }