Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.2-dev into master #32

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ arboard = { version = "3.4.0", default-features = false, features = [
"windows-sys",
] }
enum_dispatch = "0.3.13"
directories = "5.0"

rusqlite = { version = "0.32.1", features = ["functions"]}
serde_yaml = "0.9.34+deprecated"
Expand Down
Binary file added icons/withdraws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 41 additions & 6 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use crate::app_dir::{
app_user_data_file_path, copy_env_file_if_not_exists,
create_app_user_data_directory_if_not_exists,
};
use crate::context::AppContext;
use crate::database::Database;
use crate::logging::initialize_logger;
use crate::platform::{BackendTask, BackendTaskSuccessResult};
use crate::ui::document_query_screen::DocumentQueryScreen;
use crate::ui::dpns_contested_names_screen::DPNSContestedNamesScreen;
use crate::ui::dpns_contested_names_screen::{DPNSContestedNamesScreen, DPNSSubscreen};
use crate::ui::identities::identities_screen::IdentitiesScreen;
use crate::ui::network_chooser_screen::NetworkChooserScreen;
use crate::ui::transition_visualizer_screen::TransitionVisualizerScreen;
use crate::ui::withdraws_status_screen::WithdrawsStatusScreen;
use crate::ui::{MessageType, RootScreenType, Screen, ScreenLike, ScreenType};
use dash_sdk::dpp::dashcore::Network;
use derive_more::From;
Expand Down Expand Up @@ -98,8 +103,11 @@ impl BitOrAssign for AppAction {
}
impl AppState {
pub fn new() -> Self {
create_app_user_data_directory_if_not_exists().expect("Failed to create app user_data directory");
copy_env_file_if_not_exists();
Comment on lines +106 to +107
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Inconsistent error handling between function calls

In the AppState::new() method, the call to create_app_user_data_directory_if_not_exists() uses .expect("Failed to create app user_data directory") to handle potential errors by panicking with a message. However, the subsequent call to copy_env_file_if_not_exists() does not have any error handling. If copy_env_file_if_not_exists() can fail or return a Result, it's important to handle the error consistently to prevent silent failures.

Apply this diff to handle potential errors from copy_env_file_if_not_exists():

     create_app_user_data_directory_if_not_exists().expect("Failed to create app user_data directory");
-    copy_env_file_if_not_exists();
+    copy_env_file_if_not_exists().expect("Failed to copy env file");
     initialize_logger();

Committable suggestion skipped: line range outside the PR's diff.

initialize_logger();
let db = Arc::new(Database::new("identities.db").unwrap());
let db_file_path = app_user_data_file_path("data.db").expect("should create db file path");
let db = Arc::new(Database::new(db_file_path).unwrap());
db.initialize().unwrap();

let settings = db.get_settings().expect("expected to get settings");
Expand All @@ -116,10 +124,16 @@ impl AppState {
let testnet_app_context = AppContext::new(Network::Testnet, db.clone());

let mut identities_screen = IdentitiesScreen::new(&mainnet_app_context);
let mut dpns_contested_names_screen = DPNSContestedNamesScreen::new(&mainnet_app_context);
let mut dpns_active_contests_screen =
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Active);
let mut dpns_past_contests_screen =
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Past);
let mut dpns_my_usernames_screen =
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Owned);
let mut transition_visualizer_screen =
TransitionVisualizerScreen::new(&mainnet_app_context);
let mut document_query_screen = DocumentQueryScreen::new(&mainnet_app_context);
let mut withdraws_status_screen = WithdrawsStatusScreen::new(&mainnet_app_context);
let mut network_chooser_screen = NetworkChooserScreen::new(
&mainnet_app_context,
testnet_app_context.as_ref(),
Expand All @@ -136,9 +150,15 @@ impl AppState {
if network == Network::Testnet && testnet_app_context.is_some() {
let testnet_app_context = testnet_app_context.as_ref().unwrap();
identities_screen = IdentitiesScreen::new(testnet_app_context);
dpns_contested_names_screen = DPNSContestedNamesScreen::new(testnet_app_context);
dpns_active_contests_screen =
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Active);
dpns_past_contests_screen =
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Past);
dpns_my_usernames_screen =
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Owned);
transition_visualizer_screen = TransitionVisualizerScreen::new(testnet_app_context);
document_query_screen = DocumentQueryScreen::new(testnet_app_context);
withdraws_status_screen = WithdrawsStatusScreen::new(testnet_app_context);
}
network_chooser_screen.current_network = chosen_network;
}
Expand All @@ -156,8 +176,16 @@ impl AppState {
Screen::IdentitiesScreen(identities_screen),
),
(
RootScreenType::RootScreenDPNSContestedNames,
Screen::DPNSContestedNamesScreen(dpns_contested_names_screen),
RootScreenType::RootScreenDPNSActiveContests,
Screen::DPNSContestedNamesScreen(dpns_active_contests_screen),
),
(
RootScreenType::RootScreenDPNSPastContests,
Screen::DPNSContestedNamesScreen(dpns_past_contests_screen),
),
(
RootScreenType::RootScreenDPNSOwnedNames,
Screen::DPNSContestedNamesScreen(dpns_my_usernames_screen),
),
(
RootScreenType::RootScreenTransitionVisualizerScreen,
Expand All @@ -167,6 +195,10 @@ impl AppState {
RootScreenType::RootScreenDocumentQuery,
Screen::DocumentQueryScreen(document_query_screen),
),
(
RootScreenType::RootScreenWithdrawsStatus,
Screen::WithdrawsStatusScreen(withdraws_status_screen),
),
(
RootScreenType::RootScreenNetworkChooser,
Screen::NetworkChooserScreen(network_chooser_screen),
Expand Down Expand Up @@ -279,6 +311,9 @@ impl App for AppState {
BackendTaskSuccessResult::SuccessfulVotes(_) => {
self.visible_screen_mut().refresh();
}
BackendTaskSuccessResult::WithdrawalStatus(_) => {
self.visible_screen_mut().display_task_result(message);
}
},
TaskResult::Error(message) => {
self.visible_screen_mut()
Expand Down
56 changes: 56 additions & 0 deletions src/app_dir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use directories::ProjectDirs;
use std::fs;
use std::path::{Path, PathBuf};

const QUALIFIER: &str = ""; // Typically empty on macOS and Linux
const ORGANIZATION: &str = "";
const APPLICATION: &str = "DashEvoTool";

pub fn app_user_data_dir_path() -> Result<PathBuf, std::io::Error> {
let proj_dirs = ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
.ok_or_else(|| std::io::Error::new(
std::io::ErrorKind::NotFound,
"Failed to determine project directories",
))?;
Ok(proj_dirs.config_dir().to_path_buf())
}
pub fn create_app_user_data_directory_if_not_exists() -> Result<(), std::io::Error> {
let app_data_dir = app_user_data_dir_path()?;
fs::create_dir_all(&app_data_dir)?;

// Verify directory permissions
let metadata = fs::metadata(&app_data_dir)?;
if !metadata.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Created path is not a directory",
));
}
Ok(())
}
Comment on lines +17 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding permission checks for Unix systems.

