From 58def483aae9fe87578ca038156da4bc6904b6a9 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 30 May 2024 09:42:28 +0200 Subject: [PATCH] feat: added distro routes and manager --- server/src/cli.rs | 18 ++-- server/src/db/entities/distro.rs | 11 ++- server/src/db/entities/repo.rs | 15 ++++ .../m20230730_000001_create_repo_tables.rs | 9 ++ server/src/db/query/distro.rs | 47 ++++++++++ server/src/db/query/mod.rs | 1 + server/src/db/query/repo.rs | 8 +- server/src/distro.rs | 69 +++++++++++++++ server/src/main.rs | 3 +- server/src/repo/manager.rs | 8 +- server/src/repo/mod.rs | 87 ++++++++++--------- 11 files changed, 216 insertions(+), 60 deletions(-) create mode 100644 server/src/db/query/distro.rs create mode 100644 server/src/distro.rs diff --git a/server/src/cli.rs b/server/src/cli.rs index 2419606..740e2ad 100644 --- a/server/src/cli.rs +++ b/server/src/cli.rs @@ -1,4 +1,4 @@ -use crate::{repo::MetaRepoMgr, Config, Global}; +use crate::{distro::MetaDistroMgr, repo::MetaRepoMgr, Config, Global}; use axum::{extract::FromRef, Router}; use clap::Parser; @@ -42,12 +42,6 @@ pub struct Cli { pub log: String, } -impl FromRef for Arc> { - fn from_ref(global: &Global) -> Self { - Arc::clone(&global.repo_manager) - } -} - impl Cli { pub fn init_tracing(&self) { tracing_subscriber::registry() @@ -81,13 +75,11 @@ impl Cli { let config = Config { data_dir: self.data_dir.clone(), }; - let repo_manager = MetaRepoMgr::new(&self.data_dir.join("repos"), db.clone()); - let global = Global { - config, - repo_manager: Arc::new(RwLock::new(repo_manager)), - db, - }; + let mgr = MetaDistroMgr::new(&self.data_dir.join("distros"), db.clone()); + mgr.bootstrap().await?; + + let global = Global { config, mgr, db }; // build our application with a single route let app = Router::new() diff --git a/server/src/db/entities/distro.rs b/server/src/db/entities/distro.rs index d819fae..f39ad53 100644 --- a/server/src/db/entities/distro.rs +++ b/server/src/db/entities/distro.rs @@ -13,6 +13,15 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +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/repo.rs b/server/src/db/entities/repo.rs index 25291da..1ddd39e 100644 --- a/server/src/db/entities/repo.rs +++ b/server/src/db/entities/repo.rs @@ -8,16 +8,31 @@ use serde::{Deserialize, Serialize}; pub struct Model { #[sea_orm(primary_key)] pub id: i32, + pub distro_id: i32, pub name: String, pub description: Option, } #[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/migrator/m20230730_000001_create_repo_tables.rs b/server/src/db/migrator/m20230730_000001_create_repo_tables.rs index 45ea97c..b1e1fbe 100644 --- a/server/src/db/migrator/m20230730_000001_create_repo_tables.rs +++ b/server/src/db/migrator/m20230730_000001_create_repo_tables.rs @@ -43,8 +43,16 @@ impl MigrationTrait for Migration { .auto_increment() .primary_key(), ) + .col(ColumnDef::new(Repo::DistroId).integer().not_null()) .col(ColumnDef::new(Repo::Name).string().not_null().unique_key()) .col(ColumnDef::new(Repo::Description).string()) + .foreign_key( + ForeignKey::create() + .name("fk-repo-distro_id") + .from(Repo::Table, Repo::DistroId) + .to(Distro::Table, Distro::Id) + .on_delete(ForeignKeyAction::Cascade), + ) .to_owned(), ) .await?; @@ -232,6 +240,7 @@ pub enum Distro { pub enum Repo { Table, Id, + DistroId, Name, Description, } diff --git a/server/src/db/query/distro.rs b/server/src/db/query/distro.rs new file mode 100644 index 0000000..c4fc70f --- /dev/null +++ b/server/src/db/query/distro.rs @@ -0,0 +1,47 @@ +use crate::db::*; + +use sea_orm::{sea_query::IntoCondition, *}; + +#[derive(Deserialize)] +pub struct Filter { + name: Option, +} + +impl IntoCondition for Filter { + fn into_condition(self) -> Condition { + Condition::all().add_option( + self.name + .map(|name| distro::Column::Name.like(format!("%{}%", name))), + ) + } +} + +pub async fn page( + conn: &DbConn, + per_page: u64, + page: u64, + filter: Filter, +) -> Result<(u64, Vec)> { + let paginator = Distro::find() + .filter(filter) + .order_by_asc(distro::Column::Id) + .paginate(conn, per_page); + let repos = paginator.fetch_page(page).await?; + let total_pages = paginator.num_pages().await?; + + Ok((total_pages, repos)) +} + +pub async fn by_id(conn: &DbConn, id: i32) -> Result> { + distro::Entity::find_by_id(id).one(conn).await +} + +pub async fn insert(conn: &DbConn, name: &str, description: Option<&str>) -> Result { + let model = distro::ActiveModel { + id: NotSet, + name: Set(String::from(name)), + description: Set(description.map(String::from)), + }; + + model.insert(conn).await +} diff --git a/server/src/db/query/mod.rs b/server/src/db/query/mod.rs index 87d61e3..f0a809b 100644 --- a/server/src/db/query/mod.rs +++ b/server/src/db/query/mod.rs @@ -1,3 +1,4 @@ +pub mod distro; pub mod package; pub mod repo; diff --git a/server/src/db/query/repo.rs b/server/src/db/query/repo.rs index 0370c2b..2ad54bf 100644 --- a/server/src/db/query/repo.rs +++ b/server/src/db/query/repo.rs @@ -43,9 +43,15 @@ pub async fn by_name(conn: &DbConn, name: &str) -> Result> { .await } -pub async fn insert(conn: &DbConn, name: &str, description: Option<&str>) -> Result { +pub async fn insert( + conn: &DbConn, + distro_id: i32, + name: &str, + description: Option<&str>, +) -> Result { let model = repo::ActiveModel { id: NotSet, + distro_id: Set(distro_id), name: Set(String::from(name)), description: Set(description.map(String::from)), }; diff --git a/server/src/distro.rs b/server/src/distro.rs new file mode 100644 index 0000000..209d227 --- /dev/null +++ b/server/src/distro.rs @@ -0,0 +1,69 @@ +use crate::{db, MetaRepoMgr}; + +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::Arc, +}; + +use sea_orm::{DbConn, EntityTrait}; +use tokio::sync::Mutex; + +#[derive(Clone)] +pub struct MetaDistroMgr { + distro_dir: PathBuf, + conn: DbConn, + distros: Arc>>>, +} + +impl MetaDistroMgr { + pub fn new>(distro_dir: P, conn: DbConn) -> Self { + Self { + distro_dir: distro_dir.as_ref().to_path_buf(), + conn, + distros: Arc::new(Mutex::new(HashMap::new())), + } + } + + /// Populate the manager with the currently known distros from the database. + pub async fn bootstrap(&self) -> crate::Result<()> { + let mut map = self.distros.lock().await; + let distros = db::Distro::find().all(&self.conn).await?; + + for distro in distros { + let mgr = MetaRepoMgr::new( + self.distro_dir.join(&distro.name), + distro.id, + self.conn.clone(), + ); + map.insert(distro.name, Arc::new(mgr)); + } + + Ok(()) + } + + pub async fn get_mgr(&self, distro: &str) -> Option> { + let map = self.distros.lock().await; + + map.get(distro).map(|mgr| Arc::clone(mgr)) + } + + pub async fn get_or_create_mgr(&self, distro: &str) -> crate::Result> { + let mut map = self.distros.lock().await; + + if let Some(mgr) = map.get(distro) { + Ok(Arc::clone(mgr)) + } else { + let distro = db::query::distro::insert(&self.conn, distro, None).await?; + + let mgr = Arc::new(MetaRepoMgr::new( + self.distro_dir.join(&distro.name), + distro.id, + self.conn.clone(), + )); + map.insert(distro.name, Arc::clone(&mgr)); + + Ok(mgr) + } + } +} diff --git a/server/src/main.rs b/server/src/main.rs index c57420a..a428e39 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,6 +1,7 @@ mod api; mod cli; pub mod db; +mod distro; mod error; mod repo; @@ -19,7 +20,7 @@ pub struct Config { #[derive(Clone)] pub struct Global { config: Config, - repo_manager: Arc>, + mgr: distro::MetaDistroMgr, db: sea_orm::DbConn, } diff --git a/server/src/repo/manager.rs b/server/src/repo/manager.rs index e2d9c4d..a877d0d 100644 --- a/server/src/repo/manager.rs +++ b/server/src/repo/manager.rs @@ -12,13 +12,15 @@ pub const ANY_ARCH: &'static str = "any"; pub struct MetaRepoMgr { repo_dir: PathBuf, + distro_id: i32, conn: DbConn, } impl MetaRepoMgr { - pub fn new>(repo_dir: P, conn: DbConn) -> Self { + pub fn new>(repo_dir: P, distro_id: i32, conn: DbConn) -> Self { MetaRepoMgr { repo_dir: repo_dir.as_ref().to_path_buf(), + distro_id, conn, } } @@ -237,7 +239,9 @@ impl MetaRepoMgr { let repo_id = if let Some(repo_entity) = res { repo_entity.id } else { - db::query::repo::insert(&self.conn, repo, None).await?.id + db::query::repo::insert(&self.conn, self.distro_id, repo, None) + .await? + .id }; // If the package already exists in the database, we remove it first diff --git a/server/src/repo/mod.rs b/server/src/repo/mod.rs index 2f2dacb..7544faa 100644 --- a/server/src/repo/mod.rs +++ b/server/src/repo/mod.rs @@ -20,19 +20,19 @@ use tower_http::{services::ServeFile, validate_request::ValidateRequestHeaderLay pub fn router(api_key: &str) -> Router { Router::new() .route( - "/:repo", + "/:distro/:repo", post(post_package_archive) .delete(delete_repo) .route_layer(ValidateRequestHeaderLayer::bearer(api_key)), ) .route( - "/:repo/:arch", + "/: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( - "/:repo/:arch/:filename", + "/:distro/:repo/:arch/:filename", delete(delete_package) .route_layer(ValidateRequestHeaderLayer::bearer(api_key)) .get(get_file), @@ -44,10 +44,15 @@ pub fn router(api_key: &str) -> Router { /// is returned. async fn get_file( State(global): State, - Path((repo, arch, file_name)): Path<(String, String, String)>, + Path((distro, repo, arch, file_name)): Path<(String, String, String, String)>, req: Request, ) -> crate::Result { - let repo_dir = global.config.data_dir.join("repos").join(&repo); + 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) { @@ -65,16 +70,12 @@ async fn get_file( async fn post_package_archive( State(global): State, - Path(repo): Path, + 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 (name, version, arch) = global - .repo_manager - .write() - .await - .add_pkg_from_reader(&mut body, &repo) - .await?; + 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 '{}' ({})", @@ -89,14 +90,18 @@ async fn post_package_archive( async fn delete_repo( State(global): State, - Path(repo): Path, + Path((distro, repo)): Path<(String, String)>, ) -> crate::Result { - let repo_removed = global.repo_manager.write().await.remove_repo(&repo).await?; + 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); + if repo_removed { + tracing::info!("Removed repository '{}'", repo); - Ok(StatusCode::OK) + Ok(StatusCode::OK) + } else { + Ok(StatusCode::NOT_FOUND) + } } else { Ok(StatusCode::NOT_FOUND) } @@ -104,19 +109,18 @@ async fn delete_repo( async fn delete_arch_repo( State(global): State, - Path((repo, arch)): Path<(String, String)>, + Path((distro, repo, arch)): Path<(String, String, String)>, ) -> crate::Result { - let repo_removed = global - .repo_manager - .write() - .await - .remove_repo_arch(&repo, &arch) - .await?; + 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); + if repo_removed { + tracing::info!("Removed arch '{}' from repository '{}'", arch, repo); - Ok(StatusCode::OK) + Ok(StatusCode::OK) + } else { + Ok(StatusCode::NOT_FOUND) + } } else { Ok(StatusCode::NOT_FOUND) } @@ -124,24 +128,23 @@ async fn delete_arch_repo( async fn delete_package( State(global): State, - Path((repo, arch, pkg_name)): Path<(String, String, String)>, + Path((distro, repo, arch, pkg_name)): Path<(String, String, String, String)>, ) -> crate::Result { - let pkg_removed = global - .repo_manager - .write() - .await - .remove_pkg(&repo, &arch, &pkg_name) - .await?; + 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 - ); + if pkg_removed { + tracing::info!( + "Removed package '{}' ({}) from repository '{}'", + pkg_name, + arch, + repo + ); - Ok(StatusCode::OK) + Ok(StatusCode::OK) + } else { + Ok(StatusCode::NOT_FOUND) + } } else { Ok(StatusCode::NOT_FOUND) }