Skip to main content

max / goingson

3.8 KB · 118 lines History Blame Raw
1 //! Email snooze and waiting-for-response state.
2 //!
3 //! The email analogue of `task_repo_state`'s six functions over `tasks`; the
4 //! shared shape is deliberate and left visible rather than consolidated.
5
6 use chrono::{DateTime, Utc};
7 use goingson_core::{Email, EmailId, Result, UserId};
8 use rusqlite::{Connection, params};
9
10 use crate::utils::{execute, format_datetime, format_datetime_now, format_datetime_opt, query_all};
11
12 use super::query;
13 use super::row::{EMAIL_SELECT_COLUMNS, EmailRow};
14
15 /// Snooze one email until the given time.
16 pub(super) fn snooze(
17 conn: &Connection,
18 id: EmailId,
19 user_id: UserId,
20 until: DateTime<Utc>,
21 ) -> Result<Option<Email>> {
22 let until_str = format_datetime(&until);
23 let result = execute(
24 conn,
25 "UPDATE emails SET snoozed_until = ? WHERE id = ? AND user_id = ?",
26 params![&until_str, id.to_string(), user_id.to_string()],
27 )?;
28 if result > 0 {
29 query::get_by_id(conn, id, user_id)
30 } else {
31 Ok(None)
32 }
33 }
34
35 /// Remove the snooze from one email.
36 pub(super) fn unsnooze(conn: &Connection, id: EmailId, user_id: UserId) -> Result<Option<Email>> {
37 let result = execute(
38 conn,
39 "UPDATE emails SET snoozed_until = NULL WHERE id = ? AND user_id = ?",
40 params![id.to_string(), user_id.to_string()],
41 )?;
42 if result > 0 {
43 query::get_by_id(conn, id, user_id)
44 } else {
45 Ok(None)
46 }
47 }
48
49 /// Emails still snoozed into the future, soonest first.
50 pub(super) fn list_snoozed(conn: &Connection, user_id: UserId) -> Result<Vec<Email>> {
51 let query = format!(
52 "SELECT {EMAIL_SELECT_COLUMNS} FROM emails e LEFT JOIN projects p ON e.project_id = p.id AND p.user_id = ? WHERE e.user_id = ? AND e.snoozed_until IS NOT NULL AND datetime(e.snoozed_until) > datetime('now') ORDER BY e.snoozed_until ASC"
53 );
54 let rows = query_all(
55 conn,
56 &query,
57 params![user_id.to_string(), user_id.to_string()],
58 EmailRow::from_row,
59 )?;
60 rows.into_iter().map(Email::try_from).collect()
61 }
62
63 /// Flag one email as awaiting a reply, optionally by a date.
64 pub(super) fn mark_waiting(
65 conn: &Connection,
66 id: EmailId,
67 user_id: UserId,
68 expected_response: Option<DateTime<Utc>>,
69 ) -> Result<Option<Email>> {
70 let now = format_datetime_now();
71 let expected = format_datetime_opt(expected_response);
72
73 let result = execute(
74 conn,
75 "UPDATE emails SET waiting_for_response = 1, waiting_since = ?, expected_response_date = ? WHERE id = ? AND user_id = ?",
76 params![&now, &expected, id.to_string(), user_id.to_string()],
77 )?;
78
79 if result > 0 {
80 query::get_by_id(conn, id, user_id)
81 } else {
82 Ok(None)
83 }
84 }
85
86 /// Clear the waiting-for-response flag on one email.
87 pub(super) fn clear_waiting(
88 conn: &Connection,
89 id: EmailId,
90 user_id: UserId,
91 ) -> Result<Option<Email>> {
92 let result = execute(
93 conn,
94 "UPDATE emails SET waiting_for_response = 0, waiting_since = NULL, expected_response_date = NULL WHERE id = ? AND user_id = ?",
95 params![id.to_string(), user_id.to_string()],
96 )?;
97
98 if result > 0 {
99 query::get_by_id(conn, id, user_id)
100 } else {
101 Ok(None)
102 }
103 }
104
105 /// Emails awaiting a reply, soonest expected response first.
106 pub(super) fn list_waiting(conn: &Connection, user_id: UserId) -> Result<Vec<Email>> {
107 let query = format!(
108 "SELECT {EMAIL_SELECT_COLUMNS} FROM emails e LEFT JOIN projects p ON e.project_id = p.id AND p.user_id = ? WHERE e.user_id = ? AND e.waiting_for_response = 1 ORDER BY e.expected_response_date ASC"
109 );
110 let rows = query_all(
111 conn,
112 &query,
113 params![user_id.to_string(), user_id.to_string()],
114 EmailRow::from_row,
115 )?;
116 rows.into_iter().map(Email::try_from).collect()
117 }
118