//! User management subcommand use crate::util::{confirm_async, table}; use eyre::Result; use paris::*; use tabled::Tabled; use theseus::prelude::*; use tokio::sync::oneshot; #[derive(argh::FromArgs, Debug)] #[argh(subcommand, name = "user")] /// manage Minecraft accounts pub struct UserCommand { #[argh(subcommand)] action: UserSubcommand, } #[derive(argh::FromArgs, Debug)] #[argh(subcommand)] pub enum UserSubcommand { Add(UserAdd), List(UserList), Remove(UserRemove), SetDefault(UserDefault), } #[derive(argh::FromArgs, Debug)] /// add a new user to Theseus #[argh(subcommand, name = "add")] pub struct UserAdd { #[argh(option)] /// the browser to authenticate using browser: Option, } impl UserAdd { pub async fn run( &self, _args: &crate::Args, _largs: &UserCommand, ) -> Result<()> { info!("Adding new user account to Theseus"); info!("A browser window will now open, follow the login flow there."); let (tx, rx) = oneshot::channel::(); let flow = tokio::spawn(auth::authenticate(tx)); let url = rx.await?; match self.browser { Some(browser) => webbrowser::open_browser(browser, url.as_str()), None => webbrowser::open(url.as_str()), }?; let credentials = flow.await??; State::sync().await?; success!("Logged in user {}.", credentials.username); Ok(()) } } #[derive(argh::FromArgs, Debug)] /// list all known users #[argh(subcommand, name = "list")] pub struct UserList {} #[derive(Tabled)] struct UserRow<'a> { username: &'a str, id: uuid::Uuid, default: bool, } impl<'a> UserRow<'a> { pub fn from( credentials: &'a Credentials, default: Option, ) -> Self { Self { username: &credentials.username, id: credentials.id, default: Some(credentials.id) == default, } } } impl UserList { pub async fn run( &self, _args: &crate::Args, _largs: &UserCommand, ) -> Result<()> { let state = State::get().await?; let default = state.settings.read().await.default_user; let users = auth::users().await?; let rows = users.iter().map(|user| UserRow::from(user, default)); let table = table(rows); println!("{table}"); Ok(()) } } #[derive(argh::FromArgs, Debug)] /// remove a user #[argh(subcommand, name = "remove")] pub struct UserRemove { /// the user to remove #[argh(positional)] user: uuid::Uuid, } impl UserRemove { pub async fn run( &self, _args: &crate::Args, _largs: &UserCommand, ) -> Result<()> { info!("Removing user {}", self.user.as_hyphenated()); if confirm_async(String::from("Do you wish to continue"), true).await? { if !auth::has_user(self.user).await? { warn!("Profile was not managed by Theseus!"); } else { auth::remove_user(self.user).await?; State::sync().await?; success!("User removed!"); } } else { error!("Aborted!"); } Ok(()) } } #[derive(argh::FromArgs, Debug)] /// set the default user #[argh(subcommand, name = "set-default")] pub struct UserDefault { /// the user to set as default #[argh(positional)] user: uuid::Uuid, } impl UserDefault { pub async fn run( &self, _args: &crate::Args, _largs: &UserCommand, ) -> Result<()> { info!("Setting user {} as default", self.user.as_hyphenated()); // TODO: settings API let state: std::sync::Arc = State::get().await?; let mut settings = state.settings.write().await; if settings.default_user == Some(self.user) { warn!("User is already the default!"); } else { settings.default_user = Some(self.user); success!("User set as default!"); } Ok(()) } } impl UserCommand { pub async fn run(&self, args: &crate::Args) -> Result<()> { dispatch!(&self.action, (args, self) => { UserSubcommand::Add, UserSubcommand::List, UserSubcommand::Remove, UserSubcommand::SetDefault }) } }