Add API key prompt, switch command, update config format

Add handling and printing of `AddressResponse` to `stdout`
This commit is contained in:
Aadi Desai 2023-08-30 14:55:56 +01:00
parent d0345f0381
commit 275ca23c12
Signed by: supleed2
SSH key fingerprint: SHA256:CkbNRs0yVzXEiUp2zd0PSxsfRUMFF9bLlKXtE1xEbKM
2 changed files with 123 additions and 28 deletions

View file

@ -12,11 +12,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Changelog file (this file) and reference in the [readme](README.md) - Changelog file (this file) and reference in the [readme](README.md)
- Automated release with pre-built binaries using GitHub Actions: [release.yaml](.github/workflows/release.yaml) - Automated release with pre-built binaries using GitHub Actions: [release.yaml](.github/workflows/release.yaml)
- Prami logo, taken from [here](https://sarajoy.dev/blog/short/2023-01-18-ascii-art-heart/) with permission - Prami logo, taken from [here](https://sarajoy.dev/blog/short/2023-01-18-ascii-art-heart/) with permission
- Prompt for API key so it isn't recorded in shell history
- `switch` subcommand for selecting `default_name` and `api_key` to load
- Handling of `AddressResponse` and printing to `stdout`
### Changed ### Changed
- Switch to external version of commands crate, [omg-api](https://github.com/supleed2/omg-api) - Switch to external version of commands crate, [omg-api](https://github.com/supleed2/omg-api)
- Update [omg-api](https://github.com/supleed2/omg-api) to v0.2.0 - Update [omg-api](https://github.com/supleed2/omg-api) to v0.2.0
- Update config format and handling in `main.rs`
### Deprecated ### Deprecated

View file

@ -1,52 +1,128 @@
use anyhow::Context; use anyhow::Context;
use clap::Parser; use clap::Parser;
use directories::ProjectDirs; use directories::ProjectDirs;
use omg_api::Commands; use omg_api::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::read_to_string; use std::fs::read_to_string;
mod cli; mod cli;
use cli::Cli; use cli::Cli;
#[derive(Default, Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct Config { struct Config {
api_key: Option<String>, default_name: Option<String>,
name: Option<String>, names: HashMap<String, String>,
} }
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
if let Some(Commands::Auth { api_key }) = cli.command { if let Commands::Auth { name } = cli.command {
match save_api_key(&api_key) { let api_key = dialoguer::Password::new()
Ok(_) => std::process::exit(0), .with_prompt("API Key")
Err(_) => std::process::exit(1), .interact()?;
}; save_api_key(&api_key, &name)?;
println!("API key for address \"{name}\" saved to config.toml");
std::process::exit(0);
} }
let config = ProjectDirs::from("com", "supleed2", "omg") let config = ProjectDirs::from("com", "supleed2", "omg")
.and_then(|dirs| read_to_string(dirs.config_dir().join("config.toml")).ok()) .and_then(|dirs| read_to_string(dirs.config_dir().join("config.toml")).ok())
.and_then(|str| toml::from_str::<Config>(&str).ok()) .and_then(|str| toml::from_str::<Config>(&str).ok())
.context("Unable to parse config.json as config struct.")?; .context("Unable to parse config.toml as config struct.")?;
if let Commands::Switch { address } = cli.command {
match address {
Some(new) => {
set_default_name(config, &new)?;
println!("Default address in Config.toml updated to \"{new}\"")
}
None => list_saved_addresses(config)?,
}
std::process::exit(0)
}
let address = std::env::var("OMGLOL_USERNAME")
.ok()
.or(config.default_name)
.expect("omg.lol address not provided as either environment variable (`OMGLOL_USERNAME`) or in config file as default_name");
let api_key = std::env::var("OMGLOL_API_KEY") let api_key = std::env::var("OMGLOL_API_KEY")
.ok() .ok()
.or(config.api_key) .or_else(|| config.names.get(&address).cloned())
.expect("omg.lol API key not provided as either environment variable or in config file"); .expect("omg.lol API key not provided as either environment variable (`OMGLOL_API_KEY`) or in config file");
let name = cli.name let resp = cli.command.process(&address, &api_key)?;
.or(std::env::var("OMGLOL_USERNAME").ok())
.or(config.name)
.expect("omg.lol username not provided as command line option, environment variable or in config file");
println!("omg-rs, ready for @{name}");
if cli.verbose > 0 { if cli.verbose > 0 {
println!("API key: {}", api_key); println!("\nAPI Response: {resp:?}");
}
Ok(())
} }
fn save_api_key(api_key: &str) -> std::io::Result<()> { match resp {
CommandResponse::Todo(_) => println!(
"This command has not been implemented yet, please look forward to future releases!"
),
CommandResponse::Address(r) => match r {
AddressResponse::IsAvailable(IsAvailable { response: r }) => {
println!(
"Address \"{}\" is {}available",
r.address,
if r.available { "" } else { "NOT " }
)
}
AddressResponse::GetExpiry(GetExpiry { response: r }) => println!("{}", r.message),
AddressResponse::GetPublicInfo(GetPublicInfo { response: r }) => {
println!(
"Address \"{}\":\n{}\nExpired: {}\nVerified: {}",
r.address, r.message, r.expiration.expired, r.verification.verified
)
}
AddressResponse::GetInfo(GetInfo { response: r }) => {
println!(
"Address \"{}\" (owned by \"{}\"):\n{}\nExpired: {}\nVerified: {}",
r.address, r.owner, r.message, r.expiration.expired, r.verification.verified
)
}
},
}
anyhow::Ok(())
}
fn list_saved_addresses(config: Config) -> anyhow::Result<()> {
println!("Saved addresses:");
for name in config.names.keys() {
println!("{name}");
}
let exe = std::env::current_exe()?;
let exe = exe.file_name().and_then(|e| e.to_str()).unwrap_or("omg");
if let Some(name) = config.default_name {
println!("Current default: {name}, use `{exe} switch [name]` to change");
} else {
println!("Current default unset, use `{exe} switch [name]` to set");
}
anyhow::Ok(())
}
fn set_default_name(config: Config, new: &str) -> std::io::Result<()> {
if config.names.contains_key(new) {
let config_path = ProjectDirs::from("com", "supleed2", "omg")
.expect("Unable to access app config directory (while setting default address).")
.config_dir()
.join("config.toml");
let toml_str = toml::to_string_pretty(&Config {
default_name: Some(new.to_string()),
names: config.names,
})
.expect("Unable to convert updated config to TOML (while setting default address).");
std::fs::write(config_path, toml_str)
} else {
eprintln!("Address \"{new}\" not in config.toml saved addresses");
std::process::exit(1)
}
}
fn save_api_key(api_key: &str, name: &str) -> std::io::Result<()> {
let config_path = ProjectDirs::from("com", "supleed2", "omg") let config_path = ProjectDirs::from("com", "supleed2", "omg")
.expect("Unable to access app config directory (while saving API key).") .expect("Unable to access app config directory (while saving API key).")
.config_dir() .config_dir()
@ -56,17 +132,32 @@ fn save_api_key(api_key: &str) -> std::io::Result<()> {
.parent() .parent()
.expect("Unable to get parent dir of config.toml"), .expect("Unable to get parent dir of config.toml"),
); );
let Config { api_key: _, name } = read_to_string(&config_path) let Config {
default_name,
mut names,
} = read_to_string(&config_path)
.ok() .ok()
.and_then(|str| toml::from_str::<Config>(&str).ok()) .and_then(|str| toml::from_str::<Config>(&str).ok())
.unwrap_or_default(); .unwrap_or_else(|| {
eprintln!("Failed to load config, recreating... (Old config will be overwritten)");
Config {
default_name: Some(name.to_string()),
names: HashMap::new(),
}
});
let default_name = default_name.or_else(|| {
eprintln!("No default_name in config, setting to \"{name}\"");
Some(name.to_string())
});
names.insert(name.to_string(), api_key.to_string());
let toml_str = toml::to_string_pretty(&Config { let toml_str = toml::to_string_pretty(&Config {
api_key: Some(api_key.to_string()), default_name,
name, names,
}) })
.expect("Unable to convert updated config to TOML (when trying to save API key)."); .expect("Unable to convert updated config to TOML (while saving API key).");
std::fs::write(&config_path, toml_str) std::fs::write(&config_path, toml_str)
} }
// Tutorial at: https://docs.rs/clap/latest/clap/_derive/_tutorial/index.html // Clap arg relations: https://docs.rs/clap/latest/clap/_derive/_tutorial/index.html#argument-relations
// More info at: https://docs.rs/clap/latest/clap/_derive/index.html // Clap derive docs: https://docs.rs/clap/latest/clap/_derive/index.html
// Thiserror docs: https://docs.rs/thiserror/latest/thiserror/index.html