1use serde::{Deserialize, Serialize};
5use std::sync::Arc;
6use tokio::sync::RwLock;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Schedule {
11 pub id: String,
12 pub cron_expression: String,
13 pub task_goal: String,
14 pub priority: u8,
15 pub enabled: bool,
16 pub last_run: Option<chrono::DateTime<chrono::Utc>>,
17 pub next_run: Option<chrono::DateTime<chrono::Utc>>,
18 pub created_at: chrono::DateTime<chrono::Utc>,
19}
20
21pub struct Scheduler {
23 schedules: Arc<RwLock<Vec<Schedule>>>,
24}
25
26impl Scheduler {
27 pub fn new() -> Self {
28 Self {
29 schedules: Arc::new(RwLock::new(Vec::new())),
30 }
31 }
32
33 pub async fn schedule(&self, cron: &str, goal: &str, priority: u8) -> anyhow::Result<String> {
35 let _parsed = cron::Schedule::from_str(cron)
37 .map_err(|e| anyhow::anyhow!("Invalid cron expression: {}", e))?;
38
39 let schedule = Schedule {
40 id: uuid::Uuid::new_v4().to_string(),
41 cron_expression: cron.to_string(),
42 task_goal: goal.to_string(),
43 priority,
44 enabled: true,
45 last_run: None,
46 next_run: None,
47 created_at: chrono::Utc::now(),
48 };
49
50 let schedule_id = schedule.id.clone();
51 self.schedules.write().await.push(schedule);
52 Ok(schedule_id)
53 }
54
55 pub async fn list(&self) -> Vec<Schedule> {
57 self.schedules.read().await.clone()
58 }
59
60 pub async fn enable(&self, schedule_id: &str) -> anyhow::Result<()> {
62 let mut schedules = self.schedules.write().await;
63 if let Some(sched) = schedules.iter_mut().find(|s| s.id == schedule_id) {
64 sched.enabled = true;
65 Ok(())
66 } else {
67 Err(anyhow::anyhow!("Schedule not found"))
68 }
69 }
70
71 pub async fn disable(&self, schedule_id: &str) -> anyhow::Result<()> {
73 let mut schedules = self.schedules.write().await;
74 if let Some(sched) = schedules.iter_mut().find(|s| s.id == schedule_id) {
75 sched.enabled = false;
76 Ok(())
77 } else {
78 Err(anyhow::anyhow!("Schedule not found"))
79 }
80 }
81
82 pub async fn delete(&self, schedule_id: &str) -> anyhow::Result<()> {
84 let mut schedules = self.schedules.write().await;
85 if let Some(pos) = schedules.iter().position(|s| s.id == schedule_id) {
86 schedules.remove(pos);
87 Ok(())
88 } else {
89 Err(anyhow::anyhow!("Schedule not found"))
90 }
91 }
92
93 pub async fn next_tasks(&self) -> Vec<Schedule> {
95 let now = chrono::Utc::now();
96 let schedules = self.schedules.read().await;
97
98 schedules
99 .iter()
100 .filter_map(|sched| {
101 if !sched.enabled {
102 return None;
103 }
104
105 if let Ok(schedule) = cron::Schedule::from_str(&sched.cron_expression) {
106 let mut iter = schedule.after(&now);
108
109 if let Some(next_time) = iter.next() {
110 if next_time <= now {
112 return Some(sched.clone());
113 }
114 }
115 }
116
117 None
118 })
119 .collect()
120 }
121}
122
123impl Default for Scheduler {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129use std::str::FromStr;
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[tokio::test]
136 async fn test_schedule_creation() {
137 let scheduler = Scheduler::new();
138
139 let id = scheduler
140 .schedule("0 0 9 * * MON *", "Monday digest", 7)
141 .await
142 .unwrap();
143 assert!(!id.is_empty());
144
145 let schedules = scheduler.list().await;
146 assert_eq!(schedules.len(), 1);
147 }
148
149 #[tokio::test]
150 async fn test_invalid_cron() {
151 let scheduler = Scheduler::new();
152
153 let result = scheduler.schedule("invalid", "test", 5).await;
154 assert!(result.is_err());
155 }
156
157 #[tokio::test]
158 async fn test_enable_disable() {
159 let scheduler = Scheduler::new();
160
161 let id = scheduler
162 .schedule("0 0 9 * * * *", "daily", 5)
163 .await
164 .unwrap();
165
166 scheduler.disable(&id).await.unwrap();
167 let schedules = scheduler.list().await;
168 assert!(!schedules[0].enabled);
169
170 scheduler.enable(&id).await.unwrap();
171 let schedules = scheduler.list().await;
172 assert!(schedules[0].enabled);
173 }
174}