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 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}