Skip to main content

max / goingson

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