Skip to main content

max / makenotwork

1.7 KB · 62 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,)> =
37 sqlx::query_as("SELECT thread_id FROM patch_message_ids WHERE message_id = $1")
38 .bind(message_id)
39 .fetch_optional(pool)
40 .await?;
41 Ok(row.map(|r| r.0))
42 }
43
44 /// Look up a thread by any of several message IDs (from In-Reply-To + References headers).
45 /// Returns the first match found.
46 #[tracing::instrument(skip_all)]
47 pub async fn get_thread_id_by_any_message_id(
48 pool: &PgPool,
49 message_ids: &[&str],
50 ) -> Result<Option<MtThreadId>> {
51 if message_ids.is_empty() {
52 return Ok(None);
53 }
54 let row: Option<(MtThreadId,)> = sqlx::query_as(
55 "SELECT thread_id FROM patch_message_ids WHERE message_id = ANY($1) LIMIT 1",
56 )
57 .bind(message_ids)
58 .fetch_optional(pool)
59 .await?;
60 Ok(row.map(|r| r.0))
61 }
62