Skip to main content

max / goingson

12.4 KB · 285 lines History Blame Raw
1 use super::*;
2
3 /// Repository for email message operations.
4 ///
5 /// Supports IMAP-synced emails with read/archive status, project linking,
6 /// snoozing, and follow-up tracking.
7 #[async_trait]
8 pub trait EmailRepository: Send + Sync {
9 /// Lists all emails, optionally including archived. Selects full bodies and is
10 /// unbounded -- intended for backup export, NOT for list views (use
11 /// [`list_metadata`](Self::list_metadata) there to avoid materializing every
12 /// body at once).
13 async fn list_all(&self, user_id: UserId, include_archived: bool) -> Result<Vec<Email>>;
14
15 /// Lists emails for a flat list view: body/html_body are omitted (empty) and the
16 /// result is capped, so a large mailbox doesn't load every body into memory
17 /// (ultra-fuzz Run #27 Perf S3). Open an email via `get_by_id` for its body.
18 async fn list_metadata(&self, user_id: UserId, include_archived: bool) -> Result<Vec<Email>>;
19
20 /// Lists emails grouped by thread, with metadata pre-computed and pagination.
21 /// Returns (threads, total_count) sorted by most recent email (newest first).
22 /// Optional `folder` filter restricts to emails from a specific source_folder.
23 /// Optional `label` filter restricts to emails with a specific label.
24 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)>;
25
26 /// Lists emails linked to a specific project.
27 async fn list_by_project(&self, user_id: UserId, project_id: ProjectId) -> Result<Vec<Email>>;
28
29 /// Lists emails sent from or to any of the given addresses.
30 async fn list_by_addresses(&self, user_id: UserId, addresses: &[&str]) -> Result<Vec<Email>>;
31
32 /// Lists emails not linked to any project.
33 async fn list_unlinked(&self, user_id: UserId) -> Result<Vec<Email>>;
34
35 /// Retrieves an email by ID.
36 async fn get_by_id(&self, id: EmailId, user_id: UserId) -> Result<Option<Email>>;
37
38 /// Replaces an email's body with a full (re-fetched) version and clears the
39 /// `body_truncated` flag. Used to lazily load a body truncated at sync.
40 async fn set_full_body(&self, id: EmailId, user_id: UserId, body: &str) -> Result<()>;
41
42 /// Creates a new email record.
43 async fn create(&self, user_id: UserId, email: NewEmail) -> Result<Email>;
44
45 /// Restores an email from a backup, preserving its original ID and
46 /// `message_id` so re-restore dedupes. Round-trips the durable fields
47 /// (addresses, subject, body, html_body, read/archived/outgoing flags,
48 /// received_at, threading, labels); transient IMAP-sync and draft-compose
49 /// state is not restored (emails re-sync from the server). Idempotent
50 /// (`INSERT OR IGNORE`).
51 async fn restore(&self, user_id: UserId, email: &Email) -> Result<()>;
52
53 /// Creates an email with follow-up tracking fields.
54 async fn create_with_tracking(&self, user_id: UserId, email: NewEmailWithTracking) -> Result<Email>;
55
56 /// Batch-inserts emails with tracking in a single transaction, skipping post-insert SELECTs.
57 /// Returns the count of successfully inserted emails.
58 async fn create_with_tracking_batch(&self, user_id: UserId, emails: Vec<NewEmailWithTracking>) -> Result<usize>;
59
60 /// Deletes an email.
61 async fn delete(&self, id: EmailId, user_id: UserId) -> Result<bool>;
62
63 /// Marks an email as read.
64 async fn mark_read(&self, id: EmailId, user_id: UserId) -> Result<bool>;
65
66 /// Marks an email as unread.
67 async fn mark_unread(&self, id: EmailId, user_id: UserId) -> Result<bool>;
68
69 /// Archives an email.
70 async fn archive(&self, id: EmailId, user_id: UserId) -> Result<bool>;
71
72 /// Unarchives an email.
73 async fn unarchive(&self, id: EmailId, user_id: UserId) -> Result<bool>;
74
75 /// Updates the IMAP source folder for an email.
76 async fn update_source_folder(&self, id: EmailId, user_id: UserId, new_folder: &str) -> Result<bool>;
77
78 /// Marks all emails as read, returning the count updated.
79 async fn mark_all_read(&self, user_id: UserId) -> Result<u64>;
80
81 /// Links or unlinks an email to a project.
82 async fn link_to_project(&self, id: EmailId, user_id: UserId, project_id: Option<ProjectId>) -> Result<bool>;
83
84 /// Counts unread emails.
85 async fn count_unread(&self, user_id: UserId) -> Result<i64>;
86
87 /// Checks if an email with the given Message-ID header exists.
88 async fn exists_by_message_id(&self, user_id: UserId, message_id: &str) -> Result<bool>;
89
90 /// Batch check for existing Message-IDs, returns the set that exist.
91 async fn exists_by_message_ids(&self, user_id: UserId, message_ids: &[&str]) -> Result<HashSet<String>>;
92
93 /// Batch check which email addresses have appeared as senders.
94 /// Returns the set of addresses (lowercased) that have sent at least one email.
95 async fn exists_as_senders(&self, user_id: UserId, addresses: &[&str]) -> Result<HashSet<String>>;
96
97 /// Snoozes an email until the specified time.
98 async fn snooze(&self, id: EmailId, user_id: UserId, until: DateTime<Utc>) -> Result<Option<Email>>;
99
100 /// Removes snooze from an email.
101 async fn unsnooze(&self, id: EmailId, user_id: UserId) -> Result<Option<Email>>;
102
103 /// Lists all currently snoozed emails.
104 async fn list_snoozed(&self, user_id: UserId) -> Result<Vec<Email>>;
105
106 /// Marks an email as waiting for response.
107 async fn mark_waiting(&self, id: EmailId, user_id: UserId, expected_response: Option<DateTime<Utc>>) -> Result<Option<Email>>;
108
109 /// Clears the waiting status from an email.
110 async fn clear_waiting(&self, id: EmailId, user_id: UserId) -> Result<Option<Email>>;
111
112 /// Lists all emails marked as waiting.
113 async fn list_waiting(&self, user_id: UserId) -> Result<Vec<Email>>;
114
115 /// Lists all emails in a thread, ordered by date ascending.
116 async fn list_by_thread(&self, user_id: UserId, thread_id: &str) -> Result<Vec<Email>>;
117
118 /// Gets an email by its Message-ID header.
119 async fn get_by_message_id(&self, user_id: UserId, message_id: &str) -> Result<Option<Email>>;
120
121 /// Updates labels/tags on an email.
122 async fn update_labels(&self, id: EmailId, user_id: UserId, labels: &[String]) -> Result<Option<Email>>;
123
124 /// Lists distinct source_folder values across all non-draft emails.
125 async fn list_folders(&self, user_id: UserId) -> Result<Vec<String>>;
126
127 /// Lists all distinct labels used across all emails.
128 async fn list_labels(&self, user_id: UserId) -> Result<Vec<String>>;
129
130 /// Lists all draft emails.
131 async fn list_drafts(&self, user_id: UserId) -> Result<Vec<Email>>;
132
133 /// Creates or updates a draft email.
134 #[allow(clippy::too_many_arguments)]
135 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>;
136 }
137
138 /// Repository for email account (IMAP/SMTP/OAuth2) configuration.
139 #[allow(clippy::too_many_arguments)]
140 #[async_trait]
141 pub trait EmailAccountRepository: Send + Sync {
142 /// Lists all email accounts for a user.
143 async fn list_by_user(&self, user_id: UserId) -> Result<Vec<EmailAccount>>;
144
145 /// Retrieves an email account by ID.
146 async fn get_by_id(&self, id: EmailAccountId, user_id: UserId) -> Result<Option<EmailAccount>>;
147
148 /// Creates a new email account configuration (password-based IMAP/SMTP).
149 async fn create(
150 &self,
151 user_id: UserId,
152 account_name: &str,
153 email_address: &str,
154 imap_server: &str,
155 imap_port: i32,
156 smtp_server: &str,
157 smtp_port: i32,
158 username: &str,
159 password: &str,
160 use_tls: bool,
161 archive_folder_name: Option<&str>,
162 ) -> Result<EmailAccount>;
163
164 /// Creates a new OAuth2 email account (Fastmail JMAP).
165 async fn create_oauth(
166 &self,
167 user_id: UserId,
168 account_name: &str,
169 email_address: &str,
170 access_token: &str,
171 refresh_token: &str,
172 expires_at: DateTime<Utc>,
173 jmap_session_url: &str,
174 jmap_account_id: &str,
175 ) -> Result<EmailAccount>;
176
177 /// Creates a new OAuth2 email account with IMAP/SMTP (Google, Microsoft, Yahoo).
178 async fn create_oauth_imap(
179 &self,
180 user_id: UserId,
181 account_name: &str,
182 email_address: &str,
183 auth_type: EmailAuthType,
184 access_token: &str,
185 refresh_token: &str,
186 expires_at: DateTime<Utc>,
187 imap_server: &str,
188 imap_port: i32,
189 smtp_server: &str,
190 smtp_port: i32,
191 ) -> Result<EmailAccount>;
192
193 /// Updates an email account. Password is only updated if provided.
194 async fn update(
195 &self,
196 id: EmailAccountId,
197 user_id: UserId,
198 account_name: &str,
199 email_address: &str,
200 imap_server: &str,
201 imap_port: i32,
202 smtp_server: &str,
203 smtp_port: i32,
204 username: &str,
205 password: Option<&str>,
206 use_tls: bool,
207 archive_folder_name: Option<&str>,
208 ) -> Result<Option<EmailAccount>>;
209
210 /// Updates OAuth2 tokens for an account.
211 async fn update_oauth_tokens(
212 &self,
213 id: EmailAccountId,
214 user_id: UserId,
215 access_token: &str,
216 refresh_token: Option<&str>,
217 expires_at: DateTime<Utc>,
218 ) -> Result<Option<EmailAccount>>;
219
220 /// Updates JMAP session info for an account.
221 async fn update_jmap_session(
222 &self,
223 id: EmailAccountId,
224 user_id: UserId,
225 session_url: &str,
226 account_id: &str,
227 ) -> Result<Option<EmailAccount>>;
228
229 /// Deletes an email account.
230 async fn delete(&self, id: EmailAccountId, user_id: UserId) -> Result<bool>;
231
232 /// Updates the last sync timestamp.
233 async fn update_last_sync(&self, id: EmailAccountId, user_id: UserId) -> Result<bool>;
234
235 /// Updates the sync interval setting for an account.
236 async fn update_sync_interval(&self, id: EmailAccountId, user_id: UserId, interval_minutes: Option<i32>) -> Result<Option<EmailAccount>>;
237
238 /// Updates the email signature for an account.
239 async fn update_signature(&self, id: EmailAccountId, user_id: UserId, signature: Option<&str>) -> Result<Option<EmailAccount>>;
240
241 /// Updates the notification preference for an account.
242 async fn update_notify_new_emails(&self, id: EmailAccountId, user_id: UserId, enabled: bool) -> Result<Option<EmailAccount>>;
243
244 /// Lists accounts that need automatic sync based on their sync_interval_minutes.
245 /// Returns accounts where sync is enabled and last_sync_at + interval < now.
246 async fn list_accounts_needing_sync(&self, user_id: UserId) -> Result<Vec<EmailAccount>>;
247
248 /// Gets the IMAP folder sync state for incremental UID-based fetching.
249 async fn get_folder_sync_state(&self, account_id: EmailAccountId, folder: &str) -> Result<Option<FolderSyncState>>;
250
251 /// Upserts the IMAP folder sync state after a successful sync.
252 async fn upsert_folder_sync_state(&self, account_id: EmailAccountId, folder: &str, uid_validity: u32, last_seen_uid: u32) -> Result<()>;
253
254 /// Deletes stale folder sync state (e.g. on UIDVALIDITY change).
255 async fn delete_folder_sync_state(&self, account_id: EmailAccountId, folder: &str) -> Result<()>;
256 }
257
258 /// Repository for file attachment operations.
259 #[async_trait]
260 pub trait AttachmentRepository: Send + Sync {
261 /// Creates a new attachment record.
262 async fn create(&self, user_id: UserId, attachment: NewAttachment) -> Result<Attachment>;
263
264 /// Lists attachments for a task.
265 async fn list_for_task(&self, task_id: TaskId, user_id: UserId) -> Result<Vec<Attachment>>;
266
267 /// Lists attachments for a project.
268 async fn list_for_project(&self, project_id: ProjectId, user_id: UserId) -> Result<Vec<Attachment>>;
269
270 /// Retrieves an attachment by ID.
271 async fn get_by_id(&self, id: AttachmentId, user_id: UserId) -> Result<Option<Attachment>>;
272
273 /// Deletes an attachment record, returning `true` if deleted.
274 async fn delete(&self, id: AttachmentId, user_id: UserId) -> Result<bool>;
275
276 /// Lists all attachments sharing a blob hash (for dedup checks).
277 async fn list_by_blob_hash(&self, blob_hash: &str, user_id: UserId) -> Result<Vec<Attachment>>;
278
279 /// Lists all distinct blob hashes for a user (for blob sync).
280 async fn list_all_blob_hashes(&self, user_id: UserId) -> Result<Vec<String>>;
281
282 /// Lists every attachment record for a user (for full backup export).
283 async fn list_all(&self, user_id: UserId) -> Result<Vec<Attachment>>;
284 }
285