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:
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};
|
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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue