Our Personal Data Server from scratch!
0

Configure Feed

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

1use serde::{Deserialize, Serialize}; 2use smallvec::SmallVec; 3 4use super::encoding::KeyBuilder; 5use super::keys::{KeyTag, UserHash}; 6 7const SCHEMA_VERSION: u8 = 1; 8 9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 10#[repr(u8)] 11pub enum RepoStatus { 12 Active = 0, 13 Takendown = 1, 14 Suspended = 2, 15 Deactivated = 3, 16 Deleted = 4, 17} 18 19impl RepoStatus { 20 pub fn is_active(self) -> bool { 21 matches!(self, Self::Active) 22 } 23} 24 25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 26pub struct RepoMetaValue { 27 pub repo_root_cid: Vec<u8>, 28 pub repo_rev: String, 29 pub handle: String, 30 pub status: RepoStatus, 31 pub deactivated_at_ms: Option<u64>, 32 pub takedown_ref: Option<String>, 33 #[serde(default)] 34 pub did: Option<String>, 35} 36 37impl RepoMetaValue { 38 pub fn serialize(&self) -> Vec<u8> { 39 let payload = postcard::to_allocvec(self).expect("RepoMetaValue serialization cannot fail"); 40 let mut buf = Vec::with_capacity(1 + payload.len()); 41 buf.push(SCHEMA_VERSION); 42 buf.extend_from_slice(&payload); 43 buf 44 } 45 46 pub fn deserialize(bytes: &[u8]) -> Option<Self> { 47 let (&version, payload) = bytes.split_first()?; 48 match version { 49 SCHEMA_VERSION => postcard::from_bytes(payload).ok(), 50 _ => None, 51 } 52 } 53} 54 55pub fn repo_meta_key(user_hash: UserHash) -> SmallVec<[u8; 128]> { 56 KeyBuilder::new() 57 .tag(KeyTag::REPO_META) 58 .u64(user_hash.raw()) 59 .build() 60} 61 62pub fn repo_meta_prefix() -> SmallVec<[u8; 128]> { 63 KeyBuilder::new().tag(KeyTag::REPO_META).build() 64} 65 66pub fn handle_key(handle_lower: &str) -> SmallVec<[u8; 128]> { 67 KeyBuilder::new() 68 .tag(KeyTag::HANDLES) 69 .string(handle_lower) 70 .build() 71} 72 73#[cfg(test)] 74mod tests { 75 use super::*; 76 use crate::metastore::encoding::KeyReader; 77 78 #[test] 79 fn repo_meta_value_roundtrip() { 80 let value = RepoMetaValue { 81 repo_root_cid: vec![0x01, 0x71, 0x12, 0x20, 0xAB], 82 repo_rev: "3k2a7bcd".to_string(), 83 handle: "alice.bsky.social".to_string(), 84 status: RepoStatus::Active, 85 deactivated_at_ms: None, 86 takedown_ref: None, 87 did: None, 88 }; 89 let bytes = value.serialize(); 90 let decoded = RepoMetaValue::deserialize(&bytes).unwrap(); 91 assert_eq!(decoded, value); 92 } 93 94 #[test] 95 fn repo_meta_value_with_optional_fields() { 96 let value = RepoMetaValue { 97 repo_root_cid: vec![0x01], 98 repo_rev: "rev1".to_string(), 99 handle: "bob.example.com".to_string(), 100 status: RepoStatus::Deactivated, 101 deactivated_at_ms: Some(1700000000000), 102 takedown_ref: Some("DMCA-123".to_string()), 103 did: Some("did:plc:bob".to_string()), 104 }; 105 let bytes = value.serialize(); 106 let decoded = RepoMetaValue::deserialize(&bytes).unwrap(); 107 assert_eq!(decoded, value); 108 } 109 110 #[test] 111 fn repo_meta_key_roundtrip() { 112 let hash = UserHash::from_raw(0xDEAD_BEEF_CAFE_BABE); 113 let key = repo_meta_key(hash); 114 let mut reader = KeyReader::new(&key); 115 assert_eq!(reader.tag(), Some(KeyTag::REPO_META.raw())); 116 assert_eq!(reader.u64(), Some(0xDEAD_BEEF_CAFE_BABE)); 117 assert!(reader.is_empty()); 118 } 119 120 #[test] 121 fn repo_meta_keys_sort_by_user_hash() { 122 let k1 = repo_meta_key(UserHash::from_raw(1)); 123 let k2 = repo_meta_key(UserHash::from_raw(2)); 124 let k3 = repo_meta_key(UserHash::from_raw(u64::MAX)); 125 assert!(k1.as_slice() < k2.as_slice()); 126 assert!(k2.as_slice() < k3.as_slice()); 127 } 128 129 #[test] 130 fn handle_key_roundtrip() { 131 let key = handle_key("alice.bsky.social"); 132 let mut reader = KeyReader::new(&key); 133 assert_eq!(reader.tag(), Some(KeyTag::HANDLES.raw())); 134 assert_eq!(reader.string(), Some("alice.bsky.social".to_string())); 135 assert!(reader.is_empty()); 136 } 137 138 #[test] 139 fn handle_keys_sort_lexicographically() { 140 let k1 = handle_key("alice.example.com"); 141 let k2 = handle_key("bob.example.com"); 142 assert!(k1.as_slice() < k2.as_slice()); 143 } 144 145 #[test] 146 fn all_repo_statuses_roundtrip() { 147 [ 148 RepoStatus::Active, 149 RepoStatus::Takendown, 150 RepoStatus::Suspended, 151 RepoStatus::Deactivated, 152 RepoStatus::Deleted, 153 ] 154 .iter() 155 .for_each(|&status| { 156 let value = RepoMetaValue { 157 repo_root_cid: vec![0x01], 158 repo_rev: "r".to_string(), 159 handle: "h.test".to_string(), 160 status, 161 deactivated_at_ms: None, 162 takedown_ref: None, 163 did: None, 164 }; 165 let decoded = RepoMetaValue::deserialize(&value.serialize()).unwrap(); 166 assert_eq!(decoded.status, status); 167 }); 168 } 169 170 #[test] 171 fn repo_status_serialization_stability() { 172 [ 173 (RepoStatus::Active, 0u8), 174 (RepoStatus::Takendown, 1), 175 (RepoStatus::Suspended, 2), 176 (RepoStatus::Deactivated, 3), 177 (RepoStatus::Deleted, 4), 178 ] 179 .iter() 180 .for_each(|&(status, expected_byte)| { 181 let bytes = postcard::to_allocvec(&status).unwrap(); 182 assert_eq!( 183 bytes, 184 [expected_byte], 185 "{status:?} serialized to {bytes:?}, expected [{expected_byte}]" 186 ); 187 }); 188 } 189 190 #[test] 191 fn schema_version_is_first_byte() { 192 let value = RepoMetaValue { 193 repo_root_cid: vec![0x01], 194 repo_rev: "r".to_string(), 195 handle: "h.test".to_string(), 196 status: RepoStatus::Active, 197 deactivated_at_ms: None, 198 takedown_ref: None, 199 did: None, 200 }; 201 let bytes = value.serialize(); 202 assert_eq!(bytes[0], SCHEMA_VERSION); 203 assert_eq!(bytes[0], 1, "schema version must remain 1 for this format"); 204 } 205 206 #[test] 207 fn deserialize_rejects_unknown_schema_version() { 208 let value = RepoMetaValue { 209 repo_root_cid: vec![0x01], 210 repo_rev: "r".to_string(), 211 handle: "h.test".to_string(), 212 status: RepoStatus::Active, 213 deactivated_at_ms: None, 214 takedown_ref: None, 215 did: None, 216 }; 217 let mut bytes = value.serialize(); 218 bytes[0] = 99; 219 assert!(RepoMetaValue::deserialize(&bytes).is_none()); 220 } 221 222 #[test] 223 fn deserialize_rejects_empty_input() { 224 assert!(RepoMetaValue::deserialize(&[]).is_none()); 225 } 226}