Skip to main content

max / goingson

41.9 KB · 986 lines History Blame Raw
1 //! Repository trait definitions for data access abstraction.
2 //!
3 //! This module defines the contracts for data persistence operations.
4 //! Implementations are provided by database-specific crates (e.g., `goingson-db-sqlite`).
5
6 use async_trait::async_trait;
7 use chrono::{DateTime, NaiveDate, Utc};
8 use std::collections::HashSet;
9 use crate::id_types::{
10 AnnotationId, AttachmentId, ContactEmailId, ContactId, ContactPhoneId, CustomFieldId,
11 EmailAccountId, EmailId, EventId, MilestoneId, ProjectId, SavedViewId,
12 SocialHandleId, SubtaskId, SyncAccountId, TaskId, UserId,
13 };
14 use uuid::Uuid;
15
16 use crate::contact::{
17 Contact, ContactCustomField, ContactEmail, ContactPhone, NewContact, NewContactCustomField,
18 NewContactEmail, NewContactPhone, NewSocialHandle, SocialHandle, UpdateContact,
19 };
20 use crate::error::CoreError;
21 use crate::models::{
22 Annotation, Attachment, Email, EmailAccount, EmailAuthType, EmailThread, Event,
23 FolderSyncState, NewAttachment, NewEmail, NewEmailWithTracking, NewEvent, NewProject,
24 NewSavedView, NewTask, Project, SavedView, Subtask, Task, TaskFilterQuery, TimeSession,
25 TimeTrackingSummary, UpdateTask, User,
26 };
27
28 /// Convenience type alias for repository operation results.
29 pub type Result<T> = std::result::Result<T, CoreError>;
30
31 /// Repository for project CRUD operations.
32 ///
33 /// All operations are scoped to a specific user for multi-tenancy support.
34 #[async_trait]
35 pub trait ProjectRepository: Send + Sync {
36 /// Lists all projects for a user.
37 async fn list_all(&self, user_id: UserId) -> Result<Vec<Project>>;
38
39 /// Retrieves a project by ID, returning `None` if not found.
40 async fn get_by_id(&self, id: ProjectId, user_id: UserId) -> Result<Option<Project>>;
41
42 /// Creates a new project.
43 async fn create(&self, user_id: UserId, project: NewProject) -> Result<Project>;
44
45 /// Updates an existing project, returning `None` if not found.
46 async fn update(
47 &self,
48 id: ProjectId,
49 user_id: UserId,
50 project: crate::models::UpdateProject,
51 ) -> Result<Option<Project>>;
52
53 /// Deletes a project, returning `true` if deleted.
54 async fn delete(&self, id: ProjectId, user_id: UserId) -> Result<bool>;
55
56 /// Finds a project by exact name match.
57 async fn find_by_name(&self, user_id: UserId, name: &str) -> Result<Option<Project>>;
58 }
59
60 /// Repository for task management operations.
61 ///
62 /// Provides CRUD operations plus specialized functionality for annotations,
63 /// subtasks, snoozing, follow-up tracking, and time-block scheduling.
64 #[async_trait]
65 pub trait TaskRepository: Send + Sync {
66 /// Lists all non-deleted tasks for a user.
67 async fn list_all(&self, user_id: UserId) -> Result<Vec<Task>>;
68
69 /// Lists tasks belonging to a specific project.
70 async fn list_by_project(&self, user_id: UserId, project_id: ProjectId) -> Result<Vec<Task>>;
71
72 /// Lists tasks linked to a specific contact.
73 async fn list_by_contact(&self, user_id: UserId, contact_id: ContactId) -> Result<Vec<Task>>;
74
75 /// Lists tasks matching the given filter criteria with pagination.
76 /// Returns (tasks, total_count) for pagination UI.
77 async fn list_filtered(&self, user_id: UserId, query: TaskFilterQuery) -> Result<(Vec<Task>, i64)>;
78
79 /// Retrieves a task by ID.
80 async fn get_by_id(&self, id: TaskId, user_id: UserId) -> Result<Option<Task>>;
81
82 /// Lightweight fetch of fields needed for update logic (avoids annotation/subtask/session sub-queries).
83 /// Returns (created_at, status, completed_at, scheduled_start, scheduled_duration).
84 async fn get_update_context(&self, id: TaskId, user_id: UserId) -> Result<Option<crate::models::TaskUpdateContext>>;
85
86 /// Creates a new task.
87 async fn create(&self, user_id: UserId, task: NewTask) -> Result<Task>;
88
89 /// Updates an existing task.
90 async fn update(&self, id: TaskId, user_id: UserId, task: UpdateTask) -> Result<Option<Task>>;
91
92 /// Soft-deletes a task.
93 async fn delete(&self, id: TaskId, user_id: UserId) -> Result<bool>;
94
95 /// Marks a task as started.
96 async fn start(&self, id: TaskId, user_id: UserId) -> Result<bool>;
97
98 /// Marks a task as completed and returns it.
99 /// The caller is responsible for handling recurrence and milestone auto-completion.
100 async fn complete(&self, id: TaskId, user_id: UserId) -> Result<Option<Task>>;
101
102 /// Atomically completes a task and creates the next recurring instance.
103 /// Returns (completed_task, next_task). If the task has no recurrence,
104 /// next_task is None. The entire operation is wrapped in a transaction
105 /// so a crash cannot break the recurrence chain.
106 async fn complete_recurring(&self, id: TaskId, user_id: UserId, next: Option<NewTask>) -> Result<(Option<Task>, Option<Task>)>;
107
108 /// Counts non-deleted, non-completed tasks in a milestone.
109 async fn count_incomplete_by_milestone(&self, milestone_id: MilestoneId, user_id: UserId) -> Result<i64>;
110
111 /// Gets all annotations for a task.
112 async fn get_annotations_for_task(&self, task_id: TaskId) -> Result<Vec<Annotation>>;
113
114 /// Adds an annotation (note) to a task.
115 async fn add_annotation(&self, task_id: TaskId, user_id: UserId, note: &str) -> Result<Option<Annotation>>;
116
117 /// Deletes an annotation.
118 async fn delete_annotation(&self, annotation_id: AnnotationId, user_id: UserId) -> Result<bool>;
119
120 /// Gets all subtasks for a task.
121 async fn get_subtasks_for_task(&self, task_id: TaskId) -> Result<Vec<Subtask>>;
122
123 /// Adds a subtask to a task.
124 async fn add_subtask(&self, task_id: TaskId, user_id: UserId, text: &str) -> Result<Option<Subtask>>;
125
126 /// Toggles a subtask's completion status.
127 async fn toggle_subtask(&self, subtask_id: SubtaskId, user_id: UserId) -> Result<Option<Subtask>>;
128
129 /// Updates subtask text.
130 async fn update_subtask(&self, subtask_id: SubtaskId, user_id: UserId, text: &str) -> Result<Option<Subtask>>;
131
132 /// Deletes a subtask.
133 async fn delete_subtask(&self, subtask_id: SubtaskId, user_id: UserId) -> Result<bool>;
134
135 /// Adds a linked task as a subtask.
136 ///
137 /// This creates a subtask that links to another task, enabling multi-phase
138 /// features where Phase 2 is a full task linked as a subtask of Phase 1.
139 /// The linked subtask's completion status syncs with the linked task's status.
140 async fn add_subtask_link(&self, task_id: TaskId, user_id: UserId, linked_task_id: TaskId) -> Result<Option<Subtask>>;
141
142 /// Snoozes a task until the specified time.
143 async fn snooze(&self, id: TaskId, user_id: UserId, until: DateTime<Utc>) -> Result<Option<Task>>;
144
145 /// Removes snooze from a task.
146 async fn unsnooze(&self, id: TaskId, user_id: UserId) -> Result<Option<Task>>;
147
148 /// Lists all currently snoozed tasks.
149 async fn list_snoozed(&self, user_id: UserId) -> Result<Vec<Task>>;
150
151 /// Marks a task as waiting for external response.
152 async fn mark_waiting(&self, id: TaskId, user_id: UserId, expected_response: Option<DateTime<Utc>>) -> Result<Option<Task>>;
153
154 /// Clears the waiting status from a task.
155 async fn clear_waiting(&self, id: TaskId, user_id: UserId) -> Result<Option<Task>>;
156
157 /// Lists all tasks marked as waiting.
158 async fn list_waiting(&self, user_id: UserId) -> Result<Vec<Task>>;
159
160 /// Lists tasks scheduled for a specific date.
161 async fn list_scheduled_for_date(&self, user_id: UserId, date: NaiveDate) -> Result<Vec<Task>>;
162
163 /// Lists unscheduled tasks due on a specific date.
164 async fn list_unscheduled_due_on_date(&self, user_id: UserId, date: NaiveDate) -> Result<Vec<Task>>;
165
166 /// Updates a task's time-block schedule.
167 async fn update_schedule(&self, id: TaskId, user_id: UserId, start: Option<DateTime<Utc>>, duration: Option<i32>) -> Result<Option<Task>>;
168
169 /// Sets or clears the focus status on a task.
170 async fn set_focus(&self, id: TaskId, user_id: UserId, is_focus: bool) -> Result<Option<Task>>;
171
172 /// Lists all tasks marked as focus.
173 async fn list_focused(&self, user_id: UserId) -> Result<Vec<Task>>;
174
175 /// Clears focus from all tasks.
176 async fn clear_all_focus(&self, user_id: UserId) -> Result<u64>;
177
178 /// Lists tasks completed within a date range (for weekly review).
179 async fn list_completed_between(&self, user_id: UserId, start: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Task>>;
180
181 /// Lists tasks that became overdue within a date range (for weekly review).
182 async fn list_became_overdue_between(&self, user_id: UserId, start: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Task>>;
183
184 /// Lists tasks due within a date range (for weekly review).
185 async fn list_due_between(&self, user_id: UserId, start: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Task>>;
186
187 /// Lists tasks created within a date range (for monthly review).
188 async fn list_created_between(&self, user_id: UserId, start: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Task>>;
189
190 /// Lists high-priority pending tasks for focus selection.
191 async fn list_available_for_focus(&self, user_id: UserId, limit: i64) -> Result<Vec<Task>>;
192
193 // ---- Time Tracking ----
194
195 /// Starts a timer on a task. Fails if any session is already active for the user.
196 async fn start_timer(&self, task_id: TaskId, user_id: UserId) -> Result<TimeSession>;
197
198 /// Stops the active timer on a task, updating duration and actual_minutes cache.
199 async fn stop_timer(&self, task_id: TaskId, user_id: UserId) -> Result<Option<TimeSession>>;
200
201 /// Discards the active timer without updating actual_minutes.
202 async fn discard_timer(&self, task_id: TaskId, user_id: UserId) -> Result<bool>;
203
204 /// Gets the currently active timer for a user (at most one).
205 async fn get_active_timer(&self, user_id: UserId) -> Result<Option<(TimeSession, String)>>;
206
207 /// Lists all time sessions for a task.
208 async fn list_time_sessions(&self, task_id: TaskId, user_id: UserId) -> Result<Vec<TimeSession>>;
209
210 /// Logs a manual time entry (retroactive, no live timer).
211 async fn log_manual_time(&self, task_id: TaskId, user_id: UserId, minutes: i32, date: DateTime<Utc>) -> Result<TimeSession>;
212
213 /// Gets aggregated time tracking summary grouped by project and date.
214 async fn get_time_summary(&self, user_id: UserId, start: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<TimeTrackingSummary>>;
215
216 /// Lists all tasks in a recurrence chain (root + all descendants).
217 async fn list_recurrence_chain(&self, root_id: TaskId, user_id: UserId) -> Result<Vec<Task>>;
218 }
219
220 /// Repository for calendar event operations.
221 ///
222 /// Events can be standalone or linked to tasks (for time-blocking).
223 ///
224 /// # Ordering Contract
225 ///
226 /// All methods returning `Vec<Event>` **MUST** return results sorted by
227 /// `start_time ASC`. This is enforced at the SQL level (`ORDER BY e.start_time ASC`)
228 /// and callers rely on this guarantee — no post-fetch sorting is needed.
229 #[async_trait]
230 pub trait EventRepository: Send + Sync {
231 /// Lists all events for a user, ordered by `start_time ASC`.
232 async fn list_all(&self, user_id: UserId) -> Result<Vec<Event>>;
233
234 /// Lists events belonging to a specific project, ordered by `start_time ASC`.
235 async fn list_by_project(&self, user_id: UserId, project_id: ProjectId) -> Result<Vec<Event>>;
236
237 /// Lists events linked to a specific contact, ordered by `start_time DESC`.
238 async fn list_by_contact(&self, user_id: UserId, contact_id: ContactId) -> Result<Vec<Event>>;
239
240 /// Retrieves an event by ID.
241 async fn get_by_id(&self, id: EventId, user_id: UserId) -> Result<Option<Event>>;
242
243 /// Creates a new event.
244 async fn create(&self, user_id: UserId, event: NewEvent) -> Result<Event>;
245
246 /// Updates an existing event.
247 async fn update(&self, id: EventId, user_id: UserId, event: crate::models::UpdateEvent) -> Result<Option<Event>>;
248
249 /// Deletes an event.
250 async fn delete(&self, id: EventId, user_id: UserId) -> Result<bool>;
251
252 /// Deletes multiple events by ID, returning the number deleted.
253 async fn delete_many(&self, ids: &[EventId], user_id: UserId) -> Result<u64>;
254
255 /// Gets events starting within the next N days, ordered by `start_time ASC`.
256 async fn get_upcoming(&self, user_id: UserId, days: i64) -> Result<Vec<Event>>;
257
258 /// Finds the event linked to a specific task (for time-blocking).
259 async fn get_by_linked_task(&self, user_id: UserId, task_id: TaskId) -> Result<Option<Event>>;
260
261 /// Deletes the event linked to a task.
262 async fn delete_by_linked_task(&self, user_id: UserId, task_id: TaskId) -> Result<bool>;
263
264 /// Lists events occurring on a specific date, ordered by `start_time ASC`.
265 async fn list_for_date(&self, user_id: UserId, date: NaiveDate) -> Result<Vec<Event>>;
266
267 /// Lists events within a date range (for weekly review), ordered by `start_time ASC`.
268 async fn list_between(&self, user_id: UserId, start: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Event>>;
269
270 /// Lists all recurring events (recurrence != 'None' or recurrence_rule is set).
271 async fn list_recurring(&self, user_id: UserId) -> Result<Vec<Event>>;
272
273 /// Finds an event by external source and ID (for dedup during import).
274 async fn find_by_external_id(&self, source: &str, ext_id: &str, user_id: UserId) -> Result<Option<Event>>;
275
276 /// Snoozes an event until `until`. Returns the updated event, or `None` if not found.
277 async fn snooze(&self, id: EventId, user_id: UserId, until: DateTime<Utc>) -> Result<Option<Event>>;
278
279 /// Clears any snooze on an event. Returns the updated event, or `None` if not found.
280 async fn unsnooze(&self, id: EventId, user_id: UserId) -> Result<Option<Event>>;
281
282 /// Lists currently snoozed events (snoozed_until is in the future).
283 async fn list_snoozed(&self, user_id: UserId) -> Result<Vec<Event>>;
284 }
285
286 /// Repository for email message operations.
287 ///
288 /// Supports IMAP-synced emails with read/archive status, project linking,
289 /// snoozing, and follow-up tracking.
290 #[async_trait]
291 pub trait EmailRepository: Send + Sync {
292 /// Lists all emails, optionally including archived.
293 async fn list_all(&self, user_id: UserId, include_archived: bool) -> Result<Vec<Email>>;
294
295 /// Lists emails grouped by thread, with metadata pre-computed and pagination.
296 /// Returns (threads, total_count) sorted by most recent email (newest first).
297 /// Optional `folder` filter restricts to emails from a specific source_folder.
298 /// Optional `label` filter restricts to emails with a specific label.
299 async fn list_threaded(&self, user_id: UserId, include_archived: bool, offset: Option<i64>, limit: Option<i64>, folder: Option<&str>, label: Option<&str>) -> Result<(Vec<EmailThread>, i64)>;
300
301 /// Lists emails linked to a specific project.
302 async fn list_by_project(&self, user_id: UserId, project_id: ProjectId) -> Result<Vec<Email>>;
303
304 /// Lists emails sent from or to any of the given addresses.
305 async fn list_by_addresses(&self, user_id: UserId, addresses: &[&str]) -> Result<Vec<Email>>;
306
307 /// Lists emails not linked to any project.
308 async fn list_unlinked(&self, user_id: UserId) -> Result<Vec<Email>>;
309
310 /// Retrieves an email by ID.
311 async fn get_by_id(&self, id: EmailId, user_id: UserId) -> Result<Option<Email>>;
312
313 /// Creates a new email record.
314 async fn create(&self, user_id: UserId, email: NewEmail) -> Result<Email>;
315
316 /// Creates an email with follow-up tracking fields.
317 async fn create_with_tracking(&self, user_id: UserId, email: NewEmailWithTracking) -> Result<Email>;
318
319 /// Batch-inserts emails with tracking in a single transaction, skipping post-insert SELECTs.
320 /// Returns the count of successfully inserted emails.
321 async fn create_with_tracking_batch(&self, user_id: UserId, emails: Vec<NewEmailWithTracking>) -> Result<usize>;
322
323 /// Deletes an email.
324 async fn delete(&self, id: EmailId, user_id: UserId) -> Result<bool>;
325
326 /// Marks an email as read.
327 async fn mark_read(&self, id: EmailId, user_id: UserId) -> Result<bool>;
328
329 /// Marks an email as unread.
330 async fn mark_unread(&self, id: EmailId, user_id: UserId) -> Result<bool>;
331
332 /// Archives an email.
333 async fn archive(&self, id: EmailId, user_id: UserId) -> Result<bool>;
334
335 /// Unarchives an email.
336 async fn unarchive(&self, id: EmailId, user_id: UserId) -> Result<bool>;
337
338 /// Updates the IMAP source folder for an email.
339 async fn update_source_folder(&self, id: EmailId, user_id: UserId, new_folder: &str) -> Result<bool>;
340
341 /// Marks all emails as read, returning the count updated.
342 async fn mark_all_read(&self, user_id: UserId) -> Result<u64>;
343
344 /// Links or unlinks an email to a project.
345 async fn link_to_project(&self, id: EmailId, user_id: UserId, project_id: Option<ProjectId>) -> Result<bool>;
346
347 /// Counts unread emails.
348 async fn count_unread(&self, user_id: UserId) -> Result<i64>;
349
350 /// Checks if an email with the given Message-ID header exists.
351 async fn exists_by_message_id(&self, user_id: UserId, message_id: &str) -> Result<bool>;
352
353 /// Batch check for existing Message-IDs, returns the set that exist.
354 async fn exists_by_message_ids(&self, user_id: UserId, message_ids: &[&str]) -> Result<HashSet<String>>;
355
356 /// Batch check which email addresses have appeared as senders.
357 /// Returns the set of addresses (lowercased) that have sent at least one email.
358 async fn exists_as_senders(&self, user_id: UserId, addresses: &[&str]) -> Result<HashSet<String>>;
359
360 /// Snoozes an email until the specified time.
361 async fn snooze(&self, id: EmailId, user_id: UserId, until: DateTime<Utc>) -> Result<Option<Email>>;
362
363 /// Removes snooze from an email.
364 async fn unsnooze(&self, id: EmailId, user_id: UserId) -> Result<Option<Email>>;
365
366 /// Lists all currently snoozed emails.
367 async fn list_snoozed(&self, user_id: UserId) -> Result<Vec<Email>>;
368
369 /// Marks an email as waiting for response.
370 async fn mark_waiting(&self, id: EmailId, user_id: UserId, expected_response: Option<DateTime<Utc>>) -> Result<Option<Email>>;
371
372 /// Clears the waiting status from an email.
373 async fn clear_waiting(&self, id: EmailId, user_id: UserId) -> Result<Option<Email>>;
374
375 /// Lists all emails marked as waiting.
376 async fn list_waiting(&self, user_id: UserId) -> Result<Vec<Email>>;
377
378 /// Lists all emails in a thread, ordered by date ascending.
379 async fn list_by_thread(&self, user_id: UserId, thread_id: &str) -> Result<Vec<Email>>;
380
381 /// Gets an email by its Message-ID header.
382 async fn get_by_message_id(&self, user_id: UserId, message_id: &str) -> Result<Option<Email>>;
383
384 /// Updates labels/tags on an email.
385 async fn update_labels(&self, id: EmailId, user_id: UserId, labels: &[String]) -> Result<Option<Email>>;
386
387 /// Lists distinct source_folder values across all non-draft emails.
388 async fn list_folders(&self, user_id: UserId) -> Result<Vec<String>>;
389
390 /// Lists all distinct labels used across all emails.
391 async fn list_labels(&self, user_id: UserId) -> Result<Vec<String>>;
392
393 /// Lists all draft emails.
394 async fn list_drafts(&self, user_id: UserId) -> Result<Vec<Email>>;
395
396 /// Creates or updates a draft email.
397 #[allow(clippy::too_many_arguments)]
398 async fn save_draft(&self, id: EmailId, user_id: UserId, from: &str, to: &str, cc: Option<&str>, bcc: Option<&str>, subject: &str, body: &str, account_id: Option<EmailAccountId>, in_reply_to: Option<&str>, references: Option<&str>, thread_id: Option<&str>) -> Result<Email>;
399 }
400
401 /// Repository for user account operations.
402 #[async_trait]
403 pub trait UserRepository: Send + Sync {
404 /// Creates a new user account with hashed password.
405 async fn create(&self, email: &str, password: &str, display_name: &str) -> Result<User>;
406
407 /// Finds a user by email address.
408 async fn find_by_email(&self, email: &str) -> Result<Option<User>>;
409
410 /// Authenticates a user, returning the user if credentials are valid.
411 async fn authenticate(&self, email: &str, password: &str) -> Result<Option<User>>;
412
413 /// Updates the user's last login timestamp.
414 async fn update_last_login(&self, user_id: UserId) -> Result<()>;
415 }
416
417 /// Repository for email account (IMAP/SMTP/OAuth2) configuration.
418 #[allow(clippy::too_many_arguments)]
419 #[async_trait]
420 pub trait EmailAccountRepository: Send + Sync {
421 /// Lists all email accounts for a user.
422 async fn list_by_user(&self, user_id: UserId) -> Result<Vec<EmailAccount>>;
423
424 /// Retrieves an email account by ID.
425 async fn get_by_id(&self, id: EmailAccountId, user_id: UserId) -> Result<Option<EmailAccount>>;
426
427 /// Creates a new email account configuration (password-based IMAP/SMTP).
428 async fn create(
429 &self,
430 user_id: UserId,
431 account_name: &str,
432 email_address: &str,
433 imap_server: &str,
434 imap_port: i32,
435 smtp_server: &str,
436 smtp_port: i32,
437 username: &str,
438 password: &str,
439 use_tls: bool,
440 archive_folder_name: Option<&str>,
441 ) -> Result<EmailAccount>;
442
443 /// Creates a new OAuth2 email account (Fastmail JMAP).
444 async fn create_oauth(
445 &self,
446 user_id: UserId,
447 account_name: &str,
448 email_address: &str,
449 access_token: &str,
450 refresh_token: &str,
451 expires_at: DateTime<Utc>,
452 jmap_session_url: &str,
453 jmap_account_id: &str,
454 ) -> Result<EmailAccount>;
455
456 /// Creates a new OAuth2 email account with IMAP/SMTP (Google, Microsoft, Yahoo).
457 async fn create_oauth_imap(
458 &self,
459 user_id: UserId,
460 account_name: &str,
461 email_address: &str,
462 auth_type: EmailAuthType,
463 access_token: &str,
464 refresh_token: &str,
465 expires_at: DateTime<Utc>,
466 imap_server: &str,
467 imap_port: i32,
468 smtp_server: &str,
469 smtp_port: i32,
470 ) -> Result<EmailAccount>;
471
472 /// Updates an email account. Password is only updated if provided.
473 async fn update(
474 &self,
475 id: EmailAccountId,
476 user_id: UserId,
477 account_name: &str,
478 email_address: &str,
479 imap_server: &str,
480 imap_port: i32,
481 smtp_server: &str,
482 smtp_port: i32,
483 username: &str,
484 password: Option<&str>,
485 use_tls: bool,
486 archive_folder_name: Option<&str>,
487 ) -> Result<Option<EmailAccount>>;
488
489 /// Updates OAuth2 tokens for an account.
490 async fn update_oauth_tokens(
491 &self,
492 id: EmailAccountId,
493 user_id: UserId,
494 access_token: &str,
495 refresh_token: Option<&str>,
496 expires_at: DateTime<Utc>,
497 ) -> Result<Option<EmailAccount>>;
498
499 /// Updates JMAP session info for an account.
500 async fn update_jmap_session(
501 &self,
502 id: EmailAccountId,
503 user_id: UserId,
504 session_url: &str,
505 account_id: &str,
506 ) -> Result<Option<EmailAccount>>;
507
508 /// Deletes an email account.
509 async fn delete(&self, id: EmailAccountId, user_id: UserId) -> Result<bool>;
510
511 /// Updates the last sync timestamp.
512 async fn update_last_sync(&self, id: EmailAccountId, user_id: UserId) -> Result<bool>;
513
514 /// Updates the sync interval setting for an account.
515 async fn update_sync_interval(&self, id: EmailAccountId, user_id: UserId, interval_minutes: Option<i32>) -> Result<Option<EmailAccount>>;
516
517 /// Updates the email signature for an account.
518 async fn update_signature(&self, id: EmailAccountId, user_id: UserId, signature: Option<&str>) -> Result<Option<EmailAccount>>;
519
520 /// Updates the notification preference for an account.
521 async fn update_notify_new_emails(&self, id: EmailAccountId, user_id: UserId, enabled: bool) -> Result<Option<EmailAccount>>;
522
523 /// Lists accounts that need automatic sync based on their sync_interval_minutes.
524 /// Returns accounts where sync is enabled and last_sync_at + interval < now.
525 async fn list_accounts_needing_sync(&self, user_id: UserId) -> Result<Vec<EmailAccount>>;
526
527 /// Gets the IMAP folder sync state for incremental UID-based fetching.
528 async fn get_folder_sync_state(&self, account_id: EmailAccountId, folder: &str) -> Result<Option<FolderSyncState>>;
529
530 /// Upserts the IMAP folder sync state after a successful sync.
531 async fn upsert_folder_sync_state(&self, account_id: EmailAccountId, folder: &str, uid_validity: u32, last_seen_uid: u32) -> Result<()>;
532
533 /// Deletes stale folder sync state (e.g. on UIDVALIDITY change).
534 async fn delete_folder_sync_state(&self, account_id: EmailAccountId, folder: &str) -> Result<()>;
535 }
536
537 /// Aggregated statistics for the dashboard.
538 #[derive(Debug, Clone, serde::Serialize)]
539 pub struct DashboardStats {
540 /// Number of tasks due today.
541 pub tasks_due_today: i64,
542 /// Number of tasks due within the next 7 days.
543 pub tasks_due_this_week: i64,
544 /// Number of overdue tasks.
545 pub overdue_count: i64,
546 /// Number of unread emails.
547 pub unread_emails: i64,
548 /// Number of events in the next 7 days.
549 pub upcoming_events: i64,
550 /// Number of active projects.
551 pub active_projects: i64,
552 /// Top tasks by urgency score.
553 pub high_urgency_tasks: Vec<HighUrgencyTask>,
554 }
555
556 /// A task with high urgency for dashboard display.
557 #[derive(Debug, Clone, serde::Serialize)]
558 pub struct HighUrgencyTask {
559 /// Task ID as string.
560 pub id: String,
561 /// Task description.
562 pub description: String,
563 /// Calculated urgency score.
564 pub urgency: f64,
565 /// Task status.
566 pub status: String,
567 /// Due date if set, formatted as ISO string.
568 pub due: Option<String>,
569 }
570
571 /// Repository for dashboard statistics.
572 #[async_trait]
573 pub trait StatsRepository: Send + Sync {
574 /// Computes aggregated dashboard statistics for a user.
575 async fn get_dashboard_stats(&self, user_id: UserId) -> Result<DashboardStats>;
576 }
577
578 /// Type of item in search results.
579 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
580 #[serde(rename_all = "lowercase")]
581 pub enum SearchResultType {
582 /// A task item.
583 Task,
584 /// An email item.
585 Email,
586 /// A project item.
587 Project,
588 /// A calendar event.
589 Event,
590 /// A contact.
591 Contact,
592 }
593
594 /// A single search result item with ranking information.
595 #[derive(Debug, Clone, serde::Serialize)]
596 pub struct SearchResultItem {
597 /// Item ID.
598 pub id: Uuid,
599 /// Type of the result.
600 pub result_type: SearchResultType,
601 /// Display title.
602 pub title: String,
603 /// Text snippet showing match context.
604 pub snippet: Option<String>,
605 /// Associated project ID if any.
606 pub project_id: Option<ProjectId>,
607 /// Associated project name if any.
608 pub project_name: Option<String>,
609 /// Relevance rank (higher is better).
610 pub rank: f64,
611 }
612
613 /// Search query with filtering and pagination options.
614 #[derive(Debug, Clone, Default)]
615 pub struct SearchQuery {
616 /// The search terms (FTS text after filters extracted).
617 pub query: String,
618 /// Filter to specific result types.
619 pub types: Option<Vec<SearchResultType>>,
620 /// Filter to a specific project by ID.
621 pub project_id: Option<ProjectId>,
622 /// Filter to a specific project by name (partial match).
623 pub project_name: Option<String>,
624 /// Filter to items on or after this date.
625 pub date_from: Option<DateTime<Utc>>,
626 /// Filter to items on or before this date.
627 pub date_to: Option<DateTime<Utc>>,
628 /// Maximum results to return.
629 pub limit: Option<i64>,
630 /// Offset for pagination.
631 pub offset: Option<i64>,
632 /// `is:` filters for time/state conditions.
633 pub is_filters: Vec<crate::search_parser::IsFilter>,
634 /// Priority filter (`priority:high` etc).
635 pub priority: Option<crate::models::Priority>,
636 /// Tags to include (`tag:name`).
637 pub tags_include: Vec<String>,
638 /// Tags to exclude (`-tag:name`).
639 pub tags_exclude: Vec<String>,
640 }
641
642 impl SearchQuery {
643 /// Creates a new search query with default options.
644 pub fn new(query: impl Into<String>) -> Self {
645 Self {
646 query: query.into(),
647 types: None,
648 project_id: None,
649 project_name: None,
650 date_from: None,
651 date_to: None,
652 limit: Some(50),
653 offset: None,
654 is_filters: Vec::new(),
655 priority: None,
656 tags_include: Vec::new(),
657 tags_exclude: Vec::new(),
658 }
659 }
660
661 /// Filters results to specific types.
662 pub fn with_types(mut self, types: Vec<SearchResultType>) -> Self {
663 self.types = Some(types);
664 self
665 }
666
667 /// Filters results to a specific project by ID.
668 pub fn with_project(mut self, project_id: ProjectId) -> Self {
669 self.project_id = Some(project_id);
670 self
671 }
672
673 /// Filters results to a specific project by name.
674 pub fn with_project_name(mut self, name: impl Into<String>) -> Self {
675 self.project_name = Some(name.into());
676 self
677 }
678
679 /// Sets the maximum number of results.
680 pub fn with_limit(mut self, limit: i64) -> Self {
681 self.limit = Some(limit);
682 self
683 }
684
685 /// Sets the pagination offset.
686 pub fn with_offset(mut self, offset: i64) -> Self {
687 self.offset = Some(offset);
688 self
689 }
690
691 /// Filters results to items on or after this date.
692 pub fn with_date_from(mut self, date: DateTime<Utc>) -> Self {
693 self.date_from = Some(date);
694 self
695 }
696
697 /// Filters results to items on or before this date.
698 pub fn with_date_to(mut self, date: DateTime<Utc>) -> Self {
699 self.date_to = Some(date);
700 self
701 }
702
703 /// Adds `is:` filters.
704 pub fn with_is_filters(mut self, filters: Vec<crate::search_parser::IsFilter>) -> Self {
705 self.is_filters = filters;
706 self
707 }
708
709 /// Sets priority filter.
710 pub fn with_priority(mut self, priority: crate::models::Priority) -> Self {
711 self.priority = Some(priority);
712 self
713 }
714
715 /// Sets tags to include.
716 pub fn with_tags_include(mut self, tags: Vec<String>) -> Self {
717 self.tags_include = tags;
718 self
719 }
720
721 /// Sets tags to exclude.
722 pub fn with_tags_exclude(mut self, tags: Vec<String>) -> Self {
723 self.tags_exclude = tags;
724 self
725 }
726 }
727
728 /// Repository for full-text search across all content types.
729 #[async_trait]
730 pub trait SearchRepository: Send + Sync {
731 /// Searches across all indexed content using FTS.
732 /// Returns (results, total_count) where total_count is the pre-pagination count.
733 async fn search(&self, user_id: UserId, query: SearchQuery) -> Result<(Vec<SearchResultItem>, usize)>;
734 }
735
736 /// Repository for saved view / filter configurations.
737 #[async_trait]
738 pub trait SavedViewRepository: Send + Sync {
739 /// Lists all saved views for a user.
740 async fn list_all(&self, user_id: UserId) -> Result<Vec<SavedView>>;
741
742 /// Lists pinned views for sidebar display.
743 async fn list_pinned(&self, user_id: UserId) -> Result<Vec<SavedView>>;
744
745 /// Gets a saved view by ID.
746 async fn get_by_id(&self, id: SavedViewId, user_id: UserId) -> Result<Option<SavedView>>;
747
748 /// Creates a new saved view.
749 async fn create(&self, user_id: UserId, view: NewSavedView) -> Result<SavedView>;
750
751 /// Updates an existing saved view.
752 async fn update(&self, id: SavedViewId, user_id: UserId, view: NewSavedView) -> Result<Option<SavedView>>;
753
754 /// Deletes a saved view.
755 async fn delete(&self, id: SavedViewId, user_id: UserId) -> Result<bool>;
756
757 /// Toggles the pinned status of a view.
758 async fn toggle_pinned(&self, id: SavedViewId, user_id: UserId) -> Result<Option<SavedView>>;
759
760 /// Updates the position of a view in the sidebar.
761 async fn update_position(&self, id: SavedViewId, user_id: UserId, position: i32) -> Result<Option<SavedView>>;
762 }
763
764 /// Repository for weekly review tracking.
765 #[async_trait]
766 pub trait WeeklyReviewRepository: Send + Sync {
767 /// Gets the weekly review for a specific week.
768 async fn get_for_week(&self, user_id: UserId, week_start: NaiveDate) -> Result<Option<crate::models::WeeklyReview>>;
769
770 /// Creates or updates a weekly review.
771 async fn upsert(&self, user_id: UserId, week_start: NaiveDate, notes: &str) -> Result<crate::models::WeeklyReview>;
772
773 /// Checks if the current week's review is completed.
774 async fn is_current_week_completed(&self, user_id: UserId) -> Result<bool>;
775
776 /// Sets vacation days for a specific week.
777 async fn set_vacation_days(&self, user_id: UserId, week_start: NaiveDate, days: &[u8]) -> Result<()>;
778 }
779
780 /// Repository for daily review notes.
781 #[async_trait]
782 pub trait DailyNoteRepository: Send + Sync {
783 /// Gets the daily note for a specific date.
784 async fn get_by_date(&self, user_id: UserId, date: NaiveDate) -> Result<Option<crate::models::DailyNote>>;
785
786 /// Creates or updates a daily note (upsert by user_id + date).
787 async fn upsert(&self, user_id: UserId, date: NaiveDate, went_well: &str, could_improve: &str, is_reviewed: bool) -> Result<crate::models::DailyNote>;
788 }
789
790 /// Repository for monthly review goals and reflections.
791 #[async_trait]
792 pub trait MonthlyReviewRepository: Send + Sync {
793 /// Gets all goals for a specific month.
794 async fn list_goals(&self, user_id: UserId, month: &str) -> Result<Vec<crate::models::MonthlyGoal>>;
795
796 /// Creates or updates a goal for a month at a given position (1-3).
797 async fn upsert_goal(&self, user_id: UserId, month: &str, text: &str, position: i32) -> Result<crate::models::MonthlyGoal>;
798
799 /// Updates the status of a goal.
800 async fn update_goal_status(&self, id: crate::MonthlyGoalId, user_id: UserId, status: &crate::models::MonthlyGoalStatus) -> Result<Option<crate::models::MonthlyGoal>>;
801
802 /// Deletes a goal.
803 async fn delete_goal(&self, id: crate::MonthlyGoalId, user_id: UserId) -> Result<bool>;
804
805 /// Gets the reflection for a specific month.
806 async fn get_reflection(&self, user_id: UserId, month: &str) -> Result<Option<crate::models::MonthlyReflection>>;
807
808 /// Creates or updates the reflection for a month.
809 async fn upsert_reflection(&self, user_id: UserId, month: &str, highlight: &str, change: &str) -> Result<crate::models::MonthlyReflection>;
810 }
811
812 /// Repository for milestone management within projects.
813 #[async_trait]
814 pub trait MilestoneRepository: Send + Sync {
815 /// Lists all milestones for a project, ordered by position.
816 async fn list_by_project(&self, project_id: ProjectId, user_id: UserId) -> Result<Vec<crate::models::Milestone>>;
817
818 /// Gets a milestone by ID.
819 async fn get_by_id(&self, id: MilestoneId, user_id: UserId) -> Result<Option<crate::models::Milestone>>;
820
821 /// Creates a new milestone.
822 async fn create(&self, user_id: UserId, milestone: crate::models::NewMilestone) -> Result<crate::models::Milestone>;
823
824 /// Updates an existing milestone.
825 async fn update(
826 &self,
827 id: MilestoneId,
828 user_id: UserId,
829 name: &str,
830 description: &str,
831 target_date: Option<NaiveDate>,
832 status: &crate::models::MilestoneStatus,
833 ) -> Result<Option<crate::models::Milestone>>;
834
835 /// Deletes a milestone.
836 async fn delete(&self, id: MilestoneId, user_id: UserId) -> Result<bool>;
837
838 /// Reorders milestones within a project.
839 async fn reorder(&self, project_id: ProjectId, user_id: UserId, milestone_ids: &[MilestoneId]) -> Result<()>;
840 }
841
842 /// Repository for backup settings management.
843 #[async_trait]
844 pub trait BackupSettingsRepository: Send + Sync {
845 /// Gets the backup settings for a user.
846 async fn get(&self, user_id: UserId) -> Result<Option<crate::models::BackupSettings>>;
847
848 /// Creates or updates backup settings.
849 async fn upsert(
850 &self,
851 user_id: UserId,
852 settings: crate::models::NewBackupSettings,
853 ) -> Result<crate::models::BackupSettings>;
854
855 /// Updates the last backup timestamp.
856 async fn update_last_backup_at(&self, user_id: UserId, timestamp: DateTime<Utc>) -> Result<()>;
857 }
858
859 /// Repository for contact CRUD operations and sub-collection management.
860 ///
861 /// Contacts have sub-collections (emails, phones, social handles) stored in
862 /// separate tables to enable querying by email address for future integrations.
863 #[async_trait]
864 pub trait ContactRepository: Send + Sync {
865 /// Lists all contacts for a user.
866 async fn list_all(&self, user_id: UserId) -> Result<Vec<Contact>>;
867
868 /// Retrieves a contact by ID, returning `None` if not found.
869 async fn get_by_id(&self, id: ContactId, user_id: UserId) -> Result<Option<Contact>>;
870
871 /// Creates a new contact.
872 async fn create(&self, user_id: UserId, contact: NewContact) -> Result<Contact>;
873
874 /// Updates an existing contact, returning `None` if not found.
875 async fn update(&self, id: ContactId, user_id: UserId, contact: UpdateContact) -> Result<Option<Contact>>;
876
877 /// Deletes a contact (CASCADE removes sub-entities), returning `true` if deleted.
878 async fn delete(&self, id: ContactId, user_id: UserId) -> Result<bool>;
879
880 /// Deletes multiple contacts by ID, returning the number deleted.
881 async fn delete_many(&self, ids: &[ContactId], user_id: UserId) -> Result<u64>;
882
883 /// Adds a tag to multiple contacts (skips contacts that already have the tag).
884 async fn tag_many(&self, ids: &[ContactId], user_id: UserId, tag: &str) -> Result<u64>;
885
886 /// Lists contacts matching a tag.
887 async fn list_by_tag(&self, user_id: UserId, tag: &str) -> Result<Vec<Contact>>;
888
889 /// Lists contacts matching a search query and/or tag filter.
890 /// Searches across display_name, nickname, company, title, notes, and email addresses.
891 async fn list_filtered(&self, user_id: UserId, search: Option<&str>, tag: Option<&str>, include_implicit: bool) -> Result<Vec<Contact>>;
892
893 /// Finds a contact by email address.
894 async fn find_by_email(&self, user_id: UserId, email: &str) -> Result<Option<Contact>>;
895
896 /// Batch check which email addresses belong to known contacts.
897 /// Returns the set of addresses (lowercased) that match at least one contact.
898 async fn find_emails_in_contacts(&self, user_id: UserId, addresses: &[&str]) -> Result<HashSet<String>>;
899
900 /// Promotes an implicit contact to explicit by setting is_implicit = 0.
901 async fn promote_contact(&self, id: ContactId, user_id: UserId) -> Result<Option<Contact>>;
902
903 /// Finds a contact by external source and ID (for dedup during import).
904 async fn find_by_external_id(&self, source: &str, ext_id: &str, user_id: UserId) -> Result<Option<Contact>>;
905
906 /// Adds an email address to a contact.
907 async fn add_email(&self, contact_id: ContactId, user_id: UserId, email: NewContactEmail) -> Result<ContactEmail>;
908
909 /// Removes an email address from a contact.
910 async fn remove_email(&self, email_id: ContactEmailId, user_id: UserId) -> Result<bool>;
911
912 /// Adds a phone number to a contact.
913 async fn add_phone(&self, contact_id: ContactId, user_id: UserId, phone: NewContactPhone) -> Result<ContactPhone>;
914
915 /// Removes a phone number from a contact.
916 async fn remove_phone(&self, phone_id: ContactPhoneId, user_id: UserId) -> Result<bool>;
917
918 /// Adds a social handle to a contact.
919 async fn add_social_handle(&self, contact_id: ContactId, user_id: UserId, handle: NewSocialHandle) -> Result<SocialHandle>;
920
921 /// Removes a social handle from a contact.
922 async fn remove_social_handle(&self, handle_id: SocialHandleId, user_id: UserId) -> Result<bool>;
923
924 /// Adds a custom field to a contact.
925 async fn add_custom_field(&self, contact_id: ContactId, user_id: UserId, field: NewContactCustomField) -> Result<ContactCustomField>;
926
927 /// Removes a custom field from a contact.
928 async fn remove_custom_field(&self, field_id: CustomFieldId, user_id: UserId) -> Result<bool>;
929
930 /// Updates a contact email row (address/label/is_primary). Returns the updated row, or `None` if not found.
931 async fn update_email(&self, email_id: ContactEmailId, user_id: UserId, email: NewContactEmail) -> Result<Option<ContactEmail>>;
932
933 /// Updates a contact phone row (number/label/is_primary). Returns the updated row, or `None` if not found.
934 async fn update_phone(&self, phone_id: ContactPhoneId, user_id: UserId, phone: NewContactPhone) -> Result<Option<ContactPhone>>;
935
936 /// Updates a social handle row (platform/handle/url). Returns the updated row, or `None` if not found.
937 async fn update_social_handle(&self, handle_id: SocialHandleId, user_id: UserId, handle: NewSocialHandle) -> Result<Option<SocialHandle>>;
938
939 /// Updates a custom field row (label/value/url). Returns the updated row, or `None` if not found.
940 async fn update_custom_field(&self, field_id: CustomFieldId, user_id: UserId, field: NewContactCustomField) -> Result<Option<ContactCustomField>>;
941 }
942
943 /// Repository for file attachment operations.
944 #[async_trait]
945 pub trait AttachmentRepository: Send + Sync {
946 /// Creates a new attachment record.
947 async fn create(&self, user_id: UserId, attachment: NewAttachment) -> Result<Attachment>;
948
949 /// Lists attachments for a task.
950 async fn list_for_task(&self, task_id: TaskId, user_id: UserId) -> Result<Vec<Attachment>>;
951
952 /// Lists attachments for a project.
953 async fn list_for_project(&self, project_id: ProjectId, user_id: UserId) -> Result<Vec<Attachment>>;
954
955 /// Retrieves an attachment by ID.
956 async fn get_by_id(&self, id: AttachmentId, user_id: UserId) -> Result<Option<Attachment>>;
957
958 /// Deletes an attachment record, returning `true` if deleted.
959 async fn delete(&self, id: AttachmentId, user_id: UserId) -> Result<bool>;
960
961 /// Lists all attachments sharing a blob hash (for dedup checks).
962 async fn list_by_blob_hash(&self, blob_hash: &str, user_id: UserId) -> Result<Vec<Attachment>>;
963
964 /// Lists all distinct blob hashes for a user (for blob sync).
965 async fn list_all_blob_hashes(&self, user_id: UserId) -> Result<Vec<String>>;
966 }
967
968 /// Repository for sync account CRUD operations.
969 #[async_trait]
970 pub trait SyncAccountRepository: Send + Sync {
971 /// Lists all sync accounts for a user.
972 async fn list_all(&self, user_id: UserId) -> Result<Vec<crate::models::SyncAccount>>;
973
974 /// Retrieves a sync account by ID.
975 async fn get_by_id(&self, id: SyncAccountId, user_id: UserId) -> Result<Option<crate::models::SyncAccount>>;
976
977 /// Creates a new sync account.
978 async fn create(&self, user_id: UserId, provider: &str, account_name: &str, email: Option<&str>) -> Result<crate::models::SyncAccount>;
979
980 /// Updates a sync account.
981 async fn update(&self, id: SyncAccountId, user_id: UserId, account_name: &str, sync_calendars: bool, sync_contacts: bool, enabled: bool) -> Result<Option<crate::models::SyncAccount>>;
982
983 /// Deletes a sync account.
984 async fn delete(&self, id: SyncAccountId, user_id: UserId) -> Result<bool>;
985 }
986