schema stuff works
This commit is contained in:
7
rust/Cargo.lock
generated
7
rust/Cargo.lock
generated
@@ -220,6 +220,9 @@ name = "either"
|
|||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
@@ -1154,6 +1157,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlformat",
|
"sqlformat",
|
||||||
@@ -1175,9 +1179,12 @@ dependencies = [
|
|||||||
"dotenvy",
|
"dotenvy",
|
||||||
"either",
|
"either",
|
||||||
"heck",
|
"heck",
|
||||||
|
"hex",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-rt",
|
"sqlx-rt",
|
||||||
|
|||||||
@@ -39,7 +39,14 @@ features = [
|
|||||||
|
|
||||||
[dependencies.sqlx]
|
[dependencies.sqlx]
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "time"]
|
features = [
|
||||||
|
"runtime-tokio-rustls",
|
||||||
|
"sqlite",
|
||||||
|
"macros",
|
||||||
|
"time",
|
||||||
|
"offline",
|
||||||
|
"migrate",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies.futures]
|
[dependencies.futures]
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
|
|||||||
5
rust/build.rs
Normal file
5
rust/build.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// generated by `sqlx migrate build-script`
|
||||||
|
fn main() {
|
||||||
|
// trigger recompilation when a new migration is added
|
||||||
|
println!("cargo:rerun-if-changed=migrations");
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
CREATE TABLE "inventory_items" (
|
CREATE TABLE "inventory_items" (
|
||||||
id TEXT,
|
id VARCHAR(36) NOT NULL,
|
||||||
name TEXT,
|
name TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
weight INT,
|
weight INTEGER NOT NULL,
|
||||||
category_id TEXT,
|
category_id VARCHAR(36) NOT NULL,
|
||||||
FOREIGN KEY (category_id) REFERENCES inventory_items_categories(id));
|
PRIMARY KEY (id),
|
||||||
|
FOREIGN KEY (category_id) REFERENCES inventory_items_categories(id)
|
||||||
|
);
|
||||||
CREATE UNIQUE INDEX ux_unique ON inventory_items(name, category_id);
|
CREATE UNIQUE INDEX ux_unique ON inventory_items(name, category_id);
|
||||||
|
|
||||||
CREATE TABLE "inventory_items_categories" (
|
CREATE TABLE "inventory_items_categories" (
|
||||||
@@ -21,7 +23,7 @@ CREATE TABLE "trips" (
|
|||||||
date_start DATE NOT NULL,
|
date_start DATE NOT NULL,
|
||||||
date_end DATE NOT NULL,
|
date_end DATE NOT NULL,
|
||||||
location TEXT,
|
location TEXT,
|
||||||
state VARCHAR(8) NOT NULL DEFAULT "Planning",
|
state VARCHAR(8) NOT NULL,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
temp_min INTEGER,
|
temp_min INTEGER,
|
||||||
temp_max INTEGER,
|
temp_max INTEGER,
|
||||||
@@ -29,7 +31,6 @@ CREATE TABLE "trips" (
|
|||||||
UNIQUE (name)
|
UNIQUE (name)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE "trips_types" (
|
CREATE TABLE "trips_types" (
|
||||||
id VARCHAR(36) NOT NULL,
|
id VARCHAR(36) NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
@@ -45,7 +46,6 @@ CREATE TABLE "trips_to_trips_types" (
|
|||||||
FOREIGN KEY(trip_type_id) REFERENCES "trips_types" (id)
|
FOREIGN KEY(trip_type_id) REFERENCES "trips_types" (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE trips_items (
|
CREATE TABLE trips_items (
|
||||||
item_id VARCHAR(36) NOT NULL,
|
item_id VARCHAR(36) NOT NULL,
|
||||||
trip_id VARCHAR(36) NOT NULL,
|
trip_id VARCHAR(36) NOT NULL,
|
||||||
|
|||||||
7
rust/rebuild-schema-data.sh
Executable file
7
rust/rebuild-schema-data.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
db="$(mktemp)"
|
||||||
|
|
||||||
|
export DATABASE_URL="sqlite://${db}"
|
||||||
|
|
||||||
|
cargo sqlx database create
|
||||||
|
cargo sqlx migrate run
|
||||||
|
cargo sqlx prepare
|
||||||
439
rust/sqlx-data.json
Normal file
439
rust/sqlx-data.json
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
{
|
||||||
|
"db": "SQLite",
|
||||||
|
"0d341935886c28710302aec9d5d085b535ad54949b87793e98cbf3bd5d828a41": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "UPDATE inventory_items AS item\n SET\n name = ?,\n weight = ?\n WHERE item.id = ?\n RETURNING inventory_items.category_id AS id\n "
|
||||||
|
},
|
||||||
|
"10886f1ddebc2a11bd2f2cbd41bd5220cde17405e1210c792dda29ca100c01cb": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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 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 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 "
|
||||||
|
},
|
||||||
|
"18cbb2893df033f5f81f42097fcae7ee036405749a5d93f2ea1d79ba280dfd20": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "DELETE FROM trips_to_trips_types AS ttt\n WHERE ttt.trip_id = ?\n AND ttt.trip_type_id = ?\n "
|
||||||
|
},
|
||||||
|
"1f08e9bebf51aab9cabff2a5c79211233a686e9ef9f96ea5c036fbba8f6b06d5": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "active",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT\n type.id as id,\n type.name as name,\n inner.id IS NOT NULL AS active\n FROM trips_types AS type\n LEFT JOIN (\n SELECT type.id as id, type.name as name\n FROM trips as trip\n INNER JOIN trips_to_trips_types as ttt\n ON ttt.trip_id = trip.id\n INNER JOIN trips_types AS type\n ON type.id == ttt.trip_type_id\n WHERE trip.id = ?\n ) AS inner\n ON inner.id = type.id\n "
|
||||||
|
},
|
||||||
|
"4d377bb01af6bbbca637d8c61326c84e8b05b1e570199c464b593bdc81b3dba6": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO trips_to_trips_types\n (trip_id, trip_type_id) VALUES (?, ?)"
|
||||||
|
},
|
||||||
|
"6973cceeb5499216475136b320b25e1355974e1213829d931abdd6b7a1448a87": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "weight",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category_id",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n id,\n name,\n weight,\n description,\n category_id\n FROM inventory_items\n WHERE category_id = ?"
|
||||||
|
},
|
||||||
|
"6e2928c8c2e66b15fc3f6f0ae4e8e0d4616b714fccbf273306d5135df31f4c19": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO inventory_items_categories\n (id, name)\n VALUES\n (?, ?)"
|
||||||
|
},
|
||||||
|
"7746dbbd63e69f7ec8ba5c1036e9ac03b83021be3189bb38ff31131cfbe99534": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO inventory_items\n (id, name, description, weight, category_id)\n VALUES\n (?, ?, ?, ?, ?)"
|
||||||
|
},
|
||||||
|
"7f23d9e4bb088de4123e93e5287c9743a417aab218d1d9484c0c6f3ac763f772": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "date_start",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "date_end",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "state",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "location",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "temp_min",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "temp_max",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "comment",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n id,\n name,\n CAST (date_start AS TEXT) date_start,\n CAST (date_end AS TEXT) date_end,\n state,\n location,\n temp_min,\n temp_max,\n comment\n FROM trips"
|
||||||
|
},
|
||||||
|
"88293d85c61e1eeaf9e46ada4154736b127c3bf305e92130de87b89ce7c6edab": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "UPDATE trips\n SET comment = ?\n WHERE id = ?"
|
||||||
|
},
|
||||||
|
"8f2499b0b98e3aa7d8c5925d7898406928572312b034d23ec96acbf19315a74e": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "date_start",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "date_end",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "state",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "location",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "temp_min",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "temp_max",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "comment",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n id,\n name,\n CAST (date_start AS TEXT) AS date_start,\n CAST (date_end AS TEXT) AS date_end,\n state,\n location,\n temp_min,\n temp_max,\n comment\n FROM trips\n WHERE id = ?"
|
||||||
|
},
|
||||||
|
"982720cc8c246f8cae25106b253394e5492ac968bd604a6fa7f848eee4174696": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO trips\n (id, name, date_start, date_end, state)\n VALUES\n (?, ?, ?, ?, ?)"
|
||||||
|
},
|
||||||
|
"a81bcbeb11260e3b4363e19c26b71b489e326b08bfacb6e11b4c4fc068dc7806": {
|
||||||
|
"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": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT id,name,description FROM inventory_items_categories"
|
||||||
|
},
|
||||||
|
"ab7c1b44121defb6c55291ef68958acfb9ba36a63cd7dd1286101d2e6c7065e0": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "DELETE FROM inventory_items\n WHERE id = ?"
|
||||||
|
},
|
||||||
|
"b916db63913aa222cef4552dffdcda0f26f16612fbb4c1e839bfd0162888fdc3": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "weight",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category_id",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT * FROM inventory_items AS item\n WHERE item.id = ?"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ pub struct InventoryCategoryList;
|
|||||||
|
|
||||||
impl InventoryCategoryList {
|
impl InventoryCategoryList {
|
||||||
pub fn build(state: &ClientState, categories: &Vec<Category>) -> Markup {
|
pub fn build(state: &ClientState, categories: &Vec<Category>) -> Markup {
|
||||||
let biggest_category_weight: u32 = categories
|
let biggest_category_weight: i64 = categories
|
||||||
.iter()
|
.iter()
|
||||||
.map(Category::total_weight)
|
.map(Category::total_weight)
|
||||||
.max()
|
.max()
|
||||||
@@ -115,8 +115,8 @@ impl InventoryCategoryList {
|
|||||||
format!(
|
format!(
|
||||||
"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
||||||
width=(
|
width=(
|
||||||
f64::from(category.total_weight())
|
(category.total_weight() as f64)
|
||||||
/ f64::from(biggest_category_weight)
|
/ (biggest_category_weight as f64)
|
||||||
* 100.0
|
* 100.0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -130,7 +130,7 @@ impl InventoryCategoryList {
|
|||||||
}
|
}
|
||||||
td ."border" ."p-0" ."m-0" {
|
td ."border" ."p-0" ."m-0" {
|
||||||
p ."p-2" ."m-2" {
|
p ."p-2" ."m-2" {
|
||||||
(categories.iter().map(Category::total_weight).sum::<u32>().to_string())
|
(categories.iter().map(Category::total_weight).sum::<i64>().to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +145,7 @@ pub struct InventoryItemList;
|
|||||||
|
|
||||||
impl InventoryItemList {
|
impl InventoryItemList {
|
||||||
pub fn build(state: &ClientState, items: &Vec<Item>) -> Markup {
|
pub fn build(state: &ClientState, items: &Vec<Item>) -> Markup {
|
||||||
let biggest_item_weight: u32 = items.iter().map(|item| item.weight).max().unwrap_or(1);
|
let biggest_item_weight: i64 = items.iter().map(|item| item.weight).max().unwrap_or(1);
|
||||||
html!(
|
html!(
|
||||||
div #items {
|
div #items {
|
||||||
@if items.is_empty() {
|
@if items.is_empty() {
|
||||||
@@ -267,7 +267,7 @@ impl InventoryItemList {
|
|||||||
position:absolute;
|
position:absolute;
|
||||||
left:0;
|
left:0;
|
||||||
bottom:0;
|
bottom:0;
|
||||||
right:0;", width=(f64::from(item.weight) / f64::from(biggest_item_weight) * 100.0))) {}
|
right:0;", width=((item.weight as f64) / (biggest_item_weight as f64) * 100.0))) {}
|
||||||
}
|
}
|
||||||
td
|
td
|
||||||
."border-none"
|
."border-none"
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ impl Root {
|
|||||||
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css";
|
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css";
|
||||||
script { (include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js"))) }
|
script { (include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js"))) }
|
||||||
}
|
}
|
||||||
body hx-boost="true" {
|
body
|
||||||
|
hx-boost="true"
|
||||||
|
{
|
||||||
header
|
header
|
||||||
."bg-gray-200"
|
."bg-gray-200"
|
||||||
."p-5"
|
."p-5"
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ pub struct TripInfoRow;
|
|||||||
impl TripInfoRow {
|
impl TripInfoRow {
|
||||||
pub fn build(
|
pub fn build(
|
||||||
name: &str,
|
name: &str,
|
||||||
value: impl std::fmt::Display,
|
value: Option<impl std::fmt::Display>,
|
||||||
attribute_key: TripAttribute,
|
attribute_key: TripAttribute,
|
||||||
edit_attribute: Option<&TripAttribute>,
|
edit_attribute: Option<&TripAttribute>,
|
||||||
input_type: InputType,
|
input_type: InputType,
|
||||||
@@ -303,7 +303,7 @@ impl TripInfoRow {
|
|||||||
id="new-value"
|
id="new-value"
|
||||||
name="new-value"
|
name="new-value"
|
||||||
form="edit-trip"
|
form="edit-trip"
|
||||||
value=(value)
|
value=(value.map_or("".to_string(), |v| v.to_string()))
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,7 +355,7 @@ impl TripInfoRow {
|
|||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
td ."border" ."p-2" { (name) }
|
td ."border" ."p-2" { (name) }
|
||||||
td ."border" ."p-2" { (value) }
|
td ."border" ."p-2" { (value.map_or("".to_string(), |v| v.to_string())) }
|
||||||
td
|
td
|
||||||
."border-none"
|
."border-none"
|
||||||
."bg-blue-100"
|
."bg-blue-100"
|
||||||
@@ -397,9 +397,9 @@ impl TripInfo {
|
|||||||
."w-full"
|
."w-full"
|
||||||
{
|
{
|
||||||
tbody {
|
tbody {
|
||||||
(TripInfoRow::build("Location", &trip.location, TripAttribute::Location, state.trip_edit_attribute.as_ref(), InputType::Text))
|
(TripInfoRow::build("Location", trip.location.as_ref(), TripAttribute::Location, state.trip_edit_attribute.as_ref(), InputType::Text))
|
||||||
(TripInfoRow::build("Start date", trip.date_start, TripAttribute::DateStart, state.trip_edit_attribute.as_ref(), InputType::Date))
|
(TripInfoRow::build("Start date", Some(trip.date_start), TripAttribute::DateStart, state.trip_edit_attribute.as_ref(), InputType::Date))
|
||||||
(TripInfoRow::build("End date", trip.date_end, TripAttribute::DateEnd, state.trip_edit_attribute.as_ref(), InputType::Date))
|
(TripInfoRow::build("End date", Some(trip.date_end), TripAttribute::DateEnd, state.trip_edit_attribute.as_ref(), InputType::Date))
|
||||||
(TripInfoRow::build("Temp (min)", trip.temp_min, TripAttribute::TempMin, state.trip_edit_attribute.as_ref(), InputType::Number))
|
(TripInfoRow::build("Temp (min)", trip.temp_min, TripAttribute::TempMin, state.trip_edit_attribute.as_ref(), InputType::Number))
|
||||||
(TripInfoRow::build("Temp (max)", trip.temp_max, TripAttribute::TempMax, state.trip_edit_attribute.as_ref(), InputType::Number))
|
(TripInfoRow::build("Temp (max)", trip.temp_max, TripAttribute::TempMax, state.trip_edit_attribute.as_ref(), InputType::Number))
|
||||||
tr .h-full {
|
tr .h-full {
|
||||||
@@ -587,7 +587,7 @@ impl TripCategoryList {
|
|||||||
pub fn build(state: &ClientState, trip: &models::Trip) -> Markup {
|
pub fn build(state: &ClientState, trip: &models::Trip) -> Markup {
|
||||||
let categories = trip.categories();
|
let categories = trip.categories();
|
||||||
|
|
||||||
let biggest_category_weight: u32 = categories
|
let biggest_category_weight: i64 = categories
|
||||||
.iter()
|
.iter()
|
||||||
.map(TripCategory::total_picked_weight)
|
.map(TripCategory::total_picked_weight)
|
||||||
.max()
|
.max()
|
||||||
@@ -655,8 +655,8 @@ impl TripCategoryList {
|
|||||||
format!(
|
format!(
|
||||||
"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
||||||
width=(
|
width=(
|
||||||
f64::from(category.total_picked_weight())
|
(category.total_picked_weight() as f64)
|
||||||
/ f64::from(biggest_category_weight)
|
/ (biggest_category_weight as f64)
|
||||||
* 100.0
|
* 100.0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -670,7 +670,7 @@ impl TripCategoryList {
|
|||||||
}
|
}
|
||||||
td ."border" ."p-0" ."m-0" {
|
td ."border" ."p-0" ."m-0" {
|
||||||
p ."p-2" ."m-2" {
|
p ."p-2" ."m-2" {
|
||||||
(categories.iter().map(TripCategory::total_picked_weight).sum::<u32>().to_string())
|
(categories.iter().map(TripCategory::total_picked_weight).sum::<i64>().to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -684,7 +684,7 @@ pub struct TripItemList;
|
|||||||
|
|
||||||
impl TripItemList {
|
impl TripItemList {
|
||||||
pub fn build(state: &ClientState, trip: &models::Trip, items: &Vec<TripItem>) -> Markup {
|
pub fn build(state: &ClientState, trip: &models::Trip, items: &Vec<TripItem>) -> Markup {
|
||||||
let biggest_item_weight: u32 = items.iter().map(|item| item.item.weight).max().unwrap_or(1);
|
let biggest_item_weight: i64 = items.iter().map(|item| item.item.weight).max().unwrap_or(1);
|
||||||
|
|
||||||
html!(
|
html!(
|
||||||
@if items.is_empty() {
|
@if items.is_empty() {
|
||||||
@@ -778,7 +778,7 @@ impl TripItemList {
|
|||||||
position:absolute;
|
position:absolute;
|
||||||
left:0;
|
left:0;
|
||||||
bottom:0;
|
bottom:0;
|
||||||
right:0;", width=(f64::from(item.item.weight) / f64::from(biggest_item_weight) * 100.0))) {}
|
right:0;", width=((item.item.weight as f64) / (biggest_item_weight as f64) * 100.0))) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
276
rust/src/main.rs
276
rust/src/main.rs
@@ -15,8 +15,8 @@ use serde_variant::to_variant_name;
|
|||||||
|
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
error::DatabaseError,
|
error::DatabaseError,
|
||||||
query,
|
query, query_as,
|
||||||
sqlite::{SqliteConnectOptions, SqliteError, SqlitePoolOptions},
|
sqlite::{SqliteConnectOptions, SqliteError, SqlitePoolOptions, SqliteRow},
|
||||||
Pool, Row, Sqlite,
|
Pool, Row, Sqlite,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,29 +170,32 @@ async fn inventory(
|
|||||||
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
||||||
state.client_state.active_category_id = active_id;
|
state.client_state.active_category_id = active_id;
|
||||||
|
|
||||||
let mut categories = query("SELECT id,name,description FROM inventory_items_categories")
|
let mut categories = query_as!(
|
||||||
.fetch(&state.database_pool)
|
DbCategoryRow,
|
||||||
.map_ok(std::convert::TryInto::try_into)
|
"SELECT id,name,description FROM inventory_items_categories"
|
||||||
.try_collect::<Vec<Result<Category, models::Error>>>()
|
)
|
||||||
.await
|
.fetch(&state.database_pool)
|
||||||
// we have two error handling lines here. these are distinct errors
|
.map_ok(|row: DbCategoryRow| row.try_into())
|
||||||
// this one is the SQL error that may arise during the query
|
.try_collect::<Vec<Result<Category, models::Error>>>()
|
||||||
.map_err(|e| {
|
.await
|
||||||
(
|
// we have two error handling lines here. these are distinct errors
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
// this one is the SQL error that may arise during the query
|
||||||
ErrorPage::build(&e.to_string()),
|
.map_err(|e| {
|
||||||
)
|
(
|
||||||
})?
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
.into_iter()
|
ErrorPage::build(&e.to_string()),
|
||||||
.collect::<Result<Vec<Category>, models::Error>>()
|
)
|
||||||
// and this one is the model mapping error that may arise e.g. during
|
})?
|
||||||
// reading of the rows
|
.into_iter()
|
||||||
.map_err(|e| {
|
.collect::<Result<Vec<Category>, models::Error>>()
|
||||||
(
|
// and this one is the model mapping error that may arise e.g. during
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
// reading of the rows
|
||||||
ErrorPage::build(&e.to_string()),
|
.map_err(|e| {
|
||||||
)
|
(
|
||||||
})?;
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
for category in &mut categories {
|
for category in &mut categories {
|
||||||
category
|
category
|
||||||
@@ -241,17 +244,21 @@ async fn inventory_item_create(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Form(new_item): Form<NewItem>,
|
Form(new_item): Form<NewItem>,
|
||||||
) -> Result<Redirect, (StatusCode, String)> {
|
) -> Result<Redirect, (StatusCode, String)> {
|
||||||
query(
|
let id = Uuid::new_v4();
|
||||||
|
let id_param = id.to_string();
|
||||||
|
let name = &new_item.name;
|
||||||
|
let category_id = new_item.category_id.to_string();
|
||||||
|
query!(
|
||||||
"INSERT INTO inventory_items
|
"INSERT INTO inventory_items
|
||||||
(id, name, description, weight, category_id)
|
(id, name, description, weight, category_id)
|
||||||
VALUES
|
VALUES
|
||||||
(?, ?, ?, ?, ?)",
|
(?, ?, ?, ?, ?)",
|
||||||
|
id_param,
|
||||||
|
name,
|
||||||
|
"",
|
||||||
|
new_item.weight,
|
||||||
|
category_id,
|
||||||
)
|
)
|
||||||
.bind(Uuid::new_v4().to_string())
|
|
||||||
.bind(&new_item.name)
|
|
||||||
.bind("")
|
|
||||||
.bind(new_item.weight)
|
|
||||||
.bind(new_item.category_id.to_string())
|
|
||||||
.execute(&state.database_pool)
|
.execute(&state.database_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
@@ -306,11 +313,12 @@ async fn inventory_item_delete(
|
|||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<Redirect, (StatusCode, String)> {
|
) -> Result<Redirect, (StatusCode, String)> {
|
||||||
let results = query(
|
let id_param = id.to_string();
|
||||||
|
let results = query!(
|
||||||
"DELETE FROM inventory_items
|
"DELETE FROM inventory_items
|
||||||
WHERE id = ?",
|
WHERE id = ?",
|
||||||
|
id_param,
|
||||||
)
|
)
|
||||||
.bind(id.to_string())
|
|
||||||
.execute(&state.database_pool)
|
.execute(&state.database_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
|
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
|
||||||
@@ -348,7 +356,7 @@ async fn inventory_item_delete(
|
|||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
// let items = query(&format!(
|
// let items = query!(&format!(
|
||||||
// //TODO bind this stuff!!!!!!! no sql injection pls
|
// //TODO bind this stuff!!!!!!! no sql injection pls
|
||||||
// "SELECT
|
// "SELECT
|
||||||
// i.id, i.name, i.description, i.weight, i.category_id
|
// i.id, i.name, i.description, i.weight, i.category_id
|
||||||
@@ -392,14 +400,29 @@ async fn inventory_item_edit(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
Form(edit_item): Form<EditItem>,
|
Form(edit_item): Form<EditItem>,
|
||||||
) -> Result<Redirect, (StatusCode, String)> {
|
) -> Result<Redirect, (StatusCode, Markup)> {
|
||||||
let id = Item::update(&state.database_pool, id, &edit_item.name, edit_item.weight)
|
let id = Item::update(
|
||||||
.await
|
&state.database_pool,
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
id,
|
||||||
.ok_or((
|
&edit_item.name,
|
||||||
StatusCode::NOT_FOUND,
|
i64::try_from(edit_item.weight).map_err(|e| {
|
||||||
format!("item with id {id} not found", id = id),
|
(
|
||||||
))?;
|
StatusCode::UNPROCESSABLE_ENTITY,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.ok_or((
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
ErrorPage::build(&format!("item with id {id} not found", id = id)),
|
||||||
|
))?;
|
||||||
|
|
||||||
Ok(Redirect::to(&format!("/inventory/category/{id}/", id = id)))
|
Ok(Redirect::to(&format!("/inventory/category/{id}/", id = id)))
|
||||||
}
|
}
|
||||||
@@ -437,16 +460,26 @@ async fn trip_create(
|
|||||||
Form(new_trip): Form<NewTrip>,
|
Form(new_trip): Form<NewTrip>,
|
||||||
) -> Result<Redirect, (StatusCode, String)> {
|
) -> Result<Redirect, (StatusCode, String)> {
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
query(
|
let id_param = id.to_string();
|
||||||
|
let date_start = new_trip
|
||||||
|
.date_start
|
||||||
|
.format(DATE_FORMAT)
|
||||||
|
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
|
||||||
|
let date_end = new_trip
|
||||||
|
.date_end
|
||||||
|
.format(DATE_FORMAT)
|
||||||
|
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
|
||||||
|
query!(
|
||||||
"INSERT INTO trips
|
"INSERT INTO trips
|
||||||
(id, name, date_start, date_end)
|
(id, name, date_start, date_end, state)
|
||||||
VALUES
|
VALUES
|
||||||
(?, ?, ?, ?)",
|
(?, ?, ?, ?, ?)",
|
||||||
|
id_param,
|
||||||
|
new_trip.name,
|
||||||
|
date_start,
|
||||||
|
date_end,
|
||||||
|
TripState::Planning,
|
||||||
)
|
)
|
||||||
.bind(id.to_string())
|
|
||||||
.bind(&new_trip.name)
|
|
||||||
.bind(new_trip.date_start)
|
|
||||||
.bind(new_trip.date_end)
|
|
||||||
.execute(&state.database_pool)
|
.execute(&state.database_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
@@ -482,35 +515,52 @@ async fn trip_create(
|
|||||||
),
|
),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Redirect::to(&format!("/trip/{id}/", id = id.to_string())))
|
Ok(Redirect::to(&format!("/trip/{id}/", id = id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn trips(
|
async fn trips(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
||||||
let trips: Vec<models::Trip> = query("SELECT * FROM trips")
|
tracing::info!("receiving trips");
|
||||||
.fetch(&state.database_pool)
|
|
||||||
.map_ok(std::convert::TryInto::try_into)
|
let trips: Vec<models::Trip> = query_as!(
|
||||||
.try_collect::<Vec<Result<models::Trip, models::Error>>>()
|
DbTripRow,
|
||||||
.await
|
"SELECT
|
||||||
// we have two error handling lines here. these are distinct errors
|
id,
|
||||||
// this one is the SQL error that may arise during the query
|
name,
|
||||||
.map_err(|e| {
|
CAST (date_start AS TEXT) date_start,
|
||||||
(
|
CAST (date_end AS TEXT) date_end,
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
state,
|
||||||
ErrorPage::build(&e.to_string()),
|
location,
|
||||||
)
|
temp_min,
|
||||||
})?
|
temp_max,
|
||||||
.into_iter()
|
comment
|
||||||
.collect::<Result<Vec<models::Trip>, models::Error>>()
|
FROM trips",
|
||||||
// and this one is the model mapping error that may arise e.g. during
|
)
|
||||||
// reading of the rows
|
.fetch(&state.database_pool)
|
||||||
.map_err(|e| {
|
.map_ok(|row| row.try_into())
|
||||||
(
|
.try_collect::<Vec<Result<models::Trip, models::Error>>>()
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
.await
|
||||||
ErrorPage::build(&e.to_string()),
|
// we have two error handling lines here. these are distinct errors
|
||||||
)
|
// this one is the SQL error that may arise during the query
|
||||||
})?;
|
.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Result<Vec<models::Trip>, models::Error>>()
|
||||||
|
// and this one is the model mapping error that may arise e.g. during
|
||||||
|
// reading of the rows
|
||||||
|
.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
tracing::info!("received trips");
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
@@ -532,20 +582,42 @@ async fn trip(
|
|||||||
state.client_state.trip_edit_attribute = trip_query.edit;
|
state.client_state.trip_edit_attribute = trip_query.edit;
|
||||||
state.client_state.active_category_id = trip_query.category;
|
state.client_state.active_category_id = trip_query.category;
|
||||||
|
|
||||||
let mut trip: models::Trip =
|
let id_param = id.to_string();
|
||||||
query("SELECT id,name,date_start,date_end,state,location,temp_min,temp_max,comment FROM trips WHERE id = ?")
|
let mut trip: models::Trip = query_as!(
|
||||||
.bind(id.to_string())
|
DbTripRow,
|
||||||
.fetch_one(&state.database_pool)
|
"SELECT
|
||||||
.map_ok(std::convert::TryInto::try_into)
|
id,
|
||||||
.await
|
name,
|
||||||
.map_err(|e: sqlx::Error| match e {
|
CAST (date_start AS TEXT) AS date_start,
|
||||||
sqlx::Error::RowNotFound => (
|
CAST (date_end AS TEXT) AS date_end,
|
||||||
StatusCode::NOT_FOUND,
|
state,
|
||||||
ErrorPage::build(&format!("trip with id {} not found", id)),
|
location,
|
||||||
),
|
temp_min,
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, ErrorPage::build(&e.to_string())),
|
temp_max,
|
||||||
})?
|
comment
|
||||||
.map_err(|e: Error| (StatusCode::INTERNAL_SERVER_ERROR, ErrorPage::build(&e.to_string())))?;
|
FROM trips
|
||||||
|
WHERE id = ?",
|
||||||
|
id_param,
|
||||||
|
)
|
||||||
|
.fetch_one(&state.database_pool)
|
||||||
|
.map_ok(|row| row.try_into())
|
||||||
|
.await
|
||||||
|
.map_err(|e: sqlx::Error| match e {
|
||||||
|
sqlx::Error::RowNotFound => (
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
ErrorPage::build(&format!("trip with id {} not found", id)),
|
||||||
|
),
|
||||||
|
_ => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
),
|
||||||
|
})?
|
||||||
|
.map_err(|e: Error| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
trip.load_trips_types(&state.database_pool)
|
trip.load_trips_types(&state.database_pool)
|
||||||
.await
|
.await
|
||||||
@@ -586,14 +658,16 @@ async fn trip_type_remove(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path((trip_id, type_id)): Path<(Uuid, Uuid)>,
|
Path((trip_id, type_id)): Path<(Uuid, Uuid)>,
|
||||||
) -> Result<Redirect, (StatusCode, Markup)> {
|
) -> Result<Redirect, (StatusCode, Markup)> {
|
||||||
let results = query(
|
let trip_id = trip_id.to_string();
|
||||||
|
let type_id = type_id.to_string();
|
||||||
|
let results = query!(
|
||||||
"DELETE FROM trips_to_trips_types AS ttt
|
"DELETE FROM trips_to_trips_types AS ttt
|
||||||
WHERE ttt.trip_id = ?
|
WHERE ttt.trip_id = ?
|
||||||
AND ttt.trip_type_id = ?
|
AND ttt.trip_type_id = ?
|
||||||
",
|
",
|
||||||
|
trip_id,
|
||||||
|
type_id
|
||||||
)
|
)
|
||||||
.bind(trip_id.to_string())
|
|
||||||
.bind(type_id.to_string())
|
|
||||||
.execute(&state.database_pool)
|
.execute(&state.database_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
|
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
|
||||||
@@ -612,12 +686,14 @@ async fn trip_type_add(
|
|||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path((trip_id, type_id)): Path<(Uuid, Uuid)>,
|
Path((trip_id, type_id)): Path<(Uuid, Uuid)>,
|
||||||
) -> Result<Redirect, (StatusCode, Markup)> {
|
) -> Result<Redirect, (StatusCode, Markup)> {
|
||||||
query(
|
let trip_id = trip_id.to_string();
|
||||||
|
let type_id = type_id.to_string();
|
||||||
|
query!(
|
||||||
"INSERT INTO trips_to_trips_types
|
"INSERT INTO trips_to_trips_types
|
||||||
(trip_id, trip_type_id) VALUES (?, ?)",
|
(trip_id, trip_type_id) VALUES (?, ?)",
|
||||||
|
trip_id,
|
||||||
|
type_id
|
||||||
)
|
)
|
||||||
.bind(trip_id.to_string())
|
|
||||||
.bind(type_id.to_string())
|
|
||||||
.execute(&state.database_pool)
|
.execute(&state.database_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
@@ -682,13 +758,14 @@ async fn trip_comment_set(
|
|||||||
Path(trip_id): Path<Uuid>,
|
Path(trip_id): Path<Uuid>,
|
||||||
Form(comment_update): Form<CommentUpdate>,
|
Form(comment_update): Form<CommentUpdate>,
|
||||||
) -> Result<Redirect, (StatusCode, Markup)> {
|
) -> Result<Redirect, (StatusCode, Markup)> {
|
||||||
let result = query(
|
let trip_id = trip_id.to_string();
|
||||||
|
let result = query!(
|
||||||
"UPDATE trips
|
"UPDATE trips
|
||||||
SET comment = ?
|
SET comment = ?
|
||||||
WHERE id = ?",
|
WHERE id = ?",
|
||||||
|
comment_update.new_comment,
|
||||||
|
trip_id,
|
||||||
)
|
)
|
||||||
.bind(comment_update.new_comment)
|
|
||||||
.bind(trip_id.to_string())
|
|
||||||
.execute(&state.database_pool)
|
.execute(&state.database_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
|
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
|
||||||
@@ -884,14 +961,15 @@ async fn inventory_category_create(
|
|||||||
Form(new_category): Form<NewCategory>,
|
Form(new_category): Form<NewCategory>,
|
||||||
) -> Result<Redirect, (StatusCode, Markup)> {
|
) -> Result<Redirect, (StatusCode, Markup)> {
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
query(
|
let id_param = id.to_string();
|
||||||
|
query!(
|
||||||
"INSERT INTO inventory_items_categories
|
"INSERT INTO inventory_items_categories
|
||||||
(id, name)
|
(id, name)
|
||||||
VALUES
|
VALUES
|
||||||
(?, ?)",
|
(?, ?)",
|
||||||
|
id_param,
|
||||||
|
new_category.name
|
||||||
)
|
)
|
||||||
.bind(id.to_string())
|
|
||||||
.bind(&new_category.name)
|
|
||||||
.execute(&state.database_pool)
|
.execute(&state.database_pool)
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
sqlx::Error::Database(ref error) => {
|
sqlx::Error::Database(ref error) => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use sqlx::{
|
|||||||
use std::convert;
|
use std::convert;
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::num::TryFromIntError;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -16,10 +17,19 @@ use sqlx::sqlite::SqlitePoolOptions;
|
|||||||
use futures::TryFutureExt;
|
use futures::TryFutureExt;
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
|
|
||||||
|
use time::{
|
||||||
|
error::Parse as TimeParseError, format_description::FormatItem, macros::format_description,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DATE_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day]");
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
SqlError { description: String },
|
SqlError { description: String },
|
||||||
UuidError { description: String },
|
UuidError { description: String },
|
||||||
|
EnumError { description: String },
|
||||||
NotFoundError { description: String },
|
NotFoundError { description: String },
|
||||||
|
IntError { description: String },
|
||||||
|
TimeParseError { description: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@@ -34,6 +44,15 @@ impl fmt::Display for Error {
|
|||||||
Self::NotFoundError { description } => {
|
Self::NotFoundError { description } => {
|
||||||
write!(f, "Not found: {description}")
|
write!(f, "Not found: {description}")
|
||||||
}
|
}
|
||||||
|
Self::IntError { description } => {
|
||||||
|
write!(f, "Integer error: {description}")
|
||||||
|
}
|
||||||
|
Self::EnumError { description } => {
|
||||||
|
write!(f, "Enum error: {description}")
|
||||||
|
}
|
||||||
|
Self::TimeParseError { description } => {
|
||||||
|
write!(f, "Date parse error: {description}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +80,22 @@ impl convert::From<sqlx::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl convert::From<TryFromIntError> for Error {
|
||||||
|
fn from(value: TryFromIntError) -> Self {
|
||||||
|
Error::IntError {
|
||||||
|
description: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl convert::From<TimeParseError> for Error {
|
||||||
|
fn from(value: TimeParseError) -> Self {
|
||||||
|
Error::TimeParseError {
|
||||||
|
description: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl error::Error for Error {}
|
impl error::Error for Error {}
|
||||||
|
|
||||||
#[derive(sqlx::Type)]
|
#[derive(sqlx::Type)]
|
||||||
@@ -88,6 +123,25 @@ impl fmt::Display for TripState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::convert::TryFrom<&str> for TripState {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
"Planning" => Self::Planning,
|
||||||
|
"Planned" => Self::Planned,
|
||||||
|
"Active" => Self::Active,
|
||||||
|
"Review" => Self::Review,
|
||||||
|
"Done" => Self::Done,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::EnumError {
|
||||||
|
description: format!("{} is not a valid value for TripState", value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
pub enum TripItemStateKey {
|
pub enum TripItemStateKey {
|
||||||
Pick,
|
Pick,
|
||||||
@@ -114,7 +168,7 @@ pub struct TripCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TripCategory {
|
impl TripCategory {
|
||||||
pub fn total_picked_weight(&self) -> u32 {
|
pub fn total_picked_weight(&self) -> i64 {
|
||||||
self.items
|
self.items
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -132,15 +186,47 @@ pub struct TripItem {
|
|||||||
pub packed: bool,
|
pub packed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DbTripRow {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub date_start: String,
|
||||||
|
pub date_end: String,
|
||||||
|
pub state: String,
|
||||||
|
pub location: Option<String>,
|
||||||
|
pub temp_min: Option<i64>,
|
||||||
|
pub temp_max: Option<i64>,
|
||||||
|
pub comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DbTripRow> for Trip {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(row: DbTripRow) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Trip {
|
||||||
|
id: Uuid::try_parse(&row.id)?,
|
||||||
|
name: row.name,
|
||||||
|
date_start: time::Date::parse(&row.date_start, DATE_FORMAT)?,
|
||||||
|
date_end: time::Date::parse(&row.date_end, DATE_FORMAT)?,
|
||||||
|
state: row.state.as_str().try_into()?,
|
||||||
|
location: row.location,
|
||||||
|
temp_min: row.temp_min,
|
||||||
|
temp_max: row.temp_max,
|
||||||
|
comment: row.comment,
|
||||||
|
types: None,
|
||||||
|
categories: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Trip {
|
pub struct Trip {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub date_start: time::Date,
|
pub date_start: time::Date,
|
||||||
pub date_end: time::Date,
|
pub date_end: time::Date,
|
||||||
pub state: TripState,
|
pub state: TripState,
|
||||||
pub location: String,
|
pub location: Option<String>,
|
||||||
pub temp_min: i32,
|
pub temp_min: Option<i64>,
|
||||||
pub temp_max: i32,
|
pub temp_max: Option<i64>,
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
types: Option<Vec<TripType>>,
|
types: Option<Vec<TripType>>,
|
||||||
categories: Option<Vec<TripCategory>>,
|
categories: Option<Vec<TripCategory>>,
|
||||||
@@ -193,37 +279,37 @@ pub enum TripAttribute {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
impl TryFrom<SqliteRow> for Trip {
|
// impl TryFrom<SqliteRow> for Trip {
|
||||||
type Error = Error;
|
// type Error = Error;
|
||||||
|
|
||||||
fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
|
// fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
|
||||||
let name: &str = row.try_get("name")?;
|
// let name: &str = row.try_get("name")?;
|
||||||
let id: &str = row.try_get("id")?;
|
// let id: &str = row.try_get("id")?;
|
||||||
let date_start: time::Date = row.try_get("date_start")?;
|
// let date_start: time::Date = row.try_get("date_start")?;
|
||||||
let date_end: time::Date = row.try_get("date_end")?;
|
// let date_end: time::Date = row.try_get("date_end")?;
|
||||||
let state: TripState = row.try_get("state")?;
|
// let state: TripState = row.try_get("state")?;
|
||||||
let location = row.try_get("location")?;
|
// let location = row.try_get("location")?;
|
||||||
let temp_min = row.try_get("temp_min")?;
|
// let temp_min = row.try_get("temp_min")?;
|
||||||
let temp_max = row.try_get("temp_max")?;
|
// let temp_max = row.try_get("temp_max")?;
|
||||||
let comment = row.try_get("comment")?;
|
// let comment = row.try_get("comment")?;
|
||||||
|
|
||||||
let id: Uuid = Uuid::try_parse(id)?;
|
// let id: Uuid = Uuid::try_parse(id)?;
|
||||||
|
|
||||||
Ok(Trip {
|
// Ok(Trip {
|
||||||
id,
|
// id,
|
||||||
name: name.to_string(),
|
// name: name.to_string(),
|
||||||
date_start,
|
// date_start,
|
||||||
date_end,
|
// date_end,
|
||||||
state,
|
// state,
|
||||||
location,
|
// location,
|
||||||
temp_min,
|
// temp_min,
|
||||||
temp_max,
|
// temp_max,
|
||||||
comment,
|
// comment,
|
||||||
types: None,
|
// types: None,
|
||||||
categories: None,
|
// categories: None,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl<'a> Trip {
|
impl<'a> Trip {
|
||||||
pub fn types(&'a self) -> &Vec<TripType> {
|
pub fn types(&'a self) -> &Vec<TripType> {
|
||||||
@@ -244,12 +330,13 @@ impl<'a> Trip {
|
|||||||
&'a mut self,
|
&'a mut self,
|
||||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let types = sqlx::query(
|
let id = self.id.to_string();
|
||||||
|
let types = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
type.id as id,
|
type.id as id,
|
||||||
type.name as name,
|
type.name as name,
|
||||||
CASE WHEN inner.id IS NOT NULL THEN true ELSE false END AS active
|
inner.id IS NOT NULL AS active
|
||||||
FROM trips_types AS type
|
FROM trips_types AS type
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT type.id as id, type.name as name
|
SELECT type.id as id, type.name as name
|
||||||
@@ -262,10 +349,20 @@ impl<'a> Trip {
|
|||||||
) AS inner
|
) AS inner
|
||||||
ON inner.id = type.id
|
ON inner.id = type.id
|
||||||
",
|
",
|
||||||
|
id
|
||||||
)
|
)
|
||||||
.bind(self.id.to_string())
|
|
||||||
.fetch(pool)
|
.fetch(pool)
|
||||||
.map_ok(std::convert::TryInto::try_into)
|
.map_ok(|row| -> Result<TripType, Error> {
|
||||||
|
Ok(TripType {
|
||||||
|
id: Uuid::try_parse(&row.id)?,
|
||||||
|
name: row.name,
|
||||||
|
active: match row.active {
|
||||||
|
0 => false,
|
||||||
|
1 => true,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
.try_collect::<Vec<Result<TripType, Error>>>()
|
.try_collect::<Vec<Result<TripType, Error>>>()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -282,14 +379,14 @@ impl<'a> Trip {
|
|||||||
let mut categories: Vec<TripCategory> = vec![];
|
let mut categories: Vec<TripCategory> = vec![];
|
||||||
// we can ignore the return type as we collect into `categories`
|
// we can ignore the return type as we collect into `categories`
|
||||||
// in the `map_ok()` closure
|
// in the `map_ok()` closure
|
||||||
sqlx::query(
|
let id = self.id.to_string();
|
||||||
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
category.id as category_id,
|
category.id as category_id,
|
||||||
category.name as category_name,
|
category.name as category_name,
|
||||||
category.description AS category_description,
|
category.description AS category_description,
|
||||||
inner.trip_id AS trip_id,
|
inner.trip_id AS trip_id,
|
||||||
inner.category_description AS category_description,
|
|
||||||
inner.item_id AS item_id,
|
inner.item_id AS item_id,
|
||||||
inner.item_name AS item_name,
|
inner.item_name AS item_name,
|
||||||
inner.item_description AS item_description,
|
inner.item_description AS item_description,
|
||||||
@@ -314,25 +411,28 @@ impl<'a> Trip {
|
|||||||
ON item.id = trip.item_id
|
ON item.id = trip.item_id
|
||||||
INNER JOIN inventory_items_categories as category
|
INNER JOIN inventory_items_categories as category
|
||||||
ON category.id = item.category_id
|
ON category.id = item.category_id
|
||||||
WHERE trip.trip_id = 'a8b181d6-3b16-4a41-99fa-0713b94a34d9'
|
WHERE trip.trip_id = ?
|
||||||
) AS inner
|
) AS inner
|
||||||
ON inner.category_id = category.id
|
ON inner.category_id = category.id
|
||||||
",
|
",
|
||||||
|
id
|
||||||
)
|
)
|
||||||
.bind(self.id.to_string())
|
|
||||||
.fetch(pool)
|
.fetch(pool)
|
||||||
.map_ok(|row| -> Result<(), Error> {
|
.map_ok(|row| -> Result<(), Error> {
|
||||||
let mut category = TripCategory {
|
let mut category = TripCategory {
|
||||||
category: Category {
|
category: Category {
|
||||||
id: Uuid::try_parse(row.try_get("category_id")?)?,
|
id: Uuid::try_parse(&row.category_id)?,
|
||||||
name: row.try_get("category_name")?,
|
name: row.category_name,
|
||||||
description: row.try_get("category_description")?,
|
// TODO align optionality between code and database
|
||||||
|
// idea: make description nullable
|
||||||
|
description: row.category_description,
|
||||||
|
|
||||||
items: None,
|
items: None,
|
||||||
},
|
},
|
||||||
items: None,
|
items: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
match row.try_get("item_id")? {
|
match row.item_id {
|
||||||
None => {
|
None => {
|
||||||
// we have an empty (unused) category which has NULL values
|
// we have an empty (unused) category which has NULL values
|
||||||
// for the item_id column
|
// for the item_id column
|
||||||
@@ -342,14 +442,14 @@ impl<'a> Trip {
|
|||||||
Some(item_id) => {
|
Some(item_id) => {
|
||||||
let item = TripItem {
|
let item = TripItem {
|
||||||
item: Item {
|
item: Item {
|
||||||
id: Uuid::try_parse(item_id)?,
|
id: Uuid::try_parse(&item_id)?,
|
||||||
name: row.try_get("item_name")?,
|
name: row.item_name.unwrap(),
|
||||||
description: row.try_get("item_description")?,
|
description: row.item_description,
|
||||||
weight: row.try_get("item_weight")?,
|
weight: row.item_weight.unwrap(),
|
||||||
category_id: category.category.id,
|
category_id: category.category.id,
|
||||||
},
|
},
|
||||||
picked: row.try_get("item_is_picked")?,
|
picked: row.item_is_picked.unwrap(),
|
||||||
packed: row.try_get("item_is_packed")?,
|
packed: row.item_is_packed.unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(&mut ref mut c) = categories
|
if let Some(&mut ref mut c) = categories
|
||||||
@@ -385,43 +485,70 @@ pub struct TripType {
|
|||||||
pub active: bool,
|
pub active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<SqliteRow> for TripType {
|
// impl TryFrom<SqliteRow> for TripType {
|
||||||
type Error = Error;
|
// type Error = Error;
|
||||||
|
|
||||||
fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
|
// fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
|
||||||
let id: Uuid = Uuid::try_parse(row.try_get("id")?)?;
|
// let id: Uuid = Uuid::try_parse(row.try_get("id")?)?;
|
||||||
let name: String = row.try_get::<&str, _>("name")?.to_string();
|
// let name: String = row.try_get::<&str, _>("name")?.to_string();
|
||||||
let active: bool = row.try_get("active")?;
|
// let active: bool = row.try_get("active")?;
|
||||||
|
|
||||||
Ok(Self { id, name, active })
|
// Ok(Self { id, name, active })
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub struct DbCategoryRow {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Category {
|
pub struct Category {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: Option<String>,
|
||||||
items: Option<Vec<Item>>,
|
items: Option<Vec<Item>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<SqliteRow> for Category {
|
impl TryFrom<DbCategoryRow> for Category {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
|
fn try_from(row: DbCategoryRow) -> Result<Self, Self::Error> {
|
||||||
let name: &str = row.try_get("name")?;
|
|
||||||
let description: &str = row.try_get("description")?;
|
|
||||||
let id: Uuid = Uuid::try_parse(row.try_get("id")?)?;
|
|
||||||
|
|
||||||
Ok(Category {
|
Ok(Category {
|
||||||
id,
|
id: Uuid::try_parse(&row.id)?,
|
||||||
name: name.to_string(),
|
name: row.name,
|
||||||
description: description.to_string(),
|
description: row.description,
|
||||||
items: None,
|
items: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// impl TryFrom<SqliteRow> for Category {
|
||||||
|
// type Error = Error;
|
||||||
|
|
||||||
|
// fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
|
||||||
|
// let name: &str = row.try_get("name")?;
|
||||||
|
// let description: &str = row.try_get("description")?;
|
||||||
|
// let id: Uuid = Uuid::try_parse(row.try_get("id")?)?;
|
||||||
|
|
||||||
|
// Ok(Category {
|
||||||
|
// id,
|
||||||
|
// name: name.to_string(),
|
||||||
|
// description: description.to_string(),
|
||||||
|
// items: None,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub struct DbInventoryItemsRow {
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
weight: i64,
|
||||||
|
description: Option<String>,
|
||||||
|
category_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Category {
|
impl<'a> Category {
|
||||||
pub fn items(&'a self) -> &'a Vec<Item> {
|
pub fn items(&'a self) -> &'a Vec<Item> {
|
||||||
self.items
|
self.items
|
||||||
@@ -429,7 +556,7 @@ impl<'a> Category {
|
|||||||
.expect("you need to call populate_items()")
|
.expect("you need to call populate_items()")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn total_weight(&self) -> u32 {
|
pub fn total_weight(&self) -> i64 {
|
||||||
self.items().iter().map(|item| item.weight).sum()
|
self.items().iter().map(|item| item.weight).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,15 +564,21 @@ impl<'a> Category {
|
|||||||
&'a mut self,
|
&'a mut self,
|
||||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let items = sqlx::query(&format!(
|
let id = self.id.to_string();
|
||||||
|
let items = sqlx::query_as!(
|
||||||
|
DbInventoryItemsRow,
|
||||||
"SELECT
|
"SELECT
|
||||||
id,name,weight,description,category_id
|
id,
|
||||||
|
name,
|
||||||
|
weight,
|
||||||
|
description,
|
||||||
|
category_id
|
||||||
FROM inventory_items
|
FROM inventory_items
|
||||||
WHERE category_id = '{id}'",
|
WHERE category_id = ?",
|
||||||
id = self.id
|
id
|
||||||
))
|
)
|
||||||
.fetch(pool)
|
.fetch(pool)
|
||||||
.map_ok(std::convert::TryInto::try_into)
|
.map_ok(|row| row.try_into())
|
||||||
.try_collect::<Vec<Result<Item, Error>>>()
|
.try_collect::<Vec<Result<Item, Error>>>()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -460,40 +593,56 @@ impl<'a> Category {
|
|||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: Option<String>,
|
||||||
pub weight: u32,
|
pub weight: i64,
|
||||||
pub category_id: Uuid,
|
pub category_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<SqliteRow> for Item {
|
impl TryFrom<DbInventoryItemsRow> for Item {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
|
fn try_from(row: DbInventoryItemsRow) -> Result<Self, Self::Error> {
|
||||||
let name: &str = row.try_get("name")?;
|
|
||||||
let description: &str = row.try_get("description")?;
|
|
||||||
let weight: u32 = row.try_get("weight")?;
|
|
||||||
let id: Uuid = Uuid::try_parse(row.try_get("id")?)?;
|
|
||||||
let category_id: Uuid = Uuid::try_parse(row.try_get("category_id")?)?;
|
|
||||||
|
|
||||||
Ok(Item {
|
Ok(Item {
|
||||||
id,
|
id: Uuid::try_parse(&row.id)?,
|
||||||
name: name.to_string(),
|
name: row.name,
|
||||||
weight,
|
description: row.description, // TODO
|
||||||
description: description.to_string(),
|
weight: row.weight,
|
||||||
category_id,
|
category_id: Uuid::try_parse(&row.category_id)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// impl TryFrom<SqliteRow> for Item {
|
||||||
|
// type Error = Error;
|
||||||
|
|
||||||
|
// fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
|
||||||
|
// let name: &str = row.try_get("name")?;
|
||||||
|
// let description: &str = row.try_get("description")?;
|
||||||
|
// let weight: i64 = row.try_get("weight")?;
|
||||||
|
// let id: Uuid = Uuid::try_parse(row.try_get("id")?)?;
|
||||||
|
// let category_id: Uuid = Uuid::try_parse(row.try_get("category_id")?)?;
|
||||||
|
|
||||||
|
// Ok(Item {
|
||||||
|
// id,
|
||||||
|
// name: name.to_string(),
|
||||||
|
// weight,
|
||||||
|
// description: description.to_string(),
|
||||||
|
// category_id,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
pub async fn find(pool: &sqlx::Pool<sqlx::Sqlite>, id: Uuid) -> Result<Option<Item>, Error> {
|
pub async fn find(pool: &sqlx::Pool<sqlx::Sqlite>, id: Uuid) -> Result<Option<Item>, Error> {
|
||||||
let item: Result<Result<Item, Error>, sqlx::Error> = sqlx::query(
|
let id_param = id.to_string();
|
||||||
|
let item: Result<Result<Item, Error>, sqlx::Error> = sqlx::query_as!(
|
||||||
|
DbInventoryItemsRow,
|
||||||
"SELECT * FROM inventory_items AS item
|
"SELECT * FROM inventory_items AS item
|
||||||
WHERE item.id = ?",
|
WHERE item.id = ?",
|
||||||
|
id_param,
|
||||||
)
|
)
|
||||||
.bind(id.to_string())
|
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.map_ok(std::convert::TryInto::try_into)
|
.map_ok(|row| row.try_into())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match item {
|
match item {
|
||||||
@@ -509,9 +658,10 @@ impl Item {
|
|||||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
name: &str,
|
name: &str,
|
||||||
weight: u32,
|
weight: i64,
|
||||||
) -> Result<Option<Uuid>, Error> {
|
) -> Result<Option<Uuid>, Error> {
|
||||||
let id: Result<Result<Uuid, Error>, sqlx::Error> = sqlx::query(
|
let id_param = id.to_string();
|
||||||
|
let id: Result<Result<Uuid, Error>, sqlx::Error> = sqlx::query!(
|
||||||
"UPDATE inventory_items AS item
|
"UPDATE inventory_items AS item
|
||||||
SET
|
SET
|
||||||
name = ?,
|
name = ?,
|
||||||
@@ -519,13 +669,13 @@ impl Item {
|
|||||||
WHERE item.id = ?
|
WHERE item.id = ?
|
||||||
RETURNING inventory_items.category_id AS id
|
RETURNING inventory_items.category_id AS id
|
||||||
",
|
",
|
||||||
|
name,
|
||||||
|
weight,
|
||||||
|
id_param,
|
||||||
)
|
)
|
||||||
.bind(name)
|
|
||||||
.bind(weight)
|
|
||||||
.bind(id.to_string())
|
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.map_ok(|row| {
|
.map_ok(|row| {
|
||||||
let id: &str = row.try_get("id")?;
|
let id: &str = &row.id.unwrap(); // TODO
|
||||||
let uuid: Result<Uuid, uuid::Error> = Uuid::try_parse(id);
|
let uuid: Result<Uuid, uuid::Error> = Uuid::try_parse(id);
|
||||||
let uuid: Result<Uuid, Error> = uuid.map_err(|e| e.into());
|
let uuid: Result<Uuid, Error> = uuid.map_err(|e| e.into());
|
||||||
uuid
|
uuid
|
||||||
|
|||||||
Reference in New Issue
Block a user