Skip to content

Commit

Permalink
Clean up CLI stuff
Browse files Browse the repository at this point in the history
Signed-off-by: Elizabeth Myers <[email protected]>
  • Loading branch information
Elizafox committed Mar 18, 2024
1 parent 095d70b commit 0f7b2cb
Show file tree
Hide file tree
Showing 12 changed files with 519 additions and 167 deletions.
18 changes: 18 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: CC0-1.0
*
* src/cli.rs
*
* This file is a component of ShadyURL by Elizabeth Myers.
*
* To the extent possible under law, the person who associated CC0 with
* ShadyURL has waived all copyright and related or neighboring rights
* to ShadyURL.
*
* You should have received a copy of the CC0 legalcode along with this
* work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
*/

pub mod parser;
mod subcommands;

pub use parser::run_command;
67 changes: 67 additions & 0 deletions src/cli/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* SPDX-License-Identifier: CC0-1.0
*
* src/cli/parser.rs
*
* This file is a component of ShadyURL by Elizabeth Myers.
*
* To the extent possible under law, the person who associated CC0 with
* ShadyURL has waived all copyright and related or neighboring rights
* to ShadyURL.
*
* You should have received a copy of the CC0 legalcode along with this
* work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
*/

use clap::{Args, Parser, Subcommand};

use crate::cli::subcommands::{
AddUserSubcommand, ChangePasswordSubcommand, CliSubcommand, DeleteUserSubcommand,
GenerateKeysSubcommand, RunSubcommand,
};

// Simple things
#[derive(Debug, Clone, Args)]
pub struct UsernameArgument {
pub username: String,
}

#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}

#[derive(Subcommand)]
pub enum Commands {
Run,
AddUser(UsernameArgument),
DeleteUser(UsernameArgument),
ChangePassword(UsernameArgument),
GenerateKeys,
}

pub async fn run_command() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
match &cli.command {
Some(Commands::AddUser(data)) => {
AddUserSubcommand::run_command(data).await?;
Ok(())
}
Some(Commands::DeleteUser(data)) => {
DeleteUserSubcommand::run_command(data).await?;
Ok(())
}
Some(Commands::ChangePassword(data)) => {
ChangePasswordSubcommand::run_command(data).await?;
Ok(())
}
Some(Commands::GenerateKeys) => {
GenerateKeysSubcommand::run_command(&()).await?;
Ok(())
}
Some(Commands::Run) | None => {
RunSubcommand::run_command(&()).await?;
Ok(())
}
}
}
63 changes: 63 additions & 0 deletions src/cli/subcommands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: CC0-1.0
* src/cli/subcommands.rs
*
* This file is a component of ShadyURL by Elizabeth Myers.
*
* To the extent possible under law, the person who associated CC0 with
* ShadyURL has waived all copyright and related or neighboring rights
* to ShadyURL.
*
* You should have received a copy of the CC0 legalcode along with this
* work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
*/

mod adduser;
mod changepassword;
mod deleteuser;
mod generatekeys;
mod run;

use proctitle::set_title;

use crate::env::{EnvError, Vars};

#[async_trait::async_trait]
pub trait CliSubcommand {
type Error;
type PromptUserData: Send + Sync;
type CommandData: Send + Sync;

fn proc_title() -> String {
"shadyurl-rust [command]".to_string()
}

fn load_env() -> Result<Vars, EnvError> {
Vars::load_env()
}

fn prompt_user() -> Result<Self::PromptUserData, Self::Error>;

fn check_cli_args(_: Self::CommandData) -> Result<(), Self::Error> {
Ok(())
}

async fn run_command(data: &Self::CommandData) -> Result<(), Self::Error> {
set_title(Self::proc_title());
let env = Self::load_env().expect("Could not load env");
let prompt = Self::prompt_user()?;
Self::run(env, prompt, data).await
}

async fn run(
env: Vars,
prompt: Self::PromptUserData,
data: &Self::CommandData,
) -> Result<(), Self::Error>;
}

pub use adduser::AddUserSubcommand;
pub use changepassword::ChangePasswordSubcommand;
pub use deleteuser::DeleteUserSubcommand;
pub use generatekeys::GenerateKeysSubcommand;
pub use run::RunSubcommand;
72 changes: 72 additions & 0 deletions src/cli/subcommands/adduser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: CC0-1.0
*
* src/cli/subcommands/adduser.rs
*
* This file is a component of ShadyURL by Elizabeth Myers.
*
* To the extent possible under law, the person who associated CC0 with
* ShadyURL has waived all copyright and related or neighboring rights
* to ShadyURL.
*
* You should have received a copy of the CC0 legalcode along with this
* work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
*/

use password_auth::generate_hash;
use rpassword::prompt_password;

use crate::{
cli::{parser::UsernameArgument, subcommands::CliSubcommand},
env::{EnvError, Vars},
};

use service::{Database, Mutation};

