use crate::error::AppError;
use crate::repository::TodoRepository;
use super::priority::Priority;
use super::todo::Todo;
use super::validation::validate_title;
use super::SortMode;
use super::TodoStatus;
pub struct TodoService {
repo: TodoRepository,
recent_days: i64,
}
impl TodoService {
pub fn new(repo: TodoRepository) -> Self {
Self {
repo,
recent_days: 30,
}
}
pub async fn list_active_sorted(&self, sort_mode: SortMode) -> Result<Vec<Todo>, AppError> {
let todos: Vec<Todo> = match sort_mode {
SortMode::Priority => self.repo.find_active().await?,
SortMode::CreatedAt => self.repo.find_active_by_time().await?,
}
.into_iter()
.map(Into::into)
.collect();
Ok(self.filter_recent(todos))
}
pub async fn get(&self, id: &str) -> Result<Todo, AppError> {
self.repo
.find_by_id(id)
.await?
.map(Into::into)
.ok_or_else(|| AppError::NotFound(id.to_string()))
}
pub async fn create(&self, title: &str, note: &str) -> Result<Todo, AppError> {
let title = validate_title(title)?;
self.repo.create(title, note).await.map(Into::into)
}
pub async fn update(&self, id: &str, title: &str, note: &str) -> Result<Todo, AppError> {
let title = validate_title(title)?;
self.repo.update_todo(id, title, note).await.map(Into::into)
}
pub async fn toggle(&self, id: &str) -> Result<Todo, AppError> {
self.repo.toggle_status(id).await.map(Into::into)
}
pub async fn set_priority(&self, id: &str, priority: &str) -> Result<Todo, AppError> {
let prio = Priority::from_char(priority)?;
self.repo.set_priority(id, prio.into()).await.map(Into::into)
}
pub async fn delete(&self, id: &str) -> Result<(), AppError> {
self.repo.soft_delete(id).await
}
fn filter_recent(&self, todos: Vec<Todo>) -> Vec<Todo> {
let cutoff = chrono::Utc::now() - chrono::Duration::days(self.recent_days);
todos
.into_iter()
.filter(|t| {
matches!(t.status, TodoStatus::Pending)
|| t.completed_at.is_some_and(|c| c >= cutoff)
})
.collect()
}
}