//! Repository trait definitions for data access abstraction. //! //! This module defines the contracts for data persistence operations. //! Implementations are provided by database-specific crates (e.g., `goingson-db-sqlite`). use async_trait::async_trait; use chrono::{DateTime, NaiveDate, Utc}; use std::collections::HashSet; use crate::id_types::{ AnnotationId, AttachmentId, ContactEmailId, ContactId, ContactPhoneId, CustomFieldId, EmailAccountId, EmailId, EventId, MilestoneId, ProjectId, SavedViewId, SocialHandleId, SubtaskId, SyncAccountId, TaskId, UserId, }; use uuid::Uuid; use crate::contact::{ Contact, ContactCustomField, ContactEmail, ContactPhone, NewContact, NewContactCustomField, NewContactEmail, NewContactPhone, NewSocialHandle, SocialHandle, UpdateContact, }; use crate::error::CoreError; use crate::models::{ Annotation, Attachment, Email, EmailAccount, EmailAuthType, EmailThread, Event, FolderSyncState, NewAttachment, NewEmail, NewEmailWithTracking, NewEvent, NewProject, NewSavedView, NewTask, Project, SavedView, Subtask, Task, TaskFilterQuery, TimeSession, TimeTrackingSummary, UpdateTask, User, }; /// Convenience type alias for repository operation results. pub type Result = std::result::Result; /// Repository for project CRUD operations. /// /// All operations are scoped to a specific user for multi-tenancy support. #[async_trait] pub trait ProjectRepository: Send + Sync { /// Lists all projects for a user. async fn list_all(&self, user_id: UserId) -> Result>; /// Retrieves a project by ID, returning `None` if not found. async fn get_by_id(&self, id: ProjectId, user_id: UserId) -> Result>; /// Creates a new project. async fn create(&self, user_id: UserId, project: NewProject) -> Result; /// Updates an existing project, returning `None` if not found. async fn update( &self, id: ProjectId, user_id: UserId, project: crate::models::UpdateProject, ) -> Result>; /// Deletes a project, returning `true` if deleted. async fn delete(&self, id: ProjectId, user_id: UserId) -> Result; /// Finds a project by exact name match. async fn find_by_name(&self, user_id: UserId, name: &str) -> Result>; } /// Repository for task management operations. /// /// Provides CRUD operations plus specialized functionality for annotations, /// subtasks, snoozing, follow-up tracking, and time-block scheduling. #[async_trait] pub trait TaskRepository: Send + Sync { /// Lists all non-deleted tasks for a user. async fn list_all(&self, user_id: UserId) -> Result>; /// Lists tasks belonging to a specific project. async fn list_by_project(&self, user_id: UserId, project_id: ProjectId) -> Result>; /// Lists tasks linked to a specific contact. async fn list_by_contact(&self, user_id: UserId, contact_id: ContactId) -> Result>; /// Lists tasks matching the given filter criteria with pagination. /// Returns (tasks, total_count) for pagination UI. async fn list_filtered(&self, user_id: UserId, query: TaskFilterQuery) -> Result<(Vec, i64)>; /// Retrieves a task by ID. async fn get_by_id(&self, id: TaskId, user_id: UserId) -> Result>; /// Lightweight fetch of fields needed for update logic (avoids annotation/subtask/session sub-queries). /// Returns (created_at, status, completed_at, scheduled_start, scheduled_duration). async fn get_update_context(&self, id: TaskId, user_id: UserId) -> Result>; /// Creates a new task. async fn create(&self, user_id: UserId, task: NewTask) -> Result; /// Updates an existing task. async fn update(&self, id: TaskId, user_id: UserId, task: UpdateTask) -> Result>; /// Soft-deletes a task. async fn delete(&self, id: TaskId, user_id: UserId) -> Result; /// Marks a task as started. async fn start(&self, id: TaskId, user_id: UserId) -> Result; /// Marks a task as completed and returns it. /// The caller is responsible for handling recurrence and milestone auto-completion. async fn complete(&self, id: TaskId, user_id: UserId) -> Result>; /// Atomically completes a task and creates the next recurring instance. /// Returns (completed_task, next_task). If the task has no recurrence, /// next_task is None. The entire operation is wrapped in a transaction /// so a crash cannot break the recurrence chain. async fn complete_recurring(&self, id: TaskId, user_id: UserId, next: Option) -> Result<(Option, Option)>; /// Counts non-deleted, non-completed tasks in a milestone. async fn count_incomplete_by_milestone(&self, milestone_id: MilestoneId, user_id: UserId) -> Result; /// Gets all annotations for a task. async fn get_annotations_for_task(&self, task_id: TaskId) -> Result>; /// Adds an annotation (note) to a task. async fn add_annotation(&self, task_id: TaskId, user_id: UserId, note: &str) -> Result>; /// Deletes an annotation. async fn delete_annotation(&self, annotation_id: AnnotationId, user_id: UserId) -> Result; /// Gets all subtasks for a task. async fn get_subtasks_for_task(&self, task_id: TaskId) -> Result>; /// Adds a subtask to a task. async fn add_subtask(&self, task_id: TaskId, user_id: UserId, text: &str) -> Result>; /// Toggles a subtask's completion status. async fn toggle_subtask(&self, subtask_id: SubtaskId, user_id: UserId) -> Result>; /// Updates subtask text. async fn update_subtask(&self, subtask_id: SubtaskId, user_id: UserId, text: &str) -> Result>; /// Deletes a subtask. async fn delete_subtask(&self, subtask_id: SubtaskId, user_id: UserId) -> Result; /// Adds a linked task as a subtask. /// /// This creates a subtask that links to another task, enabling multi-phase /// features where Phase 2 is a full task linked as a subtask of Phase 1. /// The linked subtask's completion status syncs with the linked task's status. async fn add_subtask_link(&self, task_id: TaskId, user_id: UserId, linked_task_id: TaskId) -> Result>; /// Snoozes a task until the specified time. async fn snooze(&self, id: TaskId, user_id: UserId, until: DateTime) -> Result>; /// Removes snooze from a task. async fn unsnooze(&self, id: TaskId, user_id: UserId) -> Result>; /// Lists all currently snoozed tasks. async fn list_snoozed(&self, user_id: UserId) -> Result>; /// Marks a task as waiting for external response. async fn mark_waiting(&self, id: TaskId, user_id: UserId, expected_response: Option>) -> Result>; /// Clears the waiting status from a task. async fn clear_waiting(&self, id: TaskId, user_id: UserId) -> Result>; /// Lists all tasks marked as waiting. async fn list_waiting(&self, user_id: UserId) -> Result>; /// Lists tasks scheduled for a specific date. async fn list_scheduled_for_date(&self, user_id: UserId, date: NaiveDate) -> Result>; /// Lists unscheduled tasks due on a specific date. async fn list_unscheduled_due_on_date(&self, user_id: UserId, date: NaiveDate) -> Result>; /// Updates a task's time-block schedule. async fn update_schedule(&self, id: TaskId, user_id: UserId, start: Option>, duration: Option) -> Result>; /// Sets or clears the focus status on a task. async fn set_focus(&self, id: TaskId, user_id: UserId, is_focus: bool) -> Result>; /// Lists all tasks marked as focus. async fn list_focused(&self, user_id: UserId) -> Result>; /// Clears focus from all tasks. async fn clear_all_focus(&self, user_id: UserId) -> Result; /// Lists tasks completed within a date range (for weekly review). async fn list_completed_between(&self, user_id: UserId, start: DateTime, end: DateTime) -> Result>; /// Lists tasks that became overdue within a date range (for weekly review). async fn list_became_overdue_between(&self, user_id: UserId, start: DateTime, end: DateTime) -> Result>; /// Lists tasks due within a date range (for weekly review). async fn list_due_between(&self, user_id: UserId, start: DateTime, end: DateTime) -> Result>; /// Lists tasks created within a date range (for monthly review). async fn list_created_between(&self, user_id: UserId, start: DateTime, end: DateTime) -> Result>; /// Lists high-priority pending tasks for focus selection. async fn list_available_for_focus(&self, user_id: UserId, limit: i64) -> Result>; // ---- Time Tracking ---- /// Starts a timer on a task. Fails if any session is already active for the user. async fn start_timer(&self, task_id: TaskId, user_id: UserId) -> Result; /// Stops the active timer on a task, updating duration and actual_minutes cache. async fn stop_timer(&self, task_id: TaskId, user_id: UserId) -> Result>; /// Discards the active timer without updating actual_minutes. async fn discard_timer(&self, task_id: TaskId, user_id: UserId) -> Result; /// Gets the currently active timer for a user (at most one). async fn get_active_timer(&self, user_id: UserId) -> Result>; /// Lists all time sessions for a task. async fn list_time_sessions(&self, task_id: TaskId, user_id: UserId) -> Result>; /// Logs a manual time entry (retroactive, no live timer). async fn log_manual_time(&self, task_id: TaskId, user_id: UserId, minutes: i32, date: DateTime) -> Result; /// Gets aggregated time tracking summary grouped by project and date. async fn get_time_summary(&self, user_id: UserId, start: DateTime, end: DateTime) -> Result>; /// Lists all tasks in a recurrence chain (root + all descendants). async fn list_recurrence_chain(&self, root_id: TaskId, user_id: UserId) -> Result>; } /// Repository for calendar event operations. /// /// Events can be standalone or linked to tasks (for time-blocking). /// /// # Ordering Contract /// /// All methods returning `Vec` **MUST** return results sorted by /// `start_time ASC`. This is enforced at the SQL level (`ORDER BY e.start_time ASC`) /// and callers rely on this guarantee — no post-fetch sorting is needed. #[async_trait] pub trait EventRepository: Send + Sync { /// Lists all events for a user, ordered by `start_time ASC`. async fn list_all(&self, user_id: UserId) -> Result>; /// Lists events belonging to a specific project, ordered by `start_time ASC`. async fn list_by_project(&self, user_id: UserId, project_id: ProjectId) -> Result>; /// Lists events linked to a specific contact, ordered by `start_time DESC`. async fn list_by_contact(&self, user_id: UserId, contact_id: ContactId) -> Result>; /// Retrieves an event by ID. async fn get_by_id(&self, id: EventId, user_id: UserId) -> Result>; /// Creates a new event. async fn create(&self, user_id: UserId, event: NewEvent) -> Result; /// Updates an existing event. async fn update(&self, id: EventId, user_id: UserId, event: crate::models::UpdateEvent) -> Result>; /// Deletes an event. async fn delete(&self, id: EventId, user_id: UserId) -> Result; /// Deletes multiple events by ID, returning the number deleted. async fn delete_many(&self, ids: &[EventId], user_id: UserId) -> Result; /// Gets events starting within the next N days, ordered by `start_time ASC`. async fn get_upcoming(&self, user_id: UserId, days: i64) -> Result>; /// Finds the event linked to a specific task (for time-blocking). async fn get_by_linked_task(&self, user_id: UserId, task_id: TaskId) -> Result>; /// Deletes the event linked to a task. async fn delete_by_linked_task(&self, user_id: UserId, task_id: TaskId) -> Result; /// Lists events occurring on a specific date, ordered by `start_time ASC`. async fn list_for_date(&self, user_id: UserId, date: NaiveDate) -> Result>; /// Lists events within a date range (for weekly review), ordered by `start_time ASC`. async fn list_between(&self, user_id: UserId, start: DateTime, end: DateTime) -> Result>; /// Lists all recurring events (recurrence != 'None' or recurrence_rule is set). async fn list_recurring(&self, user_id: UserId) -> Result>; /// Finds an event by external source and ID (for dedup during import). async fn find_by_external_id(&self, source: &str, ext_id: &str, user_id: UserId) -> Result>; /// Snoozes an event until `until`. Returns the updated event, or `None` if not found. async fn snooze(&self, id: EventId, user_id: UserId, until: DateTime) -> Result>; /// Clears any snooze on an event. Returns the updated event, or `None` if not found. async fn unsnooze(&self, id: EventId, user_id: UserId) -> Result>; /// Lists currently snoozed events (snoozed_until is in the future). async fn list_snoozed(&self, user_id: UserId) -> Result>; } /// Repository for email message operations. /// /// Supports IMAP-synced emails with read/archive status, project linking, /// snoozing, and follow-up tracking. #[async_trait] pub trait EmailRepository: Send + Sync { /// Lists all emails, optionally including archived. async fn list_all(&self, user_id: UserId, include_archived: bool) -> Result>; /// Lists emails grouped by thread, with metadata pre-computed and pagination. /// Returns (threads, total_count) sorted by most recent email (newest first). /// Optional `folder` filter restricts to emails from a specific source_folder. /// Optional `label` filter restricts to emails with a specific label. async fn list_threaded(&self, user_id: UserId, include_archived: bool, offset: Option, limit: Option, folder: Option<&str>, label: Option<&str>) -> Result<(Vec, i64)>; /// Lists emails linked to a specific project. async fn list_by_project(&self, user_id: UserId, project_id: ProjectId) -> Result>; /// Lists emails sent from or to any of the given addresses. async fn list_by_addresses(&self, user_id: UserId, addresses: &[&str]) -> Result>; /// Lists emails not linked to any project. async fn list_unlinked(&self, user_id: UserId) -> Result>; /// Retrieves an email by ID. async fn get_by_id(&self, id: EmailId, user_id: UserId) -> Result>; /// Creates a new email record. async fn create(&self, user_id: UserId, email: NewEmail) -> Result; /// Creates an email with follow-up tracking fields. async fn create_with_tracking(&self, user_id: UserId, email: NewEmailWithTracking) -> Result; /// Batch-inserts emails with tracking in a single transaction, skipping post-insert SELECTs. /// Returns the count of successfully inserted emails. async fn create_with_tracking_batch(&self, user_id: UserId, emails: Vec) -> Result; /// Deletes an email. async fn delete(&self, id: EmailId, user_id: UserId) -> Result; /// Marks an email as read. async fn mark_read(&self, id: EmailId, user_id: UserId) -> Result; /// Marks an email as unread. async fn mark_unread(&self, id: EmailId, user_id: UserId) -> Result; /// Archives an email. async fn archive(&self, id: EmailId, user_id: UserId) -> Result; /// Unarchives an email. async fn unarchive(&self, id: EmailId, user_id: UserId) -> Result; /// Updates the IMAP source folder for an email. async fn update_source_folder(&self, id: EmailId, user_id: UserId, new_folder: &str) -> Result; /// Marks all emails as read, returning the count updated. async fn mark_all_read(&self, user_id: UserId) -> Result; /// Links or unlinks an email to a project. async fn link_to_project(&self, id: EmailId, user_id: UserId, project_id: Option) -> Result; /// Counts unread emails. async fn count_unread(&self, user_id: UserId) -> Result; /// Checks if an email with the given Message-ID header exists. async fn exists_by_message_id(&self, user_id: UserId, message_id: &str) -> Result; /// Batch check for existing Message-IDs, returns the set that exist. async fn exists_by_message_ids(&self, user_id: UserId, message_ids: &[&str]) -> Result>; /// Batch check which email addresses have appeared as senders. /// Returns the set of addresses (lowercased) that have sent at least one email. async fn exists_as_senders(&self, user_id: UserId, addresses: &[&str]) -> Result>; /// Snoozes an email until the specified time. async fn snooze(&self, id: EmailId, user_id: UserId, until: DateTime) -> Result>; /// Removes snooze from an email. async fn unsnooze(&self, id: EmailId, user_id: UserId) -> Result>; /// Lists all currently snoozed emails. async fn list_snoozed(&self, user_id: UserId) -> Result>; /// Marks an email as waiting for response. async fn mark_waiting(&self, id: EmailId, user_id: UserId, expected_response: Option>) -> Result>; /// Clears the waiting status from an email. async fn clear_waiting(&self, id: EmailId, user_id: UserId) -> Result>; /// Lists all emails marked as waiting. async fn list_waiting(&self, user_id: UserId) -> Result>; /// Lists all emails in a thread, ordered by date ascending. async fn list_by_thread(&self, user_id: UserId, thread_id: &str) -> Result>; /// Gets an email by its Message-ID header. async fn get_by_message_id(&self, user_id: UserId, message_id: &str) -> Result>; /// Updates labels/tags on an email. async fn update_labels(&self, id: EmailId, user_id: UserId, labels: &[String]) -> Result>; /// Lists distinct source_folder values across all non-draft emails. async fn list_folders(&self, user_id: UserId) -> Result>; /// Lists all distinct labels used across all emails. async fn list_labels(&self, user_id: UserId) -> Result>; /// Lists all draft emails. async fn list_drafts(&self, user_id: UserId) -> Result>; /// Creates or updates a draft email. #[allow(clippy::too_many_arguments)] 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, in_reply_to: Option<&str>, references: Option<&str>, thread_id: Option<&str>) -> Result; } /// Repository for user account operations. #[async_trait] pub trait UserRepository: Send + Sync { /// Creates a new user account with hashed password. async fn create(&self, email: &str, password: &str, display_name: &str) -> Result; /// Finds a user by email address. async fn find_by_email(&self, email: &str) -> Result>; /// Authenticates a user, returning the user if credentials are valid. async fn authenticate(&self, email: &str, password: &str) -> Result>; /// Updates the user's last login timestamp. async fn update_last_login(&self, user_id: UserId) -> Result<()>; } /// Repository for email account (IMAP/SMTP/OAuth2) configuration. #[allow(clippy::too_many_arguments)] #[async_trait] pub trait EmailAccountRepository: Send + Sync { /// Lists all email accounts for a user. async fn list_by_user(&self, user_id: UserId) -> Result>; /// Retrieves an email account by ID. async fn get_by_id(&self, id: EmailAccountId, user_id: UserId) -> Result>; /// Creates a new email account configuration (password-based IMAP/SMTP). async fn create( &self, user_id: UserId, account_name: &str, email_address: &str, imap_server: &str, imap_port: i32, smtp_server: &str, smtp_port: i32, username: &str, password: &str, use_tls: bool, archive_folder_name: Option<&str>, ) -> Result; /// Creates a new OAuth2 email account (Fastmail JMAP). async fn create_oauth( &self, user_id: UserId, account_name: &str, email_address: &str, access_token: &str, refresh_token: &str, expires_at: DateTime, jmap_session_url: &str, jmap_account_id: &str, ) -> Result; /// Creates a new OAuth2 email account with IMAP/SMTP (Google, Microsoft, Yahoo). async fn create_oauth_imap( &self, user_id: UserId, account_name: &str, email_address: &str, auth_type: EmailAuthType, access_token: &str, refresh_token: &str, expires_at: DateTime, imap_server: &str, imap_port: i32, smtp_server: &str, smtp_port: i32, ) -> Result; /// Updates an email account. Password is only updated if provided. async fn update( &self, id: EmailAccountId, user_id: UserId, account_name: &str, email_address: &str, imap_server: &str, imap_port: i32, smtp_server: &str, smtp_port: i32, username: &str, password: Option<&str>, use_tls: bool, archive_folder_name: Option<&str>, ) -> Result>; /// Updates OAuth2 tokens for an account. async fn update_oauth_tokens( &self, id: EmailAccountId, user_id: UserId, access_token: &str, refresh_token: Option<&str>, expires_at: DateTime, ) -> Result>; /// Updates JMAP session info for an account. async fn update_jmap_session( &self, id: EmailAccountId, user_id: UserId, session_url: &str, account_id: &str, ) -> Result>; /// Deletes an email account. async fn delete(&self, id: EmailAccountId, user_id: UserId) -> Result; /// Updates the last sync timestamp. async fn update_last_sync(&self, id: EmailAccountId, user_id: UserId) -> Result; /// Updates the sync interval setting for an account. async fn update_sync_interval(&self, id: EmailAccountId, user_id: UserId, interval_minutes: Option) -> Result>; /// Updates the email signature for an account. async fn update_signature(&self, id: EmailAccountId, user_id: UserId, signature: Option<&str>) -> Result>; /// Updates the notification preference for an account. async fn update_notify_new_emails(&self, id: EmailAccountId, user_id: UserId, enabled: bool) -> Result>; /// Lists accounts that need automatic sync based on their sync_interval_minutes. /// Returns accounts where sync is enabled and last_sync_at + interval < now. async fn list_accounts_needing_sync(&self, user_id: UserId) -> Result>; /// Gets the IMAP folder sync state for incremental UID-based fetching. async fn get_folder_sync_state(&self, account_id: EmailAccountId, folder: &str) -> Result>; /// Upserts the IMAP folder sync state after a successful sync. async fn upsert_folder_sync_state(&self, account_id: EmailAccountId, folder: &str, uid_validity: u32, last_seen_uid: u32) -> Result<()>; /// Deletes stale folder sync state (e.g. on UIDVALIDITY change). async fn delete_folder_sync_state(&self, account_id: EmailAccountId, folder: &str) -> Result<()>; } /// Aggregated statistics for the dashboard. #[derive(Debug, Clone, serde::Serialize)] pub struct DashboardStats { /// Number of tasks due today. pub tasks_due_today: i64, /// Number of tasks due within the next 7 days. pub tasks_due_this_week: i64, /// Number of overdue tasks. pub overdue_count: i64, /// Number of unread emails. pub unread_emails: i64, /// Number of events in the next 7 days. pub upcoming_events: i64, /// Number of active projects. pub active_projects: i64, /// Top tasks by urgency score. pub high_urgency_tasks: Vec, } /// A task with high urgency for dashboard display. #[derive(Debug, Clone, serde::Serialize)] pub struct HighUrgencyTask { /// Task ID as string. pub id: String, /// Task description. pub description: String, /// Calculated urgency score. pub urgency: f64, /// Task status. pub status: String, /// Due date if set, formatted as ISO string. pub due: Option, } /// Repository for dashboard statistics. #[async_trait] pub trait StatsRepository: Send + Sync { /// Computes aggregated dashboard statistics for a user. async fn get_dashboard_stats(&self, user_id: UserId) -> Result; } /// Type of item in search results. #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "lowercase")] pub enum SearchResultType { /// A task item. Task, /// An email item. Email, /// A project item. Project, /// A calendar event. Event, /// A contact. Contact, } /// A single search result item with ranking information. #[derive(Debug, Clone, serde::Serialize)] pub struct SearchResultItem { /// Item ID. pub id: Uuid, /// Type of the result. pub result_type: SearchResultType, /// Display title. pub title: String, /// Text snippet showing match context. pub snippet: Option, /// Associated project ID if any. pub project_id: Option, /// Associated project name if any. pub project_name: Option, /// Relevance rank (higher is better). pub rank: f64, } /// Search query with filtering and pagination options. #[derive(Debug, Clone, Default)] pub struct SearchQuery { /// The search terms (FTS text after filters extracted). pub query: String, /// Filter to specific result types. pub types: Option>, /// Filter to a specific project by ID. pub project_id: Option, /// Filter to a specific project by name (partial match). pub project_name: Option, /// Filter to items on or after this date. pub date_from: Option>, /// Filter to items on or before this date. pub date_to: Option>, /// Maximum results to return. pub limit: Option, /// Offset for pagination. pub offset: Option, /// `is:` filters for time/state conditions. pub is_filters: Vec, /// Priority filter (`priority:high` etc). pub priority: Option, /// Tags to include (`tag:name`). pub tags_include: Vec, /// Tags to exclude (`-tag:name`). pub tags_exclude: Vec, } impl SearchQuery { /// Creates a new search query with default options. pub fn new(query: impl Into) -> Self { Self { query: query.into(), types: None, project_id: None, project_name: None, date_from: None, date_to: None, limit: Some(50), offset: None, is_filters: Vec::new(), priority: None, tags_include: Vec::new(), tags_exclude: Vec::new(), } } /// Filters results to specific types. pub fn with_types(mut self, types: Vec) -> Self { self.types = Some(types); self } /// Filters results to a specific project by ID. pub fn with_project(mut self, project_id: ProjectId) -> Self { self.project_id = Some(project_id); self } /// Filters results to a specific project by name. pub fn with_project_name(mut self, name: impl Into) -> Self { self.project_name = Some(name.into()); self } /// Sets the maximum number of results. pub fn with_limit(mut self, limit: i64) -> Self { self.limit = Some(limit); self } /// Sets the pagination offset. pub fn with_offset(mut self, offset: i64) -> Self { self.offset = Some(offset); self } /// Filters results to items on or after this date. pub fn with_date_from(mut self, date: DateTime) -> Self { self.date_from = Some(date); self } /// Filters results to items on or before this date. pub fn with_date_to(mut self, date: DateTime) -> Self { self.date_to = Some(date); self } /// Adds `is:` filters. pub fn with_is_filters(mut self, filters: Vec) -> Self { self.is_filters = filters; self } /// Sets priority filter. pub fn with_priority(mut self, priority: crate::models::Priority) -> Self { self.priority = Some(priority); self } /// Sets tags to include. pub fn with_tags_include(mut self, tags: Vec) -> Self { self.tags_include = tags; self } /// Sets tags to exclude. pub fn with_tags_exclude(mut self, tags: Vec) -> Self { self.tags_exclude = tags; self } } /// Repository for full-text search across all content types. #[async_trait] pub trait SearchRepository: Send + Sync { /// Searches across all indexed content using FTS. /// Returns (results, total_count) where total_count is the pre-pagination count. async fn search(&self, user_id: UserId, query: SearchQuery) -> Result<(Vec, usize)>; } /// Repository for saved view / filter configurations. #[async_trait] pub trait SavedViewRepository: Send + Sync { /// Lists all saved views for a user. async fn list_all(&self, user_id: UserId) -> Result>; /// Lists pinned views for sidebar display. async fn list_pinned(&self, user_id: UserId) -> Result>; /// Gets a saved view by ID. async fn get_by_id(&self, id: SavedViewId, user_id: UserId) -> Result>; /// Creates a new saved view. async fn create(&self, user_id: UserId, view: NewSavedView) -> Result; /// Updates an existing saved view. async fn update(&self, id: SavedViewId, user_id: UserId, view: NewSavedView) -> Result>; /// Deletes a saved view. async fn delete(&self, id: SavedViewId, user_id: UserId) -> Result; /// Toggles the pinned status of a view. async fn toggle_pinned(&self, id: SavedViewId, user_id: UserId) -> Result>; /// Updates the position of a view in the sidebar. async fn update_position(&self, id: SavedViewId, user_id: UserId, position: i32) -> Result>; } /// Repository for weekly review tracking. #[async_trait] pub trait WeeklyReviewRepository: Send + Sync { /// Gets the weekly review for a specific week. async fn get_for_week(&self, user_id: UserId, week_start: NaiveDate) -> Result>; /// Creates or updates a weekly review. async fn upsert(&self, user_id: UserId, week_start: NaiveDate, notes: &str) -> Result; /// Checks if the current week's review is completed. async fn is_current_week_completed(&self, user_id: UserId) -> Result; /// Sets vacation days for a specific week. async fn set_vacation_days(&self, user_id: UserId, week_start: NaiveDate, days: &[u8]) -> Result<()>; } /// Repository for daily review notes. #[async_trait] pub trait DailyNoteRepository: Send + Sync { /// Gets the daily note for a specific date. async fn get_by_date(&self, user_id: UserId, date: NaiveDate) -> Result>; /// Creates or updates a daily note (upsert by user_id + date). async fn upsert(&self, user_id: UserId, date: NaiveDate, went_well: &str, could_improve: &str, is_reviewed: bool) -> Result; } /// Repository for monthly review goals and reflections. #[async_trait] pub trait MonthlyReviewRepository: Send + Sync { /// Gets all goals for a specific month. async fn list_goals(&self, user_id: UserId, month: &str) -> Result>; /// Creates or updates a goal for a month at a given position (1-3). async fn upsert_goal(&self, user_id: UserId, month: &str, text: &str, position: i32) -> Result; /// Updates the status of a goal. async fn update_goal_status(&self, id: crate::MonthlyGoalId, user_id: UserId, status: &crate::models::MonthlyGoalStatus) -> Result>; /// Deletes a goal. async fn delete_goal(&self, id: crate::MonthlyGoalId, user_id: UserId) -> Result; /// Gets the reflection for a specific month. async fn get_reflection(&self, user_id: UserId, month: &str) -> Result>; /// Creates or updates the reflection for a month. async fn upsert_reflection(&self, user_id: UserId, month: &str, highlight: &str, change: &str) -> Result; } /// Repository for milestone management within projects. #[async_trait] pub trait MilestoneRepository: Send + Sync { /// Lists all milestones for a project, ordered by position. async fn list_by_project(&self, project_id: ProjectId, user_id: UserId) -> Result>; /// Gets a milestone by ID. async fn get_by_id(&self, id: MilestoneId, user_id: UserId) -> Result>; /// Creates a new milestone. async fn create(&self, user_id: UserId, milestone: crate::models::NewMilestone) -> Result; /// Updates an existing milestone. async fn update( &self, id: MilestoneId, user_id: UserId, name: &str, description: &str, target_date: Option, status: &crate::models::MilestoneStatus, ) -> Result>; /// Deletes a milestone. async fn delete(&self, id: MilestoneId, user_id: UserId) -> Result; /// Reorders milestones within a project. async fn reorder(&self, project_id: ProjectId, user_id: UserId, milestone_ids: &[MilestoneId]) -> Result<()>; } /// Repository for backup settings management. #[async_trait] pub trait BackupSettingsRepository: Send + Sync { /// Gets the backup settings for a user. async fn get(&self, user_id: UserId) -> Result>; /// Creates or updates backup settings. async fn upsert( &self, user_id: UserId, settings: crate::models::NewBackupSettings, ) -> Result; /// Updates the last backup timestamp. async fn update_last_backup_at(&self, user_id: UserId, timestamp: DateTime) -> Result<()>; } /// Repository for contact CRUD operations and sub-collection management. /// /// Contacts have sub-collections (emails, phones, social handles) stored in /// separate tables to enable querying by email address for future integrations. #[async_trait] pub trait ContactRepository: Send + Sync { /// Lists all contacts for a user. async fn list_all(&self, user_id: UserId) -> Result>; /// Retrieves a contact by ID, returning `None` if not found. async fn get_by_id(&self, id: ContactId, user_id: UserId) -> Result>; /// Creates a new contact. async fn create(&self, user_id: UserId, contact: NewContact) -> Result; /// Updates an existing contact, returning `None` if not found. async fn update(&self, id: ContactId, user_id: UserId, contact: UpdateContact) -> Result>; /// Deletes a contact (CASCADE removes sub-entities), returning `true` if deleted. async fn delete(&self, id: ContactId, user_id: UserId) -> Result; /// Deletes multiple contacts by ID, returning the number deleted. async fn delete_many(&self, ids: &[ContactId], user_id: UserId) -> Result; /// Adds a tag to multiple contacts (skips contacts that already have the tag). async fn tag_many(&self, ids: &[ContactId], user_id: UserId, tag: &str) -> Result; /// Lists contacts matching a tag. async fn list_by_tag(&self, user_id: UserId, tag: &str) -> Result>; /// Lists contacts matching a search query and/or tag filter. /// Searches across display_name, nickname, company, title, notes, and email addresses. async fn list_filtered(&self, user_id: UserId, search: Option<&str>, tag: Option<&str>, include_implicit: bool) -> Result>; /// Finds a contact by email address. async fn find_by_email(&self, user_id: UserId, email: &str) -> Result>; /// Batch check which email addresses belong to known contacts. /// Returns the set of addresses (lowercased) that match at least one contact. async fn find_emails_in_contacts(&self, user_id: UserId, addresses: &[&str]) -> Result>; /// Promotes an implicit contact to explicit by setting is_implicit = 0. async fn promote_contact(&self, id: ContactId, user_id: UserId) -> Result>; /// Finds a contact by external source and ID (for dedup during import). async fn find_by_external_id(&self, source: &str, ext_id: &str, user_id: UserId) -> Result>; /// Adds an email address to a contact. async fn add_email(&self, contact_id: ContactId, user_id: UserId, email: NewContactEmail) -> Result; /// Removes an email address from a contact. async fn remove_email(&self, email_id: ContactEmailId, user_id: UserId) -> Result; /// Adds a phone number to a contact. async fn add_phone(&self, contact_id: ContactId, user_id: UserId, phone: NewContactPhone) -> Result; /// Removes a phone number from a contact. async fn remove_phone(&self, phone_id: ContactPhoneId, user_id: UserId) -> Result; /// Adds a social handle to a contact. async fn add_social_handle(&self, contact_id: ContactId, user_id: UserId, handle: NewSocialHandle) -> Result; /// Removes a social handle from a contact. async fn remove_social_handle(&self, handle_id: SocialHandleId, user_id: UserId) -> Result; /// Adds a custom field to a contact. async fn add_custom_field(&self, contact_id: ContactId, user_id: UserId, field: NewContactCustomField) -> Result; /// Removes a custom field from a contact. async fn remove_custom_field(&self, field_id: CustomFieldId, user_id: UserId) -> Result; /// Updates a contact email row (address/label/is_primary). Returns the updated row, or `None` if not found. async fn update_email(&self, email_id: ContactEmailId, user_id: UserId, email: NewContactEmail) -> Result>; /// Updates a contact phone row (number/label/is_primary). Returns the updated row, or `None` if not found. async fn update_phone(&self, phone_id: ContactPhoneId, user_id: UserId, phone: NewContactPhone) -> Result>; /// Updates a social handle row (platform/handle/url). Returns the updated row, or `None` if not found. async fn update_social_handle(&self, handle_id: SocialHandleId, user_id: UserId, handle: NewSocialHandle) -> Result>; /// Updates a custom field row (label/value/url). Returns the updated row, or `None` if not found. async fn update_custom_field(&self, field_id: CustomFieldId, user_id: UserId, field: NewContactCustomField) -> Result>; } /// Repository for file attachment operations. #[async_trait] pub trait AttachmentRepository: Send + Sync { /// Creates a new attachment record. async fn create(&self, user_id: UserId, attachment: NewAttachment) -> Result; /// Lists attachments for a task. async fn list_for_task(&self, task_id: TaskId, user_id: UserId) -> Result>; /// Lists attachments for a project. async fn list_for_project(&self, project_id: ProjectId, user_id: UserId) -> Result>; /// Retrieves an attachment by ID. async fn get_by_id(&self, id: AttachmentId, user_id: UserId) -> Result>; /// Deletes an attachment record, returning `true` if deleted. async fn delete(&self, id: AttachmentId, user_id: UserId) -> Result; /// Lists all attachments sharing a blob hash (for dedup checks). async fn list_by_blob_hash(&self, blob_hash: &str, user_id: UserId) -> Result>; /// Lists all distinct blob hashes for a user (for blob sync). async fn list_all_blob_hashes(&self, user_id: UserId) -> Result>; } /// Repository for sync account CRUD operations. #[async_trait] pub trait SyncAccountRepository: Send + Sync { /// Lists all sync accounts for a user. async fn list_all(&self, user_id: UserId) -> Result>; /// Retrieves a sync account by ID. async fn get_by_id(&self, id: SyncAccountId, user_id: UserId) -> Result>; /// Creates a new sync account. async fn create(&self, user_id: UserId, provider: &str, account_name: &str, email: Option<&str>) -> Result; /// Updates a sync account. async fn update(&self, id: SyncAccountId, user_id: UserId, account_name: &str, sync_calendars: bool, sync_contacts: bool, enabled: bool) -> Result>; /// Deletes a sync account. async fn delete(&self, id: SyncAccountId, user_id: UserId) -> Result; }