1
0
Fork 0
mirror of https://codeberg.org/tyy/aspm synced 2024-12-23 02:29:29 -07:00

Add profiles edit command

This took too much effort, someone better appreciate it
This commit is contained in:
Tyler Beckman 2024-02-29 21:11:46 -07:00
parent a80acaa83d
commit 77f545b32e
Signed by: Ty
GPG key ID: 2813440C772555A4
3 changed files with 311 additions and 0 deletions

View file

@ -0,0 +1,308 @@
use anstyle::{AnsiColor, Reset, Style as Anstyle};
use anyhow::{bail, Context};
use asp::profiles::{Email, HexColor, Url};
use clap::Parser;
use dialoguer::{theme::ColorfulTheme, Input, Select};
use indoc::writedoc;
use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait, IntoActiveModel, IntoActiveValue};
use std::{io::Write, str::FromStr};
use crate::{
commands::AspmSubcommand,
entities::{claims::ActiveModel as ClaimActiveModel, prelude::*},
};
/// Edits an existing profile
#[derive(Parser, Debug)]
pub struct ProfilesEditCommand {
/// The profile to edit. If not provided, it will be queried for interactively.
profile: Option<String>,
}
#[async_trait::async_trait]
impl AspmSubcommand for ProfilesEditCommand {
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
let (profile, claims) = match &self.profile {
Some(fingerprint) => {
let mut profiles = Profiles::find_by_id(fingerprint)
.find_with_related(Claims)
.all(&state.db)
.await
.context("Unable to query database for a profile")?;
if profiles.is_empty() {
// Impossible to take element out of vec without panicing on OOB, and sea-orm removed SelectTwoMany::one()
bail!("No profile found for the specified fingerprint")
} else {
profiles.swap_remove(0)
}
}
None => {
let mut profiles = Profiles::find()
.find_with_related(Claims)
.all(&state.db)
.await
.context("Unable to query database for profiles")?;
if profiles.is_empty() {
bail!("No profiles found to edit")
}
let choice = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Please select the profile to edit")
.items(
profiles
.iter()
.map(|(profile, _)| &profile.key)
.collect::<Vec<_>>()
.as_slice(),
)
.default(0)
.interact()
.context("Unable to prompt on stderr")?;
profiles.swap_remove(choice)
}
};
let (mut profile, mut claims) = (
profile.into_active_model(),
claims
.into_iter()
.map(|claim| claim.into_active_model())
.collect::<Vec<_>>(),
);
let mut removed_claims = Vec::<ClaimActiveModel>::new();
loop {
let operation = Select::with_theme(&ColorfulTheme::default())
.default(0)
.with_prompt("Please select what to do next (or exit without saving with Esc/q)")
.items(&[
"Edit alias",
"Edit name",
"Edit email",
"Edit description",
"Edit avatar URL",
"Edit color",
"Edit claims",
"Preview",
"Save & exit",
"Exit without saving",
])
.interact_opt()
.context("Unable to prompt on stderr")?;
match operation {
Some(0) => {
profile.alias = ActiveValue::set(
Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter the new profile alias")
.allow_empty(false)
.interact()
.context("Unable to prompt on stderr")?,
)
}
Some(1) => {
profile.name = ActiveValue::set(
Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter the new profile name")
.allow_empty(false)
.interact()
.context("Unable to prompt on stderr")?,
)
}
Some(2) => {
profile.email = ActiveValue::set(
Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter the new profile email")
.allow_empty(true)
.validate_with(|input: &String| -> Result<(), &str> {
Email::from_str(input.as_str())
.map(|_| ())
.or(Err("Invalid email"))
})
.interact()
.map(|res| if res.is_empty() { None } else { Some(res) })
.context("Unable to prompt on stderr")?,
)
}
Some(3) => {
profile.description = ActiveValue::set(
Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter the new profile description")
.allow_empty(true)
.interact()
.map(|res: String| if res.is_empty() { None } else { Some(res) })
.context("Unable to prompt on stderr")?,
)
}
Some(4) => {
profile.avatar_url = ActiveValue::set(
Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter the new profile avatar URL")
.allow_empty(true)
.validate_with(|input: &String| -> Result<(), &str> {
Url::from_str(input.as_str())
.map(|_| ())
.or(Err("Invalid URL"))
})
.interact()
.map(|res| if res.is_empty() { None } else { Some(res) })
.context("Unable to prompt on stderr")?,
)
}
Some(5) => {
profile.color = ActiveValue::set(
Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter the new profile color")
.allow_empty(true)
.validate_with(|input: &String| -> Result<(), &str> {
HexColor::from_str(input.as_str())
.map(|_| ())
.or(Err("Invalid color"))
})
.interact()
.map(|res| if res.is_empty() { None } else { Some(res) })
.context("Unable to prompt on stderr")?,
)
}
Some(6) => loop {
match Select::with_theme(&ColorfulTheme::default())
.with_prompt("How would you like to edit the claims?")
.default(0)
.items(&[
"Add a new claim",
"Edit an existing claim",
"Remove a claim",
"Stop editing claims",
])
.interact_opt()
.context("Unable to prompt on stderr")?
{
Some(0) => claims.push(ClaimActiveModel {
profile: profile.key.clone(),
uri: Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter the URL of the claim")
.allow_empty(false)
.validate_with(|input: &String| -> Result<(), &str> {
Url::from_str(input.as_str())
.map(|_| ())
.or(Err("Invalid URL"))
})
.interact()
.context("Unable to prompt on stderr")?
.into_active_value(),
..Default::default()
}),
Some(1) => {
let index = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which claim would you like to edit?")
.default(0)
.items(
claims
.iter()
.map(|claim| claim.uri.as_ref())
.collect::<Vec<_>>()
.as_slice(),
)
.interact()
.context("Unable to prompt on stderr")?;
claims[index].uri = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Please enter the URL of the claim")
.allow_empty(false)
.validate_with(|input: &String| -> Result<(), &str> {
Url::from_str(input.as_str())
.map(|_| ())
.or(Err("Invalid URL"))
})
.interact()
.context("Unable to prompt on stderr")?
.into_active_value()
}
Some(2) => removed_claims.push(
claims.remove(
Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which claim would you like to edit?")
.default(0)
.items(
claims
.iter()
.map(|claim| claim.uri.as_ref())
.collect::<Vec<_>>()
.as_slice(),
)
.interact()
.context("Unable to prompt on stderr")?,
),
),
Some(3) | None => break,
_ => unreachable!(),
}
},
Some(7) => {
let reset = Reset.render();
let alias_style = Anstyle::new()
.underline()
.fg_color(Some(anstyle::Color::Ansi(AnsiColor::BrightCyan)))
.render();
let key_style = Anstyle::new()
.fg_color(Some(anstyle::Color::Ansi(AnsiColor::BrightGreen)))
.render();
let value_style = Anstyle::new()
.fg_color(Some(anstyle::Color::Ansi(AnsiColor::BrightYellow)))
.render();
let _ = writedoc! {
std::io::stdout(),
"
{alias_style}{alias}{reset}:
{key_style}Fingerprint{reset} {value_style}{fingerprint}{reset}
{key_style}Name{reset} {value_style}{name}{reset}
{key_style}Email{reset} {value_style}{email}{reset}
{key_style}Description{reset} {value_style}{description}{reset}
{key_style}Avatar URL{reset} {value_style}{avatar_url}{reset}
{key_style}Color{reset} {value_style}{color}{reset}
{key_style}Claims{reset}
{claims}
",
alias = profile.alias.as_ref(),
fingerprint = profile.key.as_ref(),
name = profile.name.as_ref(),
description = profile.description.as_ref().clone().unwrap_or("None".to_string()),
avatar_url = profile.avatar_url.as_ref().clone().unwrap_or("None".to_string()),
email = profile.email.as_ref().clone().unwrap_or("None".to_string()),
color = profile.color.as_ref().clone().unwrap_or("None".to_string()),
claims = claims.iter().map(|claim| format!("\t\t- {value_style}{uri}{reset}", uri = claim.uri.as_ref())).collect::<Vec<_>>().join("\n"),
};
}
Some(8) => {
// Update profile
profile
.update(&state.db)
.await
.context("Unable to save profile to database")?;
// Update and insert all claims
for claim in claims {
claim
.save(&state.db)
.await
.context("Unable to save claim to database")?;
}
// Remove all deleted claims
for claim in removed_claims {
claim
.delete(&state.db)
.await
.context("Unable to remove claim from database")?;
}
println!("All changes saved");
break;
}
None | Some(9) => {
println!("Changes discarded");
break;
}
_ => unreachable!(),
}
}
Ok(())
}
}

View file

@ -1,6 +1,7 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
pub mod create; pub mod create;
pub mod edit;
pub mod export; pub mod export;
pub mod list; pub mod list;
@ -16,4 +17,5 @@ pub enum ProfilesSubcommands {
Create(create::ProfilesCreateCommand), Create(create::ProfilesCreateCommand),
Export(export::ProfilesExportCommand), Export(export::ProfilesExportCommand),
List(list::ProfilesListCommand), List(list::ProfilesListCommand),
Edit(edit::ProfilesEditCommand),
} }

View file

@ -160,6 +160,7 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> {
ProfilesSubcommands::Create(subcommand) => subcommand.execute_sync(state, runtime), ProfilesSubcommands::Create(subcommand) => subcommand.execute_sync(state, runtime),
ProfilesSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime), ProfilesSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
ProfilesSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime), ProfilesSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
ProfilesSubcommands::Edit(subcommand) => subcommand.execute_sync(state, runtime),
}, },
} }
} }