user_store.rs
1 //! User-Scoped Storage 2 //! 3 //! Provides isolated storage for a single user with automatic key prefixing. 4 5 use std::sync::Arc; 6 use super::{StorageBackend, StorageError}; 7 use crate::codec; 8 9 /// Collection names for structured storage 10 pub mod collections { 11 pub const CHATS: &str = "chats"; 12 pub const CONTACTS: &str = "contacts"; 13 pub const CIRCLES: &str = "circles"; 14 pub const CIRCLE_MESSAGES: &str = "circle_messages"; 15 pub const CHUNKS: &str = "chunks"; 16 pub const META: &str = "meta"; 17 } 18 19 /// User-scoped storage with automatic key prefixing 20 /// 21 /// All operations are isolated to this user's keyspace. 22 /// Key format: `{user_id:32}/{collection}/{key}` 23 pub struct UserStore { 24 user_id: [u8; 32], 25 backend: Arc<dyn StorageBackend>, 26 } 27 28 impl UserStore { 29 /// Create a new user store 30 pub fn new(user_id: [u8; 32], backend: Arc<dyn StorageBackend>) -> Self { 31 Self { user_id, backend } 32 } 33 34 /// Get the user ID 35 pub fn user_id(&self) -> &[u8; 32] { 36 &self.user_id 37 } 38 39 /// Build a prefixed key for this user 40 fn prefixed_key(&self, collection: &str, key: &[u8]) -> Vec<u8> { 41 let mut prefixed = Vec::with_capacity(32 + 1 + collection.len() + 1 + key.len()); 42 prefixed.extend_from_slice(&self.user_id); 43 prefixed.push(b'/'); 44 prefixed.extend_from_slice(collection.as_bytes()); 45 prefixed.push(b'/'); 46 prefixed.extend_from_slice(key); 47 prefixed 48 } 49 50 /// Get the prefix for a collection (for scanning) 51 fn collection_prefix(&self, collection: &str) -> Vec<u8> { 52 let mut prefix = Vec::with_capacity(32 + 1 + collection.len() + 1); 53 prefix.extend_from_slice(&self.user_id); 54 prefix.push(b'/'); 55 prefix.extend_from_slice(collection.as_bytes()); 56 prefix.push(b'/'); 57 prefix 58 } 59 60 /// Get user's root prefix (for complete user deletion) 61 fn user_prefix(&self) -> Vec<u8> { 62 let mut prefix = Vec::with_capacity(33); 63 prefix.extend_from_slice(&self.user_id); 64 prefix.push(b'/'); 65 prefix 66 } 67 68 // ───────────────────────────────────────────────────────────────────────── 69 // Generic Operations 70 // ───────────────────────────────────────────────────────────────────────── 71 72 /// Get a raw value from a collection 73 pub fn get_raw(&self, collection: &str, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError> { 74 let prefixed = self.prefixed_key(collection, key); 75 self.backend.get(&prefixed) 76 } 77 78 /// Put a raw value in a collection 79 pub fn put_raw(&self, collection: &str, key: &[u8], value: &[u8]) -> Result<(), StorageError> { 80 let prefixed = self.prefixed_key(collection, key); 81 self.backend.put(&prefixed, value) 82 } 83 84 /// Delete a key from a collection 85 pub fn delete(&self, collection: &str, key: &[u8]) -> Result<bool, StorageError> { 86 let prefixed = self.prefixed_key(collection, key); 87 self.backend.delete(&prefixed) 88 } 89 90 /// Scan all entries in a collection 91 pub fn scan_collection(&self, collection: &str) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> { 92 let prefix = self.collection_prefix(collection); 93 let results = self.backend.scan_prefix(&prefix)?; 94 95 // Strip the prefix from keys to return clean keys 96 let prefix_len = prefix.len(); 97 Ok(results.into_iter() 98 .map(|(k, v)| (k[prefix_len..].to_vec(), v)) 99 .collect()) 100 } 101 102 /// Scan entries in a collection with an additional key prefix 103 /// Uses the underlying B-tree index for O(log N) lookup instead of O(N) full scan 104 pub fn scan_with_prefix(&self, collection: &str, key_prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> { 105 // Build full prefix: user_id + collection + key_prefix 106 let mut prefix = self.collection_prefix(collection); 107 prefix.extend_from_slice(key_prefix); 108 109 let results = self.backend.scan_prefix(&prefix)?; 110 111 // Strip the full prefix from keys to return clean keys 112 let strip_len = self.collection_prefix(collection).len(); 113 Ok(results.into_iter() 114 .map(|(k, v)| (k[strip_len..].to_vec(), v)) 115 .collect()) 116 } 117 118 /// Delete all entries in a collection 119 pub fn delete_collection(&self, collection: &str) -> Result<u64, StorageError> { 120 let prefix = self.collection_prefix(collection); 121 self.backend.delete_prefix(&prefix) 122 } 123 124 /// Delete ALL user data (GDPR full delete) 125 pub fn delete_all_data(&self) -> Result<u64, StorageError> { 126 let prefix = self.user_prefix(); 127 self.backend.delete_prefix(&prefix) 128 } 129 130 // ───────────────────────────────────────────────────────────────────────── 131 // Typed Operations (using codec) 132 // ───────────────────────────────────────────────────────────────────────── 133 134 /// Get and decode a typed value 135 pub fn get<T: serde::de::DeserializeOwned>( 136 &self, 137 collection: &str, 138 key: &[u8], 139 ) -> Result<Option<T>, StorageError> { 140 match self.get_raw(collection, key)? { 141 Some(bytes) => { 142 let value = codec::decode(&bytes) 143 .map_err(|e| StorageError::Serialization(e.to_string()))?; 144 Ok(Some(value)) 145 } 146 None => Ok(None), 147 } 148 } 149 150 /// Encode and put a typed value 151 pub fn put<T: serde::Serialize>( 152 &self, 153 collection: &str, 154 key: &[u8], 155 value: &T, 156 ) -> Result<(), StorageError> { 157 let bytes = codec::encode(value) 158 .map_err(|e| StorageError::Serialization(e.to_string()))?; 159 self.put_raw(collection, key, &bytes) 160 } 161 } 162 163 #[cfg(test)] 164 mod tests { 165 use super::*; 166 use crate::storage::SledBackend; 167 use tempfile::tempdir; 168 169 fn test_backend() -> Arc<dyn StorageBackend> { 170 let dir = tempdir().unwrap(); 171 Arc::new(SledBackend::open(dir.path()).unwrap()) 172 } 173 174 #[test] 175 fn test_user_store_isolation() { 176 let backend = test_backend(); 177 178 let user1 = [1u8; 32]; 179 let user2 = [2u8; 32]; 180 181 let store1 = UserStore::new(user1, backend.clone()); 182 let store2 = UserStore::new(user2, backend.clone()); 183 184 // User 1 writes 185 store1.put_raw(collections::CHATS, b"msg1", b"hello from user1").unwrap(); 186 187 // User 2 writes same key 188 store2.put_raw(collections::CHATS, b"msg1", b"hello from user2").unwrap(); 189 190 // Each user sees only their data 191 assert_eq!( 192 store1.get_raw(collections::CHATS, b"msg1").unwrap(), 193 Some(b"hello from user1".to_vec()) 194 ); 195 assert_eq!( 196 store2.get_raw(collections::CHATS, b"msg1").unwrap(), 197 Some(b"hello from user2".to_vec()) 198 ); 199 } 200 201 #[test] 202 fn test_user_store_collection_scan() { 203 let backend = test_backend(); 204 let user = [42u8; 32]; 205 let store = UserStore::new(user, backend); 206 207 store.put_raw(collections::CHATS, b"1", b"msg1").unwrap(); 208 store.put_raw(collections::CHATS, b"2", b"msg2").unwrap(); 209 store.put_raw(collections::CONTACTS, b"alice", b"data").unwrap(); 210 211 let chats = store.scan_collection(collections::CHATS).unwrap(); 212 assert_eq!(chats.len(), 2); 213 214 // Keys are clean (no prefix) 215 let keys: Vec<_> = chats.iter().map(|(k, _)| k.clone()).collect(); 216 assert!(keys.contains(&b"1".to_vec())); 217 assert!(keys.contains(&b"2".to_vec())); 218 } 219 220 #[test] 221 fn test_user_store_gdpr_delete() { 222 let backend = test_backend(); 223 let user = [99u8; 32]; 224 let store = UserStore::new(user, backend.clone()); 225 226 store.put_raw(collections::CHATS, b"1", b"msg1").unwrap(); 227 store.put_raw(collections::CONTACTS, b"a", b"alice").unwrap(); 228 store.put_raw(collections::CIRCLES, b"c1", b"circle").unwrap(); 229 230 // Delete all user data 231 let deleted = store.delete_all_data().unwrap(); 232 assert_eq!(deleted, 3); 233 234 // Verify all gone 235 assert!(store.get_raw(collections::CHATS, b"1").unwrap().is_none()); 236 assert!(store.get_raw(collections::CONTACTS, b"a").unwrap().is_none()); 237 } 238 239 #[test] 240 fn test_user_store_typed() { 241 let backend = test_backend(); 242 let user = [7u8; 32]; 243 let store = UserStore::new(user, backend); 244 245 #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)] 246 struct TestData { 247 name: String, 248 count: u32, 249 } 250 251 let data = TestData { name: "test".into(), count: 42 }; 252 store.put(collections::META, b"config", &data).unwrap(); 253 254 let loaded: TestData = store.get(collections::META, b"config").unwrap().unwrap(); 255 assert_eq!(loaded, data); 256 } 257 }