Skip to main content

max / goingson

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