From aef2c823e5c0dba4bdbc6d85a3483de614792be5 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 4 Aug 2023 15:23:38 +0200 Subject: [PATCH 1/2] feat(server): add more package metadata tables --- libarchive/src/archive.rs | 2 +- libarchive/src/write/file.rs | 2 +- server/src/api/mod.rs | 31 +++ server/src/db/entities/mod.rs | 5 + server/src/db/entities/package.rs | 40 ++++ server/src/db/entities/package_conflicts.rs | 33 +++ server/src/db/entities/package_depends.rs | 35 ++++ server/src/db/entities/package_group.rs | 33 +++ server/src/db/entities/package_provides.rs | 33 +++ server/src/db/entities/package_replaces.rs | 33 +++ server/src/db/entities/prelude.rs | 5 + .../m20230730_000001_create_repo_tables.rs | 198 +++++++++++++++++- server/src/repo/manager.rs | 4 + server/src/repo/mod.rs | 16 +- server/src/repo/package.rs | 4 +- 15 files changed, 466 insertions(+), 8 deletions(-) create mode 100644 server/src/db/entities/package_conflicts.rs create mode 100644 server/src/db/entities/package_depends.rs create mode 100644 server/src/db/entities/package_group.rs create mode 100644 server/src/db/entities/package_provides.rs create mode 100644 server/src/db/entities/package_replaces.rs diff --git a/libarchive/src/archive.rs b/libarchive/src/archive.rs index d8f7086..97c9d0a 100644 --- a/libarchive/src/archive.rs +++ b/libarchive/src/archive.rs @@ -39,7 +39,7 @@ pub enum ReadFormat { Zip, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum ReadFilter { All, Bzip2, diff --git a/libarchive/src/write/file.rs b/libarchive/src/write/file.rs index 9c7abe2..5d932f1 100644 --- a/libarchive/src/write/file.rs +++ b/libarchive/src/write/file.rs @@ -54,7 +54,7 @@ impl FileWriter { self.handle_mut(), &buf[written] as *const u8 as *const _, buf_len - written, - ) + ) } as isize; // Negative values signal errors diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index 011f008..abb67c3 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -6,11 +6,13 @@ use axum::Json; use axum::Router; use sea_orm::entity::EntityTrait; use sea_orm::query::QueryOrder; +use sea_orm::ModelTrait; use sea_orm::PaginatorTrait; use pagination::PaginatedResponse; use crate::db::entities::package; +use crate::db::entities::package_license; use crate::db::entities::repo; pub fn router() -> Router { @@ -18,6 +20,7 @@ pub fn router() -> Router { .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( @@ -57,3 +60,31 @@ async fn get_packages( Ok(Json(pagination.res(pkgs))) } + +#[derive(serde::Serialize)] +pub struct PackageRes { + #[serde(flatten)] + entry: package::Model, + licenses: Vec, +} + +async fn get_single_package( + State(global): State, + Path(id): Path, +) -> crate::Result> { + let entry = package::Entity::find_by_id(id) + .one(&global.db) + .await? + .ok_or(axum::http::StatusCode::NOT_FOUND)?; + let licenses = entry + .find_related(package_license::Entity) + .all(&global.db) + .await? + .iter() + .map(|e| e.value.clone()) + .collect(); + + let res = PackageRes { entry, licenses }; + + Ok(Json(res)) +} diff --git a/server/src/db/entities/mod.rs b/server/src/db/entities/mod.rs index e5ad810..828fec2 100644 --- a/server/src/db/entities/mod.rs +++ b/server/src/db/entities/mod.rs @@ -3,5 +3,10 @@ pub mod prelude; pub mod package; +pub mod package_conflicts; +pub mod package_depends; +pub mod package_group; pub mod package_license; +pub mod package_provides; +pub mod package_replaces; pub mod repo; diff --git a/server/src/db/entities/package.rs b/server/src/db/entities/package.rs index 86606bf..c34abb1 100644 --- a/server/src/db/entities/package.rs +++ b/server/src/db/entities/package.rs @@ -26,8 +26,18 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { + #[sea_orm(has_many = "super::package_conflicts::Entity")] + PackageConflicts, + #[sea_orm(has_many = "super::package_depends::Entity")] + PackageDepends, + #[sea_orm(has_many = "super::package_group::Entity")] + PackageGroup, #[sea_orm(has_many = "super::package_license::Entity")] PackageLicense, + #[sea_orm(has_many = "super::package_provides::Entity")] + PackageProvides, + #[sea_orm(has_many = "super::package_replaces::Entity")] + PackageReplaces, #[sea_orm( belongs_to = "super::repo::Entity", from = "Column::RepoId", @@ -38,12 +48,42 @@ pub enum Relation { Repo, } +impl Related for Entity { + fn to() -> RelationDef { + Relation::PackageConflicts.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::PackageDepends.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::PackageGroup.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::PackageLicense.def() } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::PackageProvides.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::PackageReplaces.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::Repo.def() diff --git a/server/src/db/entities/package_conflicts.rs b/server/src/db/entities/package_conflicts.rs new file mode 100644 index 0000000..e9e8ba8 --- /dev/null +++ b/server/src/db/entities/package_conflicts.rs @@ -0,0 +1,33 @@ +//! `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 = "package_conflicts")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub package_id: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub value: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::package::Entity", + from = "Column::PackageId", + to = "super::package::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + Package, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Package.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/server/src/db/entities/package_depends.rs b/server/src/db/entities/package_depends.rs new file mode 100644 index 0000000..b22f717 --- /dev/null +++ b/server/src/db/entities/package_depends.rs @@ -0,0 +1,35 @@ +//! `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 = "package_depends")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub package_id: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub r#type: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub value: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::package::Entity", + from = "Column::PackageId", + to = "super::package::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + Package, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Package.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/server/src/db/entities/package_group.rs b/server/src/db/entities/package_group.rs new file mode 100644 index 0000000..61e69f2 --- /dev/null +++ b/server/src/db/entities/package_group.rs @@ -0,0 +1,33 @@ +//! `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 = "package_group")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub package_id: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub value: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::package::Entity", + from = "Column::PackageId", + to = "super::package::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + Package, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Package.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/server/src/db/entities/package_provides.rs b/server/src/db/entities/package_provides.rs new file mode 100644 index 0000000..7fca6ee --- /dev/null +++ b/server/src/db/entities/package_provides.rs @@ -0,0 +1,33 @@ +//! `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 = "package_provides")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub package_id: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub value: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::package::Entity", + from = "Column::PackageId", + to = "super::package::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + Package, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Package.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/server/src/db/entities/package_replaces.rs b/server/src/db/entities/package_replaces.rs new file mode 100644 index 0000000..0946b2d --- /dev/null +++ b/server/src/db/entities/package_replaces.rs @@ -0,0 +1,33 @@ +//! `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 = "package_replaces")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub package_id: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub value: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::package::Entity", + from = "Column::PackageId", + to = "super::package::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + Package, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Package.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/server/src/db/entities/prelude.rs b/server/src/db/entities/prelude.rs index 1f8176c..9eae171 100644 --- a/server/src/db/entities/prelude.rs +++ b/server/src/db/entities/prelude.rs @@ -1,5 +1,10 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1 pub use super::package::Entity as Package; +pub use super::package_conflicts::Entity as PackageConflicts; +pub use super::package_depends::Entity as PackageDepends; +pub use super::package_group::Entity as PackageGroup; pub use super::package_license::Entity as PackageLicense; +pub use super::package_provides::Entity as PackageProvides; +pub use super::package_replaces::Entity as PackageReplaces; pub use super::repo::Entity as Repo; 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 df56657..aceae78 100644 --- a/server/src/db/migrator/m20230730_000001_create_repo_tables.rs +++ b/server/src/db/migrator/m20230730_000001_create_repo_tables.rs @@ -90,7 +90,152 @@ impl MigrationTrait for Migration { ) .to_owned(), ) - .await + .await?; + manager + .create_table( + Table::create() + .table(PackageGroup::Table) + .col(ColumnDef::new(PackageGroup::PackageId).integer().not_null()) + .col( + ColumnDef::new(PackageGroup::Value) + .string_len(255) + .not_null(), + ) + .primary_key( + Index::create() + .col(PackageGroup::PackageId) + .col(PackageGroup::Value), + ) + .foreign_key( + ForeignKey::create() + .name("fk-package_group-package_id") + .from(PackageGroup::Table, PackageGroup::PackageId) + .to(Package::Table, Package::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; + manager + .create_table( + Table::create() + .table(PackageReplaces::Table) + .col( + ColumnDef::new(PackageReplaces::PackageId) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(PackageReplaces::Value) + .string_len(255) + .not_null(), + ) + .primary_key( + Index::create() + .col(PackageReplaces::PackageId) + .col(PackageReplaces::Value), + ) + .foreign_key( + ForeignKey::create() + .name("fk-package_replaces-package_id") + .from(PackageReplaces::Table, PackageReplaces::PackageId) + .to(Package::Table, Package::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; + manager + .create_table( + Table::create() + .table(PackageConflicts::Table) + .col( + ColumnDef::new(PackageConflicts::PackageId) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(PackageConflicts::Value) + .string_len(255) + .not_null(), + ) + .primary_key( + Index::create() + .col(PackageConflicts::PackageId) + .col(PackageConflicts::Value), + ) + .foreign_key( + ForeignKey::create() + .name("fk-package_conflicts-package_id") + .from(PackageConflicts::Table, PackageConflicts::PackageId) + .to(Package::Table, Package::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; + manager + .create_table( + Table::create() + .table(PackageProvides::Table) + .col( + ColumnDef::new(PackageProvides::PackageId) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(PackageProvides::Value) + .string_len(255) + .not_null(), + ) + .primary_key( + Index::create() + .col(PackageProvides::PackageId) + .col(PackageProvides::Value), + ) + .foreign_key( + ForeignKey::create() + .name("fk-package_provides-package_id") + .from(PackageProvides::Table, PackageProvides::PackageId) + .to(Package::Table, Package::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; + manager + .create_table( + Table::create() + .table(PackageDepends::Table) + .col( + ColumnDef::new(PackageDepends::PackageId) + .integer() + .not_null(), + ) + .col(ColumnDef::new(PackageDepends::Type).integer().not_null()) + .col( + ColumnDef::new(PackageDepends::Value) + .string_len(255) + .not_null(), + ) + .primary_key( + Index::create() + .col(PackageDepends::PackageId) + .col(PackageDepends::Type) + .col(PackageDepends::Value), + ) + .foreign_key( + ForeignKey::create() + .name("fk-package_depends-package_id") + .from(PackageDepends::Table, PackageDepends::PackageId) + .to(Package::Table, Package::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; + + Ok(()) } // Define how to rollback this migration: Drop the Bakery table. @@ -98,6 +243,21 @@ impl MigrationTrait for Migration { manager .drop_table(Table::drop().table(PackageLicense::Table).to_owned()) .await?; + manager + .drop_table(Table::drop().table(PackageGroup::Table).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(PackageReplaces::Table).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(PackageConflicts::Table).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(PackageProvides::Table).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(PackageDepends::Table).to_owned()) + .await?; manager .drop_table(Table::drop().table(Package::Table).to_owned()) .await?; @@ -141,3 +301,39 @@ pub enum PackageLicense { PackageId, Value, } + +#[derive(Iden)] +pub enum PackageGroup { + Table, + PackageId, + Value, +} + +#[derive(Iden)] +pub enum PackageReplaces { + Table, + PackageId, + Value, +} + +#[derive(Iden)] +pub enum PackageConflicts { + Table, + PackageId, + Value, +} + +#[derive(Iden)] +pub enum PackageProvides { + Table, + PackageId, + Value, +} + +#[derive(Iden)] +pub enum PackageDepends { + Table, + PackageId, + Type, + Value, +} diff --git a/server/src/repo/manager.rs b/server/src/repo/manager.rs index 5846b5d..c288f30 100644 --- a/server/src/repo/manager.rs +++ b/server/src/repo/manager.rs @@ -136,6 +136,10 @@ impl RepoGroupManager { /// Add a package to the given repo, returning to what architectures the package was added. pub fn add_pkg(&mut self, repo: &str, pkg: &Package) -> io::Result<()> { + // TODO + // * if arch is "any", check if package doesn't already exist for other architecture + // * if arch isn't "any", check if package doesn't already exist for "any" architecture + // We first remove any existing version of the package self.remove_pkg(repo, &pkg.info.arch, &pkg.info.name, false)?; diff --git a/server/src/repo/mod.rs b/server/src/repo/mod.rs index d300ffe..b2c2fec 100644 --- a/server/src/repo/mod.rs +++ b/server/src/repo/mod.rs @@ -5,7 +5,9 @@ pub use manager::RepoGroupManager; use std::path::PathBuf; -use crate::db::entities::{package as db_package, repo as db_repo}; +use crate::db::entities::{ + package as db_package, package_license as db_package_license, repo as db_repo, +}; use axum::body::Body; use axum::extract::{BodyStream, Path, State}; use axum::http::Request; @@ -159,10 +161,18 @@ async fn post_package_archive( } // Insert the package's data into the database - let mut model: db_package::ActiveModel = pkg.into(); + let mut model: db_package::ActiveModel = pkg.clone().into(); model.repo_id = sea_orm::Set(repo_id); - model.insert(&global.db).await?; + let pkg_entry = model.insert(&global.db).await?; + db_package_license::Entity::insert_many(pkg.info.licenses.iter().map(|s| { + db_package_license::ActiveModel { + package_id: sea_orm::Set(pkg_entry.id), + value: sea_orm::Set(s.to_string()), + } + })) + .exec(&global.db) + .await?; Ok(()) } diff --git a/server/src/repo/package.rs b/server/src/repo/package.rs index ddafbd9..18c69c3 100644 --- a/server/src/repo/package.rs +++ b/server/src/repo/package.rs @@ -11,7 +11,7 @@ use crate::db::entities::package; const IGNORED_FILES: [&str; 5] = [".BUILDINFO", ".INSTALL", ".MTREE", ".PKGINFO", ".CHANGELOG"]; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Package { pub path: PathBuf, pub info: PkgInfo, @@ -19,7 +19,7 @@ pub struct Package { pub compression: ReadFilter, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct PkgInfo { pub base: String, pub name: String, From 428bf6ec4ccfc89952699bb560481d72e558f57c Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 4 Aug 2023 16:52:51 +0200 Subject: [PATCH 2/2] feat(server): start db query abstraction --- server/src/api/mod.rs | 52 ++++++++++++++--------------- server/src/api/pagination.rs | 8 ++++- server/src/cli.rs | 2 +- server/src/db/conn.rs | 61 ++++++++++++++++++++++++++++++++++ server/src/db/mod.rs | 64 ++++++++++++++++++++++++++++++++---- server/src/main.rs | 3 +- server/src/repo/mod.rs | 58 +++++++++++++------------------- 7 files changed, 176 insertions(+), 72 deletions(-) create mode 100644 server/src/db/conn.rs diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index abb67c3..184c1a4 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -4,16 +4,11 @@ use axum::extract::{Path, Query, State}; use axum::routing::get; use axum::Json; use axum::Router; -use sea_orm::entity::EntityTrait; -use sea_orm::query::QueryOrder; use sea_orm::ModelTrait; -use sea_orm::PaginatorTrait; use pagination::PaginatedResponse; -use crate::db::entities::package; -use crate::db::entities::package_license; -use crate::db::entities::repo; +use crate::db; pub fn router() -> Router { Router::new() @@ -26,22 +21,24 @@ pub fn router() -> Router { async fn get_repos( State(global): State, Query(pagination): Query, -) -> crate::Result>> { - let repos = repo::Entity::find() - .order_by_asc(repo::Column::Id) - .paginate(&global.db, pagination.per_page.unwrap_or(25)) - .fetch_page(pagination.page.unwrap_or(1) - 1) +) -> crate::Result>> { + let (total_pages, repos) = global + .db + .repos( + pagination.per_page.unwrap_or(25), + pagination.page.unwrap_or(1) - 1, + ) .await?; - - Ok(Json(pagination.res(repos))) + Ok(Json(pagination.res(total_pages, repos))) } async fn get_single_repo( State(global): State, Path(id): Path, -) -> crate::Result> { - let repo = repo::Entity::find_by_id(id) - .one(&global.db) +) -> crate::Result> { + let repo = global + .db + .repo(id) .await? .ok_or(axum::http::StatusCode::NOT_FOUND)?; @@ -51,20 +48,22 @@ async fn get_single_repo( async fn get_packages( State(global): State, Query(pagination): Query, -) -> crate::Result>> { - let pkgs = package::Entity::find() - .order_by_asc(package::Column::Id) - .paginate(&global.db, pagination.per_page.unwrap_or(25)) - .fetch_page(pagination.page.unwrap_or(1) - 1) +) -> crate::Result>> { + let (total_pages, pkgs) = global + .db + .packages( + pagination.per_page.unwrap_or(25), + pagination.page.unwrap_or(1) - 1, + ) .await?; - Ok(Json(pagination.res(pkgs))) + Ok(Json(pagination.res(total_pages, pkgs))) } #[derive(serde::Serialize)] pub struct PackageRes { #[serde(flatten)] - entry: package::Model, + entry: db::package::Model, licenses: Vec, } @@ -72,12 +71,13 @@ async fn get_single_package( State(global): State, Path(id): Path, ) -> crate::Result> { - let entry = package::Entity::find_by_id(id) - .one(&global.db) + let entry = global + .db + .package(id) .await? .ok_or(axum::http::StatusCode::NOT_FOUND)?; let licenses = entry - .find_related(package_license::Entity) + .find_related(db::PackageLicense) .all(&global.db) .await? .iter() diff --git a/server/src/api/pagination.rs b/server/src/api/pagination.rs index 376e06c..aa1e5cb 100644 --- a/server/src/api/pagination.rs +++ b/server/src/api/pagination.rs @@ -16,15 +16,21 @@ where { pub page: u64, pub per_page: u64, + pub total_pages: u64, pub count: usize, pub items: Vec, } impl Query { - pub fn res Serialize>(self, items: Vec) -> PaginatedResponse { + pub fn res Serialize>( + self, + total_pages: u64, + items: Vec, + ) -> PaginatedResponse { PaginatedResponse { page: self.page.unwrap_or(DEFAULT_PAGE), per_page: self.per_page.unwrap_or(DEFAULT_PER_PAGE), + total_pages, count: items.len(), items, } diff --git a/server/src/cli.rs b/server/src/cli.rs index 8b160d5..d1647dd 100644 --- a/server/src/cli.rs +++ b/server/src/cli.rs @@ -65,7 +65,7 @@ impl Cli { debug!("Connecting to database with URL {}", db_url); - let db = crate::db::init(db_url).await?; + let db = crate::db::RieterDb::connect(db_url).await?; // let db = crate::db::init("postgres://rieter:rieter@localhost:5432/rieter") // .await // .unwrap(); diff --git a/server/src/db/conn.rs b/server/src/db/conn.rs new file mode 100644 index 0000000..2756236 --- /dev/null +++ b/server/src/db/conn.rs @@ -0,0 +1,61 @@ +use super::RieterDb; +use sea_orm::{DbBackend, DbErr, ExecResult, QueryResult, Statement}; +use std::{future::Future, pin::Pin}; + +// Allows RieterDb objects to be passed to ORM functions +impl sea_orm::ConnectionTrait for RieterDb { + fn get_database_backend(&self) -> DbBackend { + self.conn.get_database_backend() + } + fn execute<'life0, 'async_trait>( + &'life0 self, + stmt: Statement, + ) -> Pin> + Send + 'async_trait>> + where + Self: 'async_trait, + 'life0: 'async_trait, + { + self.conn.execute(stmt) + } + fn execute_unprepared<'life0, 'life1, 'async_trait>( + &'life0 self, + sql: &'life1 str, + ) -> Pin> + Send + 'async_trait>> + where + Self: 'async_trait, + 'life0: 'async_trait, + 'life1: 'async_trait, + { + self.conn.execute_unprepared(sql) + } + fn query_one<'life0, 'async_trait>( + &'life0 self, + stmt: Statement, + ) -> Pin< + Box< + dyn Future, DbErr>> + + Send + + 'async_trait, + >, + > + where + Self: 'async_trait, + 'life0: 'async_trait, + { + self.conn.query_one(stmt) + } + fn query_all<'life0, 'async_trait>( + &'life0 self, + stmt: Statement, + ) -> Pin< + Box< + dyn Future, DbErr>> + Send + 'async_trait, + >, + > + where + Self: 'async_trait, + 'life0: 'async_trait, + { + self.conn.query_all(stmt) + } +} diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index 4e0e804..02c4284 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -1,17 +1,69 @@ +mod conn; pub mod entities; mod migrator; use migrator::Migrator; +use sea_orm::ColumnTrait; use sea_orm::ConnectOptions; use sea_orm::Database; +use sea_orm::DatabaseConnection; +use sea_orm::EntityTrait; +use sea_orm::PaginatorTrait; +use sea_orm::QueryFilter; +use sea_orm::QueryOrder; use sea_orm_migration::MigratorTrait; -pub async fn init>( - opt: C, -) -> Result { - let db = Database::connect(opt).await?; +pub use entities::prelude::*; +pub use entities::*; - Migrator::up(&db, None).await?; +type Result = std::result::Result; - Ok(db) +#[derive(Clone, Debug)] +pub struct RieterDb { + pub conn: DatabaseConnection, +} + +impl RieterDb { + pub async fn connect>(opt: C) -> Result { + let db = Database::connect(opt).await?; + + Migrator::up(&db, None).await?; + + Ok(Self { conn: db }) + } + + pub async fn repos(&self, per_page: u64, page: u64) -> Result<(u64, Vec)> { + let paginator = Repo::find() + .order_by_asc(repo::Column::Id) + .paginate(&self.conn, per_page); + let repos = paginator.fetch_page(page).await?; + let total_pages = paginator.num_pages().await?; + + Ok((total_pages, repos)) + } + + pub async fn repo(&self, id: i32) -> Result> { + repo::Entity::find_by_id(id).one(&self.conn).await + } + + pub async fn repo_by_name(&self, name: &str) -> Result> { + Repo::find() + .filter(repo::Column::Name.eq(name)) + .one(&self.conn) + .await + } + + pub async fn packages(&self, per_page: u64, page: u64) -> Result<(u64, Vec)> { + let paginator = Package::find() + .order_by_asc(package::Column::Id) + .paginate(&self.conn, per_page); + let packages = paginator.fetch_page(page).await?; + let total_pages = paginator.num_pages().await?; + + Ok((total_pages, packages)) + } + + pub async fn package(&self, id: i32) -> Result> { + package::Entity::find_by_id(id).one(&self.conn).await + } } diff --git a/server/src/main.rs b/server/src/main.rs index c2b0eaf..fc5c110 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,7 +7,6 @@ mod repo; use clap::Parser; pub use error::{Result, ServerError}; use repo::RepoGroupManager; -use sea_orm::DatabaseConnection; use std::path::PathBuf; use std::sync::{Arc, RwLock}; @@ -23,7 +22,7 @@ pub struct Config { pub struct Global { config: Config, repo_manager: Arc>, - db: DatabaseConnection, + db: db::RieterDb, } #[tokio::main] diff --git a/server/src/repo/mod.rs b/server/src/repo/mod.rs index b2c2fec..8ab2b89 100644 --- a/server/src/repo/mod.rs +++ b/server/src/repo/mod.rs @@ -5,9 +5,7 @@ pub use manager::RepoGroupManager; use std::path::PathBuf; -use crate::db::entities::{ - package as db_package, package_license as db_package_license, repo as db_repo, -}; +use crate::db; use axum::body::Body; use axum::extract::{BodyStream, Path, State}; use axum::http::Request; @@ -129,30 +127,27 @@ async fn post_package_archive( 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 = db_repo::Entity::find() - .filter(db_repo::Column::Name.eq(&repo)) - .one(&global.db) - .await?; + let res = global.db.repo_by_name(&repo).await?; let repo_id = if let Some(repo_entity) = res { repo_entity.id } else { - let model = db_repo::ActiveModel { + let model = db::repo::ActiveModel { name: sea_orm::Set(repo.clone()), ..Default::default() }; - db_repo::Entity::insert(model) + db::Repo::insert(model) .exec(&global.db) .await? .last_insert_id }; // If the package already exists in the database, we remove it first - let res = db_package::Entity::find() - .filter(db_package::Column::RepoId.eq(repo_id)) - .filter(db_package::Column::Name.eq(&pkg.info.name)) - .filter(db_package::Column::Arch.eq(&pkg.info.arch)) + let res = db::Package::find() + .filter(db::package::Column::RepoId.eq(repo_id)) + .filter(db::package::Column::Name.eq(&pkg.info.name)) + .filter(db::package::Column::Arch.eq(&pkg.info.arch)) .one(&global.db) .await?; @@ -161,12 +156,12 @@ async fn post_package_archive( } // Insert the package's data into the database - let mut model: db_package::ActiveModel = pkg.clone().into(); + let mut model: db::package::ActiveModel = pkg.clone().into(); model.repo_id = sea_orm::Set(repo_id); let pkg_entry = model.insert(&global.db).await?; - db_package_license::Entity::insert_many(pkg.info.licenses.iter().map(|s| { - db_package_license::ActiveModel { + db::PackageLicense::insert_many(pkg.info.licenses.iter().map(|s| { + db::package_license::ActiveModel { package_id: sea_orm::Set(pkg_entry.id), value: sea_orm::Set(s.to_string()), } @@ -197,10 +192,7 @@ async fn delete_repo( .await??; if repo_removed { - let res = db_repo::Entity::find() - .filter(db_repo::Column::Name.eq(&repo)) - .one(&global.db) - .await?; + let res = global.db.repo_by_name(&repo).await?; if let Some(repo_entry) = res { repo_entry.delete(&global.db).await?; @@ -231,16 +223,13 @@ async fn delete_arch_repo( .await??; if repo_removed { - let res = db_repo::Entity::find() - .filter(db_repo::Column::Name.eq(&repo)) - .one(&global.db) - .await?; + let res = global.db.repo_by_name(&repo).await?; if let Some(repo_entry) = res { // Also remove all packages for that architecture from database - db_package::Entity::delete_many() - .filter(db_package::Column::RepoId.eq(repo_entry.id)) - .filter(db_package::Column::Arch.eq(&arch)) + db::Package::delete_many() + .filter(db::package::Column::RepoId.eq(repo_entry.id)) + .filter(db::package::Column::Arch.eq(&arch)) .exec(&global.db) .await?; } @@ -265,18 +254,15 @@ async fn delete_package( .await??; if let Some((name, version, release, arch)) = res { - let res = db_repo::Entity::find() - .filter(db_repo::Column::Name.eq(&repo)) - .one(&global.db) - .await?; + let res = global.db.repo_by_name(&repo).await?; if let Some(repo_entry) = res { // Also remove entry from database - let res = db_package::Entity::find() - .filter(db_package::Column::RepoId.eq(repo_entry.id)) - .filter(db_package::Column::Name.eq(name)) - .filter(db_package::Column::Version.eq(format!("{}-{}", version, release))) - .filter(db_package::Column::Arch.eq(arch)) + let res = db::Package::find() + .filter(db::package::Column::RepoId.eq(repo_entry.id)) + .filter(db::package::Column::Name.eq(name)) + .filter(db::package::Column::Version.eq(format!("{}-{}", version, release))) + .filter(db::package::Column::Arch.eq(arch)) .one(&global.db) .await?;