diff --git a/.vscode/settings.json b/.vscode/settings.json index f6831eb..934e178 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,6 @@ "subkeys", "userid", "writedoc" - ] + ], + "rust-analyzer.check.command": "clippy" } \ No newline at end of file diff --git a/crates/asp/src/profiles/mod.rs b/crates/asp/src/profiles/mod.rs index 2dc8ccc..2e26524 100644 --- a/crates/asp/src/profiles/mod.rs +++ b/crates/asp/src/profiles/mod.rs @@ -1,7 +1,7 @@ -use hex_color::HexColor; +pub use hex_color::HexColor; use serde::{Deserialize, Serialize}; -use serde_email::Email; -use url::Url; +pub use serde_email::Email; +pub use url::Url; use crate::utils::jwt::{AspJwsType, JwtSerializable}; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 9907a54..462267a 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,5 @@ pub mod keys; +pub mod profiles; use clap::Parser; use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder}; diff --git a/src/commands/profiles/create.rs b/src/commands/profiles/create.rs new file mode 100644 index 0000000..019b621 --- /dev/null +++ b/src/commands/profiles/create.rs @@ -0,0 +1,92 @@ +use std::str::FromStr; + +use anyhow::Context; +use asp::{profiles::*, utils::jwt::AspJwsType}; +use clap::Parser; +use dialoguer::{theme::ColorfulTheme, Input}; + +use crate::commands::AspmSubcommand; + +/// Creates a new profile containing claims and other metadata. +/// A key is needed to attach this profile to, but multiple profiles can be created for the same key. +#[derive(Parser, Debug)] +pub struct ProfilesCreateCommand { + /// The alias of the profile to create. This can be anything, and it can also be omitted to prompt interactively. + /// This has no purpose other than providing a way to nicely distinguish profiles. + #[arg(short = 'n', long)] + profile_alias: Option, +} + +#[async_trait::async_trait] +impl AspmSubcommand for ProfilesCreateCommand { + async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> { + let theme = ColorfulTheme::default(); + + let alias = if let Some(alias) = &self.profile_alias { + alias.clone() + } else { + Input::with_theme(&theme) + .with_prompt("Please enter an alias to give to this profile") + .allow_empty(false) + .interact() + .context("Unable to prompt on stderr")? + }; + + let profile = AriadneSignatureProfile { + version: 0, + r#type: AspJwsType::Profile, + name: Input::with_theme(&theme) + .with_prompt("Please enter a public name to show on this profile (usually a username or real name, though anything works)") + .allow_empty(false) + .interact() + .context("Unable to prompt on stderr")?, + claims: { + let mut claims = Vec::::new(); + loop { + let claim: String = Input::with_theme(&theme) + .with_prompt("Please enter a claim and then press enter. To finish, enter an empty string") + .allow_empty(true) + .interact() + .context("Unable to prompt on stderr")?; + + if claim.is_empty() { + break + } else { + claims.push(claim); + } + } + claims + }, + description: Some(Input::with_theme(&theme) + .with_prompt("If you want to add a description to this profile, enter it or an empty string") + .allow_empty(true) + .interact() + .context("Unable to prompt on stderr")?).filter(|x: &String| !x.is_empty()), + avatar_url: Some(Input::with_theme(&theme) + .with_prompt("If you want to add an avatar to this profile, enter the URL or an empty string") + .allow_empty(true) + .interact() + .context("Unable to prompt on stderr")?) + .filter(|x: &String| !x.is_empty()) + .map_or(Ok(None), |string: String| Url::parse(&string).context("Unable to parse avatar URL").map(|url| Some(url)))?, + email: Some(Input::with_theme(&theme) + .with_prompt("If you want to add an email to this profile, enter it or an empty string") + .allow_empty(true) + .interact() + .context("Unable to prompt on stderr")?) + .filter(|x: &String| !x.is_empty()) + .map_or(Ok(None), |string: String| Email::from_string(string).context("Unable to parse email").map(|email| Some(email)))?, + color: Some(Input::with_theme(&theme) + .with_prompt("If you want to add a color to this profile, enter it in hex format (#AABBCC) or an empty string") + .allow_empty(true) + .interact() + .context("Unable to prompt on stderr")?) + .filter(|x: &String| !x.is_empty()) + .map_or(Ok(None), |hex: String| HexColor::from_str(&hex).context("Unable to parse color code").map(|color| Some(color)))?, + }; + + dbg!(profile); + + Ok(()) + } +} diff --git a/src/commands/profiles/mod.rs b/src/commands/profiles/mod.rs new file mode 100644 index 0000000..7594c5e --- /dev/null +++ b/src/commands/profiles/mod.rs @@ -0,0 +1,15 @@ +use clap::{Parser, Subcommand}; + +pub mod create; + +/// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles. +#[derive(Parser)] +pub struct ProfilesSubcommand { + #[command(subcommand)] + pub subcommand: ProfilesSubcommands, +} + +#[derive(Subcommand)] +pub enum ProfilesSubcommands { + Create(create::ProfilesCreateCommand), +} diff --git a/src/main.rs b/src/main.rs index 5e277bb..8d3f839 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle}; use anyhow::Context; use app_dirs2::{AppDataType, AppInfo}; use clap::{Parser, Subcommand}; -use commands::{keys::KeysSubcommands, AspmSubcommand}; +use commands::{keys::KeysSubcommands, profiles::ProfilesSubcommands, AspmSubcommand}; use migrations::{Migrator, MigratorTrait, SchemaManager}; use sea_orm::{Database, DatabaseConnection}; use thiserror::Error; @@ -77,6 +77,7 @@ impl AspmCommand { #[derive(Subcommand)] pub enum AspmSubcommands { Keys(commands::keys::KeysSubcommand), + Profiles(commands::profiles::ProfilesSubcommand), } fn main() { @@ -151,6 +152,9 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> { KeysSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime), KeysSubcommands::Import(subcommand) => subcommand.execute_sync(state, runtime), }, + AspmSubcommands::Profiles(subcommand) => match &subcommand.subcommand { + ProfilesSubcommands::Create(subcommand) => subcommand.execute_sync(state, runtime), + }, } }