tododo 0.1.2

A minimal terminal todo manager built with Rust and Ratatui
Documentation
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
    }

    pub async fn list_completed_since(
        &self,
        since: chrono::DateTime<chrono::FixedOffset>,
    ) -> Result<Vec<Todo>, AppError> {
        self.repo
            .find_completed_since(since)
            .await
            .map(|todos| todos.into_iter().map(Into::into).collect())
    }
    
    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()
    }
}