use super::{ Attachment, AttachmentId, DateTime, Email, EmailAccount, EmailAccountId, EmailAuthType, EmailId, EmailThread, FolderSyncState, HashSet, NewAttachment, NewEmail, NewEmailWithTracking, ProjectId, Result, TaskId, UserId, Utc, async_trait, }; /// 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. Selects full bodies and is /// unbounded, intended for backup export, NOT for list views (use /// [`list_metadata`](Self::list_metadata) there to avoid materializing every /// body at once). async fn list_all(&self, user_id: UserId, include_archived: bool) -> Result>; /// Lists emails for a flat list view: body/html_body are omitted (empty) and the /// result is capped, so a large mailbox doesn't load every body into memory /// (ultra-fuzz Run #27 Perf S3). Open an email via `get_by_id` for its body. async fn list_metadata(&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>; /// Replaces an email's body with a full (re-fetched) version and clears the /// `body_truncated` flag. Used to lazily load a body truncated at sync. async fn set_full_body(&self, id: EmailId, user_id: UserId, body: &str) -> Result<()>; /// Creates a new email record. async fn create(&self, user_id: UserId, email: NewEmail) -> Result; /// Restores an email from a backup, preserving its original ID and /// `message_id` so re-restore dedupes. Round-trips the durable fields /// (addresses, subject, body, html_body, read/archived/outgoing flags, /// received_at, threading, labels); transient IMAP-sync and draft-compose /// state is not restored (emails re-sync from the server). Idempotent /// (`INSERT OR IGNORE`). async fn restore(&self, user_id: UserId, email: &Email) -> 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 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<()>; } /// 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>; /// Lists every attachment record for a user (for full backup export). async fn list_all(&self, user_id: UserId) -> Result>; }