b3_users/data/
user.rs

1// user.rs
2use super::account::{UserAccountArgs, UserAccountData};
3use crate::error::UserStateError;
4
5use candid::Principal;
6use ic_cdk::export::{candid::CandidType, serde::Deserialize};
7use std::collections::hash_map::DefaultHasher;
8use std::collections::HashMap;
9use std::hash::{Hash, Hasher};
10
11#[derive(Debug, CandidType, Deserialize, Default, Clone)]
12pub struct UserData {
13    password: Option<u64>,
14    profile: UserProfile,
15    pub name: String,
16    pub email: String,
17    pub balance: u128,
18    pub accounts: Vec<UserAccountData>,
19    pub settings: HashMap<String, UserDataSettingValue>,
20}
21
22#[derive(Debug, CandidType, Deserialize, Default, Clone)]
23pub struct UserProfile {
24    pub full_name: Option<String>,
25    pub address: Option<String>,
26    pub phone_number: Option<String>,
27    pub attributes: HashMap<String, String>,
28}
29
30impl UserData {
31    // New values for the user data.
32    pub fn new(user_args: UserDataArgs, account_args: UserAccountArgs) -> Self {
33        let mut user_data = UserData::default();
34
35        user_data.update(user_args);
36
37        user_data.accounts = Vec::with_capacity(256);
38
39        let name = if let Some(name) = account_args.name {
40            name
41        } else {
42            "Account 0".to_string()
43        };
44
45        user_data
46            .accounts
47            .insert(0, UserAccountData::new(account_args.public_key, name));
48
49        user_data
50    }
51
52    /// Updates the user's data based on the provided UserDataArgs struct.
53    /// Only updates fields that are set to Some(value) in the options.
54    pub fn update(&mut self, args: UserDataArgs) -> UserDataArgs {
55        if let Some(name) = args.name {
56            self.name = name;
57        }
58
59        if let Some(email) = args.email {
60            self.email = email;
61        }
62
63        if let Some(balance) = args.balance {
64            self.balance = balance;
65        }
66
67        if let Some(settings) = args.settings {
68            for (key, value) in settings {
69                self.settings.insert(key, value);
70            }
71        }
72
73        if let Some(profile) = args.profile {
74            self.update_profile(profile);
75        }
76
77        if let Some(password) = args.password {
78            self.password = Some(hash_password(&password));
79        }
80
81        UserDataArgs {
82            name: Some(self.name.clone()),
83            email: Some(self.email.clone()),
84            balance: Some(self.balance),
85            settings: Some(self.settings.clone()),
86            profile: Some(UserProfileArgs {
87                full_name: self.profile.full_name.clone(),
88                address: self.profile.address.clone(),
89                phone_number: self.profile.phone_number.clone(),
90                attributes: Some(self.profile.attributes.clone()),
91            }),
92            password: None,
93        }
94    }
95
96    /// Update the user's profile.
97    /// Only updates fields that are set to Some(value) in the options.
98    /// Returns the updated profile.
99    pub fn update_profile(&mut self, args: UserProfileArgs) -> UserProfileArgs {
100        if let Some(full_name) = args.full_name {
101            self.profile.full_name = Some(full_name);
102        }
103
104        if let Some(account) = args.address {
105            self.profile.address = Some(account);
106        }
107
108        if let Some(phone_number) = args.phone_number {
109            self.profile.phone_number = Some(phone_number);
110        }
111
112        if let Some(attributes) = args.attributes {
113            for (key, value) in attributes {
114                self.profile.attributes.insert(key, value);
115            }
116        }
117
118        UserProfileArgs {
119            full_name: self.profile.full_name.clone(),
120            address: self.profile.address.clone(),
121            phone_number: self.profile.phone_number.clone(),
122            attributes: Some(self.profile.attributes.clone()),
123        }
124    }
125
126    /// Adds a new account for the user with the given key and account data.
127    /// Returns an error if the key already exists.
128    pub fn create_account(
129        &mut self,
130        args: UserAccountArgs,
131    ) -> Result<UserAccountData, UserStateError> {
132        let key = self.get_new_key()?;
133
134        let public_key = args.public_key;
135
136        let name = if let Some(name) = args.name {
137            name
138        } else {
139            format!("Account {}", key)
140        };
141
142        let account_data = UserAccountData::new(public_key, name);
143
144        self.accounts.insert(key, account_data.clone());
145
146        Ok(account_data)
147    }
148
149    /// New Key to add to derivation path based on the number of accounts created.
150    /// Returns the new key.
151    pub fn get_new_key(&self) -> Result<usize, UserStateError> {
152        if self.accounts.len() > 255 {
153            return Err(UserStateError::AccountLimitReached);
154        }
155
156        let key = self.accounts.len();
157
158        Ok(key)
159    }
160
161    /// Get Derivation Path for the given key.
162    /// Returns the derivation path.
163    pub fn get_derivation_path(
164        &self,
165        principal: Principal,
166        key: u8,
167    ) -> Result<Vec<u8>, UserStateError> {
168        if key as usize > self.accounts.len() {
169            return Err(UserStateError::InvalidAccountKey);
170        }
171
172        let mut derivation_path = principal.as_slice().to_vec();
173
174        derivation_path.push(key);
175
176        Ok(derivation_path)
177    }
178
179    /// Checks if the key is valid for the user.
180    /// Returns an error if the key is invalid.
181    pub fn get_key_is_valid(&self, key: usize) -> Result<(), UserStateError> {
182        if key >= self.accounts.len() {
183            return Err(UserStateError::InvalidAccountKey);
184        }
185
186        Ok(())
187    }
188
189    /// Retrieves an account by key for the user.
190    /// Returns the account data if the key is found, or error if the key was not found.
191    pub fn get_account(&self, key: usize) -> Result<UserAccountData, UserStateError> {
192        self.get_key_is_valid(key)?;
193
194        Ok(self.accounts[key].clone())
195    }
196
197    /// Retrieves a mutable reference to the account by key for the user.
198    /// Returns the account data if the key is found, or error if the key was not found.
199    pub fn get_account_mut(&mut self, key: usize) -> Result<&mut UserAccountData, UserStateError> {
200        self.get_key_is_valid(key)?;
201
202        Ok(&mut self.accounts[key])
203    }
204
205    /// Retrieves all the accounts created by the user.
206    /// Returns a Vec of UserAccountData objects.
207    pub fn get_accounts(&self) -> &Vec<UserAccountData> {
208        &self.accounts
209    }
210
211    /// Sets the password for the user.
212    pub fn set_password(&mut self, password: &str) -> Result<(), UserStateError> {
213        let hashed_password = hash_password(password);
214
215        self.password = Some(hashed_password);
216
217        Ok(())
218    }
219
220    /// Checks if the provided password matches the user's password.
221    pub fn check_password(&self, password: &str) -> Result<bool, UserStateError> {
222        let input_hash = hash_password(password);
223
224        if let Some(password_hash) = self.password {
225            Ok(input_hash == password_hash)
226        } else {
227            Err(UserStateError::PasswordNotSet)
228        }
229    }
230
231    /// Retrieves the user's setting.
232    /// Returns the value if the key is found, or None if the key was not found.
233    pub fn get_setting(&self, key: &str) -> Result<UserDataSettingValue, UserStateError> {
234        if let Some(value) = self.settings.get(key) {
235            Ok(value.clone())
236        } else {
237            Err(UserStateError::SettingNotFound)
238        }
239    }
240
241    /// Retrieves a mutable reference to the user's setting.
242    /// Returns the value if the key is found, or None if the key was not found.
243    pub fn get_setting_mut(
244        &mut self,
245        key: &str,
246    ) -> Result<&mut UserDataSettingValue, UserStateError> {
247        if let Some(value) = self.settings.get_mut(key) {
248            Ok(value)
249        } else {
250            Err(UserStateError::SettingNotFound)
251        }
252    }
253
254    /// Retrieves a setting value by key for the user.
255    /// Returns the value if the key is found, or None if the key was not found.
256    pub fn get_settings(&self) -> &HashMap<String, UserDataSettingValue> {
257        &self.settings
258    }
259
260    /// Sets a setting key-value pair for the user.
261    pub fn set_setting(&mut self, key: String, value: UserDataSettingValue) -> () {
262        let value = value.into();
263        self.settings.insert(key, value);
264    }
265
266    /// Removes a setting key for the user.
267    /// Returns the removed value if it existed, or None if the key was not found.
268    pub fn remove_setting(&mut self, key: &str) -> Result<bool, UserStateError> {
269        if self.settings.remove(key).is_some() {
270            Ok(true)
271        } else {
272            Err(UserStateError::SettingNotFound)
273        }
274    }
275
276    /// Updates all the settings for the user with the new settings.
277    pub fn update_settings(&mut self, new_settings: HashMap<String, UserDataSettingValue>) {
278        self.settings = new_settings;
279    }
280}
281
282fn hash_password(password: &str) -> u64 {
283    let mut hasher = DefaultHasher::new();
284    password.hash(&mut hasher);
285
286    hasher.finish()
287}
288
289#[derive(CandidType, Deserialize, Default, Debug, Clone, PartialEq)]
290pub struct UserDataArgs {
291    pub name: Option<String>,
292    pub email: Option<String>,
293    pub balance: Option<u128>,
294    pub password: Option<String>,
295    pub profile: Option<UserProfileArgs>,
296    pub settings: Option<HashMap<String, UserDataSettingValue>>,
297}
298
299#[derive(Debug, CandidType, Clone, Default, Deserialize, PartialEq)]
300pub struct UserProfileArgs {
301    pub full_name: Option<String>,
302    pub address: Option<String>,
303    pub phone_number: Option<String>,
304    pub attributes: Option<HashMap<String, String>>,
305}
306
307#[derive(Debug, CandidType, Deserialize, Clone, PartialEq)]
308pub enum UserDataSettingValue {
309    StringValue(String),
310    NumberValue(u128),
311    FloatValue(f64),
312    BoolValue(bool),
313}
314
315impl From<String> for UserDataSettingValue {
316    fn from(value: String) -> Self {
317        UserDataSettingValue::StringValue(value)
318    }
319}
320
321impl From<f64> for UserDataSettingValue {
322    fn from(value: f64) -> Self {
323        UserDataSettingValue::FloatValue(value)
324    }
325}
326
327impl From<u128> for UserDataSettingValue {
328    fn from(value: u128) -> Self {
329        UserDataSettingValue::NumberValue(value)
330    }
331}
332
333impl From<bool> for UserDataSettingValue {
334    fn from(value: bool) -> Self {
335        UserDataSettingValue::BoolValue(value)
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342    use proptest::prelude::*;
343
344    proptest! {
345
346        #[test]
347        fn test_user_data_arbitrary( user_args: UserDataArgs,account_args: UserAccountArgs) {
348            let user_data = UserData::new(user_args.clone(), account_args);
349
350            assert_eq!(user_data.name, user_args.name.unwrap_or_default());
351            assert_eq!(user_data.email, user_args.email.unwrap_or_default());
352            assert_eq!(user_data.balance, user_args.balance.unwrap_or_default());
353        }
354
355        #[test]
356        fn test_create_account(account_args: UserAccountArgs, account_args2: UserAccountArgs, profile: UserProfileArgs) {
357            let args = UserDataArgs {
358                name: Some("test".to_string()),
359                email: Some("test@example.com".to_string()),
360                balance: Some(0),
361                password: Some("password".to_string()),
362                profile: Some(profile),
363                settings: None,
364            };
365
366            let mut user_data = UserData::new(args, account_args);
367
368            let account_data = user_data.create_account(account_args2.clone()).unwrap();
369
370            assert_eq!(account_data.public_key, account_args2.public_key);
371        }
372
373        #[test]
374        fn test_get_account(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
375            let mut user_data = UserData::new(user_args, account_args.clone());
376
377            let account_data = user_data.get_account(0).unwrap();
378
379            assert_eq!(account_data.public_key, account_args.public_key);
380
381            let mut account_data2 = user_data.get_account_mut(0).unwrap();
382
383            account_data2.name = name.clone();
384
385            assert_eq!(user_data.get_account(0).unwrap().name, name);
386        }
387
388        #[test]
389        fn test_update_account(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
390            let mut user_data = UserData::new(user_args, account_args.clone());
391
392            let mut account_data = user_data.get_account_mut(0).unwrap();
393
394            account_data.name = name.clone();
395
396            assert_eq!(user_data.get_account(0).unwrap().name, name);
397        }
398
399        #[test]
400        fn test_update_settings(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
401            let mut user_data = UserData::new(user_args, account_args.clone());
402
403            let mut settings = HashMap::new();
404            settings.insert("name".to_string(), name.clone().into());
405
406            user_data.update_settings(settings);
407
408            assert_eq!(user_data.settings.get("name").unwrap(), &name.into());
409        }
410
411        #[test]
412        fn test_update_password(user_args: UserDataArgs, account_args: UserAccountArgs, password: String) {
413            let mut user_data = UserData::new(user_args, account_args.clone());
414
415            user_data.set_password(&password.clone()).unwrap();
416
417
418            assert!(user_data.check_password(&password).unwrap());
419        }
420
421        #[test]
422        fn test_update_email(user_args: UserDataArgs, account_args: UserAccountArgs, email: String) {
423            let mut user_data = UserData::new(user_args, account_args.clone());
424
425            user_data.email = email.clone();
426
427            assert_eq!(user_data.email, email);
428        }
429
430        #[test]
431        fn test_update_name(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
432            let mut user_data = UserData::new(user_args, account_args.clone());
433
434            user_data.name = name.clone();
435
436            assert_eq!(user_data.name, name);
437        }
438
439        #[test]
440        fn test_update_balance(user_args: UserDataArgs, account_args: UserAccountArgs, balance: u128) {
441            let mut user_data = UserData::new(user_args, account_args.clone());
442
443            user_data.balance = balance;
444
445            assert_eq!(user_data.balance, balance);
446        }
447
448        #[test]
449        fn test_update_profile(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
450            let mut user_data = UserData::new(user_args, account_args.clone());
451
452            let mut profile = UserProfileArgs::default();
453            profile.full_name = Some(name.clone());
454            profile.address = Some("account".to_string());
455
456            user_data.update_profile(profile);
457
458            assert_eq!(user_data.profile.full_name.unwrap(), name);
459        }
460
461        #[test]
462        fn test_update_profile_attributes(user_args: UserDataArgs, account_args: UserAccountArgs, key: String, value: String) {
463            let mut user_data = UserData::new(user_args, account_args);
464
465            let mut profile = UserProfileArgs::default();
466
467            let mut attributes = HashMap::new();
468
469            attributes.insert(key.clone(), value.clone());
470
471            profile.attributes = Some(attributes);
472
473            user_data.update_profile(profile);
474
475            assert_eq!(user_data.profile.attributes.get(&key).unwrap(), &value);
476        }
477    }
478}