Skip to main content

max / makenotwork

1.7 KB · 63 lines History Blame Raw
1 //! Patch inbound email: message-ID → MT thread mapping for multi-part patch threading.
2
3 use sqlx::PgPool;
4
5 use super::{MtThreadId, ProjectId};
6 use crate::error::Result;
7
8 /// Store a mapping from an email Message-ID to an MT thread.
9 #[tracing::instrument(skip_all)]
10 pub async fn insert_patch_message_id(
11 pool: &PgPool,
12 message_id: &str,
13 project_id: ProjectId,
14 thread_id: MtThreadId,
15 ) -> Result<()> {
16 sqlx::query(
17 "INSERT INTO patch_message_ids (message_id, project_id, thread_id)
18 VALUES ($1, $2, $3)
19 ON CONFLICT (message_id) DO NOTHING",
20 )
21 .bind(message_id)
22 .bind(project_id)
23 .bind(thread_id)
24 .execute(pool)
25 .await?;
26 Ok(())
27 }
28
29 /// Look up a thread by a single email Message-ID.
30 #[tracing::instrument(skip_all)]
31 #[allow(dead_code)]
32 pub async fn get_thread_id_by_message_id(
33 pool: &PgPool,
34 message_id: &str,
35 ) -> Result<Option<MtThreadId>> {
36 let row: Option<(MtThreadId,)> = sqlx::query_as(
37 "SELECT thread_id FROM patch_message_ids WHERE message_id = $1",
38 )
39 .bind(message_id)
40 .fetch_optional(pool)
41 .await?;
42 Ok(row.map(|r| r.0))
43 }
44
45 /// Look up a thread by any of several message IDs (from In-Reply-To + References headers).
46 /// Returns the first match found.
47 #[tracing::instrument(skip_all)]
48 pub async fn get_thread_id_by_any_message_id(
49 pool: &PgPool,
50 message_ids: &[&str],
51 ) -> Result<Option<MtThreadId>> {
52 if message_ids.is_empty() {
53 return Ok(None);
54 }
55 let row: Option<(MtThreadId,)> = sqlx::query_as(
56 "SELECT thread_id FROM patch_message_ids WHERE message_id = ANY($1) LIMIT 1",
57 )
58 .bind(message_ids)
59 .fetch_optional(pool)
60 .await?;
61 Ok(row.map(|r| r.0))
62 }
63