reminder_cli/
reminder.rs

1use chrono::{DateTime, Local};
2use cron::Schedule;
3use serde::{Deserialize, Serialize};
4use std::collections::HashSet;
5use std::str::FromStr;
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Reminder {
10    pub id: Uuid,
11    pub title: String,
12    pub description: Option<String>,
13    pub schedule: ReminderSchedule,
14    pub created_at: DateTime<Local>,
15    pub next_trigger: Option<DateTime<Local>>,
16    pub completed: bool,
17    #[serde(default)]
18    pub paused: bool,
19    #[serde(default)]
20    pub tags: HashSet<String>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub enum ReminderSchedule {
25    OneTime(DateTime<Local>),
26    Cron(String),
27}
28
29impl Reminder {
30    pub fn new_one_time(
31        title: String,
32        description: Option<String>,
33        time: DateTime<Local>,
34        tags: HashSet<String>,
35    ) -> Self {
36        Self {
37            id: Uuid::new_v4(),
38            title,
39            description,
40            schedule: ReminderSchedule::OneTime(time),
41            created_at: Local::now(),
42            next_trigger: Some(time),
43            completed: false,
44            paused: false,
45            tags,
46        }
47    }
48
49    pub fn new_cron(
50        title: String,
51        description: Option<String>,
52        cron_expr: String,
53        tags: HashSet<String>,
54    ) -> anyhow::Result<Self> {
55        let schedule = Schedule::from_str(&cron_expr)?;
56        let next = schedule.upcoming(Local).next();
57
58        Ok(Self {
59            id: Uuid::new_v4(),
60            title,
61            description,
62            schedule: ReminderSchedule::Cron(cron_expr),
63            created_at: Local::now(),
64            next_trigger: next,
65            completed: false,
66            paused: false,
67            tags,
68        })
69    }
70
71    pub fn calculate_next_trigger(&mut self) {
72        match &self.schedule {
73            ReminderSchedule::OneTime(_) => {
74                self.completed = true;
75                self.next_trigger = None;
76            }
77            ReminderSchedule::Cron(expr) => {
78                if let Ok(schedule) = Schedule::from_str(expr) {
79                    self.next_trigger = schedule.upcoming(Local).next();
80                }
81            }
82        }
83    }
84
85    pub fn is_due(&self) -> bool {
86        if self.completed || self.paused {
87            return false;
88        }
89        if let Some(next) = self.next_trigger {
90            return Local::now() >= next;
91        }
92        false
93    }
94
95    pub fn pause(&mut self) {
96        self.paused = true;
97    }
98
99    pub fn resume(&mut self) {
100        self.paused = false;
101        // Recalculate next trigger for cron jobs
102        if let ReminderSchedule::Cron(expr) = &self.schedule {
103            if let Ok(schedule) = Schedule::from_str(expr) {
104                self.next_trigger = schedule.upcoming(Local).next();
105            }
106        }
107    }
108
109    pub fn status(&self) -> &'static str {
110        if self.completed {
111            "Completed"
112        } else if self.paused {
113            "Paused"
114        } else {
115            "Active"
116        }
117    }
118}