diff --git a/backend/src/api/add.rs b/backend/src/api/add.rs deleted file mode 100644 index ec0f868..0000000 --- a/backend/src/api/add.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::aur::aur::get_info_by_name; -use crate::builder::types::Action; -use crate::db::prelude::{Packages, Versions}; -use crate::db::{packages, versions}; -use rocket::response::status::BadRequest; -use rocket::serde::json::Json; -use rocket::serde::Deserialize; -use rocket::{post, State}; -use rocket_okapi::okapi::schemars; -use rocket_okapi::{openapi, JsonSchema}; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter}; -use sea_orm::{DatabaseConnection, Set}; -use tokio::sync::broadcast::Sender; - -#[derive(Deserialize, JsonSchema)] -#[serde(crate = "rocket::serde")] -pub struct AddBody { - name: String, - force_build: bool, -} - -#[openapi(tag = "test")] -#[post("/packages/add", data = "")] -pub async fn package_add( - db: &State, - input: Json, - tx: &State>, -) -> Result<(), BadRequest> { - let db = db as &DatabaseConnection; - - // remove leading and trailing whitespaces - let pkg_name = input.name.trim(); - - let pkg = get_info_by_name(pkg_name) - .await - .map_err(|_| BadRequest(Some("couldn't download package metadata".to_string())))?; - - let mut pkg_model = match Packages::find() - .filter(packages::Column::Name.eq(pkg_name)) - .one(db) - .await - .map_err(|e| BadRequest(Some(e.to_string())))? - { - None => { - let new_package = packages::ActiveModel { - name: Set(pkg_name.to_string()), - status: Set(3), - latest_aur_version: Set(pkg.version.clone()), - ..Default::default() - }; - - new_package.save(db).await.expect("TODO: panic message") - } - Some(p) => p.into(), - }; - - let version_model = match Versions::find() - .filter(versions::Column::Version.eq(pkg.version.clone())) - .one(db) - .await - .map_err(|e| BadRequest(Some(e.to_string())))? - { - None => { - let new_version = versions::ActiveModel { - version: Set(pkg.version.clone()), - package_id: Set(pkg_model.id.clone().unwrap()), - ..Default::default() - }; - - new_version.save(db).await.expect("TODO: panic message") - } - Some(p) => { - // todo add check if this version was successfully built - // if not allow build - if input.force_build { - p.into() - } else { - return Err(BadRequest(Some("Version already existing".to_string()))); - } - } - }; - - pkg_model.status = Set(3); - pkg_model.latest_version_id = Set(Some(version_model.id.clone().unwrap())); - pkg_model.save(db).await.expect("todo error message"); - - let _ = tx.send(Action::Build( - pkg.name, - pkg.version, - pkg.url_path.unwrap(), - version_model, - )); - - Ok(()) -} diff --git a/backend/src/api/backend.rs b/backend/src/api/backend.rs index c568d9f..cedb98d 100644 --- a/backend/src/api/backend.rs +++ b/backend/src/api/backend.rs @@ -1,16 +1,20 @@ -use crate::api::add::okapi_add_operation_for_package_add_; -use crate::api::add::package_add; +use crate::api::list::build_output; use crate::api::list::okapi_add_operation_for_get_build_; -use crate::api::list::okapi_add_operation_for_get_package_; use crate::api::list::okapi_add_operation_for_stats_; -use crate::api::list::{build_output, okapi_add_operation_for_package_list_}; -use crate::api::list::{get_build, get_package, okapi_add_operation_for_list_builds_}; +use crate::api::list::search; +use crate::api::list::{get_build, okapi_add_operation_for_list_builds_}; use crate::api::list::{list_builds, okapi_add_operation_for_search_}; use crate::api::list::{okapi_add_operation_for_build_output_, stats}; -use crate::api::list::{package_list, search}; -use crate::api::remove::okapi_add_operation_for_package_del_; +use crate::api::package::okapi_add_operation_for_get_package_; +use crate::api::package::okapi_add_operation_for_package_del_; +use crate::api::package::okapi_add_operation_for_package_list_; +use crate::api::package::okapi_add_operation_for_package_update_; +use crate::api::package::package_add; +use crate::api::package::{ + get_package, okapi_add_operation_for_package_add_, package_del, package_list, package_update, +}; use crate::api::remove::okapi_add_operation_for_version_del_; -use crate::api::remove::{package_del, version_del}; +use crate::api::remove::version_del; use rocket::Route; use rocket_okapi::openapi_get_routes; @@ -25,6 +29,7 @@ pub fn build_api() -> Vec { list_builds, stats, get_build, - get_package + get_package, + package_update ] } diff --git a/backend/src/api/list.rs b/backend/src/api/list.rs index dfd95c5..68a6e29 100644 --- a/backend/src/api/list.rs +++ b/backend/src/api/list.rs @@ -50,62 +50,6 @@ pub struct ListPackageModel { latest_aur_version: String, } -#[openapi(tag = "test")] -#[get("/packages/list?")] -pub async fn package_list( - db: &State, - limit: Option, -) -> Result>, NotFound> { - let db = db as &DatabaseConnection; - - let all: Vec = Packages::find() - .join_rev(JoinType::LeftJoin, versions::Relation::LatestPackage.def()) - .select_only() - .column(packages::Column::Name) - .column(packages::Column::Id) - .column(packages::Column::Status) - .column_as(packages::Column::OutOfDate, "outofdate") - .column_as(packages::Column::LatestAurVersion, "latest_aur_version") - .column_as(versions::Column::Version, "latest_version") - .column_as(packages::Column::LatestVersionId, "latest_version_id") - .order_by(packages::Column::Id, Order::Desc) - .limit(limit) - .into_model::() - .all(db) - .await - .map_err(|e| NotFound(e.to_string()))?; - - Ok(Json(all)) -} - -#[openapi(tag = "test")] -#[get("/package/")] -pub async fn get_package( - db: &State, - id: u64, -) -> Result, NotFound> { - let db = db as &DatabaseConnection; - - let all: ListPackageModel = Packages::find() - .join_rev(JoinType::LeftJoin, versions::Relation::LatestPackage.def()) - .filter(packages::Column::Id.eq(id)) - .select_only() - .column(packages::Column::Name) - .column(packages::Column::Id) - .column(packages::Column::Status) - .column_as(packages::Column::OutOfDate, "outofdate") - .column_as(packages::Column::LatestAurVersion, "latest_aur_version") - .column_as(versions::Column::Version, "latest_version") - .column_as(packages::Column::LatestVersionId, "latest_version_id") - .into_model::() - .one(db) - .await - .map_err(|e| NotFound(e.to_string()))? - .ok_or(NotFound("id not found".to_string()))?; - - Ok(Json(all)) -} - #[openapi(tag = "test")] #[get("/builds/output?&")] pub async fn build_output( diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 1de5bb2..1721aac 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,6 +1,6 @@ -mod add; pub mod backend; #[cfg(feature = "static")] pub mod embed; mod list; +mod package; mod remove; diff --git a/backend/src/api/package.rs b/backend/src/api/package.rs new file mode 100644 index 0000000..ed2f9c1 --- /dev/null +++ b/backend/src/api/package.rs @@ -0,0 +1,225 @@ +use crate::api::list::ListPackageModel; +use crate::aur::aur::get_info_by_name; +use crate::builder::types::Action; +use crate::db::migration::{JoinType, Order}; +use crate::db::prelude::{Packages, Versions}; +use crate::db::{packages, versions}; +use crate::repo::repo::remove_pkg; +use rocket::response::status::{BadRequest, NotFound}; +use rocket::serde::json::Json; +use rocket::serde::Deserialize; +use rocket::{get, post, State}; +use rocket_okapi::okapi::schemars; +use rocket_okapi::{openapi, JsonSchema}; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, RelationTrait, +}; +use sea_orm::{DatabaseConnection, Set}; +use tokio::sync::broadcast::Sender; + +#[derive(Deserialize, JsonSchema)] +#[serde(crate = "rocket::serde")] +pub struct AddBody { + name: String, +} + +#[openapi(tag = "Packages")] +#[post("/packages/add", data = "")] +pub async fn package_add( + db: &State, + input: Json, + tx: &State>, +) -> Result<(), BadRequest> { + let db = db as &DatabaseConnection; + + // remove leading and trailing whitespaces + let pkg_name = input.name.trim(); + + let pkg = get_info_by_name(pkg_name) + .await + .map_err(|_| BadRequest(Some("couldn't download package metadata".to_string())))?; + + if let None = Packages::find() + .filter(packages::Column::Name.eq(pkg_name)) + .one(db) + .await + .map_err(|e| BadRequest(Some(e.to_string())))? + { + return Err(BadRequest(Some("Package already exists".to_string()))); + } + + let mut new_package = packages::ActiveModel { + name: Set(pkg_name.to_string()), + status: Set(3), + latest_aur_version: Set(pkg.version.clone()), + ..Default::default() + }; + + new_package + .clone() + .save(db) + .await + .map_err(|e| BadRequest(Some(e.to_string())))?; + + let new_version = versions::ActiveModel { + version: Set(pkg.version.clone()), + package_id: Set(new_package.id.clone().unwrap()), + ..Default::default() + }; + + new_version + .clone() + .save(db) + .await + .map_err(|e| BadRequest(Some(e.to_string())))?; + + new_package.status = Set(3); + new_package.latest_version_id = Set(Some(new_version.id.clone().unwrap())); + new_package + .save(db) + .await + .map_err(|e| BadRequest(Some(e.to_string())))?; + + let _ = tx.send(Action::Build( + pkg.name, + pkg.version, + pkg.url_path.unwrap(), + new_version, + )); + + Ok(()) +} + +#[derive(Deserialize, JsonSchema)] +#[serde(crate = "rocket::serde")] +pub struct UpdateBody { + force: bool, +} + +#[openapi(tag = "Packages")] +#[post("/packages//update", data = "")] +pub async fn package_update( + db: &State, + id: i32, + input: Json, + tx: &State>, +) -> Result<(), BadRequest> { + let db = db as &DatabaseConnection; + + let mut pkg_model: packages::ActiveModel = Packages::find_by_id(id) + .one(db) + .await + .map_err(|e| BadRequest(Some(e.to_string())))? + .ok_or(BadRequest(Some("id not found".to_string())))? + .into(); + + let pkg = get_info_by_name(pkg_model.name.clone().unwrap().as_str()) + .await + .map_err(|_| BadRequest(Some("couldn't download package metadata".to_string())))?; + + let version_model = match Versions::find() + .filter(versions::Column::Version.eq(pkg.version.clone())) + .filter(versions::Column::PackageId.eq(pkg.id.clone())) + .one(db) + .await + .map_err(|e| BadRequest(Some(e.to_string())))? + { + None => { + let new_version = versions::ActiveModel { + version: Set(pkg.version.clone()), + package_id: Set(pkg_model.id.clone().unwrap()), + ..Default::default() + }; + + new_version.save(db).await.expect("TODO: panic message") + } + Some(p) => { + // todo add check if this version was successfully built + // if not allow build + if input.force { + p.into() + } else { + return Err(BadRequest(Some("Version already existing".to_string()))); + } + } + }; + + pkg_model.status = Set(3); + pkg_model.latest_version_id = Set(Some(version_model.id.clone().unwrap())); + pkg_model.save(db).await.expect("todo error message"); + + let _ = tx.send(Action::Build( + pkg.name, + pkg.version, + pkg.url_path.unwrap(), + version_model, + )); + + Ok(()) +} + +#[openapi(tag = "Packages")] +#[post("/package/delete/")] +pub async fn package_del(db: &State, id: i32) -> Result<(), String> { + let db = db as &DatabaseConnection; + + remove_pkg(db, id).await.map_err(|e| e.to_string())?; + + Ok(()) +} + +#[openapi(tag = "Packages")] +#[get("/packages/list?")] +pub async fn package_list( + db: &State, + limit: Option, +) -> Result>, NotFound> { + let db = db as &DatabaseConnection; + + let all: Vec = Packages::find() + .join_rev(JoinType::LeftJoin, versions::Relation::LatestPackage.def()) + .select_only() + .column(packages::Column::Name) + .column(packages::Column::Id) + .column(packages::Column::Status) + .column_as(packages::Column::OutOfDate, "outofdate") + .column_as(packages::Column::LatestAurVersion, "latest_aur_version") + .column_as(versions::Column::Version, "latest_version") + .column_as(packages::Column::LatestVersionId, "latest_version_id") + .order_by(packages::Column::Id, Order::Desc) + .limit(limit) + .into_model::() + .all(db) + .await + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(all)) +} + +#[openapi(tag = "Packages")] +#[get("/package/")] +pub async fn get_package( + db: &State, + id: u64, +) -> Result, NotFound> { + let db = db as &DatabaseConnection; + + let all: ListPackageModel = Packages::find() + .join_rev(JoinType::LeftJoin, versions::Relation::LatestPackage.def()) + .filter(packages::Column::Id.eq(id)) + .select_only() + .column(packages::Column::Name) + .column(packages::Column::Id) + .column(packages::Column::Status) + .column_as(packages::Column::OutOfDate, "outofdate") + .column_as(packages::Column::LatestAurVersion, "latest_aur_version") + .column_as(versions::Column::Version, "latest_version") + .column_as(packages::Column::LatestVersionId, "latest_version_id") + .into_model::() + .one(db) + .await + .map_err(|e| NotFound(e.to_string()))? + .ok_or(NotFound("id not found".to_string()))?; + + Ok(Json(all)) +} diff --git a/backend/src/api/remove.rs b/backend/src/api/remove.rs index 96344e2..911449d 100644 --- a/backend/src/api/remove.rs +++ b/backend/src/api/remove.rs @@ -3,16 +3,6 @@ use rocket::{post, State}; use rocket_okapi::openapi; use sea_orm::DatabaseConnection; -#[openapi(tag = "test")] -#[post("/package/delete/")] -pub async fn package_del(db: &State, id: i32) -> Result<(), String> { - let db = db as &DatabaseConnection; - - remove_pkg(db, id).await.map_err(|e| e.to_string())?; - - Ok(()) -} - #[openapi(tag = "test")] #[post("/versions/delete/")] pub async fn version_del(db: &State, id: i32) -> Result<(), String> { diff --git a/backend/src/repo/repo.rs b/backend/src/repo/repo.rs index fcb773a..8805857 100644 --- a/backend/src/repo/repo.rs +++ b/backend/src/repo/repo.rs @@ -4,7 +4,10 @@ use crate::db::prelude::{Builds, Packages}; use crate::db::{builds, versions}; use crate::pkgbuild::build::build_pkgbuild; use anyhow::anyhow; -use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter}; +use sea_orm::{ + ColumnTrait, DatabaseConnection, DatabaseTransaction, EntityTrait, ModelTrait, QueryFilter, + TransactionTrait, +}; use std::fs; use std::process::Command; use tokio::sync::broadcast::Sender; @@ -46,46 +49,55 @@ pub async fn add_pkg( } pub async fn remove_pkg(db: &DatabaseConnection, pkg_id: i32) -> anyhow::Result<()> { + let txn = db.begin().await?; + let pkg = Packages::find_by_id(pkg_id) - .one(db) + .one(&txn) .await? .ok_or(anyhow!("id not found"))?; // remove build dir if available let _ = fs::remove_dir_all(format!("./builds/{}", pkg.name)); + // remove package db entry + pkg.clone().delete(&txn).await?; + let versions = Versions::find() .filter(versions::Column::PackageId.eq(pkg.id)) - .all(db) + .all(&txn) .await?; for v in versions { - rem_ver(db, v).await?; + rem_ver(&txn, v).await?; } // remove corresponding builds let builds = Builds::find() .filter(builds::Column::PkgId.eq(pkg.id)) - .all(db) + .all(&txn) .await?; for b in builds { - b.delete(db).await?; + b.delete(&txn).await?; } - // remove package db entry - pkg.delete(db).await?; + txn.commit().await?; Ok(()) } pub async fn remove_version(db: &DatabaseConnection, version_id: i32) -> anyhow::Result<()> { + let txn = db.begin().await?; + let version = Versions::find() .filter(versions::Column::PackageId.eq(version_id)) - .one(db) + .one(&txn) .await?; if let Some(version) = version { - rem_ver(db, version).await?; + rem_ver(&txn, version).await?; } + + txn.commit().await?; + Ok(()) } @@ -129,7 +141,7 @@ fn repo_remove(pkg_file_name: String) -> anyhow::Result<()> { Ok(()) } -async fn rem_ver(db: &DatabaseConnection, version: versions::Model) -> anyhow::Result<()> { +async fn rem_ver(db: &DatabaseTransaction, version: versions::Model) -> anyhow::Result<()> { if let Some(filename) = version.file_name.clone() { // so repo-remove only supports passing a package name and removing the whole package // it seems that repo-add removes an older version when called diff --git a/frontend/lib/api/packages.dart b/frontend/lib/api/packages.dart index 5d9010d..a0cd7c0 100644 --- a/frontend/lib/api/packages.dart +++ b/frontend/lib/api/packages.dart @@ -19,9 +19,15 @@ extension PackagesAPI on ApiClient { return package; } - Future addPackage({bool force = false, required String name}) async { + Future addPackage({required String name}) async { + final resp = + await getRawClient().post("/packages/add", data: {'name': name}); + print(resp.data); + } + + Future updatePackage({bool force = false, required int id}) async { final resp = await getRawClient() - .post("/packages/add", data: {'force_build': force, 'name': name}); + .post("/packages/$id/update", data: {'force': force}); print(resp.data); } diff --git a/frontend/lib/components/packages_table.dart b/frontend/lib/components/packages_table.dart index 29e13da..a901049 100644 --- a/frontend/lib/components/packages_table.dart +++ b/frontend/lib/components/packages_table.dart @@ -54,11 +54,19 @@ class PackagesTable extends StatelessWidget { DataCell(IconButton( icon: Icon( package.outofdate ? Icons.update : Icons.verified, - color: package.outofdate ? Color(0xFF6B43A4) : Color(0xFF0A6900), + color: package.outofdate + ? const Color(0xFF6B43A4) + : const Color(0xFF0A6900), ), onPressed: package.outofdate - ? () { - // todo open build info with logs + ? () async { + await API.updatePackage(id: package.id); + Provider.of(context, listen: false) + .refresh(context); + Provider.of(context, listen: false) + .refresh(context); + Provider.of(context, listen: false) + .refresh(context); } : null, )), diff --git a/frontend/lib/screens/package_screen.dart b/frontend/lib/screens/package_screen.dart index 83dd30f..eeb4790 100644 --- a/frontend/lib/screens/package_screen.dart +++ b/frontend/lib/screens/package_screen.dart @@ -56,18 +56,19 @@ class _PackageScreenState extends State { style: const TextStyle(fontSize: 32), ), ), - Container( - margin: const EdgeInsets.only(right: 15), - child: ElevatedButton( - onPressed: () async { - final confirmResult = - await showConfirmationDialog( - context, - "Delete Package", - "Are you sure to delete this Package?", - () async { - final succ = await API.deletePackage(pkg.id); - if (succ) { + Row( + children: [ + ElevatedButton( + onPressed: () async { + final confirmResult = + await showConfirmationDialog( + context, + "Force update Package", + "Are you sure to force an Package rebuild?", + () async { + await API.updatePackage( + force: true, id: pkg.id); + context.pop(); Provider.of(context, @@ -79,16 +80,51 @@ class _PackageScreenState extends State { Provider.of(context, listen: false) .refresh(context); - } - }, - () {}, - ); - }, - child: const Text( - "Delete", - style: TextStyle(color: Colors.redAccent), + }, + () {}, + ); + }, + child: const Text( + "Force Update", + style: TextStyle(color: Colors.yellowAccent), + ), ), - ), + ElevatedButton( + onPressed: () async { + final confirmResult = + await showConfirmationDialog( + context, + "Delete Package", + "Are you sure to delete this Package?", + () async { + final succ = + await API.deletePackage(pkg.id); + if (succ) { + context.pop(); + + Provider.of(context, + listen: false) + .refresh(context); + Provider.of(context, + listen: false) + .refresh(context); + Provider.of(context, + listen: false) + .refresh(context); + } + }, + () {}, + ); + }, + child: const Text( + "Delete", + style: TextStyle(color: Colors.redAccent), + ), + ), + SizedBox( + width: 15, + ) + ], ) ], ),