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