postcrate_core/db/
webhooks.rs1use chrono::Utc;
4use serde::{Deserialize, Serialize};
5use sqlx::{Row, SqlitePool};
6use uuid::Uuid;
7
8use crate::error::{Error, Result};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[cfg_attr(feature = "specta", derive(specta::Type))]
12#[serde(rename_all = "camelCase")]
13pub struct Webhook {
14 pub id: String,
15 pub mailbox_id: Option<String>,
17 pub url: String,
18 pub auth_header: Option<String>,
21 pub enabled: bool,
22 pub created_at: i64,
23}
24
25#[derive(Debug, Clone, Deserialize)]
26#[cfg_attr(feature = "specta", derive(specta::Type))]
27#[serde(rename_all = "camelCase")]
28pub struct CreateWebhook {
29 pub mailbox_id: Option<String>,
30 pub url: String,
31 pub auth_header: Option<String>,
32 pub enabled: Option<bool>,
33}
34
35pub(crate) async fn insert(pool: &SqlitePool, input: CreateWebhook) -> Result<Webhook> {
36 let id = Uuid::new_v4().to_string();
37 let now = Utc::now().timestamp_millis();
38 let enabled = input.enabled.unwrap_or(true);
39 sqlx::query(
40 r"INSERT INTO webhooks (id, mailbox_id, url, auth_header, enabled, created_at)
41 VALUES (?, ?, ?, ?, ?, ?)",
42 )
43 .bind(&id)
44 .bind(&input.mailbox_id)
45 .bind(&input.url)
46 .bind(&input.auth_header)
47 .bind(i64::from(enabled))
48 .bind(now)
49 .execute(pool)
50 .await?;
51 Ok(Webhook {
52 id,
53 mailbox_id: input.mailbox_id,
54 url: input.url,
55 auth_header: input.auth_header,
56 enabled,
57 created_at: now,
58 })
59}
60
61pub(crate) async fn list(pool: &SqlitePool) -> Result<Vec<Webhook>> {
62 let rows =
63 sqlx::query("SELECT id, mailbox_id, url, auth_header, enabled, created_at FROM webhooks")
64 .fetch_all(pool)
65 .await?;
66 Ok(rows.iter().map(row_to_webhook).collect())
67}
68
69pub(crate) async fn list_for_mailbox(pool: &SqlitePool, mailbox_id: &str) -> Result<Vec<Webhook>> {
70 let rows = sqlx::query(
71 r"SELECT id, mailbox_id, url, auth_header, enabled, created_at
72 FROM webhooks
73 WHERE enabled = 1 AND (mailbox_id IS NULL OR mailbox_id = ?)",
74 )
75 .bind(mailbox_id)
76 .fetch_all(pool)
77 .await?;
78 Ok(rows.iter().map(row_to_webhook).collect())
79}
80
81pub(crate) async fn delete(pool: &SqlitePool, id: &str) -> Result<()> {
82 let res = sqlx::query("DELETE FROM webhooks WHERE id = ?")
83 .bind(id)
84 .execute(pool)
85 .await?;
86 if res.rows_affected() == 0 {
87 return Err(Error::Invalid(format!("webhook {id} not found")));
88 }
89 Ok(())
90}
91
92fn row_to_webhook(row: &sqlx::sqlite::SqliteRow) -> Webhook {
93 Webhook {
94 id: row.try_get("id").unwrap_or_default(),
95 mailbox_id: row
96 .try_get::<Option<String>, _>("mailbox_id")
97 .ok()
98 .flatten(),
99 url: row.try_get("url").unwrap_or_default(),
100 auth_header: row
101 .try_get::<Option<String>, _>("auth_header")
102 .ok()
103 .flatten(),
104 enabled: row.try_get::<i64, _>("enabled").unwrap_or(0) != 0,
105 created_at: row.try_get("created_at").unwrap_or(0),
106 }
107}