From dc94f2060c456087aa71239aeb7edfb26eb86989 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Tue, 2 Dec 2025 16:12:42 +0300 Subject: [PATCH] feat: Added interaction with accounts in CLI - Account creation - Account deletion - Account balance management - Account quest completion - Added account CRUD test in tests/io.rs --- src/bin/cli.rs | 199 +++++++++++++++++++++++++++++++-- tests/io.rs | 33 ++++-- tests/io/accounts/.placeholder | 1 + 3 files changed, 215 insertions(+), 18 deletions(-) create mode 100644 tests/io/accounts/.placeholder diff --git a/src/bin/cli.rs b/src/bin/cli.rs index 4d06a2a..05e2c24 100644 --- a/src/bin/cli.rs +++ b/src/bin/cli.rs @@ -2,13 +2,13 @@ use std::path::PathBuf; use clap::{Parser,Subcommand,Args,ValueEnum}; use serde::Deserialize; -use squad_quest::{SquadObject, config::Config, quest::{Quest,QuestDifficulty as LibQuestDifficulty}}; +use squad_quest::{SquadObject, account::Account, config::Config, quest::{Quest,QuestDifficulty as LibQuestDifficulty}}; use toml::value::Date; use chrono::{Datelike, NaiveDate, Utc}; #[derive(Deserialize)] struct DateWrapper { - date: Date + date: Date, } fn parse_date(arg: &str) -> Result { @@ -33,7 +33,10 @@ struct Cli { enum Objects { /// Operations on the quests #[command(subcommand)] - Quest(QuestCommands) + Quest(QuestCommands), + /// Operations on the accounts + #[command(subcommand)] + Account(AccountCommands), } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] @@ -45,7 +48,7 @@ enum QuestDifficulty { /// Hard quest Hard, /// Special case of hard quests. - Secret + Secret, } impl From for LibQuestDifficulty { @@ -75,11 +78,12 @@ enum QuestCommands { Publish(QuestPublishArgs), } + #[derive(Args)] struct QuestListArgs { /// Only list id and name of the quest #[arg(short, long)] - short: bool + short: bool, } #[derive(Args)] @@ -137,7 +141,7 @@ struct QuestUpdateArgs { #[derive(Args)] struct QuestDeleteArgs { /// Id of the quest to delete - id: u16 + id: u16, } #[derive(Args)] @@ -149,6 +153,61 @@ struct QuestPublishArgs { reverse: bool, } +#[derive(Subcommand)] +enum AccountCommands { + /// List accounts + List, + /// Create empty account + Create(AccountCreateArgs), + /// Update balance value + Balance(AccountBalanceArgs), + /// Approve account answer for quest + Complete(AccountCompleteArgs), + /// Delete account + Delete(AccountDeleteArgs), +} + +#[derive(Args)] +struct AccountCreateArgs { + /// Account will be created with this id + id: String, +} + +#[derive(Clone, Copy, PartialEq, Eq, ValueEnum)] +enum AccountBalanceActions { + Set, + Add, + Remove, +} + +#[derive(Args)] +struct AccountBalanceArgs { + /// Account id + id: String, + /// What to do with the balance + #[arg(value_enum)] + action: AccountBalanceActions, + /// Amount of doing + value: u32, + /// If action is remove, set balance to 0 if the result is negative instead of returning error + #[arg(short,long)] + negative_ok: bool, +} + +#[derive(Args)] +struct AccountCompleteArgs { + /// Id of the account + account: String, + /// Id of the quest + quest: u16, +} + +#[derive(Args)] +struct AccountDeleteArgs { + /// Id of the account to delete + id: String, +} + fn print_quest_short(quest: &Quest) { println!("Quest #{}: {}", quest.id, quest.name); } @@ -296,7 +355,133 @@ fn main() { eprintln!("Error: couldn't find quest with id {}.", args.id); } } - } + }, + } + }, + Objects::Account(args) => { + match args { + AccountCommands::List => { + let accounts = config.load_accounts(); + + for account in accounts { + println!("\"{}\": Balance {}", account.id, account.balance); + } + }, + AccountCommands::Create(args) => { + let account = Account { + id: args.id.clone(), + ..Default::default() + }; + + let accounts = config.load_accounts(); + + if let Some(_) = accounts.iter().find(|a| a.id == account.id) { + eprintln!("Error: account {} exists.", account.id); + return; + } + + let accounts_path = config.full_accounts_path(); + + match account.save(accounts_path) { + Ok(_) => { + println!("Successfully created account \"{}\"", account.id); + }, + Err(error) => { + eprintln!("Error while saving account: {error}"); + } + } + }, + AccountCommands::Balance(args) => { + let mut accounts = config.load_accounts(); + + let account = match accounts.iter_mut().find(|a| a.id == args.id) { + Some(acc) => acc, + None => { + eprintln!("Could not find account \"{}\"", args.id); + return; + } + }; + + match args.action { + AccountBalanceActions::Set => { + account.balance = args.value; + }, + AccountBalanceActions::Add => { + account.balance += args.value; + }, + AccountBalanceActions::Remove => { + if args.value > account.balance { + if args.negative_ok { + account.balance = 0u32; + } else { + eprintln!("Error: balance ({}) is less than {}.", account.balance, args.value); + return; + } + } else { + account.balance -= args.value; + } + } + } + + let accounts_path = config.full_accounts_path(); + + match account.save(accounts_path) { + Ok(_) => { + println!("Successfully updated account \"{}\" balance.", account.id); + }, + Err(error) => { + eprintln!("Error while saving account: {error}"); + } + }; + }, + AccountCommands::Complete(args) => { + let mut accounts = config.load_accounts(); + + let account = match accounts.iter_mut().find(|a| a.id == args.account) { + Some(acc) => acc, + None => { + eprintln!("Could not find account \"{}\"", args.account); + return; + } + }; + + let quests = config.load_quests(); + + if let None = quests.iter().find(|q| q.id == args.quest) { + eprintln!("Could not find quest #{}", args.quest); + return; + } + + match account.quests_completed.iter().find(|qid| **qid == args.quest) { + Some(_) => { + println!("Quest #{} is already completed on account \"{}\"", args.quest, args.account); + }, + None => { + account.quests_completed.push(args.quest); + let accounts_path = config.full_accounts_path(); + match account.save(accounts_path) { + Ok(_) => { + println!("Account \"{}\" completed quest #{}.", args.account, args.quest); + }, + Err(error) => { + eprintln!("Error while saving account: {error}"); + } + } + } + } + }, + AccountCommands::Delete(args) => { + let mut accounts_path = config.full_accounts_path(); + accounts_path.push(format!("{}.toml", args.id)); + match Account::delete(accounts_path) { + Ok(_) => { + println!("Successfully deleted account \"{}\".", args.id); + }, + Err(error) => { + eprintln!("Error deleting account: {error}"); + } + } + }, } } } diff --git a/tests/io.rs b/tests/io.rs index 983e4cd..356f972 100644 --- a/tests/io.rs +++ b/tests/io.rs @@ -1,5 +1,4 @@ -use squad_quest::{SquadObject, config::Config, error::Error, quest::Quest}; -use std::path::PathBuf; +use squad_quest::{SquadObject, account::Account, config::Config, quest::Quest}; const CONFIG_PATH: &str = "tests/io/config.toml"; @@ -7,23 +6,35 @@ const CONFIG_PATH: &str = "tests/io/config.toml"; // and Quest::save can override files, // so this test covers full quest CRUD #[test] -fn quest_crud() -> Result<(), Error> { +fn quest_crud() { let config = Config::load(CONFIG_PATH.into()); - let mut quests_path = PathBuf::from(CONFIG_PATH).parent().unwrap().to_owned(); - quests_path.push(config.quests_path); + let mut quests_path = config.full_quests_path(); let quest = Quest::default(); - println!("{:?}", quests_path.clone()); - - quest.save(quests_path.clone())?; + quest.save(quests_path.clone()).unwrap(); let filename = format!("{}.toml", quest.id); quests_path.push(filename); - Quest::delete(quests_path)?; - - Ok(()) + Quest::delete(quests_path).unwrap(); +} + +#[test] +fn account_crud() { + let config = Config::load(CONFIG_PATH.into()); + + let mut accounts_path = config.full_accounts_path(); + + let account = Account::default(); + + account.save(accounts_path.clone()).unwrap(); + + let filename = format!("{}.toml", account.id); + + accounts_path.push(filename); + + Account::delete(accounts_path).unwrap(); } diff --git a/tests/io/accounts/.placeholder b/tests/io/accounts/.placeholder new file mode 100644 index 0000000..16680ce --- /dev/null +++ b/tests/io/accounts/.placeholder @@ -0,0 +1 @@ +Placeholder file for git