cli: updated to be compliant with lib

main
Guus van Meerveld 5 months ago
parent cba0a34812
commit af4ae6dfa4

12
Cargo.lock generated

@ -469,6 +469,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 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]] [[package]]
name = "combine" name = "combine"
version = "3.8.1" version = "3.8.1"
@ -2205,6 +2215,7 @@ name = "sshn-cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"colored",
"env_logger", "env_logger",
"fantoccini", "fantoccini",
"keyring", "keyring",
@ -2223,6 +2234,7 @@ dependencies = [
name = "sshn-lib" name = "sshn-lib"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"base64 0.22.0", "base64 0.22.0",
"chrono", "chrono",
"digest", "digest",

@ -22,3 +22,4 @@ keyring = "2.3.2"
whoami = "1.5.1" whoami = "1.5.1"
serde_json = "1.0.116" serde_json = "1.0.116"
prettytable = "0.10.0" prettytable = "0.10.0"
colored = "2.1.0"

@ -89,12 +89,12 @@ async fn start_web_driver(webdriver: WebDriver, port: u16) -> Result<tokio::proc
Ok(process) Ok(process)
} }
pub async fn password_login<U: AsRef<str>, P: AsRef<str>>( pub async fn headless_login<U: AsRef<str>, P: AsRef<str>>(
username: U, username: U,
password: P, password: P,
options: AuthOptions, options: AuthOptions,
) -> Result<AuthenticatedClient> { ) -> Result<AuthenticatedClient> {
let client = sshn_lib::Client::new(None); let client = sshn_lib::UnAuthenticatedClient::new(None);
let (code_challenge, code_verifier) = get_code_challenge(); let (code_challenge, code_verifier) = get_code_challenge();

@ -1,7 +1,8 @@
use sshn_lib::Client;
use crate::{ use crate::{
auth::{self, AuthOptions}, auth::{self, AuthOptions},
error::Result, error::Result,
publication::{self, Publication},
secrets, secrets,
}; };
@ -10,36 +11,53 @@ pub async fn login<U: AsRef<str>, P: AsRef<str>>(
password: P, password: P,
options: AuthOptions, options: AuthOptions,
) -> Result<()> { ) -> Result<()> {
auth::password_login(username.as_ref(), password.as_ref(), options).await?; auth::headless_login(username.as_ref(), password.as_ref(), options).await?;
Ok(()) Ok(())
} }
pub async fn list(limit: usize) -> Result<()> { pub async fn list(limit: usize) -> Result<prettytable::Table> {
let data = publication::list_publications(limit).await?; use prettytable::{Cell, Row, Table};
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(),
));
}
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<I: AsRef<str>>(id: I) -> Result<()> { pub async fn reply<I: AsRef<str>>(id: I) -> Result<()> {

@ -11,9 +11,6 @@ pub enum Error {
#[error("SSHN Api did not return valid authorization code")] #[error("SSHN Api did not return valid authorization code")]
MissingAuthCode, MissingAuthCode,
#[error("SSHN Api did not return valid publications")]
MissingPublications,
#[error("Missing username and password credentials")] #[error("Missing username and password credentials")]
MissingCredentials, MissingCredentials,

@ -1,15 +1,24 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use rpassword::prompt_password; use rpassword::read_password;
use serde::Serialize; use serde::Serialize;
mod auth; mod auth;
mod commands; mod commands;
mod error; mod error;
mod publication;
mod secrets; mod secrets;
use auth::AuthOptions; 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. /// SSHN command line interface.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@ -60,14 +69,7 @@ pub enum WebDriver {
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
{ env_logger::init();
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();
}
let args = Args::parse(); let args = Args::parse();
@ -81,14 +83,14 @@ async fn main() {
let password = match password { let password = match password {
Some(pass) => pass, Some(pass) => pass,
None => { None => {
let password = prompt_password("Enter the password of your SSHN account: ") show!("Enter the password of your {} account: ", "SSHN".bold());
.expect("Failed to read password"); let password = read_password().expect("Failed to read password");
password password
} }
}; };
log::info!("Logging in as user '{}'", username); show!("Logging in as user '{}'", username.bold().green());
match commands::login( match commands::login(
&username, &username,
@ -98,28 +100,40 @@ async fn main() {
.await .await
{ {
Ok(_) => { Ok(_) => {
log::info!("Succesfully logged in as user '{}'", username) show!(
"Succesfully logged in as user '{}'.",
username.bold().green()
)
} }
Err(error) => { Err(error) => {
log::error!("Error logging in: {}", error); show!("Error logging in:\n\t {}", error);
} }
} }
} }
Commands::List { limit } => { Commands::List { limit } => {
match commands::list(limit.unwrap_or(5)).await { match commands::list(limit.unwrap_or(5)).await {
Ok(_) => {} Ok(table) => {
table.printstd();
}
Err(error) => { Err(error) => {
log::error!("Error listing publications: {}", error); show!("Error listing publications:\n\t {}", error);
} }
}; };
} }
Commands::Reply { id } => { Commands::Reply { id } => {
match commands::reply(id).await { show!("Replying to publication...");
Ok(_) => {}
match commands::reply(&id).await {
Ok(_) => {
show!(
"Successfully replied to publication with id '{}'.",
id.bold().green()
)
}
Err(error) => { Err(error) => {
log::error!("Error replying to publication: {}", error); show!("Error replying to publication:\n\t {}", error);
} }
}; };
} }

@ -1,61 +0,0 @@
use crate::error::{Error, Result};
pub async fn list_publications(limit: usize) -> Result<Vec<Publication>> {
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<String> {
vec![
self.name,
self.city,
self.nr_of_applicants.to_string(),
self.rent.to_string(),
self.id,
]
}
pub fn row_labels() -> Vec<String> {
vec![
String::from("Name"),
String::from("City"),
String::from("Number of applicants"),
String::from("Gross rent"),
String::from("ID"),
]
}
}

@ -51,7 +51,7 @@ pub fn get<I: AsRef<str>, T: DeserializeOwned>(identifier: I) -> Result<T> {
} }
pub async fn get_client() -> Result<AuthenticatedClient> { pub async fn get_client() -> Result<AuthenticatedClient> {
let client = sshn_lib::Client::new(None); let client = sshn_lib::UnAuthenticatedClient::new(None);
if let Ok(tokens) = get::<_, Tokens>("tokens") { if let Ok(tokens) = get::<_, Tokens>("tokens") {
if !tokens.access_token().has_expired() { if !tokens.access_token().has_expired() {
@ -70,7 +70,7 @@ pub async fn get_client() -> Result<AuthenticatedClient> {
log::info!("Tokens expired, logging in using credentials"); log::info!("Tokens expired, logging in using credentials");
if let Ok(credentials) = get::<_, Credentials>("credentials") { if let Ok(credentials) = get::<_, Credentials>("credentials") {
return auth::password_login( return auth::headless_login(
credentials.username, credentials.username,
credentials.password, credentials.password,
Default::default(), Default::default(),

Loading…
Cancel
Save