Compare commits

..

No commits in common. "58def483aae9fe87578ca038156da4bc6904b6a9" and "ecc33f01534a4db31e4b3e233f8e32805d65934b" have entirely different histories.

13 changed files with 59 additions and 267 deletions

View File

@ -1,4 +1,4 @@
use crate::{distro::MetaDistroMgr, repo::MetaRepoMgr, Config, Global}; use crate::{repo::MetaRepoMgr, Config, Global};
use axum::{extract::FromRef, Router}; use axum::{extract::FromRef, Router};
use clap::Parser; use clap::Parser;
@ -42,6 +42,12 @@ pub struct Cli {
pub log: String, pub log: String,
} }
impl FromRef<Global> for Arc<RwLock<MetaRepoMgr>> {
fn from_ref(global: &Global) -> Self {
Arc::clone(&global.repo_manager)
}
}
impl Cli { impl Cli {
pub fn init_tracing(&self) { pub fn init_tracing(&self) {
tracing_subscriber::registry() tracing_subscriber::registry()
@ -75,11 +81,13 @@ impl Cli {
let config = Config { let config = Config {
data_dir: self.data_dir.clone(), data_dir: self.data_dir.clone(),
}; };
let repo_manager = MetaRepoMgr::new(&self.data_dir.join("repos"), db.clone());
let mgr = MetaDistroMgr::new(&self.data_dir.join("distros"), db.clone()); let global = Global {
mgr.bootstrap().await?; config,
repo_manager: Arc::new(RwLock::new(repo_manager)),
let global = Global { config, mgr, db }; db,
};
// build our application with a single route // build our application with a single route
let app = Router::new() let app = Router::new()

View File

@ -1,27 +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,
pub name: String,
pub description: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::repo::Entity")]
Repo,
}
impl Related<super::repo::Entity> for Entity {
fn to() -> RelationDef {
Relation::Repo.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -2,7 +2,6 @@
pub mod prelude; pub mod prelude;
pub mod distro;
pub mod package; pub mod package;
pub mod package_file; pub mod package_file;
pub mod package_group; pub mod package_group;

View File

@ -1,6 +1,5 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 //! `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::Entity as Package;
pub use super::package_file::Entity as PackageFile; pub use super::package_file::Entity as PackageFile;
pub use super::package_group::Entity as PackageGroup; pub use super::package_group::Entity as PackageGroup;

View File

@ -8,31 +8,16 @@ use serde::{Deserialize, Serialize};
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
pub id: i32, pub id: i32,
pub distro_id: i32,
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation { 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")] #[sea_orm(has_many = "super::package::Entity")]
Package, Package,
} }
impl Related<super::distro::Entity> for Entity {
fn to() -> RelationDef {
Relation::Distro.def()
}
}
impl Related<super::package::Entity> for Entity { impl Related<super::package::Entity> for Entity {
fn to() -> RelationDef { fn to() -> RelationDef {
Relation::Package.def() Relation::Package.def()

View File

@ -11,27 +11,6 @@ impl MigrationName for Migration {
#[async_trait::async_trait] #[async_trait::async_trait]
impl MigrationTrait for Migration { impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Distro::Table)
.col(
ColumnDef::new(Distro::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(Distro::Name)
.string()
.not_null()
.unique_key(),
)
.col(ColumnDef::new(Distro::Description).string())
.to_owned(),
)
.await?;
manager manager
.create_table( .create_table(
Table::create() Table::create()
@ -43,16 +22,8 @@ impl MigrationTrait for Migration {
.auto_increment() .auto_increment()
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(Repo::DistroId).integer().not_null())
.col(ColumnDef::new(Repo::Name).string().not_null().unique_key()) .col(ColumnDef::new(Repo::Name).string().not_null().unique_key())
.col(ColumnDef::new(Repo::Description).string()) .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(), .to_owned(),
) )
.await?; .await?;
@ -221,26 +192,14 @@ impl MigrationTrait for Migration {
.await?; .await?;
manager manager
.drop_table(Table::drop().table(Repo::Table).to_owned()) .drop_table(Table::drop().table(Repo::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Distro::Table).to_owned())
.await .await
} }
} }
#[derive(Iden)]
pub enum Distro {
Table,
Id,
Name,
Description,
}
#[derive(Iden)] #[derive(Iden)]
pub enum Repo { pub enum Repo {
Table, Table,
Id, Id,
DistroId,
Name, Name,
Description, Description,
} }

View File

@ -1,47 +0,0 @@
use crate::db::*;
use sea_orm::{sea_query::IntoCondition, *};
#[derive(Deserialize)]
pub struct Filter {
name: Option<String>,
}
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<distro::Model>)> {
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<Option<distro::Model>> {
distro::Entity::find_by_id(id).one(conn).await
}
pub async fn insert(conn: &DbConn, name: &str, description: Option<&str>) -> Result<distro::Model> {
let model = distro::ActiveModel {
id: NotSet,
name: Set(String::from(name)),
description: Set(description.map(String::from)),
};
model.insert(conn).await
}

View File

@ -1,4 +1,3 @@
pub mod distro;
pub mod package; pub mod package;
pub mod repo; pub mod repo;

View File

@ -43,15 +43,9 @@ pub async fn by_name(conn: &DbConn, name: &str) -> Result<Option<repo::Model>> {
.await .await
} }
pub async fn insert( pub async fn insert(conn: &DbConn, name: &str, description: Option<&str>) -> Result<repo::Model> {
conn: &DbConn,
distro_id: i32,
name: &str,
description: Option<&str>,
) -> Result<repo::Model> {
let model = repo::ActiveModel { let model = repo::ActiveModel {
id: NotSet, id: NotSet,
distro_id: Set(distro_id),
name: Set(String::from(name)), name: Set(String::from(name)),
description: Set(description.map(String::from)), description: Set(description.map(String::from)),
}; };

View File

@ -1,69 +0,0 @@
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<Mutex<HashMap<String, Arc<MetaRepoMgr>>>>,
}
impl MetaDistroMgr {
pub fn new<P: AsRef<Path>>(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<Arc<MetaRepoMgr>> {
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<Arc<MetaRepoMgr>> {
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)
}
}
}

View File

@ -1,7 +1,6 @@
mod api; mod api;
mod cli; mod cli;
pub mod db; pub mod db;
mod distro;
mod error; mod error;
mod repo; mod repo;
@ -20,7 +19,7 @@ pub struct Config {
#[derive(Clone)] #[derive(Clone)]
pub struct Global { pub struct Global {
config: Config, config: Config,
mgr: distro::MetaDistroMgr, repo_manager: Arc<RwLock<MetaRepoMgr>>,
db: sea_orm::DbConn, db: sea_orm::DbConn,
} }

View File

@ -12,15 +12,13 @@ pub const ANY_ARCH: &'static str = "any";
pub struct MetaRepoMgr { pub struct MetaRepoMgr {
repo_dir: PathBuf, repo_dir: PathBuf,
distro_id: i32,
conn: DbConn, conn: DbConn,
} }
impl MetaRepoMgr { impl MetaRepoMgr {
pub fn new<P: AsRef<Path>>(repo_dir: P, distro_id: i32, conn: DbConn) -> Self { pub fn new<P: AsRef<Path>>(repo_dir: P, conn: DbConn) -> Self {
MetaRepoMgr { MetaRepoMgr {
repo_dir: repo_dir.as_ref().to_path_buf(), repo_dir: repo_dir.as_ref().to_path_buf(),
distro_id,
conn, conn,
} }
} }
@ -239,9 +237,7 @@ impl MetaRepoMgr {
let repo_id = if let Some(repo_entity) = res { let repo_id = if let Some(repo_entity) = res {
repo_entity.id repo_entity.id
} else { } else {
db::query::repo::insert(&self.conn, self.distro_id, repo, None) db::query::repo::insert(&self.conn, repo, None).await?.id
.await?
.id
}; };
// If the package already exists in the database, we remove it first // If the package already exists in the database, we remove it first

View File

@ -20,19 +20,19 @@ use tower_http::{services::ServeFile, validate_request::ValidateRequestHeaderLay
pub fn router(api_key: &str) -> Router<crate::Global> { pub fn router(api_key: &str) -> Router<crate::Global> {
Router::new() Router::new()
.route( .route(
"/:distro/:repo", "/:repo",
post(post_package_archive) post(post_package_archive)
.delete(delete_repo) .delete(delete_repo)
.route_layer(ValidateRequestHeaderLayer::bearer(api_key)), .route_layer(ValidateRequestHeaderLayer::bearer(api_key)),
) )
.route( .route(
"/:distro/:repo/:arch", "/:repo/:arch",
delete(delete_arch_repo).route_layer(ValidateRequestHeaderLayer::bearer(api_key)), 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 // Routes added after the layer do not get that layer applied, so the GET requests will not
// be authorized // be authorized
.route( .route(
"/:distro/:repo/:arch/:filename", "/:repo/:arch/:filename",
delete(delete_package) delete(delete_package)
.route_layer(ValidateRequestHeaderLayer::bearer(api_key)) .route_layer(ValidateRequestHeaderLayer::bearer(api_key))
.get(get_file), .get(get_file),
@ -44,15 +44,10 @@ pub fn router(api_key: &str) -> Router<crate::Global> {
/// is returned. /// is returned.
async fn get_file( async fn get_file(
State(global): State<crate::Global>, State(global): State<crate::Global>,
Path((distro, repo, arch, file_name)): Path<(String, String, String, String)>, Path((repo, arch, file_name)): Path<(String, String, String)>,
req: Request<Body>, req: Request<Body>,
) -> crate::Result<impl IntoResponse> { ) -> crate::Result<impl IntoResponse> {
let repo_dir = global let repo_dir = global.config.data_dir.join("repos").join(&repo);
.config
.data_dir
.join("distros")
.join(&distro)
.join(&repo);
let file_name = let file_name =
if file_name == format!("{}.db", repo) || file_name == format!("{}.db.tar.gz", repo) { if file_name == format!("{}.db", repo) || file_name == format!("{}.db.tar.gz", repo) {
@ -70,12 +65,16 @@ async fn get_file(
async fn post_package_archive( async fn post_package_archive(
State(global): State<crate::Global>, State(global): State<crate::Global>,
Path((distro, repo)): Path<(String, String)>, Path(repo): Path<String>,
body: Body, body: Body,
) -> crate::Result<()> { ) -> crate::Result<()> {
let mut body = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other)); 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) = global
let (name, version, arch) = mgr.add_pkg_from_reader(&mut body, &repo).await?; .repo_manager
.write()
.await
.add_pkg_from_reader(&mut body, &repo)
.await?;
tracing::info!( tracing::info!(
"Added '{}-{}' to repository '{}' ({})", "Added '{}-{}' to repository '{}' ({})",
@ -90,10 +89,9 @@ async fn post_package_archive(
async fn delete_repo( async fn delete_repo(
State(global): State<crate::Global>, State(global): State<crate::Global>,
Path((distro, repo)): Path<(String, String)>, Path(repo): Path<String>,
) -> crate::Result<StatusCode> { ) -> crate::Result<StatusCode> {
if let Some(mgr) = global.mgr.get_mgr(&distro).await { let repo_removed = global.repo_manager.write().await.remove_repo(&repo).await?;
let repo_removed = mgr.remove_repo(&repo).await?;
if repo_removed { if repo_removed {
tracing::info!("Removed repository '{}'", repo); tracing::info!("Removed repository '{}'", repo);
@ -102,17 +100,18 @@ async fn delete_repo(
} else { } else {
Ok(StatusCode::NOT_FOUND) Ok(StatusCode::NOT_FOUND)
} }
} else {
Ok(StatusCode::NOT_FOUND)
}
} }
async fn delete_arch_repo( async fn delete_arch_repo(
State(global): State<crate::Global>, State(global): State<crate::Global>,
Path((distro, repo, arch)): Path<(String, String, String)>, Path((repo, arch)): Path<(String, String)>,
) -> crate::Result<StatusCode> { ) -> crate::Result<StatusCode> {
if let Some(mgr) = global.mgr.get_mgr(&distro).await { let repo_removed = global
let repo_removed = mgr.remove_repo_arch(&repo, &arch).await?; .repo_manager
.write()
.await
.remove_repo_arch(&repo, &arch)
.await?;
if repo_removed { if repo_removed {
tracing::info!("Removed arch '{}' from repository '{}'", arch, repo); tracing::info!("Removed arch '{}' from repository '{}'", arch, repo);
@ -121,17 +120,18 @@ async fn delete_arch_repo(
} else { } else {
Ok(StatusCode::NOT_FOUND) Ok(StatusCode::NOT_FOUND)
} }
} else {
Ok(StatusCode::NOT_FOUND)
}
} }
async fn delete_package( async fn delete_package(
State(global): State<crate::Global>, State(global): State<crate::Global>,
Path((distro, repo, arch, pkg_name)): Path<(String, String, String, String)>, Path((repo, arch, pkg_name)): Path<(String, String, String)>,
) -> crate::Result<StatusCode> { ) -> crate::Result<StatusCode> {
if let Some(mgr) = global.mgr.get_mgr(&distro).await { let pkg_removed = global
let pkg_removed = mgr.remove_pkg(&repo, &arch, &pkg_name).await?; .repo_manager
.write()
.await
.remove_pkg(&repo, &arch, &pkg_name)
.await?;
if pkg_removed { if pkg_removed {
tracing::info!( tracing::info!(
@ -145,7 +145,4 @@ async fn delete_package(
} else { } else {
Ok(StatusCode::NOT_FOUND) Ok(StatusCode::NOT_FOUND)
} }
} else {
Ok(StatusCode::NOT_FOUND)
}
} }