Skip to main content

max / goingson

12.8 KB · 350 lines History Blame Raw
1 use super::{
2 Annotation, AnnotationId, ContactId, DateTime, MilestoneId, NaiveDate, NewTask,
3 PositiveMinutes, ProjectId, Result, StatusToken, StatusTokenId, Subtask, SubtaskId, Task,
4 TaskFilterQuery, TaskId, TimeSession, TimeTrackingSummary, TokenState, UpdateTask, UserId, Utc,
5 async_trait,
6 };
7
8 /// Core task entity persistence: CRUD, listing/filtering, bulk edits,
9 /// lifecycle transitions, and date-range read queries used by reviews.
10 #[async_trait]
11 pub trait TaskCrud: Send + Sync {
12 /// Lists all non-deleted tasks for a user.
13 async fn list_all(&self, user_id: UserId) -> Result<Vec<Task>>;
14
15 /// Lists tasks belonging to a specific project.
16 async fn list_by_project(&self, user_id: UserId, project_id: ProjectId) -> Result<Vec<Task>>;
17
18 /// Lists tasks linked to a specific contact.
19 async fn list_by_contact(&self, user_id: UserId, contact_id: ContactId) -> Result<Vec<Task>>;
20
21 /// Lists tasks matching the given filter criteria with pagination.
22 /// Returns (tasks, total_count) for pagination UI.
23 async fn list_filtered(
24 &self,
25 user_id: UserId,
26 query: TaskFilterQuery,
27 ) -> Result<(Vec<Task>, i64)>;
28
29 /// Retrieves a task by ID.
30 async fn get_by_id(&self, id: TaskId, user_id: UserId) -> Result<Option<Task>>;
31
32 /// Lightweight fetch of fields needed for update logic (avoids annotation/subtask/session sub-queries).
33 /// Returns (created_at, status, completed_at, scheduled_start, scheduled_duration).
34 async fn get_update_context(
35 &self,
36 id: TaskId,
37 user_id: UserId,
38 ) -> Result<Option<crate::models::TaskUpdateContext>>;
39
40 /// Creates a new task.
41 async fn create(&self, user_id: UserId, task: NewTask) -> Result<Task>;
42
43 /// Restores a task verbatim from a backup, preserving its original ID,
44 /// `status`, `completed_at`, and `created_at`. Idempotent (`INSERT OR
45 /// IGNORE`). Annotations and subtasks are restored separately by the caller.
46 async fn restore(&self, user_id: UserId, task: &Task) -> Result<()>;
47
48 /// Updates an existing task.
49 async fn update(&self, id: TaskId, user_id: UserId, task: UpdateTask) -> Result<Option<Task>>;
50
51 /// Sets the project for many tasks in one transaction. Returns rows affected.
52 /// Avoids the per-task fetch+update round-trips the bulk UI did before
53 /// (ultra-fuzz Run #27 Perf S4). Project does not affect urgency.
54 async fn bulk_set_project(
55 &self,
56 user_id: UserId,
57 ids: &[TaskId],
58 project_id: Option<ProjectId>,
59 ) -> Result<usize>;
60
61 /// Sets the priority for many tasks in one transaction, recomputing each task's
62 /// urgency (priority is an urgency input). Returns rows affected.
63 async fn bulk_set_priority(
64 &self,
65 user_id: UserId,
66 ids: &[TaskId],
67 priority: crate::models::Priority,
68 ) -> Result<usize>;
69
70 /// Soft-deletes a task.
71 async fn delete(&self, id: TaskId, user_id: UserId) -> Result<bool>;
72
73 /// Marks a task as started.
74 async fn start(&self, id: TaskId, user_id: UserId) -> Result<bool>;
75
76 /// Marks a task as completed and returns it.
77 /// The caller is responsible for handling recurrence and milestone auto-completion.
78 async fn complete(&self, id: TaskId, user_id: UserId) -> Result<Option<Task>>;
79
80 /// Atomically completes a task and creates the next recurring instance.
81 /// Returns (completed_task, next_task). If the task has no recurrence,
82 /// next_task is None. The entire operation is wrapped in a transaction
83 /// so a crash cannot break the recurrence chain.
84 async fn complete_recurring(
85 &self,
86 id: TaskId,
87 user_id: UserId,
88 next: Option<NewTask>,
89 ) -> Result<(Option<Task>, Option<Task>)>;
90
91 /// Counts non-deleted, non-completed tasks in a milestone.
92 async fn count_incomplete_by_milestone(
93 &self,
94 milestone_id: MilestoneId,
95 user_id: UserId,
96 ) -> Result<i64>;
97
98 /// Lists tasks completed within a date range (for weekly review).
99 async fn list_completed_between(
100 &self,
101 user_id: UserId,
102 start: DateTime<Utc>,
103 end: DateTime<Utc>,
104 ) -> Result<Vec<Task>>;
105
106 /// Lists tasks that became overdue within a date range (for weekly review).
107 async fn list_became_overdue_between(
108 &self,
109 user_id: UserId,
110 start: DateTime<Utc>,
111 end: DateTime<Utc>,
112 ) -> Result<Vec<Task>>;
113
114 /// Lists tasks due within a date range (for weekly review).
115 async fn list_due_between(
116 &self,
117 user_id: UserId,
118 start: DateTime<Utc>,
119 end: DateTime<Utc>,
120 ) -> Result<Vec<Task>>;
121
122 /// Lists tasks created within a date range (for monthly review).
123 async fn list_created_between(
124 &self,
125 user_id: UserId,
126 start: DateTime<Utc>,
127 end: DateTime<Utc>,
128 ) -> Result<Vec<Task>>;
129
130 /// Lists all tasks in a recurrence chain (root + all descendants).
131 async fn list_recurrence_chain(&self, root_id: TaskId, user_id: UserId) -> Result<Vec<Task>>;
132 }
133
134 /// Annotation (note) and subtask persistence for a task.
135 #[async_trait]
136 pub trait TaskAnnotations: Send + Sync {
137 /// Gets all annotations for a task.
138 async fn get_annotations_for_task(&self, task_id: TaskId) -> Result<Vec<Annotation>>;
139
140 /// Adds an annotation (note) to a task.
141 async fn add_annotation(
142 &self,
143 task_id: TaskId,
144 user_id: UserId,
145 note: &str,
146 ) -> Result<Option<Annotation>>;
147
148 /// Deletes an annotation.
149 async fn delete_annotation(&self, annotation_id: AnnotationId, user_id: UserId)
150 -> Result<bool>;
151
152 /// Gets all subtasks for a task.
153 async fn get_subtasks_for_task(&self, task_id: TaskId) -> Result<Vec<Subtask>>;
154
155 /// Adds a subtask to a task.
156 async fn add_subtask(
157 &self,
158 task_id: TaskId,
159 user_id: UserId,
160 text: &str,
161 ) -> Result<Option<Subtask>>;
162
163 /// Toggles a subtask's completion status.
164 async fn toggle_subtask(
165 &self,
166 subtask_id: SubtaskId,
167 user_id: UserId,
168 ) -> Result<Option<Subtask>>;
169
170 /// Updates subtask text.
171 async fn update_subtask(
172 &self,
173 subtask_id: SubtaskId,
174 user_id: UserId,
175 text: &str,
176 ) -> Result<Option<Subtask>>;
177
178 /// Deletes a subtask.
179 async fn delete_subtask(&self, subtask_id: SubtaskId, user_id: UserId) -> Result<bool>;
180
181 /// Adds a linked task as a subtask.
182 ///
183 /// This creates a subtask that links to another task, enabling multi-phase
184 /// features where Phase 2 is a full task linked as a subtask of Phase 1.
185 /// The linked subtask's completion status syncs with the linked task's status.
186 async fn add_subtask_link(
187 &self,
188 task_id: TaskId,
189 user_id: UserId,
190 linked_task_id: TaskId,
191 ) -> Result<Option<Subtask>>;
192
193 /// Gets all status tokens on a task, in append order.
194 async fn get_status_tokens_for_task(&self, task_id: TaskId) -> Result<Vec<StatusToken>>;
195
196 /// Records (upserts) a status token on a task, returning it.
197 ///
198 /// The token id is deterministic in `(task_id, kind, reference)`, so recording
199 /// the same token twice is idempotent, a re-record updates its `state` and
200 /// `is_primary` in place rather than duplicating. When `is_primary` is true this
201 /// becomes the task's primary token and any prior primary flag on the task is
202 /// cleared (at most one primary per task). Returns `None` if the task is missing
203 /// or not owned.
204 async fn record_status_token(
205 &self,
206 task_id: TaskId,
207 user_id: UserId,
208 kind: &str,
209 reference: &str,
210 state: TokenState,
211 is_primary: bool,
212 ) -> Result<Option<StatusToken>>;
213
214 /// Deletes a status token by id, scoped to the user's own tasks. Returns
215 /// whether a row was removed.
216 async fn delete_status_token(&self, token_id: StatusTokenId, user_id: UserId) -> Result<bool>;
217 }
218
219 /// Scheduling and triage state: snooze, waiting, focus, time-block schedule,
220 /// and the date-scoped listings that drive the planner views.
221 #[async_trait]
222 pub trait TaskScheduling: Send + Sync {
223 /// Snoozes a task until the specified time.
224 async fn snooze(
225 &self,
226 id: TaskId,
227 user_id: UserId,
228 until: DateTime<Utc>,
229 ) -> Result<Option<Task>>;
230
231 /// Removes snooze from a task.
232 async fn unsnooze(&self, id: TaskId, user_id: UserId) -> Result<Option<Task>>;
233
234 /// Lists all currently snoozed tasks.
235 async fn list_snoozed(&self, user_id: UserId) -> Result<Vec<Task>>;
236
237 /// Marks a task as waiting for external response.
238 async fn mark_waiting(
239 &self,
240 id: TaskId,
241 user_id: UserId,
242 expected_response: Option<DateTime<Utc>>,
243 ) -> Result<Option<Task>>;
244
245 /// Clears the waiting status from a task.
246 async fn clear_waiting(&self, id: TaskId, user_id: UserId) -> Result<Option<Task>>;
247
248 /// Lists all tasks marked as waiting.
249 async fn list_waiting(&self, user_id: UserId) -> Result<Vec<Task>>;
250
251 /// Lists tasks scheduled for a specific date.
252 async fn list_scheduled_for_date(&self, user_id: UserId, date: NaiveDate) -> Result<Vec<Task>>;
253
254 /// Lists unscheduled tasks due on a specific date.
255 async fn list_unscheduled_due_on_date(
256 &self,
257 user_id: UserId,
258 date: NaiveDate,
259 ) -> Result<Vec<Task>>;
260
261 /// Lists unscheduled tasks whose `due` falls within a UTC instant window
262 /// (inclusive upper bound). Lets callers honor a user-local day boundary
263 /// instead of an implicit UTC one.
264 async fn list_unscheduled_due_between(
265 &self,
266 user_id: UserId,
267 start: DateTime<Utc>,
268 end: DateTime<Utc>,
269 ) -> Result<Vec<Task>>;
270
271 /// Updates a task's time-block schedule.
272 async fn update_schedule(
273 &self,
274 id: TaskId,
275 user_id: UserId,
276 start: Option<DateTime<Utc>>,
277 duration: Option<i32>,
278 ) -> Result<Option<Task>>;
279
280 /// Sets or clears the focus status on a task.
281 async fn set_focus(&self, id: TaskId, user_id: UserId, is_focus: bool) -> Result<Option<Task>>;
282
283 /// Lists all tasks marked as focus.
284 async fn list_focused(&self, user_id: UserId) -> Result<Vec<Task>>;
285
286 /// Clears focus from all tasks.
287 async fn clear_all_focus(&self, user_id: UserId) -> Result<u64>;
288
289 /// Lists high-priority pending tasks for focus selection.
290 async fn list_available_for_focus(&self, user_id: UserId, limit: i64) -> Result<Vec<Task>>;
291 }
292
293 /// Time-tracking persistence: live timers, manual entries, and summaries.
294 #[async_trait]
295 pub trait TaskTimeTracking: Send + Sync {
296 /// Starts a timer on a task. Fails if any session is already active for the user.
297 async fn start_timer(&self, task_id: TaskId, user_id: UserId) -> Result<TimeSession>;
298
299 /// Stops the active timer on a task, updating duration and actual_minutes cache.
300 async fn stop_timer(&self, task_id: TaskId, user_id: UserId) -> Result<Option<TimeSession>>;
301
302 /// Discards the active timer without updating actual_minutes.
303 async fn discard_timer(&self, task_id: TaskId, user_id: UserId) -> Result<bool>;
304
305 /// Gets the currently active timer for a user (at most one).
306 async fn get_active_timer(&self, user_id: UserId) -> Result<Option<(TimeSession, String)>>;
307
308 /// Lists all time sessions for a task.
309 async fn list_time_sessions(
310 &self,
311 task_id: TaskId,
312 user_id: UserId,
313 ) -> Result<Vec<TimeSession>>;
314
315 /// Lists every time session for a user across all tasks (for full backup export).
316 async fn list_all_time_sessions(&self, user_id: UserId) -> Result<Vec<TimeSession>>;
317
318 /// Logs a manual time entry (retroactive, no live timer).
319 ///
320 /// `minutes` is a validated [`PositiveMinutes`], so the repository cannot be
321 /// handed a zero/negative duration.
322 async fn log_manual_time(
323 &self,
324 task_id: TaskId,
325 user_id: UserId,
326 minutes: PositiveMinutes,
327 date: DateTime<Utc>,
328 ) -> Result<TimeSession>;
329
330 /// Gets aggregated time tracking summary grouped by project and date.
331 async fn get_time_summary(
332 &self,
333 user_id: UserId,
334 start: DateTime<Utc>,
335 end: DateTime<Utc>,
336 ) -> Result<Vec<TimeTrackingSummary>>;
337 }
338
339 /// Aggregate task-persistence contract.
340 ///
341 /// This is the trait consumers hold as `Arc<dyn TaskRepository>`. It carries no
342 /// methods of its own; the surface lives in the four capability sub-traits
343 /// ([`TaskCrud`], [`TaskAnnotations`], [`TaskScheduling`], [`TaskTimeTracking`]).
344 /// The blanket impl below means any type implementing all four automatically
345 /// satisfies `TaskRepository`, so a single trait object still exposes the whole
346 /// API while the definition stays split by concern.
347 pub trait TaskRepository: TaskCrud + TaskAnnotations + TaskScheduling + TaskTimeTracking {}
348
349 impl<T> TaskRepository for T where T: TaskCrud + TaskAnnotations + TaskScheduling + TaskTimeTracking {}
350