Our Personal Data Server from scratch!
0

Configure Feed

Select the types of activity you want to include in your feed.

tranquil-pds / crates / tranquil-store / src / metastore / infra_schema.rs
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}