feat!: Added several fields to Quest struct

- Added field public
- Added optional field available_on
- Added optional field deadline
- Updated tests and CLI to use these fields
This commit is contained in:
Alexey 2025-12-01 13:26:38 +03:00
commit 78da6dde05
4 changed files with 60 additions and 6 deletions

View file

@ -1,7 +1,20 @@
use std::path::PathBuf; use std::path::PathBuf;
use clap::{Parser,Subcommand,Args,ValueEnum}; use clap::{Parser,Subcommand,Args,ValueEnum};
use serde::Deserialize;
use squad_quest::{config::Config,quest::{Quest,QuestDifficulty as LibQuestDifficulty}}; use squad_quest::{config::Config,quest::{Quest,QuestDifficulty as LibQuestDifficulty}};
use toml::value::Date;
#[derive(Deserialize)]
struct DateWrapper {
date: Date
}
fn parse_date(arg: &str) -> Result<Date,toml::de::Error> {
let toml_str = format!("date = {arg}");
let wrapper: DateWrapper = toml::from_str(&toml_str)?;
Ok(wrapper.date)
}
#[derive(Parser)] #[derive(Parser)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@ -76,7 +89,16 @@ struct QuestCreateArgs {
/// Visible description of the quest /// Visible description of the quest
description: String, description: String,
/// Answer for the quest for admins /// Answer for the quest for admins
answer: String answer: String,
/// Create quest and make it public immediately
#[arg(short,long)]
public: bool,
/// Make quest available on date (format = YYYY-MM-DD, ex. 2025-12-24)
#[arg(short,long,value_parser = parse_date)]
available: Option<Date>,
/// Quest expiration date (format = YYYY-MM-DD, ex. 2025-12-24)
#[arg(short,long,value_parser = parse_date)]
deadline: Option<Date>,
} }
#[derive(Args)] #[derive(Args)]
@ -97,7 +119,16 @@ struct QuestUpdateArgs {
description: Option<String>, description: Option<String>,
/// Answer for the quest for admins /// Answer for the quest for admins
#[arg(long)] #[arg(long)]
answer: Option<String> answer: Option<String>,
/// Create quest and make it public immediately
#[arg(long)]
public: Option<bool>,
/// Make quest available on date (format = YYYY-MM-DD, ex. 2025-12-24)
#[arg(long,value_parser = parse_date)]
available: Option<Date>,
/// Quest expiration date (format = YYYY-MM-DD, ex. 2025-12-24)
#[arg(long,value_parser = parse_date)]
deadline: Option<Date>,
} }
#[derive(Args)] #[derive(Args)]
@ -141,7 +172,7 @@ fn main() {
let next_id = match quests.last() { let next_id = match quests.last() {
Some(quest) if quest.id == u16::MAX => { Some(quest) if quest.id == u16::MAX => {
panic!("Error: quest list contains quest with u16::MAX id."); panic!("Error: quest list contains quest with u16::MAX id.");
} },
Some(quest) => quest.id + 1u16, Some(quest) => quest.id + 1u16,
None => 0u16 None => 0u16
}; };
@ -167,6 +198,9 @@ fn main() {
name: args.name.clone(), name: args.name.clone(),
description: args.description.clone(), description: args.description.clone(),
answer: args.answer.clone(), answer: args.answer.clone(),
public: args.public,
available_on: args.available.clone(),
deadline: args.deadline.clone()
}; };
if let Err(error) = quest.save(path) { if let Err(error) = quest.save(path) {
eprintln!("Error while saving quest: {error}."); eprintln!("Error while saving quest: {error}.");
@ -188,7 +222,10 @@ fn main() {
reward: args.reward.unwrap_or(quest.reward), reward: args.reward.unwrap_or(quest.reward),
name: args.name.clone().unwrap_or(quest.name.clone()), name: args.name.clone().unwrap_or(quest.name.clone()),
description: args.description.clone().unwrap_or(quest.description.clone()), description: args.description.clone().unwrap_or(quest.description.clone()),
answer: args.answer.clone().unwrap_or(quest.answer.clone()) answer: args.answer.clone().unwrap_or(quest.answer.clone()),
public: args.public.unwrap_or(quest.public),
available_on: args.available.clone().or(quest.available_on.clone()),
deadline: args.deadline.clone().or(quest.deadline.clone())
}; };
let path = config.full_quests_path(); let path = config.full_quests_path();
match quest.save(path) { match quest.save(path) {

View file

@ -6,6 +6,7 @@ use std::{fs, io::Write, path::PathBuf};
use serde::{ Serialize, Deserialize }; use serde::{ Serialize, Deserialize };
use error::QuestError; use error::QuestError;
use toml::value::Date;
/// Difficulty of the quest /// Difficulty of the quest
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
@ -59,6 +60,15 @@ pub struct Quest {
/// Quest answer, available for admins /// Quest answer, available for admins
pub answer: String, pub answer: String,
/// Is quest available for regular users
pub public: bool,
/// When quest becomes public
pub available_on: Option<Date>,
/// When quest expires
pub deadline: Option<Date>
} }
impl Default for Quest { impl Default for Quest {
@ -69,7 +79,10 @@ impl Default for Quest {
reward: u32::default(), reward: u32::default(),
name: default_name(), name: default_name(),
description: default_description(), description: default_description(),
answer: default_answer() answer: default_answer(),
public: false,
available_on: None,
deadline: None
} }
} }
} }

View file

@ -35,7 +35,10 @@ fn quest_one() {
reward: 100, reward: 100,
name: "Example easy quest".to_owned(), name: "Example easy quest".to_owned(),
description: "Answer this quest without any attachments or comments".to_owned(), description: "Answer this quest without any attachments or comments".to_owned(),
answer: "Accept the answer if it has no attachments and an empty comment".to_owned() answer: "Accept the answer if it has no attachments and an empty comment".to_owned(),
public: false,
available_on: None,
deadline: None
}; };
assert_eq!(*quest, expected); assert_eq!(*quest, expected);

View file

@ -6,3 +6,4 @@ reward = 100
name = "Example easy quest" name = "Example easy quest"
description = "Answer this quest without any attachments or comments" description = "Answer this quest without any attachments or comments"
answer = "Accept the answer if it has no attachments and an empty comment" answer = "Accept the answer if it has no attachments and an empty comment"
public = false