diff --git a/Cargo.lock b/Cargo.lock index a02d9ed..804a3d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1636,7 +1636,6 @@ dependencies = [ "chrono", "clap", "futures", - "hyper", "libarchive", "sea-orm", "sea-orm-migration", diff --git a/server/Cargo.toml b/server/Cargo.toml index 0bdfee3..bf4fd7f 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -8,7 +8,6 @@ authors = ["Jef Roosens"] [dependencies] axum = { version = "0.6.18", features = ["http2"] } -hyper = "*" chrono = { version = "0.4.26", features = ["serde"] } clap = { version = "4.3.12", features = ["env", "derive"] } futures = "0.3.28" diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs new file mode 100644 index 0000000..800587f --- /dev/null +++ b/server/src/api/mod.rs @@ -0,0 +1,77 @@ +mod pagination; + +use axum::extract::{Path, Query, State}; +use axum::routing::get; +use axum::Json; +use axum::Router; + +use pagination::PaginatedResponse; + +use crate::db; + +pub fn router() -> Router { + Router::new() + .route("/repos", get(get_repos)) + .route("/repos/:id", get(get_single_repo)) + .route("/packages", get(get_packages)) + .route("/packages/:id", get(get_single_package)) +} + +async fn get_repos( + State(global): State, + Query(pagination): Query, +) -> crate::Result>> { + let (total_pages, repos) = global + .db + .repo + .page( + pagination.per_page.unwrap_or(25), + pagination.page.unwrap_or(1) - 1, + ) + .await?; + Ok(Json(pagination.res(total_pages, repos))) +} + +async fn get_single_repo( + State(global): State, + Path(id): Path, +) -> crate::Result> { + let repo = global + .db + .repo + .by_id(id) + .await? + .ok_or(axum::http::StatusCode::NOT_FOUND)?; + + Ok(Json(repo)) +} + +async fn get_packages( + State(global): State, + Query(pagination): Query, +) -> crate::Result>> { + let (total_pages, pkgs) = global + .db + .pkg + .page( + pagination.per_page.unwrap_or(25), + pagination.page.unwrap_or(1) - 1, + ) + .await?; + + Ok(Json(pagination.res(total_pages, pkgs))) +} + +async fn get_single_package( + State(global): State, + Path(id): Path, +) -> crate::Result> { + let entry = global + .db + .pkg + .full(id) + .await? + .ok_or(axum::http::StatusCode::NOT_FOUND)?; + + Ok(Json(entry)) +} diff --git a/server/src/web/api/pagination.rs b/server/src/api/pagination.rs similarity index 100% rename from server/src/web/api/pagination.rs rename to server/src/api/pagination.rs diff --git a/server/src/cli.rs b/server/src/cli.rs index ec93f8b..540f457 100644 --- a/server/src/cli.rs +++ b/server/src/cli.rs @@ -2,10 +2,12 @@ use crate::repo::RepoGroupManager; use crate::{Config, Global}; use axum::extract::FromRef; +use axum::Router; use clap::Parser; use std::io; use std::path::PathBuf; use std::sync::{Arc, RwLock}; +use tower_http::trace::TraceLayer; use tracing::debug; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -93,9 +95,18 @@ impl Cli { }; // build our application with a single route - let app = crate::web::app(global, &self.api_key); - Ok(crate::web::serve(app, self.port) - .await - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?) + let app = Router::new() + .nest("/api", crate::api::router()) + .merge(crate::repo::router(&self.api_key)) + .with_state(global) + .layer(TraceLayer::new_for_http()); + + // run it with hyper on localhost:3000 + Ok( + axum::Server::bind(&format!("0.0.0.0:{}", self.port).parse().unwrap()) + .serve(app.into_make_service()) + .await + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?, + ) } } diff --git a/server/src/db/entities/distro.rs b/server/src/db/entities/distro.rs deleted file mode 100644 index 1d96872..0000000 --- a/server/src/db/entities/distro.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 - -use sea_orm::entity::prelude::*; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] -#[sea_orm(table_name = "distro")] -pub struct Model { - #[sea_orm(primary_key)] - pub id: i32, - #[sea_orm(unique)] - pub slug: String, - #[sea_orm(unique)] - pub name: String, - pub description: Option, - pub url: Option, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::repo::Entity")] - Repo, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Repo.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/server/src/db/entities/mod.rs b/server/src/db/entities/mod.rs index 96603bf..1111e7a 100644 --- a/server/src/db/entities/mod.rs +++ b/server/src/db/entities/mod.rs @@ -2,7 +2,6 @@ pub mod prelude; -pub mod distro; pub mod package; pub mod package_conflicts; pub mod package_depends; diff --git a/server/src/db/entities/prelude.rs b/server/src/db/entities/prelude.rs index 1314174..bee503c 100644 --- a/server/src/db/entities/prelude.rs +++ b/server/src/db/entities/prelude.rs @@ -1,6 +1,5 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 -pub use super::distro::Entity as Distro; pub use super::package::Entity as Package; pub use super::package_conflicts::Entity as PackageConflicts; pub use super::package_depends::Entity as PackageDepends; diff --git a/server/src/db/entities/repo.rs b/server/src/db/entities/repo.rs index d68e226..b7a1af1 100644 --- a/server/src/db/entities/repo.rs +++ b/server/src/db/entities/repo.rs @@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize}; pub struct Model { #[sea_orm(primary_key)] pub id: i32, - pub distro_id: i32, #[sea_orm(unique)] pub name: String, pub description: Option, @@ -16,24 +15,10 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { - #[sea_orm( - belongs_to = "super::distro::Entity", - from = "Column::DistroId", - to = "super::distro::Column::Id", - on_update = "NoAction", - on_delete = "Cascade" - )] - Distro, #[sea_orm(has_many = "super::package::Entity")] Package, } -impl Related for Entity { - fn to() -> RelationDef { - Relation::Distro.def() - } -} - impl Related for Entity { fn to() -> RelationDef { Relation::Package.def() diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index 32c0452..6ff5a8f 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -42,7 +42,6 @@ pub struct RieterDb { conn: DatabaseConnection, pub pkg: query::PackageQuery, pub repo: query::RepoQuery, - pub distro: query::DistroQuery, } impl RieterDb { @@ -55,7 +54,6 @@ impl RieterDb { conn: db.clone(), pkg: query::PackageQuery::new(db.clone()), repo: query::RepoQuery::new(db.clone()), - distro: query::DistroQuery::new(db.clone()), }) } } diff --git a/server/src/db/query/distro.rs b/server/src/db/query/distro.rs deleted file mode 100644 index 2d4d1c6..0000000 --- a/server/src/db/query/distro.rs +++ /dev/null @@ -1,46 +0,0 @@ -use sea_orm::*; - -use crate::db::*; - -#[derive(Clone, Debug)] -pub struct DistroQuery { - conn: DatabaseConnection, -} - -impl DistroQuery { - pub fn new(conn: DatabaseConnection) -> Self { - Self { conn } - } - - pub async fn page(&self, per_page: u64, page: u64) -> Result<(u64, Vec)> { - let paginator = Distro::find() - .order_by_asc(distro::Column::Id) - .paginate(&self.conn, per_page); - let results = paginator.fetch_page(page).await?; - let total_pages = paginator.num_pages().await?; - - Ok((total_pages, results)) - } - - pub async fn by_id(&self, id: i32) -> Result> { - distro::Entity::find_by_id(id).one(&self.conn).await - } - - pub async fn insert( - &self, - slug: &str, - name: &str, - description: Option<&str>, - url: Option<&str>, - ) -> Result> { - let model = distro::ActiveModel { - id: NotSet, - slug: Set(String::from(slug)), - name: Set(String::from(name)), - description: Set(description.map(String::from)), - url: Set(url.map(String::from)), - }; - - Distro::insert(model).exec(&self.conn).await - } -} diff --git a/server/src/db/query/mod.rs b/server/src/db/query/mod.rs index 4ffa4cb..a52cccf 100644 --- a/server/src/db/query/mod.rs +++ b/server/src/db/query/mod.rs @@ -1,8 +1,6 @@ -mod distro; mod package; mod repo; -pub use distro::DistroQuery; pub use package::PackageQuery; pub use repo::RepoQuery; diff --git a/server/src/db/query/repo.rs b/server/src/db/query/repo.rs index 8b30db8..5e54fdc 100644 --- a/server/src/db/query/repo.rs +++ b/server/src/db/query/repo.rs @@ -40,8 +40,6 @@ impl RepoQuery { ) -> Result> { let model = repo::ActiveModel { id: NotSet, - // TODO CHANGE THIS - distro_id: NotSet, name: Set(String::from(name)), description: Set(description.map(String::from)), }; diff --git a/server/src/main.rs b/server/src/main.rs index 1d90a24..fc5c110 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,8 +1,8 @@ +mod api; mod cli; pub mod db; mod error; mod repo; -mod web; use clap::Parser; pub use error::{Result, ServerError}; diff --git a/server/src/repo/mod.rs b/server/src/repo/mod.rs index 44f0e10..21acf81 100644 --- a/server/src/repo/mod.rs +++ b/server/src/repo/mod.rs @@ -1,4 +1,255 @@ -pub mod manager; +mod manager; pub mod package; pub use manager::RepoGroupManager; + +use std::path::PathBuf; + +use axum::body::Body; +use axum::extract::{BodyStream, Path, State}; +use axum::http::Request; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::routing::{delete, post}; +use axum::Router; +use futures::StreamExt; +use sea_orm::ModelTrait; +use std::sync::Arc; +use tokio::{fs, io::AsyncWriteExt}; +use tower::util::ServiceExt; +use tower_http::services::{ServeDir, ServeFile}; +use tower_http::validate_request::ValidateRequestHeaderLayer; +use uuid::Uuid; + +const DB_FILE_EXTS: [&str; 4] = [".db", ".files", ".db.tar.gz", ".files.tar.gz"]; + +pub fn router(api_key: &str) -> Router { + Router::new() + .route( + "/:repo", + post(post_package_archive) + .delete(delete_repo) + .route_layer(ValidateRequestHeaderLayer::bearer(api_key)), + ) + .route( + "/: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( + "/: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((repo, arch, mut file_name)): Path<(String, String, String)>, + req: Request, +) -> crate::Result { + let repo_dir = global.config.repo_dir.join(&repo).join(&arch); + let repo_exists = tokio::fs::try_exists(&repo_dir).await?; + + let res = if DB_FILE_EXTS.iter().any(|ext| file_name.ends_with(ext)) { + // Append tar extension to ensure we find the file + if !file_name.ends_with(".tar.gz") { + file_name.push_str(".tar.gz"); + }; + + if repo_exists { + ServeFile::new(repo_dir.join(file_name)).oneshot(req).await + } else { + let path = global + .config + .repo_dir + .join(repo) + .join(manager::ANY_ARCH) + .join(file_name); + + ServeFile::new(path).oneshot(req).await + } + } else { + let any_file = global + .config + .pkg_dir + .join(repo) + .join(manager::ANY_ARCH) + .join(file_name); + + if repo_exists { + ServeDir::new(global.config.pkg_dir) + .fallback(ServeFile::new(any_file)) + .oneshot(req) + .await + } else { + ServeFile::new(any_file).oneshot(req).await + } + }; + + Ok(res) +} + +async fn post_package_archive( + State(global): State, + Path(repo): Path, + mut body: BodyStream, +) -> crate::Result<()> { + // We first stream the uploaded file to disk + let uuid: uuid::fmt::Simple = Uuid::new_v4().into(); + let path = global.config.pkg_dir.join(uuid.to_string()); + let mut f = fs::File::create(&path).await?; + + while let Some(chunk) = body.next().await { + f.write_all(&chunk?).await?; + } + + let clone = Arc::clone(&global.repo_manager); + let path_clone = path.clone(); + let repo_clone = repo.clone(); + let res = tokio::task::spawn_blocking(move || { + clone + .write() + .unwrap() + .add_pkg_from_path(&repo_clone, &path_clone) + }) + .await?; + + match res { + // Insert the newly added package into the database + Ok(pkg) => { + tracing::info!("Added '{}' to repository '{}'", pkg.file_name(), repo); + + // Query the repo for its ID, or create it if it does not already exist + let res = global.db.repo.by_name(&repo).await?; + + let repo_id = if let Some(repo_entity) = res { + repo_entity.id + } else { + global.db.repo.insert(&repo, None).await?.last_insert_id + }; + + // If the package already exists in the database, we remove it first + let res = global + .db + .pkg + .by_fields(repo_id, &pkg.info.name, None, &pkg.info.arch) + .await?; + + if let Some(entry) = res { + entry.delete(&global.db).await?; + } + + global.db.pkg.insert(repo_id, pkg).await?; + + Ok(()) + } + // Remove the uploaded file and return the error + Err(err) => { + tokio::fs::remove_file(path).await?; + + Err(err.into()) + } + } +} + +async fn delete_repo( + State(global): State, + Path(repo): Path, +) -> crate::Result { + let clone = Arc::clone(&global.repo_manager); + + let repo_clone = repo.clone(); + let repo_removed = + tokio::task::spawn_blocking(move || clone.write().unwrap().remove_repo(&repo_clone)) + .await??; + + if repo_removed { + let res = global.db.repo.by_name(&repo).await?; + + if let Some(repo_entry) = res { + repo_entry.delete(&global.db).await?; + } + + tracing::info!("Removed repository '{}'", repo); + + Ok(StatusCode::OK) + } else { + Ok(StatusCode::NOT_FOUND) + } +} + +async fn delete_arch_repo( + State(global): State, + Path((repo, arch)): Path<(String, String)>, +) -> crate::Result { + let clone = Arc::clone(&global.repo_manager); + + let arch_clone = arch.clone(); + let repo_clone = repo.clone(); + let repo_removed = tokio::task::spawn_blocking(move || { + clone + .write() + .unwrap() + .remove_repo_arch(&repo_clone, &arch_clone) + }) + .await??; + + if repo_removed { + let res = global.db.repo.by_name(&repo).await?; + + if let Some(repo_entry) = res { + global.db.pkg.delete_with_arch(repo_entry.id, &arch).await?; + } + tracing::info!("Removed architecture '{}' from repository '{}'", arch, repo); + + Ok(StatusCode::OK) + } else { + Ok(StatusCode::NOT_FOUND) + } +} + +async fn delete_package( + State(global): State, + Path((repo, arch, file_name)): Path<(String, String, String)>, +) -> crate::Result { + let clone = Arc::clone(&global.repo_manager); + let path = PathBuf::from(&repo).join(arch).join(&file_name); + + let res = tokio::task::spawn_blocking(move || { + clone.write().unwrap().remove_pkg_from_path(path, true) + }) + .await??; + + if let Some((name, version, release, arch)) = res { + let res = global.db.repo.by_name(&repo).await?; + + if let Some(repo_entry) = res { + let res = global + .db + .pkg + .by_fields( + repo_entry.id, + &name, + Some(&format!("{}-{}", version, release)), + &arch, + ) + .await?; + + if let Some(entry) = res { + entry.delete(&global.db).await?; + } + } + + tracing::info!("Removed '{}' from repository '{}'", file_name, repo); + + Ok(StatusCode::OK) + } else { + Ok(StatusCode::NOT_FOUND) + } +} diff --git a/server/src/web/api/distros.rs b/server/src/web/api/distros.rs deleted file mode 100644 index feb3679..0000000 --- a/server/src/web/api/distros.rs +++ /dev/null @@ -1,43 +0,0 @@ -use axum::{ - extract::{Path, Query, State}, - routing::get, - Json, Router, -}; - -use super::pagination::{self, PaginatedResponse}; -use crate::db; - -pub fn router() -> Router { - Router::new() - .route("/", get(get_distros)) - .route("/:id", get(get_single_distro)) -} - -async fn get_distros( - State(global): State, - Query(pagination): Query, -) -> crate::Result>> { - let (total_pages, repos) = global - .db - .distro - .page( - pagination.per_page.unwrap_or(25), - pagination.page.unwrap_or(1) - 1, - ) - .await?; - Ok(Json(pagination.res(total_pages, repos))) -} - -async fn get_single_distro( - State(global): State, - Path(id): Path, -) -> crate::Result> { - let repo = global - .db - .distro - .by_id(id) - .await? - .ok_or(axum::http::StatusCode::NOT_FOUND)?; - - Ok(Json(repo)) -} diff --git a/server/src/web/api/mod.rs b/server/src/web/api/mod.rs deleted file mode 100644 index 16940b4..0000000 --- a/server/src/web/api/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod distros; -mod packages; -mod pagination; -mod repos; - -use axum::Router; - -pub fn router() -> Router { - Router::new() - .nest("/distros", distros::router()) - .nest("/repos", repos::router()) - .nest("/packages", packages::router()) -} diff --git a/server/src/web/api/packages.rs b/server/src/web/api/packages.rs deleted file mode 100644 index c0c9fc3..0000000 --- a/server/src/web/api/packages.rs +++ /dev/null @@ -1,44 +0,0 @@ -use axum::{ - extract::{Path, Query, State}, - routing::get, - Json, Router, -}; - -use super::pagination::{self, PaginatedResponse}; -use crate::db; - -pub fn router() -> Router { - Router::new() - .route("/", get(get_packages)) - .route("/:id", get(get_single_package)) -} - -async fn get_packages( - State(global): State, - Query(pagination): Query, -) -> crate::Result>> { - let (total_pages, pkgs) = global - .db - .pkg - .page( - pagination.per_page.unwrap_or(25), - pagination.page.unwrap_or(1) - 1, - ) - .await?; - - Ok(Json(pagination.res(total_pages, pkgs))) -} - -async fn get_single_package( - State(global): State, - Path(id): Path, -) -> crate::Result> { - let entry = global - .db - .pkg - .full(id) - .await? - .ok_or(axum::http::StatusCode::NOT_FOUND)?; - - Ok(Json(entry)) -} diff --git a/server/src/web/api/repos.rs b/server/src/web/api/repos.rs deleted file mode 100644 index 601ccb9..0000000 --- a/server/src/web/api/repos.rs +++ /dev/null @@ -1,43 +0,0 @@ -use axum::{ - extract::{Path, Query, State}, - routing::get, - Json, Router, -}; - -use super::pagination::{self, PaginatedResponse}; -use crate::db; - -pub fn router() -> Router { - Router::new() - .route("/", get(get_repos)) - .route("/:id", get(get_single_repo)) -} - -async fn get_repos( - State(global): State, - Query(pagination): Query, -) -> crate::Result>> { - let (total_pages, repos) = global - .db - .repo - .page( - pagination.per_page.unwrap_or(25), - pagination.page.unwrap_or(1) - 1, - ) - .await?; - Ok(Json(pagination.res(total_pages, repos))) -} - -async fn get_single_repo( - State(global): State, - Path(id): Path, -) -> crate::Result> { - let repo = global - .db - .repo - .by_id(id) - .await? - .ok_or(axum::http::StatusCode::NOT_FOUND)?; - - Ok(Json(repo)) -} diff --git a/server/src/web/mod.rs b/server/src/web/mod.rs deleted file mode 100644 index ce32a07..0000000 --- a/server/src/web/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod api; -mod repo; - -use axum::{Router, Server}; -use tower_http::trace::TraceLayer; - -pub fn app(global: crate::Global, api_key: &str) -> Router { - Router::new() - .nest("/api", api::router()) - .merge(repo::router(api_key)) - .with_state(global) - .layer(TraceLayer::new_for_http()) -} - -pub async fn serve(app: Router, port: u16) -> Result<(), hyper::Error> { - Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap()) - .serve(app.into_make_service()) - .await -} diff --git a/server/src/web/repo.rs b/server/src/web/repo.rs deleted file mode 100644 index f8c3d65..0000000 --- a/server/src/web/repo.rs +++ /dev/null @@ -1,250 +0,0 @@ -use std::path::PathBuf; - -use axum::body::Body; -use axum::extract::{BodyStream, Path, State}; -use axum::http::Request; -use axum::http::StatusCode; -use axum::response::IntoResponse; -use axum::routing::{delete, post}; -use axum::Router; -use futures::StreamExt; -use sea_orm::ModelTrait; -use std::sync::Arc; -use tokio::{fs, io::AsyncWriteExt}; -use tower::util::ServiceExt; -use tower_http::services::{ServeDir, ServeFile}; -use tower_http::validate_request::ValidateRequestHeaderLayer; -use uuid::Uuid; - -const DB_FILE_EXTS: [&str; 4] = [".db", ".files", ".db.tar.gz", ".files.tar.gz"]; - -pub fn router(api_key: &str) -> Router { - Router::new() - .route( - "/:repo", - post(post_package_archive) - .delete(delete_repo) - .route_layer(ValidateRequestHeaderLayer::bearer(api_key)), - ) - .route( - "/: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( - "/: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((repo, arch, mut file_name)): Path<(String, String, String)>, - req: Request, -) -> crate::Result { - let repo_dir = global.config.repo_dir.join(&repo).join(&arch); - let repo_exists = tokio::fs::try_exists(&repo_dir).await?; - - let res = if DB_FILE_EXTS.iter().any(|ext| file_name.ends_with(ext)) { - // Append tar extension to ensure we find the file - if !file_name.ends_with(".tar.gz") { - file_name.push_str(".tar.gz"); - }; - - if repo_exists { - ServeFile::new(repo_dir.join(file_name)).oneshot(req).await - } else { - let path = global - .config - .repo_dir - .join(repo) - .join(crate::repo::manager::ANY_ARCH) - .join(file_name); - - ServeFile::new(path).oneshot(req).await - } - } else { - let any_file = global - .config - .pkg_dir - .join(repo) - .join(crate::repo::manager::ANY_ARCH) - .join(file_name); - - if repo_exists { - ServeDir::new(global.config.pkg_dir) - .fallback(ServeFile::new(any_file)) - .oneshot(req) - .await - } else { - ServeFile::new(any_file).oneshot(req).await - } - }; - - Ok(res) -} - -async fn post_package_archive( - State(global): State, - Path(repo): Path, - mut body: BodyStream, -) -> crate::Result<()> { - // We first stream the uploaded file to disk - let uuid: uuid::fmt::Simple = Uuid::new_v4().into(); - let path = global.config.pkg_dir.join(uuid.to_string()); - let mut f = fs::File::create(&path).await?; - - while let Some(chunk) = body.next().await { - f.write_all(&chunk?).await?; - } - - let clone = Arc::clone(&global.repo_manager); - let path_clone = path.clone(); - let repo_clone = repo.clone(); - let res = tokio::task::spawn_blocking(move || { - clone - .write() - .unwrap() - .add_pkg_from_path(&repo_clone, &path_clone) - }) - .await?; - - match res { - // Insert the newly added package into the database - Ok(pkg) => { - tracing::info!("Added '{}' to repository '{}'", pkg.file_name(), repo); - - // Query the repo for its ID, or create it if it does not already exist - let res = global.db.repo.by_name(&repo).await?; - - let repo_id = if let Some(repo_entity) = res { - repo_entity.id - } else { - global.db.repo.insert(&repo, None).await?.last_insert_id - }; - - // If the package already exists in the database, we remove it first - let res = global - .db - .pkg - .by_fields(repo_id, &pkg.info.name, None, &pkg.info.arch) - .await?; - - if let Some(entry) = res { - entry.delete(&global.db).await?; - } - - global.db.pkg.insert(repo_id, pkg).await?; - - Ok(()) - } - // Remove the uploaded file and return the error - Err(err) => { - tokio::fs::remove_file(path).await?; - - Err(err.into()) - } - } -} - -async fn delete_repo( - State(global): State, - Path(repo): Path, -) -> crate::Result { - let clone = Arc::clone(&global.repo_manager); - - let repo_clone = repo.clone(); - let repo_removed = - tokio::task::spawn_blocking(move || clone.write().unwrap().remove_repo(&repo_clone)) - .await??; - - if repo_removed { - let res = global.db.repo.by_name(&repo).await?; - - if let Some(repo_entry) = res { - repo_entry.delete(&global.db).await?; - } - - tracing::info!("Removed repository '{}'", repo); - - Ok(StatusCode::OK) - } else { - Ok(StatusCode::NOT_FOUND) - } -} - -async fn delete_arch_repo( - State(global): State, - Path((repo, arch)): Path<(String, String)>, -) -> crate::Result { - let clone = Arc::clone(&global.repo_manager); - - let arch_clone = arch.clone(); - let repo_clone = repo.clone(); - let repo_removed = tokio::task::spawn_blocking(move || { - clone - .write() - .unwrap() - .remove_repo_arch(&repo_clone, &arch_clone) - }) - .await??; - - if repo_removed { - let res = global.db.repo.by_name(&repo).await?; - - if let Some(repo_entry) = res { - global.db.pkg.delete_with_arch(repo_entry.id, &arch).await?; - } - tracing::info!("Removed architecture '{}' from repository '{}'", arch, repo); - - Ok(StatusCode::OK) - } else { - Ok(StatusCode::NOT_FOUND) - } -} - -async fn delete_package( - State(global): State, - Path((repo, arch, file_name)): Path<(String, String, String)>, -) -> crate::Result { - let clone = Arc::clone(&global.repo_manager); - let path = PathBuf::from(&repo).join(arch).join(&file_name); - - let res = tokio::task::spawn_blocking(move || { - clone.write().unwrap().remove_pkg_from_path(path, true) - }) - .await??; - - if let Some((name, version, release, arch)) = res { - let res = global.db.repo.by_name(&repo).await?; - - if let Some(repo_entry) = res { - let res = global - .db - .pkg - .by_fields( - repo_entry.id, - &name, - Some(&format!("{}-{}", version, release)), - &arch, - ) - .await?; - - if let Some(entry) = res { - entry.delete(&global.db).await?; - } - } - - tracing::info!("Removed '{}' from repository '{}'", file_name, repo); - - Ok(StatusCode::OK) - } else { - Ok(StatusCode::NOT_FOUND) - } -}