1use kevy_embedded::{PubsubFrame, Store};
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(Clone)]
176pub struct KevyNotifier {
177 store: Store,
178}
179
180impl std::fmt::Debug for KevyNotifier {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 f.debug_struct("KevyNotifier").finish_non_exhaustive()
183 }
184}
185
186impl KevyNotifier {
187 pub fn new(store: Store) -> Self {
190 Self { store }
191 }
192}
193
194#[async_trait::async_trait]
195impl Notifier for KevyNotifier {
196 async fn notify(&self) {
197 let _ = self.store.publish(b"queue:notify", b"1");
198 }
199
200 async fn wait(&self) {
201 let store = self.store.clone();
202 let _ = tokio::task::spawn_blocking(move || {
203 let sub = store.subscribe(&[b"queue:notify"]);
206 loop {
207 match sub.recv() {
208 Ok(PubsubFrame::Message { .. } | PubsubFrame::Pmessage { .. }) => break,
209 Ok(_) => continue, Err(_) => break, }
212 }
213 })
214 .await;
215 }
216}