#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error(transparent)]
Db(#[from] sea_orm::DbErr),

#[error(transparent)]
Io(#[from] std::io::Error),

#[error(transparent)]
Env(#[from] EnvError),

#[error("Passwords did not match")]
PasswordMismatch,
}

pub struct AddUserData {
pub password: String,
}

pub struct AddUserSubcommand;

#[async_trait::async_trait]
impl CliSubcommand for AddUserSubcommand {
type Error = CliError;
type PromptUserData = AddUserData;
type CommandData = UsernameArgument;

fn prompt_user() -> Result<Self::PromptUserData, Self::Error> {
let mut password = prompt_password("Password:")?;
if password != prompt_password("Repeat password:")? {
return Err(CliError::PasswordMismatch);
}

password = generate_hash(password);

Ok(Self::PromptUserData { password })
}

async fn run(
env: Vars,
prompt: Self::PromptUserData,
data: &Self::CommandData,
) -> Result<(), Self::Error> {
let db = Database::get(&env.database_url).await?;
Mutation::create_user(&db, &data.username, &prompt.password).await?;
Ok(())
}
}
72 changes: 72 additions & 0 deletions src/cli/subcommands/changepassword.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: CC0-1.0
*
* src/cli/subcommands/changepassword.rs
*
* This file is a component of ShadyURL by Elizabeth Myers.
*
* To the extent possible under law, the person who associated CC0 with
* ShadyURL has waived all copyright and related or neighboring rights
* to ShadyURL.
*
* You should have received a copy of the CC0 legalcode along with this
* work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
*/

use password_auth::generate_hash;
use rpassword::prompt_password;

use crate::{
cli::{parser::UsernameArgument, subcommands::CliSubcommand},
env::{EnvError, Vars},
};

use service::{Database, Mutation};

#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error(transparent)]
Db(#[from] sea_orm::DbErr),

#[error(transparent)]
Io(#[from] std::io::Error),

#[error(transparent)]
Env(#[from] EnvError),

#[error("Passwords did not match")]
PasswordMismatch,
}

pub struct ChangePasswordData {
password: String,
}

pub struct ChangePasswordSubcommand;

#[async_trait::async_trait]
impl CliSubcommand for ChangePasswordSubcommand {
type Error = CliError;
type PromptUserData = ChangePasswordData;
type CommandData = UsernameArgument;

fn prompt_user() -> Result<Self::PromptUserData, Self::Error> {
let mut password = prompt_password("Password:")?;
if password != prompt_password("Repeat password:")? {
return Err(CliError::PasswordMismatch);
}

password = generate_hash(password);

Ok(Self::PromptUserData { password })
}

async fn run(
env: Vars,
prompt: Self::PromptUserData,
data: &Self::CommandData,
) -> Result<(), Self::Error> {
let db = Database::get(&env.database_url).await?;
Mutation::change_user_password(&db, &data.username, &prompt.password).await?;
Ok(())
}
}
84 changes: 84 additions & 0 deletions src/cli/subcommands/deleteuser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: CC0-1.0
*
* src/cli/subcommands/deleteuser.rs
*
* This file is a component of ShadyURL by Elizabeth Myers.
*
* To the extent possible under law, the person who associated CC0 with
* ShadyURL has waived all copyright and related or neighboring rights
* to ShadyURL.
*
* You should have received a copy of the CC0 legalcode along with this
* work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
*/

use std::io::{prelude::*, stdin, stdout};

use crate::{
cli::{parser::UsernameArgument, subcommands::CliSubcommand},
env::{EnvError, Vars},
};

use service::{Database, Mutation, Query};

#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error(transparent)]
Db(#[from] sea_orm::DbErr),

#[error(transparent)]
Io(#[from] std::io::Error),

#[error(transparent)]
Env(#[from] EnvError),

#[error("Aborted")]
Aborted,

#[error("User not found")]
NotFound,
}

pub struct DeleteUserSubcommand;

#[async_trait::async_trait]
impl CliSubcommand for DeleteUserSubcommand {
type Error = CliError;
type PromptUserData = ();
type CommandData = UsernameArgument;

fn prompt_user() -> Result<Self::PromptUserData, Self::Error> {
let mut response = String::new();
loop {
print!("Are you SURE you want to delete this user? [yes/no] ");
stdout().flush()?;
stdin().lock().read_line(&mut response)?;
response = response.trim_end().to_ascii_lowercase().to_string();
match response.as_str() {
"no" | "n" => return Err(CliError::Aborted),
"yes" => break,
_ => {
response.clear();
println!("Please type yes or no.");
}
}
}

Ok(())
}

async fn run(
env: Vars,
_: Self::PromptUserData,
data: &Self::CommandData,
) -> Result<(), Self::Error> {
let db = Database::get(&env.database_url).await?;
let user = Query::find_user_by_username(&db, &data.username)
.await?
.ok_or(CliError::NotFound)?;

Mutation::delete_user(&db, user.id).await?;

Ok(())
}
}
Loading

0 comments on commit 0f7b2cb

Please sign in to comment.