//! Patch inbound email: message-ID → MT thread mapping for multi-part patch threading. use sqlx::PgPool; use super::{MtThreadId, ProjectId}; use crate::error::Result; /// Store a mapping from an email Message-ID to an MT thread. #[tracing::instrument(skip_all)] pub async fn insert_patch_message_id( pool: &PgPool, message_id: &str, project_id: ProjectId, thread_id: MtThreadId, ) -> Result<()> { sqlx::query( "INSERT INTO patch_message_ids (message_id, project_id, thread_id) VALUES ($1, $2, $3) ON CONFLICT (message_id) DO NOTHING", ) .bind(message_id) .bind(project_id) .bind(thread_id) .execute(pool) .await?; Ok(()) } /// Look up a thread by a single email Message-ID. #[tracing::instrument(skip_all)] #[allow(dead_code)] pub async fn get_thread_id_by_message_id( pool: &PgPool, message_id: &str, ) -> Result> { let row: Option<(MtThreadId,)> = sqlx::query_as( "SELECT thread_id FROM patch_message_ids WHERE message_id = $1", ) .bind(message_id) .fetch_optional(pool) .await?; Ok(row.map(|r| r.0)) } /// Look up a thread by any of several message IDs (from In-Reply-To + References headers). /// Returns the first match found. #[tracing::instrument(skip_all)] pub async fn get_thread_id_by_any_message_id( pool: &PgPool, message_ids: &[&str], ) -> Result> { if message_ids.is_empty() { return Ok(None); } let row: Option<(MtThreadId,)> = sqlx::query_as( "SELECT thread_id FROM patch_message_ids WHERE message_id = ANY($1) LIMIT 1", ) .bind(message_ids) .fetch_optional(pool) .await?; Ok(row.map(|r| r.0)) }