From c47ca7494e6e3aad7d4ac6d598641105082e4f26 Mon Sep 17 00:00:00 2001 From: creating2morrow Date: Sun, 25 Jun 2023 17:48:52 -0400 Subject: [PATCH] organize lmdb keys and order bug fix --- neveko-core/src/auth.rs | 2 +- neveko-core/src/contact.rs | 8 +- neveko-core/src/dispute.rs | 2 +- neveko-core/src/gpg.rs | 2 + neveko-core/src/lib.rs | 29 +++- neveko-core/src/message.rs | 47 ++++--- neveko-core/src/order.rs | 57 +++++++- neveko-core/src/product.rs | 8 +- neveko-core/src/proof.rs | 2 +- neveko-core/src/user.rs | 4 +- neveko-gui/src/apps/address_book.rs | 42 +++--- neveko-gui/src/apps/market.rs | 203 +++++++++++++++++++++++++--- neveko-gui/src/lib.rs | 14 ++ 13 files changed, 332 insertions(+), 88 deletions(-) diff --git a/neveko-core/src/auth.rs b/neveko-core/src/auth.rs index fa7e8e5..22a807f 100644 --- a/neveko-core/src/auth.rs +++ b/neveko-core/src/auth.rs @@ -32,7 +32,7 @@ use std::collections::BTreeMap; /// Create authorization data to sign and expiration pub fn create(address: &String) -> Authorization { info!("creating auth"); - let aid: String = format!("auth{}", utils::generate_rnd()); + let aid: String = format!("{}{}", crate::AUTH_DB_KEY, utils::generate_rnd()); let rnd: String = utils::generate_rnd(); let created: i64 = chrono::offset::Utc::now().timestamp(); let token: String = create_token(String::from(address), created); diff --git a/neveko-core/src/contact.rs b/neveko-core/src/contact.rs index a79894c..7c16e5e 100644 --- a/neveko-core/src/contact.rs +++ b/neveko-core/src/contact.rs @@ -37,7 +37,7 @@ impl Prune { /// Create a new contact pub async fn create(c: &Json) -> Contact { - let f_cid: String = format!("c{}", utils::generate_rnd()); + let f_cid: String = format!("{}{}", crate::CONTACT_DB_KEY, utils::generate_rnd()); info!("creating contact: {}", f_cid); let new_contact = Contact { cid: String::from(&f_cid), @@ -57,8 +57,8 @@ pub async fn create(c: &Json) -> Contact { let k = &new_contact.cid; db::Interface::write(&s.env, &s.handle, k, &Contact::to_db(&new_contact)); // in order to retrieve all contact, write keys to with cl - let list_key = format!("cl"); - let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + let list_key = crate::CONTACT_LIST_DB_KEY; + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { debug!("creating contact index"); } @@ -85,7 +85,7 @@ pub fn find(cid: &String) -> Contact { /// All contact lookup pub fn find_all() -> Vec { let s = db::Interface::open(); - let list_key = format!("cl"); + let list_key = crate::CONTACT_LIST_DB_KEY; let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { error!("contact index not found"); diff --git a/neveko-core/src/dispute.rs b/neveko-core/src/dispute.rs index a302bcf..b8ab201 100644 --- a/neveko-core/src/dispute.rs +++ b/neveko-core/src/dispute.rs @@ -12,7 +12,7 @@ use rocket::serde::json::Json; /// Create a new dispute pub fn create(d: Json) -> Dispute { - let f_did: String = format!("dispute{}", utils::generate_rnd()); + let f_did: String = format!("{}{}", crate::DISPUTE_DB_KEY, utils::generate_rnd()); info!("create dispute: {}", &f_did); let new_dispute = Dispute { did: String::from(&f_did), diff --git a/neveko-core/src/gpg.rs b/neveko-core/src/gpg.rs index 2c78ff5..60a76b6 100644 --- a/neveko-core/src/gpg.rs +++ b/neveko-core/src/gpg.rs @@ -15,6 +15,8 @@ use std::{ process::Command, }; +// TODO(c2m): remove this module and use monero public keys for text encryption + /// Searches for key, returns empty string if none exists /// /// TODO(c2m): add more cli options diff --git a/neveko-core/src/lib.rs b/neveko-core/src/lib.rs index b87163a..67c9520 100644 --- a/neveko-core/src/lib.rs +++ b/neveko-core/src/lib.rs @@ -1,24 +1,43 @@ pub mod args; // command line arguments pub mod auth; // internal auth repo/service layer pub mod contact; // contact repo/service layer -pub mod dispute; // Dispute repo/service layer +pub mod dispute; // dispute repo/service layer pub mod db; // lmdb interface pub mod gpg; // gpgme interface pub mod i2p; // i2p repo/service layer pub mod message; // message repo/service layer pub mod models; // db structs pub mod monero; // monero-wallet-rpc interface -pub mod order; // Order repo/service layer -pub mod product; // Product repo/service layer +pub mod order; // order repo/service layer +pub mod product; // product repo/service layer pub mod proof; // external auth/payment proof module pub mod reqres; // http request/responses -pub mod user; // misc. -pub mod utils; // user rep/service layer +pub mod user; // user repo/service layer +pub mod utils; // misc. pub const APP_NAME: &str = "neveko"; pub const NEVEKO_JWP_SECRET_KEY: &str = "NEVEKO_JWP_SECRET_KEY"; pub const NEVEKO_JWT_SECRET_KEY: &str = "NEVEKO_JWT_SECRET_KEY"; +// LMDB Keys +pub const AUTH_DB_KEY: &str = "a"; +pub const CONTACT_DB_KEY: &str = "c"; +pub const DISPUTE_DB_KEY: &str = "d"; +pub const MESSAGE_DB_KEY: &str = "m"; +pub const ORDER_DB_KEY: &str = "o"; +pub const PRODUCT_DB_KEY: &str = "p"; +pub const USER_DB_KEY: &str = "u"; +pub const CONTACT_LIST_DB_KEY: &str = "cl"; +pub const MESSAGE_LIST_DB_KEY: &str = "ml"; +pub const ORDER_LIST_DB_KEY: &str = "ol"; +pub const PRODUCT_LIST_DB_KEY: &str = "pl"; +pub const RX_MESSAGE_DB_KEY: &str = "rx"; +pub const FTS_DB_KEY: &str = "fts"; +pub const CUSTOMER_ORDER_LIST_DB_KEY: &str = "olc"; +pub const MSIG_MESSAGE_DB_KEY: &str = "msig"; +pub const FTS_JWP_DB_KEY: &str = "fts-jwp"; +// End LMDB Keys + /// Environment variable for injecting wallet password pub const MONERO_WALLET_PASSWORD: &str = "MONERO_WALLET_PASSWORD"; /// Environment variable for I2P proxy host diff --git a/neveko-core/src/message.rs b/neveko-core/src/message.rs index 9f1ecab..1f2185a 100644 --- a/neveko-core/src/message.rs +++ b/neveko-core/src/message.rs @@ -51,9 +51,9 @@ impl Default for MultisigMessageData { /// Create a new message pub async fn create(m: Json, jwp: String, m_type: MessageType) -> Message { let rnd = utils::generate_rnd(); - let mut f_mid: String = format!("m{}", &rnd); + let mut f_mid: String = format!("{}{}", crate::MESSAGE_DB_KEY, &rnd); if m_type == MessageType::Multisig { - f_mid = format!("msig{}", &rnd); + f_mid = format!("{}{}", crate::MSIG_MESSAGE_DB_KEY, &rnd); } info!("creating message: {}", &f_mid); let created = chrono::offset::Utc::now().timestamp(); @@ -73,8 +73,8 @@ pub async fn create(m: Json, jwp: String, m_type: MessageType) -> Messa let k = &new_message.mid; db::Interface::write(&s.env, &s.handle, k, &Message::to_db(&new_message)); // in order to retrieve all message, write keys to with ml - let list_key = format!("ml"); - let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + let list_key = crate::MESSAGE_LIST_DB_KEY; + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { debug!("creating message index"); } @@ -99,10 +99,10 @@ pub async fn rx(m: Json) { if !is_in_contact_list { return; } - let f_mid: String = format!("m{}", utils::generate_rnd()); + let f_mid: String = format!("{}{}", crate::MESSAGE_DB_KEY, utils::generate_rnd()); let new_message = Message { mid: String::from(&f_mid), - uid: String::from("rx"), + uid: String::from(crate::RX_MESSAGE_DB_KEY), from: String::from(&m.from), body: m.body.iter().cloned().collect(), created: chrono::offset::Utc::now().timestamp(), @@ -113,8 +113,8 @@ pub async fn rx(m: Json) { let k = &new_message.mid; db::Interface::write(&s.env, &s.handle, k, &Message::to_db(&new_message)); // in order to retrieve all message, write keys to with rx - let list_key = format!("rx"); - let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + let list_key = crate::RX_MESSAGE_DB_KEY; + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { debug!("creating message index"); } @@ -191,7 +191,7 @@ pub async fn rx_multisig(m: Json) { let f_mid: String = format!("msig{}", utils::generate_rnd()); let new_message = Message { mid: String::from(&f_mid), - uid: String::from("rx"), + uid: String::from(crate::RX_MESSAGE_DB_KEY), from: String::from(&m.from), body: m.body.iter().cloned().collect(), created: chrono::offset::Utc::now().timestamp(), @@ -226,7 +226,7 @@ pub fn find(mid: &String) -> Message { /// Message lookup pub fn find_all() -> Vec { let i_s = db::Interface::open(); - let i_list_key = format!("ml"); + let i_list_key = crate::MESSAGE_LIST_DB_KEY; let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key)); if i_r == utils::empty_string() { error!("message index not found"); @@ -240,7 +240,7 @@ pub fn find_all() -> Vec { messages.push(message); } } - let o_list_key = format!("rx"); + let o_list_key = crate::RX_MESSAGE_DB_KEY; let o_s = db::Interface::open(); let o_r = db::Interface::read(&o_s.env, &o_s.handle, &String::from(o_list_key)); if o_r == utils::empty_string() { @@ -350,8 +350,8 @@ async fn send_to_retry(mid: String) { info!("sending {} to fts", &mid); let s = db::Interface::open(); // in order to retrieve FTS (failed-to-send), write keys to db with fts - let list_key = format!("fts"); - let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + let list_key = crate::FTS_DB_KEY; + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { debug!("creating fts message index"); } @@ -366,8 +366,7 @@ async fn send_to_retry(mid: String) { ); db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list); // restart fts if not empty - let list_key = format!("fts"); - let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); let v_mid = r.split(","); let v: Vec = v_mid.map(|s| String::from(s)).collect(); debug!("fts contents: {:#?}", v); @@ -383,8 +382,8 @@ fn remove_from_fts(mid: String) { info!("removing id {} from fts", &mid); let s = db::Interface::open(); // in order to retrieve FTS (failed-to-send), write keys to with fts - let list_key = format!("fts"); - let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + let list_key = crate::FTS_DB_KEY; + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { debug!("fts is empty"); } @@ -417,7 +416,7 @@ pub async fn retry_fts() { debug!("running retry failed-to-send thread"); tick.recv().unwrap(); let s = db::Interface::open(); - let list_key = format!("fts"); + let list_key = crate::FTS_DB_KEY; let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { info!("fts message index not found"); @@ -430,7 +429,7 @@ pub async fn retry_fts() { if cleared { // index was created but cleared info!("terminating retry fts thread"); - db::Interface::delete(&s.env, &s.handle, "fts"); + db::Interface::delete(&s.env, &s.handle, list_key); break; } for m in v { @@ -438,7 +437,7 @@ pub async fn retry_fts() { if message.mid != utils::empty_string() { let s = db::Interface::open(); // get jwp from db - let k = format!("{}-{}", "fts-jwp", &message.to); + let k = format!("{}-{}", crate::FTS_JWP_DB_KEY, &message.to); let jwp = db::Interface::read(&s.env, &s.handle, &k); if jwp != utils::empty_string() { let m_type = if message.mid.contains("misg") { @@ -479,7 +478,7 @@ pub async fn send_prepare_info(orid: &String, contact: &String) { let wallet_password = utils::empty_string(); monero::open_wallet(&orid, &wallet_password).await; let prepare_info = monero::prepare_wallet().await; - let k = format!("{}-{}", "fts-jwp", contact); + let k = format!("{}-{}", crate::FTS_JWP_DB_KEY, contact); let jwp = db::Interface::read(&s.env, &s.handle, &k); let body_str = format!( "{}:{}:{}", @@ -504,7 +503,7 @@ pub async fn send_make_info(orid: &String, contact: &String, info: Vec) let wallet_password = utils::empty_string(); monero::open_wallet(&orid, &wallet_password).await; let make_info = monero::make_wallet(info).await; - let k = format!("{}-{}", "fts-jwp", contact); + let k = format!("{}-{}", crate::FTS_JWP_DB_KEY, contact); let jwp = db::Interface::read(&s.env, &s.handle, &k); let body_str = format!("{}:{}:{}", MAKE_MSIG, orid, &make_info.result.multisig_info); let message: Message = Message { @@ -526,7 +525,7 @@ pub async fn send_exchange_info(orid: &String, contact: &String, info: Vec String { match *self { + StatusType::_Cancelled => String::from("Cancelled"), StatusType::Delivered => String::from("Delivered"), StatusType::MultisigMissing => String::from("MultisigMissing"), StatusType::MulitsigComplete => String::from("MulitsigComplete"), @@ -45,7 +47,7 @@ pub async fn create(j_order: Json) -> Order { std::env::var(crate::MONERO_WALLET_PASSWORD).unwrap_or(String::from("password")); monero::open_wallet(&wallet_name, &wallet_password).await; let ts = chrono::offset::Utc::now().timestamp(); - let orid: String = format!("O{}", utils::generate_rnd()); + let orid: String = format!("{}{}", crate::ORDER_DB_KEY, utils::generate_rnd()); let r_subaddress = monero::create_address().await; let subaddress = r_subaddress.result.address; let new_order = Order { @@ -71,8 +73,8 @@ pub async fn create(j_order: Json) -> Order { let k = &new_order.orid; db::Interface::write(&s.env, &s.handle, k, &Order::to_db(&new_order)); // in order to retrieve all orders, write keys to with ol - let list_key = format!("ol"); - let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + let list_key = crate::ORDER_LIST_DB_KEY; + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { debug!("creating order index"); } @@ -83,6 +85,23 @@ pub async fn create(j_order: Json) -> Order { new_order } +/// Backup order for customer +pub fn backup(order: &Order) { + info!("creating backup of order: {}", order.orid); + let s = db::Interface::open(); + let k = &order.orid; + db::Interface::write(&s.env, &s.handle, k, &Order::to_db(&order)); + // in order to retrieve all orders, write keys to with col + let list_key = crate::CUSTOMER_ORDER_LIST_DB_KEY; + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); + if r == utils::empty_string() { + debug!("creating customer order index"); + } + let order_list = [r, String::from(&order.orid)].join(","); + debug!("writing order index {} for id: {}", order_list, list_key); + db::Interface::write(&s.env, &s.handle, &String::from(list_key), &order_list); +} + /// Lookup order pub fn find(oid: &String) -> Order { info!("find order: {}", &oid); @@ -98,7 +117,7 @@ pub fn find(oid: &String) -> Order { /// Lookup all orders from admin server pub fn find_all() -> Vec { let i_s = db::Interface::open(); - let i_list_key = format!("ol"); + let i_list_key = crate::ORDER_LIST_DB_KEY; let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key)); if i_r == utils::empty_string() { error!("order index not found"); @@ -115,11 +134,31 @@ pub fn find_all() -> Vec { orders } +/// Lookup all orders that customer has saved from gui +pub fn find_all_backup() -> Vec { + let i_s = db::Interface::open(); + let i_list_key = crate::CUSTOMER_ORDER_LIST_DB_KEY; + let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key)); + if i_r == utils::empty_string() { + error!("customer order index not found"); + } + let i_v_oid = i_r.split(","); + let i_v: Vec = i_v_oid.map(|s| String::from(s)).collect(); + let mut orders: Vec = Vec::new(); + for o in i_v { + let order: Order = find(&o); + if order.orid != utils::empty_string() { + orders.push(order); + } + } + orders +} + /// Lookup all orders for customer pub async fn find_all_customer_orders(cid: String) -> Vec { info!("lookup orders for customer: {}", &cid); let i_s = db::Interface::open(); - let i_list_key = format!("ol"); + let i_list_key = crate::ORDER_LIST_DB_KEY; let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key)); if i_r == utils::empty_string() { error!("order index not found"); @@ -298,7 +337,11 @@ pub async fn finalize_order(orid: &String) -> reqres::FinalizeOrderResponse { } /// Send order request to vendor and start multisig flow -pub async fn transmit_order_request(contact: String, jwp: String, request: reqres::OrderRequest) -> Result> { +pub async fn transmit_order_request( + contact: String, + jwp: String, + request: reqres::OrderRequest, +) -> Result> { let host = utils::get_i2p_http_proxy(); let proxy = reqwest::Proxy::http(&host)?; let client = reqwest::Client::builder().proxy(proxy).build(); @@ -313,7 +356,7 @@ pub async fn transmit_order_request(contact: String, jwp: String, request: reqre let res = response.json::().await; debug!("create order response: {:?}", res); match res { - Ok(_r) => Ok(Default::default()), + Ok(r) => Ok(r), _ => Ok(Default::default()), } } diff --git a/neveko-core/src/product.rs b/neveko-core/src/product.rs index 1d2c38e..3feaaf3 100644 --- a/neveko-core/src/product.rs +++ b/neveko-core/src/product.rs @@ -14,7 +14,7 @@ use std::error::Error; /// Create a new product pub fn create(d: Json) -> Product { - let pid: String = format!("product{}", utils::generate_rnd()); + let pid: String = format!("{}{}", crate::PRODUCT_DB_KEY, utils::generate_rnd()); if !validate_product(&d) { error!("invalid product"); return Default::default(); @@ -33,8 +33,8 @@ pub fn create(d: Json) -> Product { let k = &new_product.pid; db::Interface::write(&s.env, &s.handle, k, &Product::to_db(&new_product)); // in order to retrieve all products, write keys to with pl - let list_key = format!("pl"); - let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key)); + let list_key = crate::PRODUCT_LIST_DB_KEY; + let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); if r == utils::empty_string() { debug!("creating product index"); } @@ -61,7 +61,7 @@ pub fn find(pid: &String) -> Product { /// Product lookup for all pub fn find_all() -> Vec { let i_s = db::Interface::open(); - let i_list_key = format!("pl"); + let i_list_key = crate::PRODUCT_LIST_DB_KEY; let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key)); if i_r == utils::empty_string() { error!("product index not found"); diff --git a/neveko-core/src/proof.rs b/neveko-core/src/proof.rs index b88df29..7d45659 100644 --- a/neveko-core/src/proof.rs +++ b/neveko-core/src/proof.rs @@ -133,7 +133,7 @@ pub async fn prove_payment(contact: String, txp: &TxProof) -> Result { // cache the jwp for for fts let s = db::Interface::open(); - let k = format!("{}-{}", "fts-jwp", &contact); + let k = format!("{}-{}", crate::FTS_JWP_DB_KEY, &contact); db::Interface::delete(&s.env, &s.handle, &k); db::Interface::write(&s.env, &s.handle, &k, &r.jwp); Ok(r) diff --git a/neveko-core/src/user.rs b/neveko-core/src/user.rs index eacf77d..c219409 100644 --- a/neveko-core/src/user.rs +++ b/neveko-core/src/user.rs @@ -12,10 +12,12 @@ use log::{ use rocket::serde::json::Json; // This module is only used for remote access +// TODO(c2m): remove this module since there is only support for a single +// authenticated user /// Create a new user pub fn create(address: &String) -> User { - let f_uid: String = format!("u{}", utils::generate_rnd()); + let f_uid: String = format!("{}{}", crate::USER_DB_KEY, utils::generate_rnd()); let new_user = User { uid: String::from(&f_uid), xmr_address: String::from(address), diff --git a/neveko-gui/src/apps/address_book.rs b/neveko-gui/src/apps/address_book.rs index 596db76..9e8943e 100644 --- a/neveko-gui/src/apps/address_book.rs +++ b/neveko-gui/src/apps/address_book.rs @@ -336,9 +336,9 @@ impl eframe::App for AddressBookApp { if ui.button("Sign Key").clicked() { contact::trust_gpg(self.status.i2p.clone()); utils::write_gui_db( - String::from("gui-signed-key"), + String::from(crate::GUI_SIGNED_GPG_DB_KEY), self.status.i2p.clone(), - String::from("1"), + String::from(crate::SIGNED_GPG_KEY), ); self.showing_status = false; } @@ -542,7 +542,7 @@ impl eframe::App for AddressBookApp { row.col(|ui| { if ui.button("Check Status").clicked() { let nick_db = utils::search_gui_db( - String::from("gui-nick"), + String::from(crate::GUI_NICK_DB_KEY), String::from(&c.i2p_address), ); let nick = if nick_db == utils::empty_string() { @@ -554,16 +554,16 @@ impl eframe::App for AddressBookApp { self.status.i2p = String::from(&c.i2p_address); // get the txp self.status.txp = utils::search_gui_db( - String::from("gui-txp"), + String::from(crate::GUI_TX_PROOF_DB_KEY), String::from(&c.i2p_address), ); // get the jwp self.status.jwp = utils::search_gui_db( - String::from("gui-jwp"), + String::from(crate::GUI_JWP_DB_KEY), String::from(&c.i2p_address), ); let r_exp = utils::search_gui_db( - String::from("gui-exp"), + String::from(crate::GUI_EXP_DB_KEY), String::from(&c.i2p_address), ); self.status.exp = r_exp; @@ -713,22 +713,22 @@ fn send_payment_req( signature: get_txp.result.signature, }; utils::write_gui_db( - String::from("gui-txp"), + String::from(crate::GUI_TX_PROOF_DB_KEY), String::from(&contact), String::from(&ftxp.signature), ); utils::write_gui_db( - String::from("gui-txp-hash"), + String::from(crate::GUI_TX_HASH_DB_KEY), String::from(&contact), String::from(&ftxp.hash), ); utils::write_gui_db( - String::from("gui-txp-sig"), + String::from(crate::GUI_TX_SIGNATURE_DB_KEY), String::from(&contact), String::from(&ftxp.signature), ); utils::write_gui_db( - String::from("gui-txp-subaddress"), + String::from(crate::GUI_TX_SUBADDRESS_DB_KEY), String::from(&contact), String::from(&ftxp.subaddress), ); @@ -746,7 +746,7 @@ fn send_payment_req( match proof::prove_payment(String::from(&contact), &ftxp).await { Ok(result) => { utils::write_gui_db( - String::from("gui-jwp"), + String::from(crate::GUI_JWP_DB_KEY), String::from(&contact), String::from(&result.jwp), ); @@ -754,7 +754,7 @@ fn send_payment_req( let seconds: i64 = expire as i64 * 2 * 60; let unix: i64 = chrono::offset::Utc::now().timestamp() + seconds; utils::write_gui_db( - String::from("gui-exp"), + String::from(crate::GUI_EXP_DB_KEY), String::from(&contact), format!("{}", unix), ); @@ -767,9 +767,9 @@ fn send_payment_req( monero::close_wallet(&wallet_name, &wallet_password).await; } if retry { - let k_hash = String::from("gui-txp-hash"); - let k_sig = String::from("gui-txp-sig"); - let k_subaddress = String::from("gui-txp-subaddress"); + let k_hash = String::from(crate::GUI_TX_HASH_DB_KEY); + let k_sig = String::from(crate::GUI_TX_SIGNATURE_DB_KEY); + let k_subaddress = String::from(crate::GUI_TX_SUBADDRESS_DB_KEY); let hash = utils::search_gui_db(k_hash, String::from(&contact)); let signature = utils::search_gui_db(k_sig, String::from(&contact)); let subaddress = utils::search_gui_db(k_subaddress, String::from(&contact)); @@ -788,7 +788,7 @@ fn send_payment_req( match proof::prove_payment(String::from(&contact), &ftxp).await { Ok(result) => { utils::write_gui_db( - String::from("gui-jwp"), + String::from(crate::GUI_JWP_DB_KEY), String::from(&contact), String::from(&result.jwp), ); @@ -825,14 +825,18 @@ fn send_message_req(tx: Sender, ctx: egui::Context, body: String, to: Stri } fn check_signed_key(contact: String) -> bool { - let v = utils::search_gui_db(String::from("gui-signed-key"), contact); + let v = utils::search_gui_db(String::from(crate::GUI_SIGNED_GPG_DB_KEY), contact); v != utils::empty_string() } fn change_nick_req(contact: String, nick: String) { log::debug!("change nick"); - utils::clear_gui_db(String::from("gui-nick"), String::from(&contact)); - utils::write_gui_db(String::from("gui-nick"), String::from(&contact), nick); + utils::clear_gui_db(String::from(crate::GUI_NICK_DB_KEY), String::from(&contact)); + utils::write_gui_db( + String::from(crate::GUI_NICK_DB_KEY), + String::from(&contact), + nick, + ); } fn send_can_transfer_req(tx: Sender, ctx: egui::Context, invoice: u128) { diff --git a/neveko-gui/src/apps/market.rs b/neveko-gui/src/apps/market.rs index b1a1b66..4e3711a 100644 --- a/neveko-gui/src/apps/market.rs +++ b/neveko-gui/src/apps/market.rs @@ -9,6 +9,7 @@ pub struct MarketApp { contact_info_rx: Receiver, contact_timeout_tx: Sender, contact_timeout_rx: Receiver, + customer_orders: Vec, find_vendor: String, get_vendor_products_tx: Sender>, get_vendor_products_rx: Receiver>, @@ -17,6 +18,8 @@ pub struct MarketApp { is_loading: bool, is_ordering: bool, is_pinging: bool, + is_customer_viewing_orders: bool, + is_managing_multisig: bool, is_product_image_set: bool, is_showing_products: bool, is_showing_product_image: bool, @@ -69,12 +72,15 @@ impl Default for MarketApp { contact_info_tx, contact_timeout_rx, contact_timeout_tx, + customer_orders: Vec::new(), find_vendor: utils::empty_string(), get_vendor_products_rx, get_vendor_products_tx, get_vendor_product_rx, get_vendor_product_tx, + is_customer_viewing_orders: false, is_loading: false, + is_managing_multisig: false, is_ordering: false, is_pinging: false, is_product_image_set: false, @@ -185,6 +191,152 @@ impl eframe::App for MarketApp { } } + // Vendor status window + //----------------------------------------------------------------------------------- + let mut is_showing_vendor_status = self.is_showing_vendor_status; + egui::Window::new(&self.vendor_status.i2p) + .open(&mut is_showing_vendor_status) + .vscroll(true) + .title_bar(false) + .id(egui::Id::new(self.vendor_status.i2p.clone())) + .show(&ctx, |ui| { + if self.is_pinging { + ui.add(egui::Spinner::new()); + ui.label("pinging..."); + } + let status = if self.s_contact.xmr_address != utils::empty_string() { + "online" + } else { + "offline" + }; + let mode = if self.vendor_status.is_vendor { + "enabled " + } else { + "disabled" + }; + ui.label(format!("status: {}", status)); + ui.label(format!("vendor mode: {}", mode)); + ui.label(format!("nick: {}", self.vendor_status.nick)); + ui.label(format!("tx proof: {}", self.vendor_status.txp)); + ui.label(format!("jwp: {}", self.vendor_status.jwp)); + ui.label(format!("expiration: {}", self.vendor_status.h_exp)); + ui.label(format!("signed key: {}", self.vendor_status.signed_key)); + if ui.button("Exit").clicked() { + self.is_showing_vendor_status = false; + } + }); + + // Product image window + //----------------------------------------------------------------------------------- + let mut is_showing_product_image = self.is_showing_product_image; + egui::Window::new("") + .open(&mut is_showing_product_image) + .vscroll(true) + .show(ctx, |ui| { + self.product_image.show(ui); + if ui.button("Exit").clicked() { + self.is_showing_product_image = false; + self.is_product_image_set = false; + let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new()); + self.product_image = + egui_extras::RetainedImage::from_image_bytes("qr.png", &read_product_image) + .unwrap(); + } + }); + + // Multisig Management window + //----------------------------------------------------------------------------------- + let mut is_managing_multisig = self.is_managing_multisig; + egui::Window::new("Multisig Management") + .open(&mut is_managing_multisig) + .vscroll(true) + .show(ctx, |ui| { + // TODO(c2m): interactive multisig checklist + if ui.button("Exit").clicked() { + self.is_managing_multisig = false; + self.is_loading = false; + } + }); + + // View orders - Customer Order Flow Management + //----------------------------------------------------------------------------------- + let mut is_customer_viewing_orders = self.is_customer_viewing_orders; + egui::Window::new("View Orders") + .open(&mut is_customer_viewing_orders) + .vscroll(true) + .show(&ctx, |ui| { + use egui_extras::{ + Column, + TableBuilder, + }; + let table = TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .min_scrolled_height(0.0); + + table + .header(20.0, |mut header| { + header.col(|ui| { + ui.strong("orid"); + }); + header.col(|ui| { + ui.strong("date"); + }); + header.col(|ui| { + ui.strong("status"); + }); + header.col(|ui| { + ui.strong(""); + }); + header.col(|ui| { + ui.strong(""); + }); + }) + .body(|mut body| { + for o in &self.customer_orders { + let row_height = 20.0; + body.row(row_height, |mut row| { + row.col(|ui| { + ui.label(format!("{}", o.orid)); + }); + row.col(|ui| { + let h_date = + chrono::NaiveDateTime::from_timestamp_opt(o.date, 0) + .unwrap() + .to_string(); + ui.label(format!("{}", h_date)); + }); + row.col(|ui| { + ui.label(format!("{}", o.status)); + }); + row.col(|ui| { + if ui.button("MSIG").clicked() { + // dynamically generate buttons for multisig wallet ops + } + }); + row.col(|ui| { + ui.style_mut().wrap = Some(false); + ui.horizontal(|ui| { + if ui.button("Cancel").clicked() { + // TODO(c2m): Cancel order logic + } + }); + }); + }); + } + }); + if ui.button("Exit").clicked() { + self.is_customer_viewing_orders = false; + self.is_loading = false; + } + }); + // Customer Order Form //----------------------------------------------------------------------------------- let mut is_ordering = self.is_ordering; @@ -192,6 +344,10 @@ impl eframe::App for MarketApp { .open(&mut is_ordering) .vscroll(true) .show(&ctx, |ui| { + if self.is_loading { + ui.add(egui::Spinner::new()); + ui.label("loading..."); + } ui.label(format!("cid: {}", self.new_order.cid)); ui.label(format!("pid: {}", self.new_order.pid)); ui.horizontal(|ui| { @@ -200,7 +356,7 @@ impl eframe::App for MarketApp { .labelled_by(shipping_name.id); }); ui.horizontal(|ui| { - let qty_name = ui.label("quantity: \t\t"); + let qty_name = ui.label("quantity: \t\t\t\t"); ui.text_edit_singleline(&mut self.new_order_quantity) .labelled_by(qty_name.id); }); @@ -214,7 +370,7 @@ impl eframe::App for MarketApp { if p.pid == self.new_order.pid { p_qty = p.qty; break; - } + } } if qty <= p_qty && qty > 0 { if ui.button("Submit Order").clicked() { @@ -232,16 +388,15 @@ impl eframe::App for MarketApp { self.is_loading = true; submit_order_req( self.submit_order_tx.clone(), - self.vendor_status.i2p.clone(), + self.vendor_status.i2p.clone(), ctx.clone(), self.vendor_status.jwp.clone(), - new_order + new_order, ); self.new_order = Default::default(); self.new_order_price = 0; self.new_order_quantity = utils::empty_string(); self.new_order_shipping_address = utils::empty_string(); - self.is_ordering = false; self.is_showing_products = false; } } @@ -313,7 +468,7 @@ impl eframe::App for MarketApp { row.col(|ui| { if ui.button("Check Status").clicked() { let nick_db = utils::search_gui_db( - String::from("gui-nick"), + String::from(crate::GUI_NICK_DB_KEY), String::from(&v.i2p_address), ); let nick = if nick_db == utils::empty_string() { @@ -325,17 +480,16 @@ impl eframe::App for MarketApp { self.vendor_status.i2p = String::from(&v.i2p_address); // get the txp self.vendor_status.txp = utils::search_gui_db( - String::from("gui-txp"), + String::from(crate::GUI_TX_PROOF_DB_KEY), String::from(&v.i2p_address), ); // get the jwp self.vendor_status.jwp = utils::search_gui_db( - String::from("gui-jwp"), + String::from(crate::GUI_JWP_DB_KEY), String::from(&v.i2p_address), ); - log::debug!("jwp: {}", self.vendor_status.jwp); let r_exp = utils::search_gui_db( - String::from("gui-exp"), + String::from(crate::GUI_EXP_DB_KEY), String::from(&v.i2p_address), ); self.vendor_status.exp = r_exp; @@ -686,10 +840,10 @@ impl eframe::App for MarketApp { } }); - // TODO(c2m): Orders window + // Vendor Orders window //----------------------------------------------------------------------------------- let mut is_showing_orders = self.is_showing_orders; - egui::Window::new("Orders") + egui::Window::new("Manage Orders") .open(&mut is_showing_orders) .vscroll(true) .show(&ctx, |ui| { @@ -781,7 +935,8 @@ impl eframe::App for MarketApp { } ui.label("\n"); if ui.button("View Orders").clicked() { - // TODO(c2m): + self.customer_orders = order::find_all_backup(); + self.is_customer_viewing_orders = true; } if self.is_vendor_enabled { ui.label("\n"); @@ -850,6 +1005,10 @@ impl eframe::App for MarketApp { self.is_showing_products = true; self.is_showing_vendors = false; } + ui.label("\n"); + if ui.button("Manage Orders").clicked() { + // TODO(c2m): vendor order management logic + } } }); } @@ -884,7 +1043,7 @@ fn send_contact_info_req( } fn check_signed_key(contact: String) -> bool { - let v = utils::search_gui_db(String::from("gui-signed-key"), contact); + let v = utils::search_gui_db(String::from(crate::GUI_SIGNED_GPG_DB_KEY), contact); v != utils::empty_string() } @@ -937,18 +1096,20 @@ fn vendor_status_timeout(tx: Sender, ctx: egui::Context) { }); } -fn submit_order_req(tx: Sender, contact: String, ctx: egui::Context, jwp: String, request: reqres::OrderRequest) { +fn submit_order_req( + tx: Sender, + contact: String, + ctx: egui::Context, + jwp: String, + request: reqres::OrderRequest, +) { tokio::spawn(async move { log::info!("submit order"); let r_contact = String::from(&contact); let order = order::transmit_order_request(r_contact, jwp, request).await; - let u_order = order.unwrap_or(Default::default()); + let u_order = order.unwrap_or_else(|_| Default::default()); // cache order request to db - utils::write_gui_db( - String::from("gui-orid"), - String::from(&contact), - String::from(&u_order.orid), - ); + order::backup(&u_order); let _ = tx.send(u_order); ctx.request_repaint(); }); diff --git a/neveko-gui/src/lib.rs b/neveko-gui/src/lib.rs index cc2067c..116a898 100644 --- a/neveko-gui/src/lib.rs +++ b/neveko-gui/src/lib.rs @@ -5,6 +5,20 @@ mod apps; mod login; mod wrap_app; +// LMDB keys +pub const GUI_JWP_DB_KEY: &str = "gui-jwp"; +pub const GUI_EXP_DB_KEY: &str = "gui-exp"; +pub const GUI_TX_PROOF_DB_KEY: &str = "gui-txp"; +pub const GUI_NICK_DB_KEY: &str = "gui-nick"; +pub const GUI_TX_SIGNATURE_DB_KEY: &str = "gui-txp-sig"; +pub const GUI_TX_HASH_DB_KEY: &str = "gui-txp-hash"; +pub const GUI_SIGNED_GPG_DB_KEY: &str = "gui-signed-key"; +pub const GUI_TX_SUBADDRESS_DB_KEY: &str = "gui-txp-subaddress"; +// End LMDB keys + +/// Designate a contact as verified and trusted +pub const SIGNED_GPG_KEY: &str = "1"; + /// key for fetching the login credential hash pub const CREDENTIAL_KEY: &str = "NEVEKO_GUI_KEY"; /// TODO(c2m): configurable lock screen timeout