summaryrefslogtreecommitdiff
path: root/crates/env/src/workspace
diff options
context:
space:
mode:
Diffstat (limited to 'crates/env/src/workspace')
-rw-r--r--crates/env/src/workspace/local.rs2
-rw-r--r--crates/env/src/workspace/local/config.rs37
-rw-r--r--crates/env/src/workspace/local/manage.rs1
-rw-r--r--crates/env/src/workspace/member.rs67
-rw-r--r--crates/env/src/workspace/vault.rs109
-rw-r--r--crates/env/src/workspace/vault/config.rs37
-rw-r--r--crates/env/src/workspace/vault/member.rs105
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
+ }
+}