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"
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",

@ -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"

@ -89,12 +89,12 @@ async fn start_web_driver(webdriver: WebDriver, port: u16) -> Result<tokio::proc
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,
password: P,
options: AuthOptions,
) -> Result<AuthenticatedClient> {
let client = sshn_lib::Client::new(None);
let client = sshn_lib::UnAuthenticatedClient::new(None);
let (code_challenge, code_verifier) = get_code_challenge();

@ -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<U: AsRef<str>, P: AsRef<str>>(
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<prettytable::Table> {
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<I: AsRef<str>>(id: I) -> Result<()> {

@ -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,

@ -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);
}
};
}

@ -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> {
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<AuthenticatedClient> {
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(),

Loading…
Cancel
Save