forked from
tranquil.farm/tranquil-pds
Our Personal Data Server from scratch!
25 kB
782 lines
1use serde::{Deserialize, Serialize};
2use smallvec::SmallVec;
3
4use super::encoding::KeyBuilder;
5use super::keys::KeyTag;
6
7const COMMS_SCHEMA_VERSION: u8 = 1;
8const INVITE_CODE_SCHEMA_VERSION: u8 = 1;
9const INVITE_USE_SCHEMA_VERSION: u8 = 1;
10const SIGNING_KEY_SCHEMA_V1: u8 = 1;
11const SIGNING_KEY_SCHEMA_V2: u8 = 2;
12const DELETION_REQUEST_SCHEMA_VERSION: u8 = 1;
13const REPORT_SCHEMA_VERSION: u8 = 1;
14const NOTIFICATION_HISTORY_SCHEMA_VERSION: u8 = 1;
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct QueuedCommsValue {
18 pub id: uuid::Uuid,
19 pub user_id: Option<uuid::Uuid>,
20 pub channel: u8,
21 pub comms_type: u8,
22 pub recipient: String,
23 pub subject: Option<String>,
24 pub body: String,
25 pub metadata: Option<Vec<u8>>,
26 pub status: u8,
27 pub error_message: Option<String>,
28 pub attempts: i32,
29 pub max_attempts: i32,
30 pub created_at_ms: i64,
31 pub scheduled_for_ms: i64,
32 pub sent_at_ms: Option<i64>,
33}
34
35impl QueuedCommsValue {
36 pub fn serialize(&self) -> Vec<u8> {
37 let payload =
38 postcard::to_allocvec(self).expect("QueuedCommsValue serialization cannot fail");
39 let mut buf = Vec::with_capacity(1 + payload.len());
40 buf.push(COMMS_SCHEMA_VERSION);
41 buf.extend_from_slice(&payload);
42 buf
43 }
44
45 pub fn deserialize(bytes: &[u8]) -> Option<Self> {
46 let (&version, payload) = bytes.split_first()?;
47 match version {
48 COMMS_SCHEMA_VERSION => postcard::from_bytes(payload).ok(),
49 _ => None,
50 }
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct InviteCodeValue {
56 pub code: String,
57 pub available_uses: i32,
58 pub disabled: bool,
59 pub for_account: Option<String>,
60 pub created_by: Option<uuid::Uuid>,
61 pub created_at_ms: i64,
62}
63
64impl InviteCodeValue {
65 pub fn serialize(&self) -> Vec<u8> {
66 let payload =
67 postcard::to_allocvec(self).expect("InviteCodeValue serialization cannot fail");
68 let mut buf = Vec::with_capacity(1 + payload.len());
69 buf.push(INVITE_CODE_SCHEMA_VERSION);
70 buf.extend_from_slice(&payload);
71 buf
72 }
73
74 pub fn deserialize(bytes: &[u8]) -> Option<Self> {
75 let (&version, payload) = bytes.split_first()?;
76 match version {
77 INVITE_CODE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(),
78 _ => None,
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct InviteCodeUseValue {
85 pub used_by: uuid::Uuid,
86 pub used_at_ms: i64,
87}
88
89impl InviteCodeUseValue {
90 pub fn serialize(&self) -> Vec<u8> {
91 let payload =
92 postcard::to_allocvec(self).expect("InviteCodeUseValue serialization cannot fail");
93 let mut buf = Vec::with_capacity(1 + payload.len());
94 buf.push(INVITE_USE_SCHEMA_VERSION);
95 buf.extend_from_slice(&payload);
96 buf
97 }
98
99 pub fn deserialize(bytes: &[u8]) -> Option<Self> {
100 let (&version, payload) = bytes.split_first()?;
101 match version {
102 INVITE_USE_SCHEMA_VERSION => postcard::from_bytes(payload).ok(),
103 _ => None,
104 }
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109struct SigningKeyValueV1 {
110 id: uuid::Uuid,
111 did: Option<String>,
112 public_key_did_key: String,
113 private_key_bytes: Vec<u8>,
114 used: bool,
115 created_at_ms: i64,
116 expires_at_ms: i64,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct SigningKeyValue {
121 pub id: uuid::Uuid,
122 pub did: Option<String>,
123 pub public_key_did_key: String,
124 pub private_key_bytes: Vec<u8>,
125 pub used: bool,
126 pub created_at_ms: i64,
127 pub expires_at_ms: i64,
128 pub used_at_ms: Option<i64>,
129}
130
131impl SigningKeyValue {
132 pub fn serialize(&self) -> Vec<u8> {
133 let payload =
134 postcard::to_allocvec(self).expect("SigningKeyValue serialization cannot fail");
135 let mut buf = Vec::with_capacity(1 + payload.len());
136 buf.push(SIGNING_KEY_SCHEMA_V2);
137 buf.extend_from_slice(&payload);
138 buf
139 }
140
141 pub fn deserialize(bytes: &[u8]) -> Option<Self> {
142 let (&version, payload) = bytes.split_first()?;
143 match version {
144 SIGNING_KEY_SCHEMA_V1 => {
145 let v1: SigningKeyValueV1 = postcard::from_bytes(payload).ok()?;
146 Some(Self {
147 id: v1.id,
148 did: v1.did,
149 public_key_did_key: v1.public_key_did_key,
150 private_key_bytes: v1.private_key_bytes,
151 used: v1.used,
152 created_at_ms: v1.created_at_ms,
153 expires_at_ms: v1.expires_at_ms,
154 used_at_ms: None,
155 })
156 }
157 SIGNING_KEY_SCHEMA_V2 => postcard::from_bytes(payload).ok(),
158 _ => None,
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct DeletionRequestValue {
165 pub token: String,
166 pub did: String,
167 pub created_at_ms: i64,
168 pub expires_at_ms: i64,
169}
170
171impl DeletionRequestValue {
172 pub fn serialize(&self) -> Vec<u8> {
173 let payload =
174 postcard::to_allocvec(self).expect("DeletionRequestValue serialization cannot fail");
175 let mut buf = Vec::with_capacity(1 + payload.len());
176 buf.push(DELETION_REQUEST_SCHEMA_VERSION);
177 buf.extend_from_slice(&payload);
178 buf
179 }
180
181 pub fn deserialize(bytes: &[u8]) -> Option<Self> {
182 let (&version, payload) = bytes.split_first()?;
183 match version {
184 DELETION_REQUEST_SCHEMA_VERSION => postcard::from_bytes(payload).ok(),
185 _ => None,
186 }
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
191pub struct ReportValue {
192 pub id: i64,
193 pub reason_type: String,
194 pub reason: Option<String>,
195 pub subject_json: Vec<u8>,
196 pub reported_by_did: String,
197 pub created_at_ms: i64,
198}
199
200impl ReportValue {
201 pub fn serialize(&self) -> Vec<u8> {
202 let payload = postcard::to_allocvec(self).expect("ReportValue serialization cannot fail");
203 let mut buf = Vec::with_capacity(1 + payload.len());
204 buf.push(REPORT_SCHEMA_VERSION);
205 buf.extend_from_slice(&payload);
206 buf
207 }
208
209 pub fn deserialize(bytes: &[u8]) -> Option<Self> {
210 let (&version, payload) = bytes.split_first()?;
211 match version {
212 REPORT_SCHEMA_VERSION => postcard::from_bytes(payload).ok(),
213 _ => None,
214 }
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
219pub struct NotificationHistoryValue {
220 pub id: uuid::Uuid,
221 pub channel: u8,
222 pub comms_type: u8,
223 pub recipient: String,
224 pub subject: Option<String>,
225 pub body: String,
226 pub status: u8,
227 pub created_at_ms: i64,
228}
229
230impl NotificationHistoryValue {
231 pub fn serialize(&self) -> Vec<u8> {
232 let payload = postcard::to_allocvec(self)
233 .expect("NotificationHistoryValue serialization cannot fail");
234 let mut buf = Vec::with_capacity(1 + payload.len());
235 buf.push(NOTIFICATION_HISTORY_SCHEMA_VERSION);
236 buf.extend_from_slice(&payload);
237 buf
238 }
239
240 pub fn deserialize(bytes: &[u8]) -> Option<Self> {
241 let (&version, payload) = bytes.split_first()?;
242 match version {
243 NOTIFICATION_HISTORY_SCHEMA_VERSION => postcard::from_bytes(payload).ok(),
244 _ => None,
245 }
246 }
247}
248
249pub fn channel_to_u8(ch: tranquil_db_traits::CommsChannel) -> u8 {
250 match ch {
251 tranquil_db_traits::CommsChannel::Email => 0,
252 tranquil_db_traits::CommsChannel::Discord => 1,
253 tranquil_db_traits::CommsChannel::Telegram => 2,
254 tranquil_db_traits::CommsChannel::Signal => 3,
255 }
256}
257
258pub fn u8_to_channel(v: u8) -> Option<tranquil_db_traits::CommsChannel> {
259 match v {
260 0 => Some(tranquil_db_traits::CommsChannel::Email),
261 1 => Some(tranquil_db_traits::CommsChannel::Discord),
262 2 => Some(tranquil_db_traits::CommsChannel::Telegram),
263 3 => Some(tranquil_db_traits::CommsChannel::Signal),
264 _ => None,
265 }
266}
267
268pub fn comms_type_to_u8(ct: tranquil_db_traits::CommsType) -> u8 {
269 match ct {
270 tranquil_db_traits::CommsType::Welcome => 0,
271 tranquil_db_traits::CommsType::EmailVerification => 1,
272 tranquil_db_traits::CommsType::PasswordReset => 2,
273 tranquil_db_traits::CommsType::EmailUpdate => 3,
274 tranquil_db_traits::CommsType::AccountDeletion => 4,
275 tranquil_db_traits::CommsType::AdminEmail => 5,
276 tranquil_db_traits::CommsType::PlcOperation => 6,
277 tranquil_db_traits::CommsType::TwoFactorCode => 7,
278 tranquil_db_traits::CommsType::PasskeyRecovery => 8,
279 tranquil_db_traits::CommsType::LegacyLoginAlert => 9,
280 tranquil_db_traits::CommsType::MigrationVerification => 10,
281 tranquil_db_traits::CommsType::ChannelVerification => 11,
282 tranquil_db_traits::CommsType::ChannelVerified => 12,
283 }
284}
285
286pub fn u8_to_comms_type(v: u8) -> Option<tranquil_db_traits::CommsType> {
287 match v {
288 0 => Some(tranquil_db_traits::CommsType::Welcome),
289 1 => Some(tranquil_db_traits::CommsType::EmailVerification),
290 2 => Some(tranquil_db_traits::CommsType::PasswordReset),
291 3 => Some(tranquil_db_traits::CommsType::EmailUpdate),
292 4 => Some(tranquil_db_traits::CommsType::AccountDeletion),
293 5 => Some(tranquil_db_traits::CommsType::AdminEmail),
294 6 => Some(tranquil_db_traits::CommsType::PlcOperation),
295 7 => Some(tranquil_db_traits::CommsType::TwoFactorCode),
296 8 => Some(tranquil_db_traits::CommsType::PasskeyRecovery),
297 9 => Some(tranquil_db_traits::CommsType::LegacyLoginAlert),
298 10 => Some(tranquil_db_traits::CommsType::MigrationVerification),
299 11 => Some(tranquil_db_traits::CommsType::ChannelVerification),
300 12 => Some(tranquil_db_traits::CommsType::ChannelVerified),
301 _ => None,
302 }
303}
304
305pub fn status_to_u8(s: tranquil_db_traits::CommsStatus) -> u8 {
306 match s {
307 tranquil_db_traits::CommsStatus::Pending => 0,
308 tranquil_db_traits::CommsStatus::Processing => 1,
309 tranquil_db_traits::CommsStatus::Sent => 2,
310 tranquil_db_traits::CommsStatus::Failed => 3,
311 }
312}
313
314pub fn u8_to_status(v: u8) -> Option<tranquil_db_traits::CommsStatus> {
315 match v {
316 0 => Some(tranquil_db_traits::CommsStatus::Pending),
317 1 => Some(tranquil_db_traits::CommsStatus::Processing),
318 2 => Some(tranquil_db_traits::CommsStatus::Sent),
319 3 => Some(tranquil_db_traits::CommsStatus::Failed),
320 _ => None,
321 }
322}
323
324pub fn comms_queue_key(id: uuid::Uuid) -> SmallVec<[u8; 128]> {
325 KeyBuilder::new()
326 .tag(KeyTag::INFRA_COMMS_QUEUE)
327 .bytes(id.as_bytes())
328 .build()
329}
330
331pub fn comms_queue_prefix() -> SmallVec<[u8; 128]> {
332 KeyBuilder::new().tag(KeyTag::INFRA_COMMS_QUEUE).build()
333}
334
335pub fn invite_code_key(code: &str) -> SmallVec<[u8; 128]> {
336 KeyBuilder::new()
337 .tag(KeyTag::INFRA_INVITE_CODE)
338 .string(code)
339 .build()
340}
341
342pub fn invite_code_prefix() -> SmallVec<[u8; 128]> {
343 KeyBuilder::new().tag(KeyTag::INFRA_INVITE_CODE).build()
344}
345
346pub fn invite_use_key(code: &str, used_by: uuid::Uuid) -> SmallVec<[u8; 128]> {
347 KeyBuilder::new()
348 .tag(KeyTag::INFRA_INVITE_USE)
349 .string(code)
350 .bytes(used_by.as_bytes())
351 .build()
352}
353
354pub fn invite_use_prefix(code: &str) -> SmallVec<[u8; 128]> {
355 KeyBuilder::new()
356 .tag(KeyTag::INFRA_INVITE_USE)
357 .string(code)
358 .build()
359}
360
361pub fn invite_by_account_key(did: &str) -> SmallVec<[u8; 128]> {
362 KeyBuilder::new()
363 .tag(KeyTag::INFRA_INVITE_BY_ACCOUNT)
364 .string(did)
365 .build()
366}
367
368pub fn invite_by_user_key(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> {
369 KeyBuilder::new()
370 .tag(KeyTag::INFRA_INVITE_BY_USER)
371 .bytes(user_id.as_bytes())
372 .build()
373}
374
375pub fn invite_by_user_prefix() -> SmallVec<[u8; 128]> {
376 KeyBuilder::new().tag(KeyTag::INFRA_INVITE_BY_USER).build()
377}
378
379pub fn signing_key_key(public_key_did_key: &str) -> SmallVec<[u8; 128]> {
380 KeyBuilder::new()
381 .tag(KeyTag::INFRA_SIGNING_KEY)
382 .string(public_key_did_key)
383 .build()
384}
385
386pub fn signing_key_by_id_key(key_id: uuid::Uuid) -> SmallVec<[u8; 128]> {
387 KeyBuilder::new()
388 .tag(KeyTag::INFRA_SIGNING_KEY_BY_ID)
389 .bytes(key_id.as_bytes())
390 .build()
391}
392
393pub fn deletion_request_key(token: &str) -> SmallVec<[u8; 128]> {
394 KeyBuilder::new()
395 .tag(KeyTag::INFRA_DELETION_REQUEST)
396 .string(token)
397 .build()
398}
399
400pub fn deletion_by_did_key(did: &str) -> SmallVec<[u8; 128]> {
401 KeyBuilder::new()
402 .tag(KeyTag::INFRA_DELETION_BY_DID)
403 .string(did)
404 .build()
405}
406
407pub fn deletion_by_did_prefix() -> SmallVec<[u8; 128]> {
408 KeyBuilder::new().tag(KeyTag::INFRA_DELETION_BY_DID).build()
409}
410
411pub fn account_pref_key(user_id: uuid::Uuid, name: &str) -> SmallVec<[u8; 128]> {
412 KeyBuilder::new()
413 .tag(KeyTag::INFRA_ACCOUNT_PREF)
414 .bytes(user_id.as_bytes())
415 .string(name)
416 .build()
417}
418
419pub fn account_pref_prefix(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> {
420 KeyBuilder::new()
421 .tag(KeyTag::INFRA_ACCOUNT_PREF)
422 .bytes(user_id.as_bytes())
423 .build()
424}
425
426pub fn server_config_key(key: &str) -> SmallVec<[u8; 128]> {
427 KeyBuilder::new()
428 .tag(KeyTag::INFRA_SERVER_CONFIG)
429 .string(key)
430 .build()
431}
432
433pub fn report_key(id: i64) -> SmallVec<[u8; 128]> {
434 KeyBuilder::new().tag(KeyTag::INFRA_REPORT).i64(id).build()
435}
436
437pub fn plc_token_key(user_id: uuid::Uuid, token: &str) -> SmallVec<[u8; 128]> {
438 KeyBuilder::new()
439 .tag(KeyTag::INFRA_PLC_TOKEN)
440 .bytes(user_id.as_bytes())
441 .string(token)
442 .build()
443}
444
445pub fn plc_token_prefix(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> {
446 KeyBuilder::new()
447 .tag(KeyTag::INFRA_PLC_TOKEN)
448 .bytes(user_id.as_bytes())
449 .build()
450}
451
452pub fn comms_history_key(
453 user_id: uuid::Uuid,
454 created_at_ms: i64,
455 seq: u32,
456 id: uuid::Uuid,
457) -> SmallVec<[u8; 128]> {
458 let reversed_ts = i64::MAX.saturating_sub(created_at_ms);
459 let reversed_seq = u32::MAX.saturating_sub(seq);
460 KeyBuilder::new()
461 .tag(KeyTag::INFRA_COMMS_HISTORY)
462 .bytes(user_id.as_bytes())
463 .i64(reversed_ts)
464 .bytes(&reversed_seq.to_be_bytes())
465 .bytes(id.as_bytes())
466 .build()
467}
468
469pub fn comms_history_prefix(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> {
470 KeyBuilder::new()
471 .tag(KeyTag::INFRA_COMMS_HISTORY)
472 .bytes(user_id.as_bytes())
473 .build()
474}
475
476pub fn invite_code_used_by_key(user_id: uuid::Uuid) -> SmallVec<[u8; 128]> {
477 KeyBuilder::new()
478 .tag(KeyTag::INFRA_INVITE_CODE_USED_BY)
479 .bytes(user_id.as_bytes())
480 .build()
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486 use crate::metastore::encoding::KeyReader;
487
488 #[test]
489 fn queued_comms_value_roundtrip() {
490 let val = QueuedCommsValue {
491 id: uuid::Uuid::new_v4(),
492 user_id: Some(uuid::Uuid::new_v4()),
493 channel: 0,
494 comms_type: 1,
495 recipient: "user@example.com".to_owned(),
496 subject: Some("test".to_owned()),
497 body: "body text".to_owned(),
498 metadata: None,
499 status: 0,
500 error_message: None,
501 attempts: 0,
502 max_attempts: 3,
503 created_at_ms: 1700000000000,
504 scheduled_for_ms: 1700000000000,
505 sent_at_ms: None,
506 };
507 let bytes = val.serialize();
508 assert_eq!(bytes[0], COMMS_SCHEMA_VERSION);
509 let decoded = QueuedCommsValue::deserialize(&bytes).unwrap();
510 assert_eq!(val, decoded);
511 }
512
513 #[test]
514 fn invite_code_value_roundtrip() {
515 let val = InviteCodeValue {
516 code: "abc-def-ghi".to_owned(),
517 available_uses: 5,
518 disabled: false,
519 for_account: Some("did:plc:test".to_owned()),
520 created_by: Some(uuid::Uuid::new_v4()),
521 created_at_ms: 1700000000000,
522 };
523 let bytes = val.serialize();
524 assert_eq!(bytes[0], INVITE_CODE_SCHEMA_VERSION);
525 let decoded = InviteCodeValue::deserialize(&bytes).unwrap();
526 assert_eq!(val, decoded);
527 }
528
529 #[test]
530 fn invite_use_value_roundtrip() {
531 let val = InviteCodeUseValue {
532 used_by: uuid::Uuid::new_v4(),
533 used_at_ms: 1700000000000,
534 };
535 let bytes = val.serialize();
536 let decoded = InviteCodeUseValue::deserialize(&bytes).unwrap();
537 assert_eq!(val, decoded);
538 }
539
540 #[test]
541 fn signing_key_value_roundtrip() {
542 let val = SigningKeyValue {
543 id: uuid::Uuid::new_v4(),
544 did: Some("did:plc:test".to_owned()),
545 public_key_did_key: "did:key:z123".to_owned(),
546 private_key_bytes: vec![1, 2, 3, 4],
547 used: false,
548 created_at_ms: 1700000000000,
549 expires_at_ms: 1700000600000,
550 used_at_ms: None,
551 };
552 let bytes = val.serialize();
553 let decoded = SigningKeyValue::deserialize(&bytes).unwrap();
554 assert_eq!(val, decoded);
555 }
556
557 #[test]
558 fn signing_key_value_v1_migration() {
559 let v1 = SigningKeyValueV1 {
560 id: uuid::Uuid::new_v4(),
561 did: Some("did:plc:test".to_owned()),
562 public_key_did_key: "did:key:z123".to_owned(),
563 private_key_bytes: vec![1, 2, 3, 4],
564 used: true,
565 created_at_ms: 1700000000000,
566 expires_at_ms: 1700000600000,
567 };
568 let payload = postcard::to_allocvec(&v1).unwrap();
569 let mut bytes = Vec::with_capacity(1 + payload.len());
570 bytes.push(SIGNING_KEY_SCHEMA_V1);
571 bytes.extend_from_slice(&payload);
572
573 let decoded = SigningKeyValue::deserialize(&bytes).unwrap();
574 assert_eq!(decoded.id, v1.id);
575 assert!(decoded.used);
576 assert_eq!(decoded.used_at_ms, None);
577 }
578
579 #[test]
580 fn deletion_request_value_roundtrip() {
581 let val = DeletionRequestValue {
582 token: "tok-abc".to_owned(),
583 did: "did:plc:test".to_owned(),
584 created_at_ms: 1700000000000,
585 expires_at_ms: 1700000600000,
586 };
587 let bytes = val.serialize();
588 let decoded = DeletionRequestValue::deserialize(&bytes).unwrap();
589 assert_eq!(val, decoded);
590 }
591
592 #[test]
593 fn report_value_roundtrip() {
594 let val = ReportValue {
595 id: 42,
596 reason_type: "spam".to_owned(),
597 reason: Some("bad content".to_owned()),
598 subject_json: b"{}".to_vec(),
599 reported_by_did: "did:plc:reporter".to_owned(),
600 created_at_ms: 1700000000000,
601 };
602 let bytes = val.serialize();
603 let decoded = ReportValue::deserialize(&bytes).unwrap();
604 assert_eq!(val, decoded);
605 }
606
607 #[test]
608 fn notification_history_value_roundtrip() {
609 let val = NotificationHistoryValue {
610 id: uuid::Uuid::new_v4(),
611 channel: 0,
612 comms_type: 1,
613 recipient: "user@example.com".to_owned(),
614 subject: None,
615 body: "notification body".to_owned(),
616 status: 2,
617 created_at_ms: 1700000000000,
618 };
619 let bytes = val.serialize();
620 let decoded = NotificationHistoryValue::deserialize(&bytes).unwrap();
621 assert_eq!(val, decoded);
622 }
623
624 #[test]
625 fn comms_queue_key_roundtrip() {
626 let id = uuid::Uuid::new_v4();
627 let key = comms_queue_key(id);
628 let mut reader = KeyReader::new(&key);
629 assert_eq!(reader.tag(), Some(KeyTag::INFRA_COMMS_QUEUE.raw()));
630 let id_bytes = reader.bytes().unwrap();
631 assert_eq!(uuid::Uuid::from_slice(&id_bytes).unwrap(), id);
632 assert!(reader.is_empty());
633 }
634
635 #[test]
636 fn invite_code_key_roundtrip() {
637 let key = invite_code_key("abc-def");
638 let mut reader = KeyReader::new(&key);
639 assert_eq!(reader.tag(), Some(KeyTag::INFRA_INVITE_CODE.raw()));
640 assert_eq!(reader.string(), Some("abc-def".to_owned()));
641 assert!(reader.is_empty());
642 }
643
644 #[test]
645 fn invite_use_key_roundtrip() {
646 let user_id = uuid::Uuid::new_v4();
647 let key = invite_use_key("code1", user_id);
648 let mut reader = KeyReader::new(&key);
649 assert_eq!(reader.tag(), Some(KeyTag::INFRA_INVITE_USE.raw()));
650 assert_eq!(reader.string(), Some("code1".to_owned()));
651 let id_bytes = reader.bytes().unwrap();
652 assert_eq!(uuid::Uuid::from_slice(&id_bytes).unwrap(), user_id);
653 assert!(reader.is_empty());
654 }
655
656 #[test]
657 fn comms_history_newest_first_ordering() {
658 let user_id = uuid::Uuid::new_v4();
659 let id_a = uuid::Uuid::new_v4();
660 let id_b = uuid::Uuid::new_v4();
661 let key_old = comms_history_key(user_id, 1000, 0, id_a);
662 let key_new = comms_history_key(user_id, 2000, 0, id_b);
663 assert!(key_new.as_slice() < key_old.as_slice());
664 }
665
666 #[test]
667 fn account_pref_key_roundtrip() {
668 let user_id = uuid::Uuid::new_v4();
669 let key = account_pref_key(user_id, "app.bsky.actor.profile");
670 let mut reader = KeyReader::new(&key);
671 assert_eq!(reader.tag(), Some(KeyTag::INFRA_ACCOUNT_PREF.raw()));
672 let id_bytes = reader.bytes().unwrap();
673 assert_eq!(uuid::Uuid::from_slice(&id_bytes).unwrap(), user_id);
674 assert_eq!(reader.string(), Some("app.bsky.actor.profile".to_owned()));
675 assert!(reader.is_empty());
676 }
677
678 #[test]
679 fn plc_token_key_roundtrip() {
680 let user_id = uuid::Uuid::new_v4();
681 let key = plc_token_key(user_id, "tok123");
682 let mut reader = KeyReader::new(&key);
683 assert_eq!(reader.tag(), Some(KeyTag::INFRA_PLC_TOKEN.raw()));
684 let id_bytes = reader.bytes().unwrap();
685 assert_eq!(uuid::Uuid::from_slice(&id_bytes).unwrap(), user_id);
686 assert_eq!(reader.string(), Some("tok123".to_owned()));
687 assert!(reader.is_empty());
688 }
689
690 #[test]
691 fn deserialize_unknown_version_returns_none() {
692 let val = QueuedCommsValue {
693 id: uuid::Uuid::new_v4(),
694 user_id: None,
695 channel: 0,
696 comms_type: 0,
697 recipient: String::new(),
698 subject: None,
699 body: String::new(),
700 metadata: None,
701 status: 0,
702 error_message: None,
703 attempts: 0,
704 max_attempts: 3,
705 created_at_ms: 0,
706 scheduled_for_ms: 0,
707 sent_at_ms: None,
708 };
709 let mut bytes = val.serialize();
710 bytes[0] = 99;
711 assert!(QueuedCommsValue::deserialize(&bytes).is_none());
712 }
713
714 #[test]
715 fn channel_u8_roundtrip() {
716 use tranquil_db_traits::CommsChannel;
717 [
718 CommsChannel::Email,
719 CommsChannel::Discord,
720 CommsChannel::Telegram,
721 CommsChannel::Signal,
722 ]
723 .iter()
724 .for_each(|&ch| {
725 assert_eq!(u8_to_channel(channel_to_u8(ch)), Some(ch));
726 });
727 }
728
729 #[test]
730 fn comms_type_u8_roundtrip() {
731 use tranquil_db_traits::CommsType;
732 [
733 CommsType::Welcome,
734 CommsType::EmailVerification,
735 CommsType::PasswordReset,
736 CommsType::EmailUpdate,
737 CommsType::AccountDeletion,
738 CommsType::AdminEmail,
739 CommsType::PlcOperation,
740 CommsType::TwoFactorCode,
741 CommsType::PasskeyRecovery,
742 CommsType::LegacyLoginAlert,
743 CommsType::MigrationVerification,
744 CommsType::ChannelVerification,
745 CommsType::ChannelVerified,
746 ]
747 .iter()
748 .for_each(|&ct| {
749 assert_eq!(u8_to_comms_type(comms_type_to_u8(ct)), Some(ct));
750 });
751 }
752
753 #[test]
754 fn status_u8_roundtrip() {
755 use tranquil_db_traits::CommsStatus;
756 [
757 CommsStatus::Pending,
758 CommsStatus::Processing,
759 CommsStatus::Sent,
760 CommsStatus::Failed,
761 ]
762 .iter()
763 .for_each(|&s| {
764 assert_eq!(u8_to_status(status_to_u8(s)), Some(s));
765 });
766 }
767
768 #[test]
769 fn u8_to_channel_invalid_returns_none() {
770 assert!(u8_to_channel(255).is_none());
771 }
772
773 #[test]
774 fn u8_to_comms_type_invalid_returns_none() {
775 assert!(u8_to_comms_type(255).is_none());
776 }
777
778 #[test]
779 fn u8_to_status_invalid_returns_none() {
780 assert!(u8_to_status(255).is_none());
781 }
782}