Skip to main content

max / goingson

4.9 KB · 177 lines History Blame Raw
1 //! Saved views commands.
2 //!
3 //! Manages custom filter views that users can save and pin for quick access.
4 //! Views can filter tasks, emails, or events with custom criteria and sort orders.
5
6 use serde::Deserialize;
7 use std::sync::Arc;
8 use tauri::State;
9 use tracing::instrument;
10
11 use goingson_core::{NewSavedView, SavedView, SavedViewId, SortDirection, SortField, ViewFilters, ViewType};
12
13 use crate::state::{AppState, DESKTOP_USER_ID};
14 use super::ApiError;
15
16 // ============ Types ============
17
18 #[derive(Debug, Deserialize)]
19 #[serde(rename_all = "camelCase")]
20 pub struct SavedViewInput {
21 pub name: String,
22 pub view_type: String,
23 pub filters: ViewFilters,
24 pub sort_by: Option<SortField>,
25 pub sort_order: Option<SortDirection>,
26 pub is_pinned: Option<bool>,
27 }
28
29 // ============ Commands ============
30
31 /// Lists all saved views for the current user.
32 ///
33 /// # Errors
34 ///
35 /// Returns `DATABASE_ERROR` if the query fails.
36 #[tauri::command]
37 #[instrument(skip_all)]
38 pub async fn list_saved_views(
39 state: State<'_, Arc<AppState>>,
40 ) -> Result<Vec<SavedView>, ApiError> {
41 Ok(state.saved_views.list_all(DESKTOP_USER_ID).await?)
42 }
43
44 /// Lists only pinned views for the current user.
45 ///
46 /// Pinned views appear in the sidebar for quick access.
47 ///
48 /// # Errors
49 ///
50 /// Returns `DATABASE_ERROR` if the query fails.
51 #[tauri::command]
52 #[instrument(skip_all)]
53 pub async fn list_pinned_views(
54 state: State<'_, Arc<AppState>>,
55 ) -> Result<Vec<SavedView>, ApiError> {
56 Ok(state.saved_views.list_pinned(DESKTOP_USER_ID).await?)
57 }
58
59 /// Retrieves a single saved view by ID.
60 ///
61 /// # Errors
62 ///
63 /// Returns `DATABASE_ERROR` if the query fails.
64 /// Returns `None` (not an error) if the view doesn't exist.
65 #[tauri::command]
66 #[instrument(skip_all)]
67 pub async fn get_saved_view(
68 state: State<'_, Arc<AppState>>,
69 id: SavedViewId,
70 ) -> Result<Option<SavedView>, ApiError> {
71 Ok(state.saved_views.get_by_id(id, DESKTOP_USER_ID).await?)
72 }
73
74 /// Creates a new saved view.
75 ///
76 /// # Arguments
77 ///
78 /// * `input` - View configuration:
79 /// - `name`: Display name for the view
80 /// - `view_type`: Type of items (tasks, emails, events)
81 /// - `filters`: Filter criteria to apply
82 /// - `sort_by`/`sort_order`: Optional sorting configuration
83 /// - `is_pinned`: Whether to pin to sidebar
84 ///
85 /// # Errors
86 ///
87 /// Returns `VALIDATION_ERROR` if view_type is invalid.
88 /// Returns `DATABASE_ERROR` if the insert fails.
89 #[tauri::command]
90 #[instrument(skip_all)]
91 pub async fn create_saved_view(
92 state: State<'_, Arc<AppState>>,
93 input: SavedViewInput,
94 ) -> Result<SavedView, ApiError> {
95 let view_type = match input.view_type.as_str() {
96 "tasks" => ViewType::Tasks,
97 "emails" => ViewType::Emails,
98 "events" => ViewType::Events,
99 _ => return Err(ApiError::validation("viewType", "Invalid view type. Must be tasks, emails, or events")),
100 };
101
102 let new_view = NewSavedView {
103 name: input.name,
104 view_type,
105 filters: input.filters,
106 sort_by: input.sort_by,
107 sort_order: input.sort_order,
108 is_pinned: input.is_pinned,
109 };
110
111 Ok(state.saved_views.create(DESKTOP_USER_ID, new_view).await?)
112 }
113
114 /// Updates an existing saved view.
115 ///
116 /// # Errors
117 ///
118 /// Returns `VALIDATION_ERROR` if view_type is invalid.
119 /// Returns `DATABASE_ERROR` if the update fails.
120 /// Returns `None` (not an error) if the view doesn't exist.
121 #[tauri::command]
122 #[instrument(skip_all)]
123 pub async fn update_saved_view(
124 state: State<'_, Arc<AppState>>,
125 id: SavedViewId,
126 input: SavedViewInput,
127 ) -> Result<Option<SavedView>, ApiError> {
128 let view_type = match input.view_type.as_str() {
129 "tasks" => ViewType::Tasks,
130 "emails" => ViewType::Emails,
131 "events" => ViewType::Events,
132 _ => return Err(ApiError::validation("viewType", "Invalid view type. Must be tasks, emails, or events")),
133 };
134
135 let new_view = NewSavedView {
136 name: input.name,
137 view_type,
138 filters: input.filters,
139 sort_by: input.sort_by,
140 sort_order: input.sort_order,
141 is_pinned: input.is_pinned,
142 };
143
144 Ok(state.saved_views.update(id, DESKTOP_USER_ID, new_view).await?)
145 }
146
147 /// Deletes a saved view.
148 ///
149 /// # Errors
150 ///
151 /// Returns `DATABASE_ERROR` if the delete fails.
152 #[tauri::command]
153 #[instrument(skip_all)]
154 pub async fn delete_saved_view(
155 state: State<'_, Arc<AppState>>,
156 id: SavedViewId,
157 ) -> Result<bool, ApiError> {
158 Ok(state.saved_views.delete(id, DESKTOP_USER_ID).await?)
159 }
160
161 /// Toggles the pinned status of a saved view.
162 ///
163 /// Pinned views appear in the sidebar for quick access.
164 ///
165 /// # Errors
166 ///
167 /// Returns `DATABASE_ERROR` if the update fails.
168 /// Returns `None` (not an error) if the view doesn't exist.
169 #[tauri::command]
170 #[instrument(skip_all)]
171 pub async fn toggle_view_pinned(
172 state: State<'_, Arc<AppState>>,
173 id: SavedViewId,
174 ) -> Result<Option<SavedView>, ApiError> {
175 Ok(state.saved_views.toggle_pinned(id, DESKTOP_USER_ID).await?)
176 }
177