Add another attempt with python

This commit is contained in:
2022-07-02 22:03:35 +02:00
parent 4ef8cc57b1
commit 8ac2237892
38 changed files with 261 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
pub mod packagelist;
use uuid::Uuid;
pub use packagelist::Duration;
pub use packagelist::ItemSize;
pub use packagelist::ItemUsage;
pub use packagelist::PackageItem;
pub use packagelist::PackageList;
pub use packagelist::Period;
pub use packagelist::Preparation;
pub use packagelist::PreparationStep;
pub mod router;
pub mod db;
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()
}
pub fn get_lists() -> Vec<packagelist::PackageList> {
self::db::get_lists().unwrap()
}
pub fn get_packagelist_items(id: Uuid) -> Vec<packagelist::PackageItem> {
self::db::get_list_items(id).unwrap()
}
pub fn get_preparation(list_id: Uuid, item_id: Uuid) -> Vec<packagelist::PreparationStep> {
self::db::get_preparation(list_id, item_id).unwrap()
}
pub fn new_item(list_id: Uuid, item_name: String, item_count: i32) -> packagelist::PackageItem {
let item = PackageItem::new(
Uuid::new_v4(),
item_name,
ItemSize::None,
item_count,
ItemUsage::Singleton,
Preparation::None,
);
self::db::save_item(list_id, item).unwrap()
}
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

@@ -0,0 +1,50 @@
use std::env;
#[tokio::main]
async fn main() {
// let lists = packager::get_lists();
// for list in &lists {
// println!("Contents of package list {:?}:", list.name);
// for item in &list.items {
// println!("\t{:?}", item);
// }
// }
// println!("\nNow we're starting an actual trip!");
// let mut trip = packager::trip::Trip::from_package_list(
// String::from("Campingtrip"),
// String::from("2021-09-06"),
// &lists[0],
// );
// println!(
// "\nPackage list for trip {:?} at {:?}:",
// trip.name, trip.date
// );
// for item in &trip.list.items {
// println!("{:?}", item);
// }
// trip.list.items[0].set_status(packager::trip::TripItemStatus::Ready);
// trip.list.items[1].set_status(packager::trip::TripItemStatus::Packed);
// for item in &trip.list.items {
// println!("{:?}", item);
// }
let args: Vec<String> = env::args().skip(1).collect();
match args.get(0) {
None => (),
Some(cmd) => match cmd.as_ref() {
"--load-example-data" => {
packager::db::load().unwrap();
}
_ => panic!("Unknown argument: \"{}\"", cmd),
},
};
let router = packager::router::new();
println!("Initialization done, listening for connections");
warp::serve(router).run(([127, 0, 0, 1], 9000)).await;
}

View File

