1use redis::AsyncCommands;
10use sqlx::PgPool;
11
12use crate::queue;
13use crate::queue::QueuedMessage;
14use crate::store::{Notifier, QueueStore, StoreError};
15
16#[derive(Debug, Clone)]
18pub struct PgQueueStore {
19 pool: PgPool,
20}
21
22impl PgQueueStore {
23 pub fn new(pool: PgPool) -> Self {
27 Self { pool }
28 }
29
30 pub fn pool(&self) -> &PgPool {
32 &self.pool
33 }
34}
35
36#[async_trait::async_trait]
37impl QueueStore for PgQueueStore {
38 async fn enqueue(
39 &self,
40 sender: &str,
41 recipient: &str,
42 domain: &str,
43 message_data: &[u8],
44 message_id: Option<&str>,
45 now: i64,
46 is_forwarded: bool,
47 ) -> Result<i64, StoreError> {
48 Ok(queue::enqueue_ex(
49 &self.pool,
50 sender,
51 recipient,
52 domain,
53 message_data,
54 message_id,
55 now,
56 is_forwarded,
57 )
58 .await?)
59 }
60
61 async fn enqueue_scheduled(
62 &self,
63 sender: &str,
64 recipient: &str,
65 domain: &str,
66 message_data: &[u8],
67 message_id: Option<&str>,
68 created_at: i64,
69 scheduled_at: i64,
70 ) -> Result<i64, StoreError> {
71 Ok(queue::enqueue_scheduled(
72 &self.pool,
73 sender,
74 recipient,
75 domain,
76 message_data,
77 message_id,
78 created_at,
79 scheduled_at,
80 )
81 .await?)
82 }
83
84 async fn dequeue(&self, now: i64, limit: u32) -> Result<Vec<QueuedMessage>, StoreError> {
85 Ok(queue::dequeue(&self.pool, now, limit).await?)
86 }
87
88 async fn recover_stale_inflight(&self, now: i64) -> Result<u64, StoreError> {
89 Ok(queue::recover_stale_inflight(&self.pool, now).await?)
90 }
91
92 async fn mark_inflight(&self, id: i64, now: i64) -> Result<(), StoreError> {
93 Ok(queue::mark_inflight(&self.pool, id, now).await?)
94 }
95
96 async fn mark_delivered(&self, id: i64, now: i64) -> Result<(), StoreError> {
97 Ok(queue::mark_delivered(&self.pool, id, now).await?)
98 }
99
100 async fn mark_failed(
101 &self,
102 id: i64,
103 error: &str,
104 next_retry: i64,
105 now: i64,
106 ) -> Result<(), StoreError> {
107 Ok(queue::mark_failed(&self.pool, id, error, next_retry, now).await?)
108 }
109
110 async fn mark_bounced(&self, id: i64, error: &str, now: i64) -> Result<(), StoreError> {
111 Ok(queue::mark_bounced(&self.pool, id, error, now).await?)
112 }
113
114 async fn get_message(&self, id: i64) -> Result<Option<QueuedMessage>, StoreError> {
115 Ok(queue::get_message(&self.pool, id).await?)
116 }
117
118 async fn queue_stats(&self) -> Result<Vec<(String, i64)>, StoreError> {
119 Ok(queue::queue_stats(&self.pool).await?)
120 }
121
122 async fn list_recent(&self, limit: i32) -> Result<Vec<QueuedMessage>, StoreError> {
123 Ok(queue::list_recent(&self.pool, limit).await?)
124 }
125
126 async fn cancel_pending(&self, id: i64) -> Result<bool, StoreError> {
127 Ok(queue::cancel_pending(&self.pool, id).await?)
128 }
129
130 async fn cancel_pending_by_message_id(
131 &self,
132 message_id: &str,
133 sender: &str,
134 ) -> Result<bool, StoreError> {
135 Ok(queue::cancel_pending_by_message_id(&self.pool, message_id, sender).await?)
136 }
137
138 async fn retry_message(&self, id: i64, now: i64) -> Result<bool, StoreError> {
139 Ok(queue::retry_message(&self.pool, id, now).await?)
140 }
141
142 async fn is_suppressed(&self, email: &str) -> bool {
143 queue::is_suppressed(&self.pool, email).await
144 }
145
146 async fn add_suppression(
147 &self,
148 email: &str,
149 reason: &str,
150 smtp_code: Option<i32>,
151 ) -> Result<(), StoreError> {
152 Ok(queue::add_suppression(&self.pool, email, reason, smtp_code).await?)
153 }
154
155 async fn remove_suppression(&self, email: &str) -> Result<bool, StoreError> {
156 Ok(queue::remove_suppression(&self.pool, email).await?)
157 }
158
159 async fn list_suppressions(
160 &self,
161 limit: i64,
162 ) -> Result<Vec<(String, String, Option<i32>, i64)>, StoreError> {
163 Ok(queue::list_suppressions(&self.pool, limit).await?)
164 }
165}
166
167#[derive(Debug, Clone)]
169pub struct RedisNotifier {
170 url: String,
171}
172
173impl RedisNotifier {
174 pub fn new(url: impl Into<String>) -> Self {
178 Self { url: url.into() }
179 }
180}
181
182#[async_trait::async_trait]
183impl Notifier for RedisNotifier {
184 async fn notify(&self) {
185 let Ok(client) = redis::Client::open(self.url.as_str()) else {
186 return;
187 };
188 let Ok(mut conn) = client.get_connection_manager().await else {
189 return;
190 };
191 let _: Result<i32, _> = conn.publish("queue:notify", "1").await;
192 }
193
194 async fn wait(&self) {
195 let Ok(client) = redis::Client::open(self.url.as_str()) else {
200 std::future::pending::<()>().await;
201 return;
202 };
203 let Ok(mut pubsub) = client.get_async_pubsub().await else {
204 std::future::pending::<()>().await;
205 return;
206 };
207 if pubsub.subscribe("queue:notify").await.is_err() {
208 std::future::pending::<()>().await;
209 return;
210 }
211 use futures_util::StreamExt;
212 let mut stream = pubsub.on_message();
213 let _ = stream.next().await;
214 }
215}