summaryrefslogtreecommitdiff
path: root/crates/vcs_data/src/data/user/accounts.rs
blob: d77bc02b7053b12ff84d9558203d4881c1f6b767 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use std::{
    fs,
    io::{Error, ErrorKind},
    path::PathBuf,
};

use cfg_file::config::ConfigFile;

use crate::{
    constants::{USER_FILE_ACCOUNTS, USER_FILE_KEY, USER_FILE_MEMBER},
    data::{
        member::{Member, MemberId},
        user::UserDirectory,
    },
};

const SELF_ID: &str = "{self_id}";

/// Account Management
impl UserDirectory {
    /// Read account from configuration file
    pub async fn account(&self, id: &MemberId) -> Result<Member, std::io::Error> {
        if let Some(cfg_file) = self.account_cfg(id) {
            let member = Member::read_from(cfg_file).await?;
            return Ok(member);
        }

        Err(Error::new(ErrorKind::NotFound, "Account not found!"))
    }

    /// List all account IDs in the user directory
    pub fn account_ids(&self) -> Result<Vec<MemberId>, std::io::Error> {
        let accounts_path = self
            .local_path
            .join(USER_FILE_ACCOUNTS.replace(SELF_ID, ""));

        if !accounts_path.exists() {
            return Ok(Vec::new());
        }

        let mut account_ids = Vec::new();

        for entry in fs::read_dir(accounts_path)? {
            let entry = entry?;
            let path = entry.path();

            if path.is_file()
                && let Some(file_name) = path.file_stem().and_then(|s| s.to_str())
                && path.extension().and_then(|s| s.to_str()) == Some("toml")
            {
                // Remove the "_private" suffix from key files if present
                let account_id = file_name.replace("_private", "");
                account_ids.push(account_id);
            }
        }

        Ok(account_ids)
    }

    /// Get all accounts
    /// This method will read and deserialize account information, please pay attention to performance issues
    pub async fn accounts(&self) -> Result<Vec<Member>, std::io::Error> {
        let mut accounts = Vec::new();

        for account_id in self.account_ids()? {
            if let Ok(account) = self.account(&account_id).await {
                accounts.push(account);
            }
        }

        Ok(accounts)
    }

    /// Update account info
    pub async fn update_account(&self, member: Member) -> Result<(), std::io::Error> {
        // Ensure account exist
        if self.account_cfg(&member.id()).is_some() {
            let account_cfg_path = self.account_cfg_path(&member.id());
            Member::write_to(&member, account_cfg_path).await?;
            return Ok(());
        }

        Err(Error::new(ErrorKind::NotFound, "Account not found!"))
    }

    /// Register an account to user directory
    pub async fn register_account(&self, member: Member) -> Result<(), std::io::Error> {
        // Ensure account not exist
        if self.account_cfg(&member.id()).is_some() {
            return Err(Error::new(
                ErrorKind::DirectoryNotEmpty,
                format!("Account `{}` already registered!", member.id()),
            ));
        }

        // Ensure accounts directory exists
        let accounts_dir = self
            .local_path
            .join(USER_FILE_ACCOUNTS.replace(SELF_ID, ""));
        if !accounts_dir.exists() {
            fs::create_dir_all(&accounts_dir)?;
        }

        // Write config file to accounts dir
        let account_cfg_path = self.account_cfg_path(&member.id());
        Member::write_to(&member, account_cfg_path).await?;

        Ok(())
    }

    /// Remove account from user directory
    pub fn remove_account(&self, id: &MemberId) -> Result<(), std::io::Error> {
        // Remove config file if exists
        if let Some(account_cfg_path) = self.account_cfg(id) {
            fs::remove_file(account_cfg_path)?;
        }

        // Remove private key file if exists
        if let Some(private_key_path) = self.account_private_key(id)
            && private_key_path.exists()
        {
            fs::remove_file(private_key_path)?;
        }

        Ok(())
    }

    /// Try to get the account's configuration file to determine if the account exists
    pub fn account_cfg(&self, id: &MemberId) -> Option<PathBuf> {
        let cfg_file = self.account_cfg_path(id);
        if cfg_file.exists() {
            Some(cfg_file)
        } else {
            None
        }
    }

    /// Try to get the account's private key file to determine if the account has a private key
    pub fn account_private_key(&self, id: &MemberId) -> Option<PathBuf> {
        let key_file = self.account_private_key_path(id);
        if key_file.exists() {
            Some(key_file)
        } else {
            None
        }
    }

    /// Check if account has private key
    pub fn has_private_key(&self, id: &MemberId) -> bool {
        self.account_private_key(id).is_some()
    }

    /// Get the account's configuration file path, but do not check if the file exists
    pub fn account_cfg_path(&self, id: &MemberId) -> PathBuf {
        self.local_path
            .join(USER_FILE_MEMBER.replace(SELF_ID, id.to_string().as_str()))
    }

    /// Get the account's private key file path, but do not check if the file exists
    pub fn account_private_key_path(&self, id: &MemberId) -> PathBuf {
        self.local_path
            .join(USER_FILE_KEY.replace(SELF_ID, id.to_string().as_str()))
    }
}