This commit is contained in:
2021-11-20 17:19:07 +01:00
parent 2baa2c7257
commit b6686811d9
9 changed files with 414 additions and 2 deletions

View File

@@ -28,7 +28,7 @@ features = ["full"]
[dependencies.serde]
version = "1.0"
features = ["derive"]
features = ["derive", "rc"]
[dependencies.uuid]
version = "0.8"

View File

@@ -1,3 +1,6 @@
use std::collections::HashMap;
use std::rc::Rc;
use rusqlite;
use uuid::Uuid;
@@ -12,6 +15,8 @@ use super::PreparationStep;
use super::Trip;
use super::TripParameters;
use super::TripState;
use super::TripItem;
use super::TripItemStatus;
pub fn load() -> rusqlite::Result<()> {
let example_lists = vec![
@@ -726,6 +731,20 @@ pub fn load() -> rusqlite::Result<()> {
[],
)?;
conn.execute(
"CREATE TABLE trip_items (
trip_id TEXT NOT NULL,
packagelistitem_id TEXT NOT NULL,
status INTEGER NOT NULL,
FOREIGN KEY(trip_id) REFERENCES trips(id)
FOREIGN KEY(packagelistitem_id) REFERENCES packagelistitems(id)
PRIMARY KEY(trip_id, packagelistitem_id)
)",
[],
)?;
for list in example_lists {
conn.execute(
"INSERT INTO
@@ -1040,3 +1059,220 @@ pub fn get_trips() -> rusqlite::Result<Vec<Trip>> {
Ok(trips)
}
pub fn get_trip(trip_id: Uuid) -> rusqlite::Result<Option<Trip>> {
let conn = rusqlite::Connection::open("./sqlite.db")?;
let mut statement = conn.prepare(
"SELECT
trips.id AS trip_id,
trips.name AS trip_name,
trips.date AS trip_date,
trips.days AS trip_days,
trips.state AS trip_state
FROM trips
WHERE
trip_id = ?1
",
)?;
let trip_query_result = statement.query_map(rusqlite::params![trip_id], |row| {
Ok(Trip{
id: row.get_unwrap(0),
name: row.get_unwrap(1),
date: row.get_unwrap(2),
parameters: TripParameters{
days: row.get_unwrap(3),
},
package_lists: vec![],
state: row.get_unwrap(4),
})
})?;
let mut trip_query_result: Vec<Trip> = trip_query_result.flatten().collect();
if trip_query_result.is_empty() {
return Ok(None);
}
let mut trip: Trip = trip_query_result.remove(0);
let mut statement = conn.prepare(
"SELECT
trip_packagelist.packagelist_id as package_list_id,
packagelists.name as package_list_name
FROM trip_packagelist
INNER JOIN packagelists
ON packagelists.id == trip_packagelist.packagelist_id
WHERE
trip_packagelist.trip_id = ?1
",
)?;
let lists: Vec<(Uuid, String)> = statement
.query_map(rusqlite::params![trip_id], |row| {
Ok((row.get_unwrap(0), row.get_unwrap(1)))
})?
.flatten()
.collect();
trip.set_package_lists(lists);
Ok(Some(trip))
}
pub fn get_trip_items(trip_id: Uuid) -> rusqlite::Result<Option<Vec<TripItem>>> {
// there has to be better way to get the rested option result
let trip: Option<Trip> = get_trip(trip_id)?;
println!("Getting trip items");
if let None = trip {
return Ok(None)
}
let trip = trip.unwrap();
let conn = rusqlite::Connection::open("./sqlite.db")?;
let mut statement = conn.prepare(
"SELECT
trip_items.packagelistitem_id,
status AS status,
pli.id AS packageitem_id,
pli.name AS packageitem_name,
pli.count AS packageitem_count,
FROM trip_items
INNER JOIN packagelistitems as pli
ON trip_items.packagelistitem_id = pli.id
WHERE
trip_items.trip_id = ?1
",
)?;
let mut result : Vec<TripItem> = Vec::new();
let trip_items : Vec<TripItem> = statement.query_map(rusqlite::params![trip_id], |row| {
let package_item = PackageItem::new(
row.get_unwrap(2),
row.get_unwrap(3),
ItemSize::None,
row.get_unwrap(4),
ItemUsage::Singleton,
Preparation::None,
);
let package_list_id = row.get_unwrap(5);
let package_list = Rc::new(PackageList::new(
package_list_id,
row.get_unwrap(6),
));
Ok(TripItem{
status: row.get_unwrap(1),
package_item,
package_list: Rc::clone(package_lists.get(&package_list_id).unwrap()),
})
})?.flatten().collect();
println!("Found {} trip items in database", trip_items.len());
// get all package list items, so we know what we SHOULD have
let mut package_list_items = vec![];
for package_list in &trip.package_lists {
package_list_items.append(&mut get_list_items(package_list.id)?);
}
println!("There should be {} items according to the package lists", package_list_items.len());
let package_list_item_ids: Vec<Uuid> = package_list_items.iter().map(|item| item.id).collect();
// three possibilities:
//
// * The trip item is there => use it
// * The trip item is not there => create it with defaults
// * There is a trip item that should no be there (e.g. because the package
// list was changed and a package item was removed)
//
// We can either just not use it (letting it rot in the database). It will
// never be removed automatically. But if we later add it again, something
// will break (e.g. if someone re-attaches the package list to the trip).
// first run: get all trip items that have the trip id set. We need to
// check this list against the items. If there are any returned that are
// NOT part of the package list items, we remove them
for package_item in package_list_items {
for trip_item in &trip_items {
let item_id = trip_item.package_item.id;
if !package_list_item_ids.contains(&item_id) {
// there is a trip item that does no longer have a corresponding
// package list item (because a package list was detached).
// Remove it
println!("Would remove package item with id {} (\"{}\")",
package_item.id, package_item.name);
// conn.execute(
// "DELETE
// FROM trip_items
// WHERE
// trip_items.trip_id = ?1
// AND
// trip_items.packagelistitem_id = ?2
// ",
// rusqlite::params![trip_id, package_item.id]
// )?;
continue
}
}
if !trip_items.iter().map(|i| i.package_item.id).collect::<Vec<Uuid>>().contains(&package_item.id) {
println!("Found new trip item!");
println!("Looking into hashmap with {:?}", package_item.id);
let list = Rc::clone(package_lists.get(&package_item.id).unwrap());
let new_trip_item = TripItem {
status: TripItemStatus::Pending,
package_item,
package_list: list,
};
conn.execute(
"INSERT INTO
trip_items (
trip_id,
packagelistitem_id,
status
) VALUES (
?1,
?2,
?3
)
",
rusqlite::params![
trip_id,
new_trip_item.package_item.id,
new_trip_item.status
]
)?;
result.push(new_trip_item);
continue;
}
// If we've come so far, the trip is actually in the database, so let's
// just use it
println!("Using trip item from database");
let list = Rc::clone(package_lists.get(&package_item.id).unwrap());
result.push(TripItem {
status: TripItemStatus::Pending,
package_item,
package_list: list,
});
}
Ok(Some(result))
}

View File

@@ -19,6 +19,8 @@ pub mod trip;
pub use trip::Trip;
pub use trip::TripParameters;
pub use trip::TripState;
pub use trip::TripItem;
pub use trip::TripItemStatus;
pub fn get_list(id: Uuid) -> Option<packagelist::PackageList> {
self::db::get_list(id).unwrap()
@@ -51,3 +53,11 @@ pub fn new_item(list_id: Uuid, item_name: String, item_count: i32) -> packagelis
pub fn get_trips() -> Vec<Trip> {
self::db::get_trips().unwrap()
}
pub fn get_trip(trip_id: Uuid) -> Option<Trip> {
self::db::get_trip(trip_id).unwrap()
}
pub fn get_trip_items(trip_id: Uuid) -> Option<Vec<TripItem>> {
self::db::get_trip_items(trip_id).unwrap()
}

View File

@@ -131,6 +131,40 @@ pub fn new() -> warp::filters::BoxedFilter<(impl warp::Reply,)> {
.map(|| warp::reply::json(&super::get_trips()))
.with(&cors);
let trip = warp::path!("v1" / "trips" / String)
.and(warp::path::end())
.and(warp::get())
.and(accept_json)
.and_then(|trip_id: String| async move {
match Uuid::parse_str(&trip_id) {
Ok(trip_id) => {
match &super::get_trip(trip_id) {
Some(trip) => Ok(warp::reply::json(trip)),
None => Err(warp::reject::not_found()),
}
},
Err(_) => Err(warp::reject::custom(InvalidUuid)),
}
})
.with(&cors);
let trip_items = warp::path!("v1" / "trips" / String / "items")
.and(warp::path::end())
.and(warp::get())
.and(accept_json)
.and_then(|trip_id: String| async move {
match Uuid::parse_str(&trip_id) {
Ok(trip_id) => {
match &super::get_trip_items(trip_id) {
Some(trip) => Ok(warp::reply::json(trip)),
None => Err(warp::reject::not_found()),
}
},
Err(_) => Err(warp::reject::custom(InvalidUuid)),
}
})
.with(&cors);
root.or(v1)
.or(lists)
.or(list)
@@ -138,6 +172,8 @@ pub fn new() -> warp::filters::BoxedFilter<(impl warp::Reply,)> {
.or(list_items)
.or(preparation)
.or(trips)
.or(trip)
.or(trip_items)
.recover(handle_rejection)
.boxed()
}

View File

@@ -1,6 +1,11 @@
use std::rc::Rc;
use serde::Serialize;
use uuid::Uuid;
use crate::PackageItem;
use crate::PackageList;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum TripItemStatus {
@@ -9,6 +14,36 @@ pub enum TripItemStatus {
Packed,
}
impl rusqlite::types::FromSql for TripItemStatus {
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
match value.as_i64()? {
1 => Ok(TripItemStatus::Pending),
2 => Ok(TripItemStatus::Ready),
3 => Ok(TripItemStatus::Packed),
v => Err(rusqlite::types::FromSqlError::OutOfRange(v)),
}
}
}
impl rusqlite::types::ToSql for TripItemStatus {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let v = rusqlite::types::Value::Integer(match self {
TripItemStatus::Pending => 1,
TripItemStatus::Ready => 2,
TripItemStatus::Packed => 3,
});
rusqlite::Result::Ok(rusqlite::types::ToSqlOutput::Owned(v))
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TripItem {
pub status: TripItemStatus,
pub package_item: PackageItem,
pub package_list: Rc<PackageList>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TripParameters {