From af4ae6dfa41fde9e980b93ad6237cb0a5c90563f Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Wed, 8 May 2024 22:38:55 +0200 Subject: [PATCH] cli: updated to be compliant with lib --- Cargo.lock | 12 +++++++ sshn-cli/Cargo.toml | 1 + sshn-cli/src/auth.rs | 4 +-- sshn-cli/src/commands/mod.rs | 68 +++++++++++++++++++++++------------- sshn-cli/src/error.rs | 3 -- sshn-cli/src/main.rs | 54 +++++++++++++++++----------- sshn-cli/src/publication.rs | 61 -------------------------------- sshn-cli/src/secrets.rs | 4 +-- 8 files changed, 94 insertions(+), 113 deletions(-) delete mode 100644 sshn-cli/src/publication.rs diff --git a/Cargo.lock b/Cargo.lock index 1b9acd7..21386dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "combine" version = "3.8.1" @@ -2205,6 +2215,7 @@ name = "sshn-cli" version = "0.1.0" dependencies = [ "clap", + "colored", "env_logger", "fantoccini", "keyring", @@ -2223,6 +2234,7 @@ dependencies = [ name = "sshn-lib" version = "0.1.0" dependencies = [ + "async-trait", "base64 0.22.0", "chrono", "digest", diff --git a/sshn-cli/Cargo.toml b/sshn-cli/Cargo.toml index fcb2ecb..46e6a41 100644 --- a/sshn-cli/Cargo.toml +++ b/sshn-cli/Cargo.toml @@ -22,3 +22,4 @@ keyring = "2.3.2" whoami = "1.5.1" serde_json = "1.0.116" prettytable = "0.10.0" +colored = "2.1.0" diff --git a/sshn-cli/src/auth.rs b/sshn-cli/src/auth.rs index 7c3d7de..8982154 100644 --- a/sshn-cli/src/auth.rs +++ b/sshn-cli/src/auth.rs @@ -89,12 +89,12 @@ async fn start_web_driver(webdriver: WebDriver, port: u16) -> Result, P: AsRef>( +pub async fn headless_login, P: AsRef>( username: U, password: P, options: AuthOptions, ) -> Result { - let client = sshn_lib::Client::new(None); + let client = sshn_lib::UnAuthenticatedClient::new(None); let (code_challenge, code_verifier) = get_code_challenge(); diff --git a/sshn-cli/src/commands/mod.rs b/sshn-cli/src/commands/mod.rs index 6661b13..73b2f76 100644 --- a/sshn-cli/src/commands/mod.rs +++ b/sshn-cli/src/commands/mod.rs @@ -1,7 +1,8 @@ +use sshn_lib::Client; + use crate::{ auth::{self, AuthOptions}, error::Result, - publication::{self, Publication}, secrets, }; @@ -10,36 +11,53 @@ pub async fn login, P: AsRef>( password: P, options: AuthOptions, ) -> Result<()> { - auth::password_login(username.as_ref(), password.as_ref(), options).await?; + auth::headless_login(username.as_ref(), password.as_ref(), options).await?; Ok(()) } -pub async fn list(limit: usize) -> Result<()> { - let data = publication::list_publications(limit).await?; - - let mut table = prettytable::Table::new(); - - table.add_row(prettytable::Row::new( - Publication::row_labels() - .iter() - .map(|label| prettytable::Cell::new(label)) - .collect(), - )); - - for publication in data { - table.add_row(prettytable::Row::new( - publication - .as_row() - .iter() - .map(|label| prettytable::Cell::new(label)) - .collect(), - )); - } +pub async fn list(limit: usize) -> Result { + use prettytable::{Cell, Row, Table}; - table.printstd(); + let limit = limit as i64; - Ok(()) + let publications = if false { + let mut client = sshn_lib::UnAuthenticatedClient::new(None); + + client.get_publications_list(limit).await? + } else { + let mut client = secrets::get_client().await?; + + client.get_publications_list(limit).await? + }; + + let mut table = Table::new(); + + table.add_row(Row::new(vec![ + Cell::new("Can reply?"), + Cell::new("Name"), + Cell::new("City"), + Cell::new("Number of applicants"), + Cell::new("Gross rent"), + Cell::new("ID"), + ])); + + for publication in publications { + let nr_of_applicants_string = publication.nr_of_applicants().to_string(); + let gross_rent_string = publication.rent().to_string(); + let is_match = if publication.is_match() { "Yes" } else { "No" }; + + table.add_row(Row::new(vec![ + Cell::new(is_match), + Cell::new(publication.name()), + Cell::new(publication.city()), + Cell::new(&nr_of_applicants_string), + Cell::new(&gross_rent_string), + Cell::new(publication.id()), + ])); + } + + Ok(table) } pub async fn reply>(id: I) -> Result<()> { diff --git a/sshn-cli/src/error.rs b/sshn-cli/src/error.rs index 0593684..70e7186 100644 --- a/sshn-cli/src/error.rs +++ b/sshn-cli/src/error.rs @@ -11,9 +11,6 @@ pub enum Error { #[error("SSHN Api did not return valid authorization code")] MissingAuthCode, - #[error("SSHN Api did not return valid publications")] - MissingPublications, - #[error("Missing username and password credentials")] MissingCredentials, diff --git a/sshn-cli/src/main.rs b/sshn-cli/src/main.rs index d333ed1..81e8578 100644 --- a/sshn-cli/src/main.rs +++ b/sshn-cli/src/main.rs @@ -1,15 +1,24 @@ use clap::{Parser, Subcommand}; -use rpassword::prompt_password; +use rpassword::read_password; use serde::Serialize; mod auth; mod commands; mod error; -mod publication; mod secrets; use auth::AuthOptions; +macro_rules! show { + ($($arg:tt)*) => ({ + use colored::*; + + let app_name = "[SSHN-CLI]".blue().bold(); + + println!("{} {}", app_name, format!($($arg)*)); + }); +} + /// SSHN command line interface. #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -60,14 +69,7 @@ pub enum WebDriver { #[tokio::main] async fn main() { - { - let mut builder = env_logger::Builder::from_default_env(); - - builder.filter_module("sshn_cli", log::LevelFilter::Info); - builder.filter_module("sshn_lib", log::LevelFilter::Info); - - builder.init(); - } + env_logger::init(); let args = Args::parse(); @@ -81,14 +83,14 @@ async fn main() { let password = match password { Some(pass) => pass, None => { - let password = prompt_password("Enter the password of your SSHN account: ") - .expect("Failed to read password"); + show!("Enter the password of your {} account: ", "SSHN".bold()); + let password = read_password().expect("Failed to read password"); password } }; - log::info!("Logging in as user '{}'", username); + show!("Logging in as user '{}'", username.bold().green()); match commands::login( &username, @@ -98,28 +100,40 @@ async fn main() { .await { Ok(_) => { - log::info!("Succesfully logged in as user '{}'", username) + show!( + "Succesfully logged in as user '{}'.", + username.bold().green() + ) } Err(error) => { - log::error!("Error logging in: {}", error); + show!("Error logging in:\n\t {}", error); } } } Commands::List { limit } => { match commands::list(limit.unwrap_or(5)).await { - Ok(_) => {} + Ok(table) => { + table.printstd(); + } Err(error) => { - log::error!("Error listing publications: {}", error); + show!("Error listing publications:\n\t {}", error); } }; } Commands::Reply { id } => { - match commands::reply(id).await { - Ok(_) => {} + show!("Replying to publication..."); + + match commands::reply(&id).await { + Ok(_) => { + show!( + "Successfully replied to publication with id '{}'.", + id.bold().green() + ) + } Err(error) => { - log::error!("Error replying to publication: {}", error); + show!("Error replying to publication:\n\t {}", error); } }; } diff --git a/sshn-cli/src/publication.rs b/sshn-cli/src/publication.rs deleted file mode 100644 index 2552964..0000000 --- a/sshn-cli/src/publication.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::error::{Error, Result}; - -pub async fn list_publications(limit: usize) -> Result> { - let client = sshn_lib::Client::new(None); - - let publications = client.get_publications_list(limit as i64).await?; - - Ok(publications - .housing_publications - .ok_or(Error::MissingPublications)? - .nodes - .ok_or(Error::MissingPublications)? - .edges - .ok_or(Error::MissingPublications)? - .into_iter() - .filter_map(|publication| { - let publication = publication?.node?; - - // let city = publication.unit?.location?.city?.name.as_ref()?.to_string(); - let rent = publication.unit?.gross_rent.as_ref()?.exact; - - Some(Publication { - id: publication.id, - name: String::new(), - city: String::new(), - nr_of_applicants: publication.total_number_of_applications, - rent, - }) - }) - .collect()) -} - -pub struct Publication { - id: String, - name: String, - city: String, - nr_of_applicants: i64, - rent: f64, -} - -impl Publication { - pub fn as_row(self) -> Vec { - vec![ - self.name, - self.city, - self.nr_of_applicants.to_string(), - self.rent.to_string(), - self.id, - ] - } - - pub fn row_labels() -> Vec { - vec![ - String::from("Name"), - String::from("City"), - String::from("Number of applicants"), - String::from("Gross rent"), - String::from("ID"), - ] - } -} diff --git a/sshn-cli/src/secrets.rs b/sshn-cli/src/secrets.rs index be9f075..a6ec3d4 100644 --- a/sshn-cli/src/secrets.rs +++ b/sshn-cli/src/secrets.rs @@ -51,7 +51,7 @@ pub fn get, T: DeserializeOwned>(identifier: I) -> Result { } pub async fn get_client() -> Result { - let client = sshn_lib::Client::new(None); + let client = sshn_lib::UnAuthenticatedClient::new(None); if let Ok(tokens) = get::<_, Tokens>("tokens") { if !tokens.access_token().has_expired() { @@ -70,7 +70,7 @@ pub async fn get_client() -> Result { log::info!("Tokens expired, logging in using credentials"); if let Ok(credentials) = get::<_, Credentials>("credentials") { - return auth::password_login( + return auth::headless_login( credentials.username, credentials.password, Default::default(),