1use super::ConnectionExt;
2use crate::schema::message_deletions::dsl;
3use crate::{DbConnection, impl_store, impl_store_or_ignore, schema::message_deletions};
4use diesel::prelude::*;
5use serde::{Deserialize, Serialize};
6
7#[derive(
8 Debug,
9 Clone,
10 Serialize,
11 Deserialize,
12 Insertable,
13 Identifiable,
14 Queryable,
15 Eq,
16 PartialEq,
17 QueryableByName,
18)]
19#[diesel(table_name = message_deletions)]
20#[diesel(primary_key(id))]
21pub struct StoredMessageDeletion {
23 pub id: Vec<u8>,
25 pub group_id: Vec<u8>,
27 pub deleted_message_id: Vec<u8>,
29 pub deleted_by_inbox_id: String,
31 pub is_super_admin_deletion: bool,
33 pub deleted_at_ns: i64,
35}
36
37impl_store!(StoredMessageDeletion, message_deletions);
38impl_store_or_ignore!(StoredMessageDeletion, message_deletions);
39
40pub trait QueryMessageDeletion {
42 fn get_message_deletion(
44 &self,
45 id: &[u8],
46 ) -> Result<Option<StoredMessageDeletion>, crate::ConnectionError>;
47
48 fn get_deletion_by_deleted_message_id(
50 &self,
51 deleted_message_id: &[u8],
52 ) -> Result<Option<StoredMessageDeletion>, crate::ConnectionError>;
53
54 fn get_deletions_for_messages(
56 &self,
57 message_ids: Vec<Vec<u8>>,
58 ) -> Result<Vec<StoredMessageDeletion>, crate::ConnectionError>;
59
60 fn get_group_deletions(
62 &self,
63 group_id: &[u8],
64 ) -> Result<Vec<StoredMessageDeletion>, crate::ConnectionError>;
65
66 fn is_message_deleted(&self, message_id: &[u8]) -> Result<bool, crate::ConnectionError>;
68}
69
70impl<T> QueryMessageDeletion for &T
71where
72 T: QueryMessageDeletion,
73{
74 fn get_message_deletion(
75 &self,
76 id: &[u8],
77 ) -> Result<Option<StoredMessageDeletion>, crate::ConnectionError> {
78 (**self).get_message_deletion(id)
79 }
80
81 fn get_deletion_by_deleted_message_id(
82 &self,
83 deleted_message_id: &[u8],
84 ) -> Result<Option<StoredMessageDeletion>, crate::ConnectionError> {
85 (**self).get_deletion_by_deleted_message_id(deleted_message_id)
86 }
87
88 fn get_deletions_for_messages(
89 &self,
90 message_ids: Vec<Vec<u8>>,
91 ) -> Result<Vec<StoredMessageDeletion>, crate::ConnectionError> {
92 (**self).get_deletions_for_messages(message_ids)
93 }
94
95 fn get_group_deletions(
96 &self,
97 group_id: &[u8],
98 ) -> Result<Vec<StoredMessageDeletion>, crate::ConnectionError> {
99 (**self).get_group_deletions(group_id)
100 }
101
102 fn is_message_deleted(&self, message_id: &[u8]) -> Result<bool, crate::ConnectionError> {
103 (**self).is_message_deleted(message_id)
104 }
105}
106
107impl<C: ConnectionExt> QueryMessageDeletion for DbConnection<C> {
108 fn get_message_deletion(
109 &self,
110 id: &[u8],
111 ) -> Result<Option<StoredMessageDeletion>, crate::ConnectionError> {
112 self.raw_query_read(|conn| {
113 dsl::message_deletions
114 .filter(dsl::id.eq(id))
115 .first(conn)
116 .optional()
117 })
118 }
119
120 fn get_deletion_by_deleted_message_id(
121 &self,
122 deleted_message_id: &[u8],
123 ) -> Result<Option<StoredMessageDeletion>, crate::ConnectionError> {
124 self.raw_query_read(|conn| {
125 dsl::message_deletions
126 .filter(dsl::deleted_message_id.eq(deleted_message_id))
127 .first(conn)
128 .optional()
129 })
130 }
131
132 fn get_deletions_for_messages(
133 &self,
134 message_ids: Vec<Vec<u8>>,
135 ) -> Result<Vec<StoredMessageDeletion>, crate::ConnectionError> {
136 if message_ids.is_empty() {
137 return Ok(vec![]);
138 }
139
140 self.raw_query_read(|conn| {
141 dsl::message_deletions
142 .filter(dsl::deleted_message_id.eq_any(message_ids))
143 .load(conn)
144 })
145 }
146
147 fn get_group_deletions(
148 &self,
149 group_id: &[u8],
150 ) -> Result<Vec<StoredMessageDeletion>, crate::ConnectionError> {
151 self.raw_query_read(|conn| {
152 dsl::message_deletions
153 .filter(dsl::group_id.eq(group_id))
154 .load(conn)
155 })
156 }
157
158 fn is_message_deleted(&self, message_id: &[u8]) -> Result<bool, crate::ConnectionError> {
159 self.raw_query_read(|conn| {
160 diesel::dsl::select(diesel::dsl::exists(
161 dsl::message_deletions.filter(dsl::deleted_message_id.eq(message_id)),
162 ))
163 .get_result::<bool>(conn)
164 })
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::encrypted_store::group::{ConversationType, GroupMembershipState, StoredGroup};
172 use crate::encrypted_store::group_message::{
173 ContentType, DeliveryStatus, GroupMessageKind, StoredGroupMessage,
174 };
175 use crate::{Store, with_connection};
176
177 fn create_test_group(conn: &DbConnection<impl ConnectionExt>, group_id: Vec<u8>) {
178 StoredGroup {
179 id: group_id,
180 created_at_ns: 0,
181 membership_state: GroupMembershipState::Allowed,
182 installations_last_checked: 0,
183 added_by_inbox_id: "test".to_string(),
184 sequence_id: Some(0),
185 rotated_at_ns: 0,
186 conversation_type: ConversationType::Group,
187 dm_id: None,
188 last_message_ns: None,
189 message_disappear_from_ns: None,
190 message_disappear_in_ns: None,
191 paused_for_version: None,
192 maybe_forked: false,
193 fork_details: "[]".to_string(),
194 originator_id: None,
195 should_publish_commit_log: false,
196 commit_log_public_key: None,
197 is_commit_log_forked: None,
198 has_pending_leave_request: None,
199 }
200 .store(conn)
201 .unwrap();
202 }
203
204 fn create_test_message(
205 conn: &DbConnection<impl ConnectionExt>,
206 id: Vec<u8>,
207 group_id: Vec<u8>,
208 ) {
209 StoredGroupMessage {
210 id,
211 group_id,
212 decrypted_message_bytes: vec![],
213 sent_at_ns: 1000,
214 kind: GroupMessageKind::Application,
215 sender_installation_id: vec![1, 2, 3],
216 sender_inbox_id: "sender".to_string(),
217 delivery_status: DeliveryStatus::Published,
218 content_type: ContentType::Text,
219 version_major: 1,
220 version_minor: 0,
221 authority_id: "xmtp.org".to_string(),
222 reference_id: None,
223 expire_at_ns: None,
224 sequence_id: 1,
225 originator_id: 1,
226 inserted_at_ns: 0,
227 should_push: false,
228 }
229 .store(conn)
230 .unwrap();
231 }
232
233 #[xmtp_common::test(unwrap_try = true)]
234 fn test_store_and_get_deletion() {
235 with_connection(|conn| {
236 let group_id = vec![1, 2, 3];
237 let message_id = vec![4, 5, 6];
238 let delete_message_id = vec![7, 8, 9];
239
240 create_test_group(conn, group_id.clone());
241 create_test_message(conn, message_id.clone(), group_id.clone());
242 create_test_message(conn, delete_message_id.clone(), group_id.clone());
243
244 let deletion = StoredMessageDeletion {
245 id: delete_message_id.clone(),
246 group_id: group_id.clone(),
247 deleted_message_id: message_id.clone(),
248 deleted_by_inbox_id: "sender".to_string(),
249 is_super_admin_deletion: false,
250 deleted_at_ns: 2000,
251 };
252
253 deletion.store(conn)?;
254
255 let retrieved = conn.get_message_deletion(&delete_message_id)?;
257 assert!(retrieved.is_some());
258 assert_eq!(retrieved.unwrap().deleted_message_id, message_id);
259
260 let by_deleted_id = conn.get_deletion_by_deleted_message_id(&message_id)?;
262 assert!(by_deleted_id.is_some());
263 assert_eq!(by_deleted_id.unwrap().id, delete_message_id);
264 })
265 }
266
267 #[xmtp_common::test(unwrap_try = true)]
268 fn test_is_message_deleted() {
269 with_connection(|conn| {
270 let group_id = vec![1, 2, 3];
271 let message_id = vec![4, 5, 6];
272 let delete_message_id = vec![7, 8, 9];
273
274 create_test_group(conn, group_id.clone());
275 create_test_message(conn, message_id.clone(), group_id.clone());
276 create_test_message(conn, delete_message_id.clone(), group_id.clone());
277
278 assert!(!conn.is_message_deleted(&message_id)?);
280
281 StoredMessageDeletion {
283 id: delete_message_id.clone(),
284 group_id: group_id.clone(),
285 deleted_message_id: message_id.clone(),
286 deleted_by_inbox_id: "sender".to_string(),
287 is_super_admin_deletion: false,
288 deleted_at_ns: 2000,
289 }
290 .store(conn)?;
291
292 assert!(conn.is_message_deleted(&message_id)?);
294 })
295 }
296
297 #[xmtp_common::test(unwrap_try = true)]
298 fn test_get_deletions_for_messages() {
299 with_connection(|conn| {
300 let group_id = vec![1, 2, 3];
301 let msg1 = vec![4, 5, 6];
302 let msg2 = vec![7, 8, 9];
303 let msg3 = vec![10, 11, 12];
304 let del1 = vec![13, 14, 15];
305 let del2 = vec![16, 17, 18];
306
307 create_test_group(conn, group_id.clone());
308 create_test_message(conn, msg1.clone(), group_id.clone());
309 create_test_message(conn, msg2.clone(), group_id.clone());
310 create_test_message(conn, msg3.clone(), group_id.clone());
311 create_test_message(conn, del1.clone(), group_id.clone());
312 create_test_message(conn, del2.clone(), group_id.clone());
313
314 StoredMessageDeletion {
316 id: del1.clone(),
317 group_id: group_id.clone(),
318 deleted_message_id: msg1.clone(),
319 deleted_by_inbox_id: "sender".to_string(),
320 is_super_admin_deletion: false,
321 deleted_at_ns: 2000,
322 }
323 .store(conn)?;
324
325 StoredMessageDeletion {
326 id: del2.clone(),
327 group_id: group_id.clone(),
328 deleted_message_id: msg2.clone(),
329 deleted_by_inbox_id: "admin".to_string(),
330 is_super_admin_deletion: true,
331 deleted_at_ns: 3000,
332 }
333 .store(conn)?;
334
335 let deletions =
337 conn.get_deletions_for_messages(vec![msg1.clone(), msg2.clone(), msg3.clone()])?;
338 assert_eq!(deletions.len(), 2);
339
340 assert!(!conn.is_message_deleted(&msg3)?);
342 })
343 }
344
345 #[xmtp_common::test(unwrap_try = true)]
346 fn test_get_group_deletions() {
347 with_connection(|conn| {
348 let group1 = vec![1, 2, 3];
349 let group2 = vec![4, 5, 6];
350 let msg1 = vec![7, 8, 9];
351 let msg2 = vec![10, 11, 12];
352 let del1 = vec![13, 14, 15];
353 let del2 = vec![16, 17, 18];
354
355 create_test_group(conn, group1.clone());
356 create_test_group(conn, group2.clone());
357 create_test_message(conn, msg1.clone(), group1.clone());
358 create_test_message(conn, msg2.clone(), group2.clone());
359 create_test_message(conn, del1.clone(), group1.clone());
360 create_test_message(conn, del2.clone(), group2.clone());
361
362 StoredMessageDeletion {
363 id: del1.clone(),
364 group_id: group1.clone(),
365 deleted_message_id: msg1.clone(),
366 deleted_by_inbox_id: "sender".to_string(),
367 is_super_admin_deletion: false,
368 deleted_at_ns: 2000,
369 }
370 .store(conn)?;
371
372 StoredMessageDeletion {
373 id: del2.clone(),
374 group_id: group2.clone(),
375 deleted_message_id: msg2.clone(),
376 deleted_by_inbox_id: "sender".to_string(),
377 is_super_admin_deletion: false,
378 deleted_at_ns: 3000,
379 }
380 .store(conn)?;
381
382 let group1_deletions = conn.get_group_deletions(&group1)?;
384 assert_eq!(group1_deletions.len(), 1);
385 assert_eq!(group1_deletions[0].deleted_message_id, msg1);
386
387 let group2_deletions = conn.get_group_deletions(&group2)?;
389 assert_eq!(group2_deletions.len(), 1);
390 assert_eq!(group2_deletions[0].deleted_message_id, msg2);
391 })
392 }
393}