/ abzu-core / src / storage / user_store.rs
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  }