While the function correctly creates and verifies the directory, it might be worth adding permission checks on Unix systems to ensure the directory is readable and writable by the current user.

     if !metadata.is_dir() {
         return Err(std::io::Error::new(
             std::io::ErrorKind::Other,
             "Created path is not a directory",
         ));
     }
+    #[cfg(unix)]
+    {
+        use std::os::unix::fs::PermissionsExt;
+        let perms = metadata.permissions();
+        if perms.mode() & 0o600 != 0o600 {
+            return Err(std::io::Error::new(
+                std::io::ErrorKind::PermissionDenied,
+                "Insufficient directory permissions",
+            ));
+        }
+    }
     Ok(())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn create_app_user_data_directory_if_not_exists() -> Result<(), std::io::Error> {
let app_data_dir = app_user_data_dir_path()?;
fs::create_dir_all(&app_data_dir)?;
// Verify directory permissions
let metadata = fs::metadata(&app_data_dir)?;
if !metadata.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Created path is not a directory",
));
}
Ok(())
}
pub fn create_app_user_data_directory_if_not_exists() -> Result<(), std::io::Error> {
let app_data_dir = app_user_data_dir_path()?;
fs::create_dir_all(&app_data_dir)?;
// Verify directory permissions
let metadata = fs::metadata(&app_data_dir)?;
if !metadata.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Created path is not a directory",
));
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = metadata.permissions();
if perms.mode() & 0o600 != 0o600 {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"Insufficient directory permissions",
));
}
}
Ok(())
}


