From 04abb53f4a5de47da0bbc2a94ae0b6757879926f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20K=C3=B6rber?= Date: Fri, 12 May 2023 00:31:08 +0200 Subject: [PATCH] update --- rust/src/components/home.rs | 4 +- rust/src/components/inventory.rs | 40 ++++++++++++++- rust/src/components/mod.rs | 3 +- rust/src/main.rs | 83 ++++++++++++++++++++++++++------ rust/src/models.rs | 58 ++++++++++++++++++++++ 5 files changed, 168 insertions(+), 20 deletions(-) diff --git a/rust/src/components/home.rs b/rust/src/components/home.rs index 8d0d7e9..9ac220f 100644 --- a/rust/src/components/home.rs +++ b/rust/src/components/home.rs @@ -9,10 +9,10 @@ impl Home { let doc: Markup = html!( div id="home" class={"p-8" "max-w-xl"} { p { - a href="/inventory" { "Inventory" } + a href="/inventory/" { "Inventory" } } p { - a href="/trips" { "Trips" } + a href="/trips/" { "Trips" } } } ); diff --git a/rust/src/components/inventory.rs b/rust/src/components/inventory.rs index f1e5cb7..904d1ad 100644 --- a/rust/src/components/inventory.rs +++ b/rust/src/components/inventory.rs @@ -162,6 +162,15 @@ impl InventoryItemList { @if items.is_empty() { p ."text-lg" ."text-center" ."py-5" ."text-gray-400" { "[Empty]" } } @else { + @if let Some(edit_item) = edit_item { + form + name="edit-item" + id="edit-item" + action=(format!("/inventory/item/{edit_item}/edit")) + target="_self" + method="post" + {} + } table ."table" ."table-auto" @@ -179,7 +188,36 @@ impl InventoryItemList { tbody { @for item in items { @if edit_item.map_or(false, |edit_item| edit_item == item.id) { - tr { td { (item.name.clone()) " is being edited" }} + tr ."h-10" { + td ."border" ."p-2" ."bg-blue-100" { + input ."w-full" + type="text" + id="edit-item-name" + name="edit-item-name" + form="edit-item" + value=(item.name) + {} + } + td ."border" ."p-2" ."bg-blue-100" { + input ."w-full" + type="number" + id="edit-item-weight" + name="edit-item-weight" + form="edit-item" + value=(item.weight) + {} + } + td ."border" ."p-2" ."bg-green-100" { + button type="submit" form="edit-item" { + span ."mdi" ."mdi-content-save" ."text-xl" {} + } + } + td ."border" ."p-2" ."bg-red-100" { + a href=(format!("/inventory/item/{id}/cancel", id = item.id)) { + span ."mdi" ."mdi-cancel" ."text-xl" {} + } + } + } } @else { tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" { td ."border" ."p-0" { diff --git a/rust/src/components/mod.rs b/rust/src/components/mod.rs index 122a035..51fbbf1 100644 --- a/rust/src/components/mod.rs +++ b/rust/src/components/mod.rs @@ -56,7 +56,8 @@ impl Root { }} { "Trips" } } } - div hx-boost="true" { + // div hx-boost="true" { + div { (body) } } diff --git a/rust/src/main.rs b/rust/src/main.rs index 6d8239c..21a0900 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -13,11 +13,12 @@ use sqlx::{ error::DatabaseError, query, sqlite::{SqliteConnectOptions, SqliteError, SqlitePoolOptions}, - Pool, Sqlite, + Pool, Row, Sqlite, }; use serde::Deserialize; +use futures::TryFutureExt; use futures::TryStreamExt; use uuid::{uuid, Uuid}; @@ -85,6 +86,8 @@ async fn main() -> Result<(), sqlx::Error> { .route("/inventory/item/", post(inventory_item_create)) .route("/inventory/category/:id", get(inventory_active)) .route("/inventory/item/:id/delete", get(inventory_item_delete)) + .route("/inventory/item/:id/edit", post(inventory_item_edit)) + .route("/inventory/item/:id/cancel", get(inventory_item_cancel)) // .route( // "/inventory/category/:id/items", // post(htmx_inventory_category_items), @@ -293,7 +296,7 @@ async fn inventory_item_delete( headers: HeaderMap, Path(id): Path, ) -> Result { - query( + let results = query( "DELETE FROM inventoryitems WHERE id = ?", ) @@ -302,21 +305,28 @@ async fn inventory_item_delete( .await .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; - Ok(Redirect::to( - headers - .get("referer") - .ok_or(( - StatusCode::BAD_REQUEST, - "no referer header found".to_string(), - ))? - .to_str() - .map_err(|e| { - ( + if results.rows_affected() == 0 { + Err(( + StatusCode::NOT_FOUND, + format!("item with id {id} not found", id = id), + )) + } else { + Ok(Redirect::to( + headers + .get("referer") + .ok_or(( StatusCode::BAD_REQUEST, - format!("referer could not be converted: {}", e), - ) - })?, - )) + "no referer header found".to_string(), + ))? + .to_str() + .map_err(|e| { + ( + StatusCode::BAD_REQUEST, + format!("referer could not be converted: {}", e), + ) + })?, + )) + } } // async fn htmx_inventory_category_items( @@ -360,3 +370,44 @@ async fn inventory_item_delete( // ), // )) // } +#[derive(Deserialize)] +struct EditItem { + #[serde(rename = "edit-item-name")] + name: String, + #[serde(rename = "edit-item-weight")] + weight: u32, +} + +async fn inventory_item_edit( + State(state): State, + Path(id): Path, + Form(edit_item): Form, +) -> Result { + let id = Item::update(&state.database_pool, id, &edit_item.name, edit_item.weight) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? + .ok_or(( + StatusCode::NOT_FOUND, + format!("item with id {id} not found", id = id), + ))?; + + Ok(Redirect::to(&format!("/inventory/category/{id}", id = id))) +} + +async fn inventory_item_cancel( + State(state): State, + Path(id): Path, +) -> Result { + let id = Item::find(&state.database_pool, id) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? + .ok_or(( + StatusCode::NOT_FOUND, + format!("item with id {id} not found", id = id), + ))?; + + Ok(Redirect::to(&format!( + "/inventory/category/{id}", + id = id.category_id + ))) +} diff --git a/rust/src/models.rs b/rust/src/models.rs index 40e6d09..8bf9b6a 100644 --- a/rust/src/models.rs +++ b/rust/src/models.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use sqlx::sqlite::SqlitePoolOptions; +use futures::TryFutureExt; use futures::TryStreamExt; pub enum Error { @@ -164,3 +165,60 @@ impl TryFrom for Item { }) } } + +impl Item { + pub async fn find(pool: &sqlx::Pool, id: Uuid) -> Result, Error> { + let item: Result, sqlx::Error> = sqlx::query( + "SELECT * FROM inventoryitems AS item + WHERE item.id = ?", + ) + .bind(id.to_string()) + .fetch_one(pool) + .map_ok(std::convert::TryInto::try_into) + .await; + + match item { + Err(e) => match e { + sqlx::Error::RowNotFound => Ok(None), + _ => Err(e.into()), + }, + Ok(v) => Ok(Some(v?)), + } + } + + pub async fn update( + pool: &sqlx::Pool, + id: Uuid, + name: &str, + weight: u32, + ) -> Result, Error> { + let id: Result, sqlx::Error> = sqlx::query( + "UPDATE inventoryitems AS item + SET + name = ?, + weight = ? + WHERE item.id = ? + RETURNING inventoryitems.category_id AS id + ", + ) + .bind(name) + .bind(weight) + .bind(id.to_string()) + .fetch_one(pool) + .map_ok(|row| { + let id: &str = row.try_get("id")?; + let uuid: Result = Uuid::try_parse(id); + let uuid: Result = uuid.map_err(|e| e.into()); + uuid + }) + .await; + + match id { + Err(e) => match e { + sqlx::Error::RowNotFound => Ok(None), + _ => Err(e.into()), + }, + Ok(v) => Ok(Some(v?)), + } + } +}