mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-22 15:59:29 -07:00
Add profiles edit command
This took too much effort, someone better appreciate it
This commit is contained in:
parent
a80acaa83d
commit
77f545b32e
3 changed files with 311 additions and 0 deletions
308
src/commands/profiles/edit.rs
Normal file
308
src/commands/profiles/edit.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
|
||||
pub mod create;
|
||||
pub mod edit;
|
||||
pub mod export;
|
||||
pub mod list;
|
||||
|
||||
|
@ -16,4 +17,5 @@ pub enum ProfilesSubcommands {
|
|||
Create(create::ProfilesCreateCommand),
|
||||
Export(export::ProfilesExportCommand),
|
||||
List(list::ProfilesListCommand),
|
||||
Edit(edit::ProfilesEditCommand),
|
||||
}
|
||||
|
|
|
@ -160,6 +160,7 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> {
|
|||
ProfilesSubcommands::Create(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
ProfilesSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
ProfilesSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
ProfilesSubcommands::Edit(subcommand) => subcommand.execute_sync(state, runtime),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue