docbox_core/notifications/
sqs.rs1use super::{NotificationQueue, NotificationQueueMessage};
2use crate::aws::SqsClient;
3use std::time::Duration;
4use tokio::{spawn, sync::mpsc, time::sleep};
5
6pub struct SqsNotificationQueue {
7 rx: mpsc::Receiver<NotificationQueueMessage>,
8}
9
10impl SqsNotificationQueue {
11 pub fn create(client: SqsClient, queue_url: String) -> SqsNotificationQueue {
12 let (tx, rx) = mpsc::channel(10);
13 let task = SqsNotificationQueueTask {
14 client,
15 queue_url,
16 tx,
17 };
18
19 spawn(process_sqs_queue(task));
20 SqsNotificationQueue { rx }
21 }
22}
23
24impl NotificationQueue for SqsNotificationQueue {
25 async fn next_message(&mut self) -> Option<NotificationQueueMessage> {
26 self.rx.recv().await
27 }
28}
29
30pub struct SqsNotificationQueueTask {
31 client: SqsClient,
33
34 queue_url: String,
36
37 tx: mpsc::Sender<NotificationQueueMessage>,
39}
40
41pub fn parse_bucket_message(value: &serde_json::Value) -> Option<(String, String)> {
42 let records = value.get("Records")?;
43 let record = records.get(0)?;
44
45 let s3 = record.get("s3")?;
46 let bucket = s3.get("bucket")?;
47 let object = s3.get("object")?;
48
49 let bucket_name = bucket.get("name")?.as_str()?.to_string();
50 let object_key = object.get("key")?.as_str()?.to_string();
51
52 Some((bucket_name, object_key))
53}
54
55async fn process_sqs_queue(task: SqsNotificationQueueTask) {
56 loop {
57 let receive_messages = match task
59 .client
60 .receive_message()
61 .queue_url(&task.queue_url)
62 .max_number_of_messages(10)
63 .wait_time_seconds(5)
64 .send()
65 .await
66 {
67 Ok(value) => value,
68 Err(error) => {
69 tracing::error!(?error, "error getting messages from sqs");
70 sleep(Duration::from_secs(10)).await;
71 continue;
72 }
73 };
74
75 let messages = match receive_messages.messages {
76 Some(value) => value,
77 None => {
78 tracing::debug!("no messages from sqs");
79 sleep(Duration::from_secs(1)).await;
80 continue;
81 }
82 };
83
84 for message in messages {
85 let (body, receipt_handle) = match (message.body, message.receipt_handle) {
86 (Some(body), Some(receipt_handle)) => (body, receipt_handle),
87 _ => continue,
88 };
89
90 let parsed: serde_json::Value = match serde_json::from_str(&body) {
91 Ok(value) => value,
92 Err(error) => {
93 if let Err(error) = task
94 .client
95 .delete_message()
96 .queue_url(&task.queue_url)
97 .receipt_handle(receipt_handle)
98 .send()
99 .await
100 {
101 tracing::error!(?error, "failed to delete message from sqs");
102 }
103
104 tracing::error!(?error, "got malformed message from sqs");
105 continue;
106 }
107 };
108
109 tracing::debug!(?parsed, "got message from sqs");
110
111 if let Some((bucket_name, object_key)) = parse_bucket_message(&parsed) {
112 tracing::debug!(?bucket_name, ?object_key, "got file upload message");
113
114 _ = task
115 .tx
116 .send(NotificationQueueMessage::FileCreated {
117 bucket_name,
118 object_key,
119 })
120 .await;
121 }
122
123 if let Err(error) = task
124 .client
125 .delete_message()
126 .queue_url(&task.queue_url)
127 .receipt_handle(receipt_handle)
128 .send()
129 .await
130 {
131 tracing::error!(?error, "failed to delete message from sqs");
132 }
133 }
134 }
135}