pub fn app_user_data_file_path(filename: &str) -> Result<PathBuf, std::io::Error> {
if filename.is_empty() || filename.contains(std::path::is_separator) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid filename",
));
}
let app_data_dir = app_user_data_dir_path()?;
Ok(app_data_dir.join(filename))
}
Comment on lines +32 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance filename validation.

While the function checks for path separators, consider adding validation for:

  1. Maximum filename length
  2. Special characters that might cause issues
  3. Reserved filenames on Windows (e.g., CON, PRN, etc.)
+const MAX_FILENAME_LENGTH: usize = 255;
+
 pub fn app_user_data_file_path(filename: &str) -> Result<PathBuf, std::io::Error> {
-    if filename.is_empty() || filename.contains(std::path::is_separator) {
+    if filename.is_empty() 
+        || filename.contains(std::path::is_separator)
+        || filename.len() > MAX_FILENAME_LENGTH
+        || filename.chars().any(|c| c.is_control())
+        || cfg!(windows) && is_windows_reserved_filename(filename)
+    {
         return Err(std::io::Error::new(
             std::io::ErrorKind::InvalidInput,
-            "Invalid filename",
+            "Invalid filename: must not be empty, contain separators, exceed length limit, or be a reserved name",
         ));
     }

Committable suggestion skipped: line range outside the PR's diff.


pub fn copy_env_file_if_not_exists() {
let app_data_dir = app_user_data_dir_path().expect("Failed to determine application data directory");
let env_file = app_data_dir.join(".env".to_string());
if env_file.exists() && env_file.is_file() {
} else {
let env_example_file = PathBuf::from(".env.example");
let target_env_file_path = app_user_data_file_path(".env").expect("should create target env file path");
fs::copy(
&env_example_file,
target_env_file_path,
)
.expect("Failed to copy main net env file");
}
}
Comment on lines +43 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Improve error handling and security in env file copying.

Several issues need attention:

  1. Using expect() can cause panics - should return Result instead
  2. No validation if source .env.example exists
  3. Potential security risk with hardcoded paths
  4. No file content validation before copying

Here's a safer implementation:

-pub fn copy_env_file_if_not_exists() {
+pub fn copy_env_file_if_not_exists() -> Result<(), std::io::Error> {
     let app_data_dir = app_user_data_dir_path()
-        .expect("Failed to determine application data directory");
+        ?;
     let env_file = app_data_dir.join(".env");
     if env_file.exists() && env_file.is_file() {
+        return Ok(());
     } else {
         let env_example_file = PathBuf::from(".env.example");
-        let target_env_file_path = app_user_data_file_path(".env")
-            .expect("should create target env file path");
+        if !env_example_file.exists() {
+            return Err(std::io::Error::new(
+                std::io::ErrorKind::NotFound,
+                "Template .env.example file not found",
+            ));
+        }
+        let target_env_file_path = app_user_data_file_path(".env")?;
-        fs::copy(
+        fs::copy(
             &env_example_file,
             target_env_file_path,
-        )
-        .expect("Failed to copy main net env file");
+        )?;
     }
+    Ok(())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn copy_env_file_if_not_exists() {
let app_data_dir = app_user_data_dir_path().expect("Failed to determine application data directory");
let env_file = app_data_dir.join(".env".to_string());
if env_file.exists() && env_file.is_file() {
} else {
let env_example_file = PathBuf::from(".env.example");
let target_env_file_path = app_user_data_file_path(".env").expect("should create target env file path");
fs::copy(
&env_example_file,
target_env_file_path,
)
.expect("Failed to copy main net env file");
}
}
pub fn copy_env_file_if_not_exists() -> Result<(), std::io::Error> {
let app_data_dir = app_user_data_dir_path()?;
let env_file = app_data_dir.join(".env");
if env_file.exists() && env_file.is_file() {
return Ok(());
} else {
let env_example_file = PathBuf::from(".env.example");
if !env_example_file.exists() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Template .env.example file not found",
));
}
let target_env_file_path = app_user_data_file_path(".env")?;
fs::copy(
&env_example_file,
target_env_file_path,
)?;
}
Ok(())
}

4 changes: 3 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::str::FromStr;

use crate::app_dir::app_user_data_file_path;
use dash_sdk::dapi_client::AddressList;
use dash_sdk::dpp::dashcore::Network;
use dash_sdk::sdk::Uri;
Expand Down Expand Up @@ -59,7 +60,8 @@ impl Config {
/// Loads the configuration for all networks from environment variables and `.env` file.
pub fn load() -> Result<Self, ConfigError> {
// Load the .env file if available
if let Err(err) = dotenvy::from_path(".env") {
let env_file_path = app_user_data_file_path(".env").expect("should create .env file path");
if let Err(err) = dotenvy::from_path(env_file_path) {
Comment on lines +63 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid potential panic with expect

The current implementation uses expect which could panic if app_user_data_file_path returns an error. This contradicts Rust's error handling philosophy and could lead to unexpected termination.

Consider propagating the error instead:

-        let env_file_path = app_user_data_file_path(".env").expect("should create .env file path");
-        if let Err(err) = dotenvy::from_path(env_file_path) {
+        let env_file_path = app_user_data_file_path(".env").map_err(|e| {
+            ConfigError::LoadError(format!("Failed to create .env file path: {}", e))
+        })?;
+        if let Err(err) = dotenvy::from_path(&env_file_path) {

This change:

  1. Properly propagates the error using ?
  2. Provides more context in the error message
  3. Maintains the robustness of the error handling chain

Committable suggestion skipped: line range outside the PR's diff.

tracing::warn!(
?err,
"Failed to load .env file. Continuing with environment variables."
Expand Down
34 changes: 32 additions & 2 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ use crate::context_provider::Provider;
use crate::database::Database;
use crate::model::contested_name::ContestedName;
use crate::model::qualified_contract::QualifiedContract;
use crate::model::qualified_identity::QualifiedIdentity;
use crate::model::qualified_identity::{DPNSNameInfo, QualifiedIdentity};
use crate::model::wallet::Wallet;
use crate::sdk_wrapper::initialize_sdk;
use crate::ui::RootScreenType;
use dash_sdk::dashcore_rpc::{Auth, Client};
use dash_sdk::dpp::dashcore::Network;
use dash_sdk::dpp::identity::accessors::IdentityGettersV0;
use dash_sdk::dpp::identity::Identity;
use dash_sdk::dpp::system_data_contracts::{load_system_data_contract, SystemDataContract};
use dash_sdk::dpp::version::PlatformVersion;
use dash_sdk::platform::DataContract;
use dash_sdk::platform::{DataContract, Identifier};
use dash_sdk::Sdk;
use rusqlite::Result;
use std::sync::atomic::AtomicBool;
Expand All @@ -27,6 +28,7 @@ pub struct AppContext {
pub(crate) sdk: Sdk,
pub(crate) config: NetworkConfig,
pub(crate) dpns_contract: Arc<DataContract>,
pub(crate) withdraws_contract: Arc<DataContract>,
pub(crate) core_client: Client,
pub(crate) has_wallet: AtomicBool,
pub(crate) wallets: RwLock<Vec<Arc<RwLock<Wallet>>>>,
Expand Down Expand Up @@ -55,6 +57,10 @@ impl AppContext {
load_system_data_contract(SystemDataContract::DPNS, PlatformVersion::latest())
.expect("expected to load dpns contract");

let withdrawal_contract =
load_system_data_contract(SystemDataContract::Withdrawals, PlatformVersion::latest())
.expect("expected to get withdrawal contract");

let addr = format!(
"http://{}:{}",
network_config.core_host, network_config.core_rpc_port
Expand Down Expand Up @@ -83,6 +89,7 @@ impl AppContext {
sdk,
config: network_config,
dpns_contract: Arc::new(dpns_contract),
withdraws_contract: Arc::new(withdrawal_contract),
core_client,
has_wallet: (!wallets.is_empty()).into(),
wallets: RwLock::new(wallets),
Expand Down Expand Up @@ -130,6 +137,29 @@ impl AppContext {
self.db.get_ongoing_contested_names(self)
}

/// Fetches the local identities from the database and then maps them to their DPNS names.
pub fn local_dpns_names(&self) -> Result<Vec<(Identifier, DPNSNameInfo)>> {
let qualified_identities = self.db.get_local_qualified_identities(self)?;

// Map each identity's DPNS names to (Identifier, DPNSNameInfo) tuples
let dpns_names = qualified_identities
.iter()
.flat_map(|qualified_identity| {
qualified_identity.dpns_names.iter().map(|dpns_name_info| {
(
qualified_identity.identity.id(),
DPNSNameInfo {
name: dpns_name_info.name.clone(),
acquired_at: dpns_name_info.acquired_at,
},
)
})
})
.collect::<Vec<(Identifier, DPNSNameInfo)>>();

Ok(dpns_names)
}
Comment on lines +140 to +161
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider optimizing the implementation and improving documentation.

While the implementation is functional, there are several potential improvements:

  1. Optimize the implementation to avoid intermediate collection:
     pub fn local_dpns_names(&self) -> Result<Vec<(Identifier, DPNSNameInfo)>> {
-        let qualified_identities = self.db.get_local_qualified_identities(self)?;
-
-        // Map each identity's DPNS names to (Identifier, DPNSNameInfo) tuples
-        let dpns_names = qualified_identities
-            .iter()
-            .flat_map(|qualified_identity| {
-                qualified_identity.dpns_names.iter().map(|dpns_name_info| {
-                    (
-                        qualified_identity.identity.id(),
-                        DPNSNameInfo {
-                            name: dpns_name_info.name.clone(),
-                            acquired_at: dpns_name_info.acquired_at,
-                        },
-                    )
-                })
-            })
-            .collect::<Vec<(Identifier, DPNSNameInfo)>>();
-
-        Ok(dpns_names)
+        /// Returns a vector of tuples containing identity identifiers and their associated DPNS names.
+        /// Each identity can have multiple DPNS names, so there might be multiple entries for the same identifier.
+        self.db
+            .get_local_qualified_identities(self)?
+            .into_iter()
+            .flat_map(|identity| {
+                identity.dpns_names.into_iter().map(move |name_info| {
+                    (
+                        identity.identity.id(),
+                        name_info,
+                    )
+                })
+            })
+            .collect()

The suggested changes:

  • Improve documentation to clarify the relationship between identifiers and DPNS names
  • Remove unnecessary cloning and intermediate variables
  • Use into_iter() to take ownership where possible
  • Simplify the nested mapping operation
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Fetches the local identities from the database and then maps them to their DPNS names.
pub fn local_dpns_names(&self) -> Result<Vec<(Identifier, DPNSNameInfo)>> {
let qualified_identities = self.db.get_local_qualified_identities(self)?;
// Map each identity's DPNS names to (Identifier, DPNSNameInfo) tuples
let dpns_names = qualified_identities
.iter()
.flat_map(|qualified_identity| {
qualified_identity.dpns_names.iter().map(|dpns_name_info| {
(
qualified_identity.identity.id(),
DPNSNameInfo {
name: dpns_name_info.name.clone(),
acquired_at: dpns_name_info.acquired_at,
},
)
})
})
.collect::<Vec<(Identifier, DPNSNameInfo)>>();
Ok(dpns_names)
}
/// Returns a vector of tuples containing identity identifiers and their associated DPNS names.
/// Each identity can have multiple DPNS names, so there might be multiple entries for the same identifier.
pub fn local_dpns_names(&self) -> Result<Vec<(Identifier, DPNSNameInfo)>> {
self.db
.get_local_qualified_identities(self)?
.into_iter()
.flat_map(|identity| {
identity.dpns_names.into_iter().map(move |name_info| {
(
identity.identity.id(),
name_info,
)
})
})
.collect()
}


/// Updates the `start_root_screen` in the settings table
pub fn update_settings(&self, root_screen_type: RootScreenType) -> Result<()> {
self.db
Expand Down
5 changes: 4 additions & 1 deletion src/logging.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::app_dir::app_user_data_file_path;
use std::panic;
use tracing::{error, info};
use tracing_subscriber::EnvFilter;

pub fn initialize_logger() {
// Initialize log file, with improved error handling
let log_file = match std::fs::File::create("explorer.log") {
let log_file_path = app_user_data_file_path("explorer.log").expect("should create log file path");
let log_file = match std::fs::File::create(log_file_path)
{
Ok(file) => file,
Err(e) => panic!("Failed to create log file: {:?}", e),
};
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod logging;
mod sdk_wrapper;
mod ui;

mod app_dir;
mod components;
mod context;
mod context_provider;
Expand All @@ -27,7 +28,7 @@ fn main() -> eframe::Result<()> {
..Default::default()
};
eframe::run_native(
"Identity Manager",
"Dash Evo Tool",
native_options,
Box::new(|_cc| Ok(Box::new(app::AppState::new()))),
)
Expand Down
8 changes: 8 additions & 0 deletions src/model/qualified_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ impl From<Purpose> for EncryptedPrivateKeyTarget {
}
}

#[derive(Debug, Encode, Decode, Clone, PartialEq)]
pub struct DPNSNameInfo {
pub name: String,
pub acquired_at: u64,
}

#[derive(Debug, Encode, Decode, Clone, PartialEq)]
pub struct QualifiedIdentity {
pub identity: Identity,
Expand All @@ -74,6 +80,7 @@ pub struct QualifiedIdentity {
pub alias: Option<String>,
pub encrypted_private_keys:
BTreeMap<(EncryptedPrivateKeyTarget, KeyID), (IdentityPublicKey, [u8; 32])>,
pub dpns_names: Vec<DPNSNameInfo>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider encapsulating DPNS names management.

The public dpns_names vector could lead to inconsistent state. Consider:

  1. Using a HashSet or BTreeSet if duplicates should be prevented
  2. Making the field private and providing methods to manage names

Example implementation:

// In QualifiedIdentity struct
- pub dpns_names: Vec<DPNSNameInfo>,
+ dpns_names: HashSet<DPNSNameInfo>,

// Add these methods
impl QualifiedIdentity {
    pub fn add_dpns_name(&mut self, name: String) -> Result<(), &'static str> {
        // Add validation logic here
        self.dpns_names.insert(DPNSNameInfo {
            name,
            acquired_at: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        });
        Ok(())
    }

    pub fn get_dpns_names(&self) -> &HashSet<DPNSNameInfo> {
        &self.dpns_names
    }
}

}

impl Signer for QualifiedIdentity {
Expand Down Expand Up @@ -271,6 +278,7 @@ impl From<Identity> for QualifiedIdentity {
identity_type: IdentityType::User,
alias: None,
encrypted_private_keys: Default::default(),
dpns_names: vec![],
}
}
}
Loading
Loading