diff options
Diffstat (limited to 'crates/env/src/workspace')
| -rw-r--r-- | crates/env/src/workspace/local.rs | 2 | ||||
| -rw-r--r-- | crates/env/src/workspace/local/config.rs | 37 | ||||
| -rw-r--r-- | crates/env/src/workspace/local/manage.rs | 1 | ||||
| -rw-r--r-- | crates/env/src/workspace/member.rs | 67 | ||||
| -rw-r--r-- | crates/env/src/workspace/vault.rs | 109 | ||||
| -rw-r--r-- | crates/env/src/workspace/vault/config.rs | 37 | ||||
| -rw-r--r-- | crates/env/src/workspace/vault/member.rs | 105 |
7 files changed, 358 insertions, 0 deletions
diff --git a/crates/env/src/workspace/local.rs b/crates/env/src/workspace/local.rs new file mode 100644 index 0000000..72092c2 --- /dev/null +++ b/crates/env/src/workspace/local.rs @@ -0,0 +1,2 @@ +pub mod config; +pub mod manage; diff --git a/crates/env/src/workspace/local/config.rs b/crates/env/src/workspace/local/config.rs new file mode 100644 index 0000000..ddb7dd0 --- /dev/null +++ b/crates/env/src/workspace/local/config.rs @@ -0,0 +1,37 @@ +use cfg_file::ConfigFile; +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; + +use crate::constants::CLIENT_FILE_WORKSPACE; +use crate::constants::PORT; + +#[derive(Serialize, Deserialize, ConfigFile)] +#[cfg_file(path = CLIENT_FILE_WORKSPACE)] +pub struct LocalConfig { + /// The vault address, representing the upstream address of the local workspace, + /// to facilitate timely retrieval of new updates from the upstream source. + vault_addr: SocketAddr, +} + +impl Default for LocalConfig { + fn default() -> Self { + Self { + vault_addr: SocketAddr::V4(std::net::SocketAddrV4::new( + std::net::Ipv4Addr::new(127, 0, 0, 1), + PORT, + )), + } + } +} + +impl LocalConfig { + /// Set the vault address. + pub fn set_vault_addr(&mut self, addr: SocketAddr) { + self.vault_addr = addr; + } + + /// Get the vault address. + pub fn vault_addr(&self) -> SocketAddr { + self.vault_addr + } +} diff --git a/crates/env/src/workspace/local/manage.rs b/crates/env/src/workspace/local/manage.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/env/src/workspace/local/manage.rs @@ -0,0 +1 @@ + diff --git a/crates/env/src/workspace/member.rs b/crates/env/src/workspace/member.rs new file mode 100644 index 0000000..b81bd82 --- /dev/null +++ b/crates/env/src/workspace/member.rs @@ -0,0 +1,67 @@ +use std::collections::HashMap; + +use cfg_file::ConfigFile; +use serde::{Deserialize, Serialize}; +use string_proc::camel_case; + +#[derive(Debug, Eq, Clone, ConfigFile, Serialize, Deserialize)] +pub struct Member { + /// Member ID, the unique identifier of the member + id: String, + + /// Member metadata + metadata: HashMap<String, String>, +} + +impl Default for Member { + fn default() -> Self { + Self::new("default_user") + } +} + +impl PartialEq for Member { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl std::fmt::Display for Member { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.id) + } +} + +impl std::convert::AsRef<str> for Member { + fn as_ref(&self) -> &str { + &self.id + } +} + +impl Member { + /// Create member struct by id + pub fn new(new_id: impl Into<String>) -> Self { + Self { + id: camel_case!(new_id.into()), + metadata: HashMap::new(), + } + } + + /// Get member id + pub fn id(&self) -> String { + self.id.clone() + } + + /// Get metadata + pub fn metadata(&self, key: impl Into<String>) -> Option<&String> { + self.metadata.get(&key.into()) + } + + /// Set metadata + pub fn set_metadata( + &mut self, + key: impl AsRef<str>, + value: impl Into<String>, + ) -> Option<String> { + self.metadata.insert(key.as_ref().to_string(), value.into()) + } +} diff --git a/crates/env/src/workspace/vault.rs b/crates/env/src/workspace/vault.rs new file mode 100644 index 0000000..b00ba80 --- /dev/null +++ b/crates/env/src/workspace/vault.rs @@ -0,0 +1,109 @@ +use std::{ + env::current_dir, + fs::{self, create_dir_all}, + path::PathBuf, +}; + +use cfg_file::config::ConfigFile; + +use crate::{ + constants::{ + SERVER_FILE_README, SERVER_FILE_VAULT, SERVER_PATH_MEMBER_PUB, SERVER_PATH_MEMBERS, + SERVER_PATH_SHEETS, SERVER_PATH_VIRTUAL_FILE_ROOT, + }, + current::{current_vault_path, find_vault_path}, + workspace::vault::config::VaultConfig, +}; + +pub mod config; +pub mod member; +pub mod vitrual_file; + +pub type MemberId = String; + +pub struct Vault { + config: VaultConfig, + vault_path: PathBuf, +} + +impl Vault { + /// Get vault path + pub fn vault_path(&self) -> &PathBuf { + &self.vault_path + } + + /// Initialize vault + pub fn init(config: VaultConfig, vault_path: impl Into<PathBuf>) -> Option<Self> { + let Some(vault_path) = find_vault_path(vault_path) else { + return None; + }; + Some(Self { config, vault_path }) + } + + /// Initialize vault + pub fn init_current_dir(config: VaultConfig) -> Option<Self> { + let Some(vault_path) = current_vault_path() else { + return None; + }; + Some(Self { config, vault_path }) + } + + /// Setup vault + pub async fn setup_vault(vault_path: impl Into<PathBuf>) -> Result<(), std::io::Error> { + let vault_path: PathBuf = vault_path.into(); + + // 1. Setup main config + let config = VaultConfig::default(); + VaultConfig::write_to(&config, vault_path.join(SERVER_FILE_VAULT)).await?; + + // 2. Setup sheets directory + create_dir_all(vault_path.join(SERVER_PATH_SHEETS))?; + + // 3. Setup key directory + create_dir_all(vault_path.join(SERVER_PATH_MEMBER_PUB))?; + + // 4. Setup member directory + create_dir_all(vault_path.join(SERVER_PATH_MEMBERS))?; + + // 5. Setup storage directory + create_dir_all(vault_path.join(SERVER_PATH_VIRTUAL_FILE_ROOT))?; + + // Final, generate README.md + let readme_content = format!( + "\ + # JustEnoughVCS Server Setup + + This directory contains the server configuration and data for `JustEnoughVCS`. + + ## User Authentication + To allow users to connect to this server, place their public keys in the `{}` directory. + Each public key file should correspond to a registered user. + + ## File Storage + All version-controlled files (Virtual File) are stored in the `{}` directory. + + ## License + This software is distributed under the MIT License. + + ## Support + Repository: `https://github.com/JustEnoughVCS/VersionControl` + Please report any issues or questions on the GitHub issue tracker. + + ## Thanks :) + Thank you for using `JustEnoughVCS!` + ", + SERVER_PATH_MEMBER_PUB, SERVER_PATH_VIRTUAL_FILE_ROOT + ) + .trim() + .to_string(); + fs::write(vault_path.join(SERVER_FILE_README), readme_content)?; + + Ok(()) + } + + /// Setup vault in current directory + pub async fn setup_vault_current_dir() -> Result<(), std::io::Error> { + Self::setup_vault(current_dir()?).await?; + Ok(()) + } +} diff --git a/crates/env/src/workspace/vault/config.rs b/crates/env/src/workspace/vault/config.rs new file mode 100644 index 0000000..983a9e5 --- /dev/null +++ b/crates/env/src/workspace/vault/config.rs @@ -0,0 +1,37 @@ +use cfg_file::ConfigFile; +use serde::{Deserialize, Serialize}; + +use crate::constants::SERVER_FILE_VAULT; +use crate::workspace::member::Member; +use crate::workspace::vault::MemberId; + +#[derive(Default, Serialize, Deserialize, ConfigFile)] +#[cfg_file(path = SERVER_FILE_VAULT)] +pub struct VaultConfig { + /// Vault name, which can be used as the project name and generally serves as a hint + vault_name: String, + + /// Vault admin id, a list of member id representing administrator identities + vault_admin_list: Vec<MemberId>, +} + +impl VaultConfig { + // Change name of the vault. + pub fn change_name(&mut self, name: impl Into<String>) { + self.vault_name = name.into() + } + + // Add admin + pub fn add_admin(&mut self, member: &Member) { + let uuid = member.id(); + if !self.vault_admin_list.contains(&uuid) { + self.vault_admin_list.push(uuid); + } + } + + // Remove admin + pub fn remove_admin(&mut self, member: &Member) { + let id = member.id(); + self.vault_admin_list.retain(|x| x != &id); + } +} diff --git a/crates/env/src/workspace/vault/member.rs b/crates/env/src/workspace/vault/member.rs new file mode 100644 index 0000000..45452c5 --- /dev/null +++ b/crates/env/src/workspace/vault/member.rs @@ -0,0 +1,105 @@ +use std::{ + fs, + io::{Error, ErrorKind}, + path::PathBuf, +}; + +use cfg_file::config::ConfigFile; + +use crate::{ + constants::{SERVER_FILE_MEMBER_INFO, SERVER_FILE_MEMBER_PUB}, + workspace::{ + member::Member, + vault::{MemberId, Vault}, + }, +}; + +const ID_PARAM: &str = "{member_id}"; + +/// Member Manage +impl Vault { + /// Read member from configuration file + pub async fn member(&self, id: MemberId) -> Result<Member, std::io::Error> { + if let Some(cfg_file) = self.member_cfg(id) { + let member = Member::read_from(cfg_file).await?; + return Ok(member); + } + + Err(Error::new(ErrorKind::NotFound, "Member not found!")) + } + + /// Update member info + pub async fn update_member(&self, member: Member) -> Result<(), std::io::Error> { + // Ensure member exist + if let Some(_) = self.member_cfg(member.id()) { + let member_cfg_path = self.member_cfg_path(member.id()); + Member::write_to(&member, member_cfg_path).await?; + return Ok(()); + } + + Err(Error::new(ErrorKind::NotFound, "Member not found!")) + } + + /// Register a member to vault + pub async fn register_member_to_vault(&self, member: Member) -> Result<(), std::io::Error> { + // Ensure member not exist + if let Some(_) = self.member_cfg(member.id()) { + return Err(Error::new( + ErrorKind::DirectoryNotEmpty, + format!("Member `{}` already registered!", member.id()), + )); + } + + // Wrtie config file to member dir + let member_cfg_path = self.member_cfg_path(member.id()); + Member::write_to(&member, member_cfg_path).await?; + + Ok(()) + } + + /// Remove member from vault + pub fn remove_member_from_vault(&self, id: MemberId) -> Result<(), std::io::Error> { + // Ensure member exist + if let Some(member_cfg_path) = self.member_cfg(id) { + fs::remove_file(member_cfg_path)?; + } + + Ok(()) + } + + /// Try to get the member's configuration file to determine if the member exists + pub fn member_cfg(&self, id: MemberId) -> Option<PathBuf> { + let cfg_file = self.member_cfg_path(id); + if cfg_file.exists() { + Some(cfg_file) + } else { + None + } + } + + /// Try to get the member's public key file to determine if the member has login permission + pub fn member_key(&self, id: MemberId) -> Option<PathBuf> { + let key_file = self.member_key_path(id); + if key_file.exists() { + Some(key_file) + } else { + None + } + } + + /// Get the member's configuration file path, but do not check if the file exists + pub fn member_cfg_path(&self, id: MemberId) -> PathBuf { + let path = self + .vault_path + .join(SERVER_FILE_MEMBER_INFO.replace(ID_PARAM, id.to_string().as_str())); + path + } + + /// Get the member's public key file path, but do not check if the file exists + pub fn member_key_path(&self, id: MemberId) -> PathBuf { + let path = self + .vault_path + .join(SERVER_FILE_MEMBER_PUB.replace(ID_PARAM, id.to_string().as_str())); + path + } +} |
