use crate::error::{JjjError, Result};
use crate::models::{
CritiqueStatus, Event, EventExtra, EventType, ProblemStatus, SolutionStatus,
};
use crate::storage::MetadataStore;
pub fn approve_solution(
store: &MetadataStore,
solution_id: &str,
force: bool,
rationale: Option<&str>,
) -> Result<()> {
let solution = store.load_solution(solution_id)?;
if solution.status == SolutionStatus::Approved {
return Ok(());
}
if solution.status != SolutionStatus::Submitted && !force {
return Err(JjjError::Validation(format!(
"Solution '{}' is {} — submit it for review first:\n jjj solution submit {}",
solution.title, solution.status, solution_id,
)));
}
let critiques = store.list_critiques()?;
let open_critiques: Vec<_> = critiques
.iter()
.filter(|c| {
c.solution_id == solution_id
&& matches!(c.status, CritiqueStatus::Open | CritiqueStatus::Valid)
})
.collect();
if !open_critiques.is_empty() && !force {
return Err(JjjError::CannotApproveSolution(format!(
"{} open critique(s) must be addressed first",
open_critiques.len()
)));
}
let user = store.get_current_user().unwrap_or_default();
let mut event = Event::new(
EventType::SolutionApproved,
solution_id.to_string(),
user.clone(),
);
if let Some(r) = rationale {
event = event.with_rationale(r);
}
let problem_id = solution.problem_id.clone();
store.with_metadata(&format!("Approve solution {}", solution_id), || {
store.set_pending_event(event.clone());
let mut sol = store.load_solution(solution_id)?;
if force {
sol.force_approved = true;
}
sol.approve().map_err(JjjError::Validation)?;
store.save_solution(&sol)?;
let (can_solve, _) = store.can_solve_problem(&sol.problem_id)?;
if can_solve {
let mut problem = store.load_problem(&sol.problem_id)?;
if problem.status != ProblemStatus::Solved {
problem
.try_set_status(ProblemStatus::Solved)
.map_err(JjjError::Validation)?;
store.save_problem(&problem)?;
let solve_event = Event::new(
EventType::ProblemSolved,
problem.id.clone(),
event.by.clone(),
);
store.set_pending_event(solve_event);
}
}
Ok(())
})?;
crate::automation::run(store, &event, solution_id);
if let Ok(problem) = store.load_problem(&problem_id) {
if problem.status == ProblemStatus::Solved {
let solve_event =
Event::new(EventType::ProblemSolved, problem.id.clone(), user);
crate::automation::run(store, &solve_event, &problem.id);
}
}
Ok(())
}
pub fn submit_solution(store: &MetadataStore, solution_id: &str) -> Result<()> {
let user = store.get_current_user().unwrap_or_default();
let solution = store.load_solution(solution_id)?;
let event = Event::new(
EventType::SolutionSubmitted,
solution_id.to_string(),
user,
)
.with_extra(EventExtra {
problem: Some(solution.problem_id.clone()),
..Default::default()
});
store.with_metadata(
&format!("Submit solution {} for review", solution_id),
|| {
let mut solution = store.load_solution(solution_id)?;
solution.submit().map_err(JjjError::Validation)?;
store.save_solution(&solution)?;
store.set_pending_event(event.clone());
let mut problem = store.load_problem(&solution.problem_id)?;
if problem.status == ProblemStatus::Open {
problem
.try_set_status(ProblemStatus::InProgress)
.map_err(JjjError::Validation)?;
store.save_problem(&problem)?;
}
Ok(())
},
)?;
crate::automation::run(store, &event, solution_id);
Ok(())
}
pub fn withdraw_solution(
store: &MetadataStore,
solution_id: &str,
rationale: Option<&str>,
) -> Result<()> {
let user = store.get_current_user().unwrap_or_default();
let mut event = Event::new(
EventType::SolutionWithdrawn,
solution_id.to_string(),
user,
);
if let Some(r) = rationale {
event = event.with_rationale(r);
}
store.with_metadata(&format!("Withdraw solution {}", solution_id), || {
store.set_pending_event(event.clone());
let mut solution = store.load_solution(solution_id)?;
solution.withdraw().map_err(JjjError::Validation)?;
store.save_solution(&solution)?;
Ok(())
})?;
crate::automation::run(store, &event, solution_id);
Ok(())
}
pub fn solve_problem(store: &MetadataStore, problem_id: &str) -> Result<()> {
let (can_solve, message) = store.can_solve_problem(problem_id)?;
let problem = store.load_problem(problem_id)?;
if !can_solve {
if problem.status == ProblemStatus::Solved {
return Ok(()); }
return Err(JjjError::CannotSolveProblem(message));
}
let user = store.get_current_user()?;
let event = Event::new(EventType::ProblemSolved, problem_id.to_string(), user);
store.with_metadata(&format!("Solve problem {}", problem_id), || {
store.set_pending_event(event.clone());
let mut p = store.load_problem(problem_id)?;
p.try_set_status(ProblemStatus::Solved)
.map_err(JjjError::Validation)?;
store.save_problem(&p)?;
Ok(())
})?;
crate::automation::run(store, &event, problem_id);
Ok(())
}
pub fn dissolve_problem(
store: &MetadataStore,
problem_id: &str,
reason: Option<&str>,
) -> Result<()> {
let user = store.get_current_user()?;
let mut event = Event::new(
EventType::ProblemDissolved,
problem_id.to_string(),
user.clone(),
);
if let Some(r) = reason {
event = event.with_rationale(r);
}
store.with_metadata(&format!("Dissolve problem {}", problem_id), || {
store.set_pending_event(event.clone());
let mut problem = store.load_problem(problem_id)?;
if let Some(r) = reason {
problem.dissolve(r);
} else {
problem
.try_set_status(ProblemStatus::Dissolved)
.map_err(JjjError::Validation)?;
}
store.save_problem(&problem)?;
Ok(())
})?;
crate::automation::run(store, &event, problem_id);
Ok(())
}
pub fn reopen_problem(store: &MetadataStore, problem_id: &str) -> Result<()> {
let user = store.get_current_user()?;
let event = Event::new(
EventType::ProblemReopened,
problem_id.to_string(),
user.clone(),
);
store.with_metadata(&format!("Reopen problem {}", problem_id), || {
store.set_pending_event(event.clone());
let mut problem = store.load_problem(problem_id)?;
problem
.try_set_status(ProblemStatus::Open)
.map_err(JjjError::Validation)?;
store.save_problem(&problem)?;
Ok(())
})?;
crate::automation::run(store, &event, problem_id);
Ok(())
}
pub fn address_critique(store: &MetadataStore, critique_id: &str) -> Result<()> {
let user = store.get_current_user().unwrap_or_default();
let event = Event::new(
EventType::CritiqueAddressed,
critique_id.to_string(),
user,
);
store.with_metadata(&format!("Address critique {}", critique_id), || {
store.set_pending_event(event.clone());
let mut critique = store.load_critique(critique_id)?;
critique.address().map_err(JjjError::Validation)?;
store.save_critique(&critique)?;
Ok(())
})
}
pub fn validate_critique(store: &MetadataStore, critique_id: &str) -> Result<()> {
let user = store.get_current_user().unwrap_or_default();
let event = Event::new(
EventType::CritiqueValidated,
critique_id.to_string(),
user,
);
store.with_metadata(&format!("Validate critique {}", critique_id), || {
store.set_pending_event(event.clone());
let mut critique = store.load_critique(critique_id)?;
critique.validate().map_err(JjjError::Validation)?;
store.save_critique(&critique)?;
Ok(())
})
}
pub fn dismiss_critique(store: &MetadataStore, critique_id: &str) -> Result<()> {
let user = store.get_current_user().unwrap_or_default();
let event = Event::new(
EventType::CritiqueDismissed,
critique_id.to_string(),
user,
);
store.with_metadata(&format!("Dismiss critique {}", critique_id), || {
store.set_pending_event(event.clone());
let mut critique = store.load_critique(critique_id)?;
critique.dismiss().map_err(JjjError::Validation)?;
store.save_critique(&critique)?;
Ok(())
})
}