Skip to main content

max / goingson

7.8 KB · 250 lines History Blame Raw
1 //! Tauri command modules for the GoingsOn desktop application.
2 //!
3 //! This module organizes all IPC commands into domain-specific submodules
4 //! for better maintainability and separation of concerns.
5 //!
6 //! # Module Organization
7 //!
8 //! - [`project`] - Project CRUD operations
9 //! - [`task`] - Task management, annotations, and subtasks
10 //! - [`event`] - Calendar event operations
11 //! - [`email`] - Email management and IMAP account sync
12 //! - [`oauth`] - OAuth2 authentication for email providers
13 //! - [`search`] - Full-text search across all entities
14 //! - [`day_planning`] - Time blocking and scheduling
15 //! - [`saved_views`] - Custom filter views
16 //! - [`stats`] - Dashboard statistics
17 //! - [`window`] - Window management commands
18
19 pub(crate) mod attachment;
20 mod contact;
21 mod daily_note;
22 mod day_planning;
23 pub(crate) mod email;
24 mod email_account;
25 mod email_sync;
26 pub mod error;
27 mod event;
28 mod export;
29 mod import_external;
30 mod milestone;
31 mod monthly_review;
32 mod oauth;
33 mod plugin;
34 mod project;
35 mod saved_views;
36 mod search;
37 mod stats;
38 mod sync;
39 mod task;
40 mod task_state;
41 mod task_subtasks;
42 mod time_tracking;
43 mod themes;
44 mod weekly_review;
45 mod window;
46
47 #[cfg(test)]
48 mod tests;
49
50 // Re-export error types for use in commands
51 pub use error::ApiError;
52 pub use error::{OptionNotFound, OptionApiError, ResultApiError};
53
54 // Re-export all commands for registration in main.rs
55 pub use attachment::*;
56 pub use contact::*;
57 pub use daily_note::*;
58 pub use day_planning::*;
59 pub use email::*;
60 pub use email_account::*;
61 pub use email_sync::*;
62 pub use event::*;
63 pub use export::*;
64 pub use import_external::*;
65 pub use milestone::*;
66 pub use monthly_review::*;
67 pub use oauth::*;
68 pub use project::*;
69 pub use saved_views::*;
70 pub use search::*;
71 pub use stats::*;
72 pub use sync::*;
73 pub use task::*;
74 pub use task_state::*;
75 pub use task_subtasks::*;
76 pub use time_tracking::*;
77 pub use themes::*;
78 pub use weekly_review::*;
79 pub use window::*;
80 pub use plugin::*;
81
82 // ============ Shared Types ============
83
84 use chrono::{DateTime, Utc};
85 use serde::Deserialize;
86
87 /// Input for snoozing tasks or emails until a specific time.
88 #[derive(Debug, Deserialize)]
89 #[serde(rename_all = "camelCase")]
90 pub struct SnoozeInput {
91 pub until: DateTime<Utc>,
92 }
93
94 /// Input for marking items as waiting for response.
95 #[derive(Debug, Deserialize)]
96 #[serde(rename_all = "camelCase")]
97 pub struct WaitingInput {
98 pub expected_response_date: Option<DateTime<Utc>>,
99 }
100
101 /// Input for linking an entity to a project.
102 #[derive(Debug, Deserialize)]
103 #[serde(rename_all = "camelCase")]
104 pub struct LinkProjectInput {
105 pub project_id: Option<goingson_core::ProjectId>,
106 }
107
108 // ============ Snooze Options ============
109
110 use chrono::{Datelike, Duration, Local, NaiveTime, TimeZone, Timelike, Weekday};
111 use serde::Serialize;
112 use tracing::instrument;
113
114 /// A single snooze option with timestamp and display label.
115 ///
116 /// Each option represents a pre-computed snooze time that the user can select,
117 /// such as "Later Today" or "Next Week". The timestamp is in UTC for storage,
118 /// while the formatted string shows local time for display.
119 #[derive(Debug, Serialize)]
120 #[serde(rename_all = "camelCase")]
121 pub struct SnoozeOption {
122 /// Unique identifier for this option (e.g., "laterToday", "tomorrow").
123 pub key: String,
124 /// Human-readable label for the UI (e.g., "Later Today", "Tomorrow").
125 pub label: String,
126 /// The snooze timestamp in UTC, used for storage and sorting.
127 pub time: DateTime<Utc>,
128 /// Formatted local time for display (e.g., "Sat, Feb 15, 10:00 AM").
129 pub formatted: String,
130 }
131
132 /// Response containing all available snooze options.
133 ///
134 /// Provides pre-computed snooze times based on the current local time,
135 /// including smart defaults like "Later Today" (which is omitted if it's
136 /// already past a reasonable hour) and "Next Week" (always the next Monday).
137 #[derive(Debug, Serialize)]
138 #[serde(rename_all = "camelCase")]
139 pub struct SnoozeOptionsResponse {
140 /// List of available snooze options, ordered by time.
141 pub options: Vec<SnoozeOption>,
142 /// Minimum allowed time for custom snooze (current time in UTC).
143 pub min_custom: DateTime<Utc>,
144 }
145
146 /// Get pre-computed snooze options for the UI.
147 ///
148 /// Returns smart snooze times, sorted chronologically:
149 /// - Later Today: 3 hours from now, or 5pm if past 2pm (omitted if already past)
150 /// - Tomorrow: 9am tomorrow
151 /// - This Weekend: Saturday 10am (next Saturday if already Saturday)
152 /// - Next Week: Monday 9am (at least 2 days away to avoid overlap with Tomorrow)
153 #[tauri::command]
154 #[instrument(skip_all)]
155 pub fn get_snooze_options() -> SnoozeOptionsResponse {
156 let now = Local::now();
157 let today = now.date_naive();
158 let mut options = Vec::new();
159
160 // Later today: 3 hours from now, or 5pm if past 2pm
161 let later_today = if now.hour() >= 14 {
162 // After 2pm, suggest 5pm
163 let five_pm = NaiveTime::from_hms_opt(17, 0, 0).expect("17:00 is valid");
164 Local.from_local_datetime(&today.and_time(five_pm)).earliest()
165 } else {
166 // 3 hours from now
167 Some(now + Duration::hours(3))
168 };
169
170 if let Some(lt) = later_today {
171 if lt > now {
172 let utc_time = lt.with_timezone(&Utc);
173 options.push(SnoozeOption {
174 key: "laterToday".to_string(),
175 label: "Later Today".to_string(),
176 time: utc_time,
177 formatted: format_snooze_time(&lt),
178 });
179 }
180 }
181
182 // Tomorrow 9am
183 let tomorrow = today + Duration::days(1);
184 if let Some(tomorrow_9am) = Local
185 .from_local_datetime(&tomorrow.and_time(NaiveTime::from_hms_opt(9, 0, 0).expect("09:00 is valid")))
186 .earliest()
187 {
188 options.push(SnoozeOption {
189 key: "tomorrow".to_string(),
190 label: "Tomorrow".to_string(),
191 time: tomorrow_9am.with_timezone(&Utc),
192 formatted: format_snooze_time(&tomorrow_9am),
193 });
194 }
195
196 // This weekend (Saturday 10am)
197 let days_until_saturday = (Weekday::Sat.num_days_from_monday() as i64
198 - now.weekday().num_days_from_monday() as i64
199 + 7) % 7;
200 let days_until_saturday = if days_until_saturday == 0 { 7 } else { days_until_saturday };
201 let saturday = today + Duration::days(days_until_saturday);
202 if let Some(weekend) = Local
203 .from_local_datetime(&saturday.and_time(NaiveTime::from_hms_opt(10, 0, 0).expect("10:00 is valid")))
204 .earliest()
205 {
206 options.push(SnoozeOption {
207 key: "weekend".to_string(),
208 label: "This Weekend".to_string(),
209 time: weekend.with_timezone(&Utc),
210 formatted: format_snooze_time(&weekend),
211 });
212 }
213
214 // Next week (Monday 9am) - ensure it's at least 2 days away to avoid overlap with "Tomorrow"
215 let days_until_monday = (Weekday::Mon.num_days_from_monday() as i64
216 - now.weekday().num_days_from_monday() as i64
217 + 7) % 7;
218 // If next Monday is tomorrow (1 day) or today (0 days), use the following Monday
219 let days_until_monday = if days_until_monday <= 1 { days_until_monday + 7 } else { days_until_monday };
220 let monday = today + Duration::days(days_until_monday);
221 if let Some(next_week) = Local
222 .from_local_datetime(&monday.and_time(NaiveTime::from_hms_opt(9, 0, 0).expect("09:00 is valid")))
223 .earliest()
224 {
225 options.push(SnoozeOption {
226 key: "nextWeek".to_string(),
227 label: "Next Week".to_string(),
228 time: next_week.with_timezone(&Utc),
229 formatted: format_snooze_time(&next_week),
230 });
231 }
232
233 // Sort options by time to ensure chronological order
234 options.sort_by_key(|o| o.time);
235
236 SnoozeOptionsResponse {
237 options,
238 min_custom: now.with_timezone(&Utc),
239 }
240 }
241
242 /// Format a snooze time for display (e.g., "Sat, Feb 15, 10:00 AM").
243 fn format_snooze_time<Tz: TimeZone>(dt: &DateTime<Tz>) -> String
244 where
245 Tz::Offset: std::fmt::Display,
246 {
247 dt.format("%a, %b %-d, %-I:%M %p").to_string()
248 }
249
250