@@ -0,0 +1,251 @@
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use uuid::Uuid;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Duration {
None,
Days(i32),
}
impl Duration {
pub fn is_none(d: &Duration) -> bool {
matches!(d, Duration::None)
}
}
#[derive(Debug)]
pub enum Period {
Daily(i32),
Weekly(i32),
Days(i32),
}
impl Serialize for Period {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Period::Daily(i) => {
let mut s = serializer.serialize_struct("period", 2)?;
s.serialize_field("type", "daily")?;
s.serialize_field("value", &i)?;
s.end()
}
Period::Weekly(i) => {
let mut s = serializer.serialize_struct("period", 2)?;
s.serialize_field("type", "weekly")?;
s.serialize_field("value", &i)?;
s.end()
}
Period::Days(i) => {
let mut s = serializer.serialize_struct("period", 2)?;
s.serialize_field("type", "days")?;
s.serialize_field("value", &i)?;
s.end()
}
}
}
}
#[derive(Debug)]
pub enum ItemUsage {
Singleton,
Periodic(Period),
Infinite,
}
impl Serialize for ItemUsage {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ItemUsage::Singleton => {
let mut s = serializer.serialize_struct("usage", 1)?;
s.serialize_field("type", "singleton")?;
s.end()
}
ItemUsage::Periodic(p) => {
let mut s = serializer.serialize_struct("usage", 2)?;
s.serialize_field("type", "peridoic")?;
s.serialize_field("value", &p)?;
s.end()
}
ItemUsage::Infinite => {
let mut s = serializer.serialize_struct("size", 1)?;
s.serialize_field("type", "infinite")?;
s.end()
}
}
}
}
#[derive(Debug)]
pub enum ItemSize {
None,
Pack(i32),
Name(String),
Gram(i32),
}
impl Serialize for ItemSize {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ItemSize::None => {
let mut s = serializer.serialize_struct("size", 1)?;
s.serialize_field("type", "none")?;
s.end()
}
ItemSize::Pack(i) => {
let mut s = serializer.serialize_struct("size", 2)?;
s.serialize_field("type", "pack")?;
s.serialize_field("value", &i)?;
s.end()
}
ItemSize::Name(n) => {
let mut s = serializer.serialize_struct("size", 2)?;
s.serialize_field("type", "name")?;
s.serialize_field("value", &n)?;
s.end()
}
ItemSize::Gram(i) => {
let mut s = serializer.serialize_struct("size", 2)?;
s.serialize_field("type", "gram")?;
s.serialize_field("value", &i)?;
s.end()
}
}
}
}
impl ItemSize {
pub fn is_none(d: &ItemSize) -> bool {
matches!(d, ItemSize::None)
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PreparationStep {
pub id: Uuid,
pub name: String,
#[serde(skip_serializing_if = "Duration::is_none")]
pub start: Duration,
}
impl PreparationStep {
pub fn new(id: Uuid, name: String, start: Duration) -> PreparationStep {
PreparationStep { id, name, start }
}
}
#[derive(Debug)]
pub enum Preparation {
None,
Steps(Vec<PreparationStep>),
}
impl Preparation {
pub fn is_none(d: &Preparation) -> bool {
matches!(d, Preparation::None)
}
}
impl Serialize for Preparation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Preparation::None => {
// serializer.serialize_unit_variant("Preparation", 0, "null")
let steps: Vec<i32> = vec![];
serializer.serialize_newtype_variant("Preparation", 1, "steps", &steps)
// let mut s = serializer.serialize_seq(Some(0))?;
// s.end()
}
Preparation::Steps(steps) => {
serializer.serialize_newtype_variant("Preparation", 1, "steps", steps)
}
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PackageItem {
pub id: Uuid,
pub name: String,
#[serde(skip_serializing_if = "ItemSize::is_none")]
size: ItemSize,
pub count: i32,
usage: ItemUsage,
pub preparation: Preparation,
}
impl PackageItem {
pub fn new(
id: Uuid,
name: String,
size: ItemSize,
count: i32,
usage: ItemUsage,
preparation: Preparation,
) -> PackageItem {
PackageItem {
id,
name,
size,
count,
usage,
preparation,
}
}
pub fn new_simple(id: Uuid, name: String) -> PackageItem {
PackageItem::new(
id,
name,
ItemSize::None,
1,
ItemUsage::Singleton,
Preparation::None,
)
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PackageList {
pub id: Uuid,
pub name: String,
pub items: Vec<PackageItem>,
}
impl PackageList {
pub fn new_from_items(id: Uuid, name: String, items: Vec<PackageItem>) -> PackageList {
PackageList { id, name, items }
}
pub fn new(id: Uuid, name: String) -> PackageList {
PackageList {
id,
name,
items: Vec::new(),
}
}
pub fn set_items(&mut self, items: Vec<PackageItem>) {
self.items = items;
}
}

View File

@@ -0,0 +1,215 @@
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
use warp;
use warp::http::StatusCode;
use warp::Filter;
use uuid::Uuid;
#[derive(Debug)]
struct InvalidUuid;
impl warp::reject::Reject for InvalidUuid {}
#[derive(Serialize)]
struct ErrorMessage {
code: u16,
success: bool,
message: String,
}
#[derive(Deserialize, Clone)]
#[serde(deny_unknown_fields)]
struct JsonListItem {
name: String,
count: i32,
}
pub fn new() -> warp::filters::BoxedFilter<(impl warp::Reply,)> {
let accept_json = warp::header::exact("accept", "application/json");
let content_json = warp::header::exact("content-type", "application/json");
let cors = warp::cors()
.allow_any_origin()
.allow_methods(&[
warp::http::Method::GET,
warp::http::Method::POST,
warp::http::Method::DELETE,
])
.allow_headers(vec!["accept", "content-type"]);
fn json_body() -> impl Filter<Extract = (JsonListItem,), Error = warp::Rejection> + Clone {
warp::body::content_length_limit(1024 * 16).and(warp::body::json())
}
let root = warp::path::end().map(|| "Hi");
let v1 = warp::path!("v1")
.and(warp::get())
.and(warp::path::end())
.map(warp::reply);
let lists = warp::path!("v1" / "lists")
.and(warp::path::end())
.and(warp::get())
.and(accept_json)
.map(|| warp::reply::json(&super::get_lists()))
.with(&cors);
let list = warp::path!("v1" / "lists" / String)
.and(warp::path::end())
.and(warp::get())
.and(accept_json)
.and_then(|id: String| async move {
match Uuid::parse_str(&id) {
Ok(uuid) => {
let list = &super::get_list(uuid);
match list {
Some(l) => Ok(warp::reply::json(l)),
None => Err(warp::reject::not_found()),
}
}
Err(_) => Err(warp::reject::custom(InvalidUuid)),
}
})
.with(&cors);
let list_items = warp::path!("v1" / "lists" / String / "items")
.and(warp::path::end())
.and(warp::get())
.and(accept_json)
.and_then(|list_id: String| async move {
match Uuid::parse_str(&list_id) {
Ok(uuid) => {
let items = &super::get_packagelist_items(uuid);
Ok(warp::reply::json(items))
}
Err(_) => Err(warp::reject::custom(InvalidUuid)),
}
})
.with(&cors);
let preparation = warp::path!("v1" / "lists" / String / "items" / String / "preparation")
.and(warp::path::end())
.and(warp::get())
.and(accept_json)
.and_then(|list_id: String, item_id: String| async move {
match Uuid::parse_str(&list_id) {
Ok(list_id) => match Uuid::parse_str(&item_id) {
Err(_) => Err(warp::reject::custom(InvalidUuid)),
Ok(item_id) => {
let items = &super::get_preparation(list_id, item_id);
Ok(warp::reply::json(items))
}
},
Err(_) => Err(warp::reject::custom(InvalidUuid)),
}
})
.with(&cors);
let new_item = warp::path!("v1" / "lists" / String / "items")
.and(warp::path::end())
.and(warp::post())
.and(accept_json)
.and(content_json)
.and(json_body())
.and_then(|list_id: String, item: JsonListItem| async move {
match Uuid::parse_str(&list_id) {
Ok(list_id) => {
let new_item = &super::new_item(list_id, item.name, item.count);
Ok(warp::reply::json(new_item))
}
Err(_) => Err(warp::reject::custom(InvalidUuid)),
}
})
.with(&cors);
let trips = warp::path!("v1" / "trips")
.and(warp::path::end())
.and(warp::get())
.and(accept_json)
.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)
.or(new_item)
.or(list_items)
.or(preparation)
.or(trips)
.or(trip)
.or(trip_items)
.recover(handle_rejection)
.boxed()
}
// See https://github.com/seanmonstar/warp/blob/master/examples/rejections.rs
async fn handle_rejection(err: warp::Rejection) -> Result<impl warp::Reply, Infallible> {
let code;
let message;
if err.is_not_found() {
message = "NOT_FOUND";
code = StatusCode::NOT_FOUND;
} else if let Some(InvalidUuid) = err.find() {
code = StatusCode::BAD_REQUEST;
message = "INVALID_UUID";
} else if err
.find::<warp::filters::body::BodyDeserializeError>()
.is_some()
{
message = "BAD_REQUEST";
code = StatusCode::BAD_REQUEST;
} else if err.find::<warp::reject::MethodNotAllowed>().is_some() {
message = "METHOD_NOT_ALLOWED";
code = StatusCode::METHOD_NOT_ALLOWED;
} else {
// We should have expected this... Just log and say its a 500
eprintln!("unhandled rejection: {:?}", err);
message = "UNHANDLED_REJECTION";
code = StatusCode::INTERNAL_SERVER_ERROR;
}
let json = warp::reply::json(&ErrorMessage {
success: false,
code: code.as_u16(),
message: message.into(),
});
Ok(warp::reply::with_status(json, code))
}

View File

@@ -0,0 +1,158 @@
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 {
Pending,
Ready,
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 {
pub days: i32,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TripPackageList {
pub id: Uuid,
pub name: String,
}
impl TripPackageList {
fn construct(item: (Uuid, String)) -> TripPackageList {
TripPackageList {
id: item.0,
name: item.1,
}
}
fn construct_vec(items: Vec<(Uuid, String)>) -> Vec<TripPackageList> {
items.into_iter().map(TripPackageList::construct).collect()
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum TripState {
Planned,
Packing,
Active,
Finished,
}
impl rusqlite::types::FromSql for TripState {
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
match value.as_i64()? {
1 => Ok(TripState::Planned),
2 => Ok(TripState::Packing),
3 => Ok(TripState::Active),
4 => Ok(TripState::Finished),
v => Err(rusqlite::types::FromSqlError::OutOfRange(v)),
}
}
}
impl rusqlite::types::ToSql for TripState {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let v = rusqlite::types::Value::Integer(match self {
TripState::Planned => 1,
TripState::Packing => 2,
TripState::Active => 3,
TripState::Finished => 4,
});
rusqlite::Result::Ok(rusqlite::types::ToSqlOutput::Owned(v))
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Trip {
pub id: Uuid,
pub name: String,
pub date: String,
pub parameters: TripParameters,
pub package_lists: Vec<TripPackageList>,
pub state: TripState,
}
impl Trip {
pub fn new(
id: Uuid,
name: String,
date: String,
parameters: TripParameters,
state: TripState,
) -> Trip {
Trip {
id,
name,
date,
parameters,
package_lists: vec![],
state,
}
}
pub fn from_package_list(
id: Uuid,
name: String,
date: String,
parameters: TripParameters,
package_lists: Vec<(Uuid, String)>,
state: TripState,
) -> Trip {
let lists = TripPackageList::construct_vec(package_lists);
Trip {
id,
name,
date,
parameters,
package_lists: lists,
state,
}
}
pub fn set_package_lists(&mut self, package_lists: Vec<(Uuid, String)>) {
let v = TripPackageList::construct_vec(package_lists);
self.package_lists = v;
}
}