From c1f16ce0351e81d5e03ec248ed13c33a9bc03f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20K=C3=B6rber?= Date: Tue, 29 Aug 2023 21:34:00 +0200 Subject: [PATCH] more oob experiments --- rust/sqlx-data.json | 144 +++++++++++++++++++++--- rust/src/components/mod.rs | 1 + rust/src/components/trip/mod.rs | 11 +- rust/src/main.rs | 187 ++++++++++++++++++-------------- rust/src/models.rs | 148 +++++++++++++++++++++++-- 5 files changed, 377 insertions(+), 114 deletions(-) diff --git a/rust/sqlx-data.json b/rust/sqlx-data.json index 0fbb4a1..83f0e18 100644 --- a/rust/sqlx-data.json +++ b/rust/sqlx-data.json @@ -162,6 +162,24 @@ }, "query": "SELECT\n id,\n name,\n weight,\n description,\n category_id\n FROM inventory_items AS item\n WHERE item.id = ?" }, + "5fd2b986fcaafe93e816f9ed665b6319d120a3987dc5adca1e3c8634203f7533": { + "describe": { + "columns": [ + { + "name": "weight", + "ordinal": 0, + "type_info": "Int" + } + ], + "nullable": [ + true + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT COALESCE(MAX(i_item.weight), 0) as weight\n FROM inventory_items_categories as category\n INNER JOIN inventory_items as i_item\n ON i_item.category_id = category.id\n WHERE category_id = ?\n " + }, "68304c19a0bee12c0b3ce9740d53389620b20e47973b41975678dbd13bd30c7f": { "describe": { "columns": [], @@ -386,24 +404,6 @@ }, "query": "INSERT INTO trips\n (id, name, date_start, date_end, state)\n VALUES\n (?, ?, ?, ?, ?)" }, - "9c4c2eb24747c3d8157ad17bacbe074d906d2891e8c68160becd8e10d045cfbc": { - "describe": { - "columns": [ - { - "name": "weight", - "ordinal": 0, - "type_info": "Int" - } - ], - "nullable": [ - true - ], - "parameters": { - "Right": 1 - } - }, - "query": "\n SELECT COALESCE(MAX(i_item.weight), 0) as weight\n FROM inventory_items_categories as category\n INNER JOIN inventory_items as i_item\n ON i_item.category_id = category.id\n WHERE category_id = (\n SELECT category_id\n FROM inventory_items\n WHERE inventory_items.id = ?\n )\n " - }, "a81bcbeb11260e3b4363e19c26b71b489e326b08bfacb6e11b4c4fc068dc7806": { "describe": { "columns": [ @@ -444,6 +444,114 @@ }, "query": "DELETE FROM inventory_items\n WHERE id = ?" }, + "cc1ad49669cff7f89975abfab3d0a8caef2e3978c826e1877db91c05a7f9d00d": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "name", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 2, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + true + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT\n id,\n name,\n description\n FROM inventory_items_categories AS category\n WHERE category.id = ?" + }, + "d0562ad92782a6ad6080c0535749c4a0a28fa78a17698933bce670db057e2628": { + "describe": { + "columns": [ + { + "name": "category_id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "category_name", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "category_description", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "trip_id", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "item_id", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "item_name", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "item_description", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "item_weight", + "ordinal": 7, + "type_info": "Int64" + }, + { + "name": "item_is_picked", + "ordinal": 8, + "type_info": "Bool" + }, + { + "name": "item_is_packed", + "ordinal": 9, + "type_info": "Bool" + }, + { + "name": "item_is_new", + "ordinal": 10, + "type_info": "Bool" + } + ], + "nullable": [ + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true + ], + "parameters": { + "Right": 2 + } + }, + "query": "\n SELECT\n category.id as category_id,\n category.name as category_name,\n category.description AS category_description,\n inner.trip_id AS trip_id,\n inner.item_id AS item_id,\n inner.item_name AS item_name,\n inner.item_description AS item_description,\n inner.item_weight AS item_weight,\n inner.item_is_picked AS item_is_picked,\n inner.item_is_packed AS item_is_packed,\n inner.item_is_new AS item_is_new\n FROM inventory_items_categories AS category\n LEFT JOIN (\n SELECT\n trip.trip_id AS trip_id,\n category.id as category_id,\n category.name as category_name,\n category.description as category_description,\n item.id as item_id,\n item.name as item_name,\n item.description as item_description,\n item.weight as item_weight,\n trip.pick as item_is_picked,\n trip.pack as item_is_packed,\n trip.new as item_is_new\n FROM trips_items as trip\n INNER JOIN inventory_items as item\n ON item.id = trip.item_id\n INNER JOIN inventory_items_categories as category\n ON category.id = item.category_id\n WHERE trip.trip_id = ?\n ) AS inner\n ON inner.category_id = category.id\n WHERE category.id = ?\n " + }, "ded3be1c8894a64e3b5f749461db7261d9224abb8a54da980db8262733d08205": { "describe": { "columns": [], diff --git a/rust/src/components/mod.rs b/rust/src/components/mod.rs index 38c123a..a204286 100644 --- a/rust/src/components/mod.rs +++ b/rust/src/components/mod.rs @@ -30,6 +30,7 @@ impl Root { link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" {} link rel="shortcut icon" type="image/svg+xml" href="/favicon.svg" {} script { (PreEscaped(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js")))) } + meta name="htmx-config" content=r#"{"useTemplateFragments":true}"# {} } body hx-boost="true" diff --git a/rust/src/components/trip/mod.rs b/rust/src/components/trip/mod.rs index df21135..fd11ab8 100644 --- a/rust/src/components/trip/mod.rs +++ b/rust/src/components/trip/mod.rs @@ -745,12 +745,14 @@ impl TripCategoryListRow { pub fn build( category: &TripCategory, active: bool, - has_new_items: bool, biggest_category_weight: i64, + htmx_swap: bool, ) -> Markup { + let has_new_items = category.items.as_ref().unwrap().iter().any(|item| item.new); html!( tr id={"category-" (category.category.id)} + hx-swap-oob=[htmx_swap.then_some("outerHTML")] ."h-10" ."hover:bg-purple-100" ."m-3" @@ -872,9 +874,8 @@ impl TripCategoryList { } tbody { @for category in trip.categories() { - @let has_new_items = category.items.as_ref().unwrap().iter().any(|item| item.new); @let active = state.active_category_id.map_or(false, |id| category.category.id == id); - (TripCategoryListRow::build(category, active, has_new_items, biggest_category_weight)) + (TripCategoryListRow::build(category, active, biggest_category_weight,false)) } tr ."h-10" ."hover:bg-purple-200" ."bg-gray-300" ."font-bold" { td ."border" ."p-0" ."m-0" { @@ -922,8 +923,8 @@ impl TripItemList { { thead ."bg-gray-200" { tr ."h-10" { - th ."border" ."p-2" { "Take?" } - th ."border" ."p-2" { "Packed?" } + th ."border" ."p-2" {} + th ."border" ."p-2" {} th ."border" ."p-2" ."w-1/2" { "Name" } th ."border" ."p-2" ."w-1/4" { "Weight" } } diff --git a/rust/src/main.rs b/rust/src/main.rs index 631c768..42234ba 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -114,54 +114,53 @@ async fn main() -> Result<(), sqlx::Error> { .route("/favicon.svg", get(icon_handler)) .route("/assets/luggage.svg", get(icon_handler)) .route("/", get(root)) - .route("/trips/", get(trips)) - .route("/trips/types/", get(trips_types).post(trip_type_create)) - .route( - "/trips/types/:id/edit/name/submit", - post(trips_types_edit_name), + .nest( + "/trips/", + Router::new() + .route("/", get(trips)) + .route("/trips/types/", get(trips_types).post(trip_type_create)) + .route("/types/:id/edit/name/submit", post(trips_types_edit_name)) + .route("/", post(trip_create)) + .route("/:id/", get(trip)) + .route("/:id/comment/submit", post(trip_comment_set)) + .route("/:id/state/:id", post(trip_state_set)) + .route("/:id/type/:id/add", get(trip_type_add)) + .route("/:id/type/:id/remove", get(trip_type_remove)) + .route("/:id/edit/:attribute/submit", post(trip_edit_attribute)) + .route( + "/:id/items/:id/pick", + get(trip_item_set_pick).post(trip_item_set_pick_htmx), + ) + .route( + "/:id/items/:id/unpick", + get(trip_item_set_unpick).post(trip_item_set_unpick_htmx), + ) + .route( + "/:id/items/:id/pack", + get(trip_item_set_pack).post(trip_item_set_pack_htmx), + ) + .route( + "/:id/items/:id/unpack", + get(trip_item_set_unpack).post(trip_item_set_unpack_htmx), + ), ) - .route("/trips/", post(trip_create)) - .route("/trips/:id/", get(trip)) - .route("/trips/:id/comment/submit", post(trip_comment_set)) - .route("/trips/:id/state/:id", post(trip_state_set)) - .route("/trips/:id/type/:id/add", get(trip_type_add)) - .route("/trips/:id/type/:id/remove", get(trip_type_remove)) - .route( - "/trips/:id/edit/:attribute/submit", - post(trip_edit_attribute), + .nest( + "/inventory/", + Router::new() + .route("/", get(inventory_inactive)) + .route("/category/", post(inventory_category_create)) + .route("/item/:id/", get(inventory_item)) + .route("/item/", post(inventory_item_create)) + .route("/item/name/validate", post(inventory_item_validate_name)) + .route("/category/:id/", get(inventory_active)) + .route("/item/:id/delete", get(inventory_item_delete)) + .route("/item/:id/edit", post(inventory_item_edit)) + .route("/item/:id/cancel", get(inventory_item_cancel)), // .route( + // "/inventory/category/:id/items", + // post(htmx_inventory_category_items), + // ); ) - .route( - "/trips/:id/items/:id/pick", - get(trip_item_set_pick).post(trip_item_set_pick_htmx), - ) - .route( - "/trips/:id/items/:id/unpick", - get(trip_item_set_unpick).post(trip_item_set_unpick_htmx), - ) - .route( - "/trips/:id/items/:id/pack", - get(trip_item_set_pack).post(trip_item_set_pack_htmx), - ) - .route( - "/trips/:id/items/:id/unpack", - get(trip_item_set_unpack).post(trip_item_set_unpack_htmx), - ) - .route("/inventory/", get(inventory_inactive)) - .route("/inventory/category/", post(inventory_category_create)) - .route("/inventory/item/:id/", get(inventory_item)) - .route("/inventory/item/", post(inventory_item_create)) - .route( - "/inventory/item/name/validate", - post(inventory_item_validate_name), - ) - .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), - // ); + .fallback(|| async { (StatusCode::NOT_FOUND, "not found") }) .with_state(state); let args = Args::parse(); @@ -996,6 +995,65 @@ async fn trip_item_set_state( } } +async fn trip_row( + state: &AppState, + trip_id: Uuid, + item_id: Uuid, +) -> Result { + let item: TripItem = TripItem::find(&state.database_pool, trip_id, item_id) + .await + .map_err(|error| { + ( + StatusCode::BAD_REQUEST, + ErrorPage::build(&error.to_string()), + ) + })? + .ok_or_else(|| { + ( + StatusCode::NOT_FOUND, + ErrorPage::build(&format!( + "item with id {} not found for trip {}", + item_id, trip_id + )), + ) + })?; + + let item_row = components::trip::TripItemListRow::build( + trip_id, + &item, + Item::get_category_max_weight(&state.database_pool, item.item.category_id) + .await + .map_err(|error| { + ( + StatusCode::BAD_REQUEST, + ErrorPage::build(&error.to_string()), + ) + })?, + ); + + let category = TripCategory::find(&state.database_pool, trip_id, item.item.category_id) + .map_err(|error| { + ( + StatusCode::BAD_REQUEST, + ErrorPage::build(&error.to_string()), + ) + }) + .await? + .ok_or_else(|| { + ( + StatusCode::NOT_FOUND, + ErrorPage::build(&format!( + "category with id {} not found", + item.item.category_id + )), + ) + })?; + + let category_row = components::trip::TripCategoryListRow::build(&category, true, 0, true); + + Ok(html!((item_row)(category_row))) +} + async fn trip_item_set_pick( State(state): State, Path((trip_id, item_id)): Path<(Uuid, Uuid)>, @@ -1056,43 +1114,6 @@ async fn trip_item_set_unpick( )? } -async fn trip_row( - state: &AppState, - trip_id: Uuid, - item_id: Uuid, -) -> Result { - let item: TripItem = TripItem::find(&state.database_pool, trip_id, item_id) - .await - .map_err(|error| { - ( - StatusCode::BAD_REQUEST, - ErrorPage::build(&error.to_string()), - ) - })? - .ok_or_else(|| { - ( - StatusCode::NOT_FOUND, - ErrorPage::build(&format!( - "item with id {} not found for trip {}", - item_id, trip_id - )), - ) - })?; - - Ok(components::trip::TripItemListRow::build( - trip_id, - &item, - Item::get_category_max_weight(&state.database_pool, item.item.category_id) - .await - .map_err(|error| { - ( - StatusCode::BAD_REQUEST, - ErrorPage::build(&error.to_string()), - ) - })?, - )) -} - async fn trip_item_set_unpick_htmx( State(state): State, Path((trip_id, item_id)): Path<(Uuid, Uuid)>, diff --git a/rust/src/models.rs b/rust/src/models.rs index 755c39c..08c547a 100644 --- a/rust/src/models.rs +++ b/rust/src/models.rs @@ -214,6 +214,114 @@ impl TripCategory { .map(|item| item.item.weight) .sum() } + + pub async fn find( + pool: &sqlx::Pool, + trip_id: Uuid, + category_id: Uuid, + ) -> Result, Error> { + let mut category: Option = None; + + let trip_id_param = trip_id.to_string(); + let category_id_param = category_id.to_string(); + + sqlx::query!( + " + SELECT + category.id as category_id, + category.name as category_name, + category.description AS category_description, + inner.trip_id AS trip_id, + inner.item_id AS item_id, + inner.item_name AS item_name, + inner.item_description AS item_description, + inner.item_weight AS item_weight, + inner.item_is_picked AS item_is_picked, + inner.item_is_packed AS item_is_packed, + inner.item_is_new AS item_is_new + FROM inventory_items_categories AS category + LEFT JOIN ( + SELECT + trip.trip_id AS trip_id, + category.id as category_id, + category.name as category_name, + category.description as category_description, + item.id as item_id, + item.name as item_name, + item.description as item_description, + item.weight as item_weight, + trip.pick as item_is_picked, + trip.pack as item_is_packed, + trip.new as item_is_new + FROM trips_items as trip + INNER JOIN inventory_items as item + ON item.id = trip.item_id + INNER JOIN inventory_items_categories as category + ON category.id = item.category_id + WHERE trip.trip_id = ? + ) AS inner + ON inner.category_id = category.id + WHERE category.id = ? + ", + trip_id_param, + category_id_param + ) + .fetch(pool) + .map_ok(|row| -> Result<(), Error> { + match &category { + Some(_) => (), + None => { + category = Some(TripCategory { + category: Category { + id: Uuid::try_parse(&row.category_id)?, + name: row.category_name, + description: row.category_description, + items: None, + }, + items: None, + }) + } + }; + + match row.item_id { + None => { + // we have an empty (unused) category which has NULL values + // for the item_id column + category.as_mut().unwrap().items = Some(vec![]); + category.as_mut().unwrap().category.items = Some(vec![]); + } + Some(item_id) => { + let item = TripItem { + item: Item { + id: Uuid::try_parse(&item_id)?, + name: row.item_name.unwrap(), + description: row.item_description, + weight: row.item_weight.unwrap(), + category_id: category.as_ref().unwrap().category.id, + }, + picked: row.item_is_picked.unwrap(), + packed: row.item_is_packed.unwrap(), + new: row.item_is_new.unwrap(), + }; + + match &mut category.as_mut().unwrap().items { + None => category.as_mut().unwrap().items = Some(vec![item]), + Some(ref mut items) => items.push(item), + } + } + } + + Ok(()) + }) + .try_collect::>>() + .await? + .into_iter() + .collect::>()?; + + // this may be None if there are no results (which + // means that the category was not found) + Ok(category) + } } #[derive(Debug)] @@ -658,6 +766,34 @@ pub struct DbInventoryItemsRow { } impl<'a> Category { + // pub async fn find( + // pool: &sqlx::Pool, + // id: Uuid, + // ) -> Result, Error> { + // let id_param = id.to_string(); + // let item: Result, sqlx::Error> = sqlx::query_as!( + // DbCategoryRow, + // "SELECT + // id, + // name, + // description + // FROM inventory_items_categories AS category + // WHERE category.id = ?", + // id_param, + // ) + // .fetch_one(pool) + // .map_ok(|row| row.try_into()) + // .await; + + // match item { + // Err(e) => match e { + // sqlx::Error::RowNotFound => Ok(None), + // _ => Err(e.into()), + // }, + // Ok(v) => Ok(Some(v?)), + // } + // } + pub fn items(&'a self) -> &'a Vec { self.items .as_ref() @@ -787,22 +923,18 @@ impl Item { pub async fn get_category_max_weight( pool: &sqlx::Pool, - id: Uuid, + category_id: Uuid, ) -> Result { - let id_param = id.to_string(); + let category_id_param = category_id.to_string(); let weight: Result = sqlx::query!( " SELECT COALESCE(MAX(i_item.weight), 0) as weight FROM inventory_items_categories as category INNER JOIN inventory_items as i_item ON i_item.category_id = category.id - WHERE category_id = ( - SELECT category_id - FROM inventory_items - WHERE inventory_items.id = ? - ) + WHERE category_id = ? ", - id_param + category_id_param ) .fetch_one(pool) .map_ok(|row| {