update
This commit is contained in:
236
api/src/db.rs
236
api/src/db.rs
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user