refactor: Remove ConfigItem

This commit is contained in:
quexeky
2026-01-20 19:02:54 +11:00
parent bf35f66961
commit 38e8ac4839
19 changed files with 178 additions and 163 deletions
+3 -2
View File
@@ -1,6 +1,7 @@
use crate::config::config::ConfigOptionCli;
use clap::{Args, Parser, Subcommand, ValueEnum};
use crate::commands::config::config_option::ConfigOptionCli;
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
@@ -19,7 +20,7 @@ pub enum Commands {
#[arg(short, long)]
name: String,
#[command(subcommand)]
option: ConfigOptionCli
option: ConfigOptionCli,
},
/// Uploads new game version to depot
Upload(UploadInfo),
@@ -1,8 +1,4 @@
use crate::config::{
s3::{S3Config, S3ConfigCli},
server::ServerConfig,
};
use clap::Subcommand;
use crate::commands::config::{config_option::ConfigOption, s3::S3Config};
use log::warn;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs};
@@ -15,6 +11,12 @@ pub struct Config {
active_s3: Option<String>,
}
impl Config {
pub fn new() -> Self {
Self {
items: HashMap::new(),
active_s3: None,
}
}
pub fn save(&self) -> anyhow::Result<()> {
let json = serde_json::to_string(self)?;
let save_path = dirs::config_dir()
@@ -41,26 +43,7 @@ impl Config {
self.items.insert(name, object);
self.save().expect("Failed to save config");
}
}
#[derive(Subcommand, Clone)]
pub enum ConfigOptionCli {
Server(ServerConfig),
S3(S3ConfigCli),
}
#[derive(Serialize, Deserialize, Clone)]
pub enum ConfigOption {
Server(ServerConfig),
S3(S3Config),
}
impl Config {
pub fn new() -> Self {
Self {
items: HashMap::new(),
active_s3: None,
}
}
pub fn get_active_s3(&self) -> Option<S3Config> {
if let Some(active_s3) = &self.active_s3 {
self.items
+18
View File
@@ -0,0 +1,18 @@
use clap::Subcommand;
use serde::{Deserialize, Serialize};
use crate::commands::config::{
s3::{S3Config, S3ConfigCli},
server::{ServerConfig, ServerConfigCli},
};
#[derive(Subcommand, Clone)]
pub enum ConfigOptionCli {
Server(ServerConfigCli),
S3(S3ConfigCli),
}
#[derive(Serialize, Deserialize, Clone)]
pub enum ConfigOption {
Server(ServerConfig),
S3(S3Config),
}
+5
View File
@@ -0,0 +1,5 @@
use crate::commands::config::config_option::ConfigOption;
pub trait Configurable {
async fn configure(self) -> anyhow::Result<ConfigOption>;
}
@@ -8,7 +8,7 @@ macro_rules! interactive_variable {
let $var = if let Some($var) = $value.$var {
$var
} else {
crate::config::interactive::query_variable($prompt).unwrap()
crate::commands::config::interactive::query_variable($prompt).unwrap()
};
};
}
@@ -18,7 +18,7 @@ macro_rules! interactive_optional_variable {
let $var = if let Some($var) = $value.$var {
Some($var)
} else {
crate::config::interactive::query_optional_variable($prompt).unwrap()
crate::commands::config::interactive::query_optional_variable($prompt).unwrap()
};
};
}
@@ -1,6 +1,7 @@
pub mod config;
pub mod configure;
pub mod s3;
pub mod server;
pub mod configure;
#[macro_use]
pub mod interactive;
pub mod config_option;
@@ -4,10 +4,12 @@ use clap::Args;
use s3::{Bucket, Region, creds::Credentials};
use serde::{Deserialize, Serialize};
use crate::{config::configure::Configurable, interactive_optional_variable, interactive_variable};
use crate::{
commands::config::{config_option::ConfigOption, configure::Configurable},
interactive_optional_variable, interactive_variable,
};
#[derive(Serialize, Deserialize, Args, Clone)]
#[derive(Args, Clone)]
pub struct S3ConfigCli {
secret_key: Option<String>,
key_id: Option<String>,
@@ -16,25 +18,6 @@ pub struct S3ConfigCli {
endpoint: Option<String>,
}
impl Configurable for S3ConfigCli {
type Out = S3Config;
fn configure(self) -> Self::Out {
interactive_variable!(self, secret_key, "S3 Secret Key");
interactive_variable!(self, key_id, "S3 Key ID");
interactive_variable!(self, region, "S3 Region");
interactive_variable!(self, bucket_name, "S3 Bucket Name");
interactive_optional_variable!(self, endpoint, "S3 Endpoint (leave blank for none");
Self::Out {
secret_key,
key_id,
region,
bucket_name,
endpoint,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct S3Config {
secret_key: String,
@@ -44,6 +27,23 @@ pub struct S3Config {
endpoint: Option<String>,
}
impl Configurable for S3ConfigCli {
async fn configure(self) -> anyhow::Result<ConfigOption> {
interactive_variable!(self, secret_key, "S3 Secret Key");
interactive_variable!(self, key_id, "S3 Key ID");
interactive_variable!(self, region, "S3 Region");
interactive_variable!(self, bucket_name, "S3 Bucket Name");
interactive_optional_variable!(self, endpoint, "S3 Endpoint (leave blank for none");
Ok(ConfigOption::S3(S3Config {
secret_key,
key_id,
region,
bucket_name,
endpoint,
}))
}
}
impl S3Config {
pub fn generate_bucket(&self) -> anyhow::Result<s3::Bucket> {
let credentials =
+92
View File
@@ -0,0 +1,92 @@
use clap::Args;
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
use anyhow::{Result, anyhow};
use dialoguer::{Confirm, Input, theme::ColorfulTheme};
use reqwest::Client;
use url::Url;
use crate::commands::config::{config_option::ConfigOption, configure::Configurable};
#[derive(Serialize, Deserialize, Clone)]
pub struct ServerConfig {
url: String,
token: String,
}
#[derive(Args, Clone)]
pub struct ServerConfigCli {
/// Endpoint of the Drop server
url: String,
#[arg(short, long)]
token: Option<String>,
}
const TOKEN_CREATE_PAYLOAD: &str =
"eyJuYW1lIjoiZG93bnBvdXIgKGNsaSkiLCJhY2xzIjpbImRlcG90Om5ldyJdfQ==";
impl Configurable for ServerConfigCli {
async fn configure(self) -> anyhow::Result<ConfigOption> {
let base_url = Url::parse(&self.url)?;
let mut token_create_url = base_url.join("/admin/settings/tokens")?;
{
let mut query = token_create_url.query_pairs_mut();
query.append_pair("payload", TOKEN_CREATE_PAYLOAD);
};
let confirm = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!(
"Open \"{}\" in your default browser?",
token_create_url.as_str()
))
.interact()?;
if !confirm {
return Err(anyhow!("User cancelled action"));
}
webbrowser::open(token_create_url.as_str())?;
let token: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("API token")
.interact_text()?;
validate_configuration(&self.url, &token).await?;
Ok(ConfigOption::Server(ServerConfig {
url: self.url,
token,
}))
}
}
static CLIENT: LazyLock<Client> = LazyLock::new(|| reqwest::Client::new());
const REQUIRED_ACLS: [&str; 1] = ["depot:new"];
pub async fn validate_configuration(url: &str, token: &str) -> Result<()> {
let base_url = Url::parse(&url)?;
let token_check_url = base_url.join("/api/v1/token")?;
let acl_check = CLIENT
.get(token_check_url)
.bearer_auth(token)
.send()
.await?;
if !acl_check.status().is_success() {
return Err(anyhow!(
"ACL check failed with response code: {}",
acl_check.status()
));
}
let acls: Vec<String> = acl_check.json().await?;
for acl in REQUIRED_ACLS {
if !acls.contains(&acl.to_string()) {
return Err(anyhow!("Token missing {} acl", acl));
}
}
Ok(())
}
-77
View File
@@ -1,77 +0,0 @@
use std::sync::LazyLock;
use anyhow::{Result, anyhow};
use dialoguer::{Confirm, Input, theme::ColorfulTheme};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use url::Url;
const TOKEN_CREATE_PAYLOAD: &str =
"eyJuYW1lIjoiZG93bnBvdXIgKGNsaSkiLCJhY2xzIjpbImRlcG90Om5ldyJdfQ==";
static CLIENT: LazyLock<Client> = LazyLock::new(|| reqwest::Client::new());
const REQUIRED_ACLS: [&str; 1] = ["depot:new"];
pub async fn interactive_configure(url: String) -> Result<()> {
let base_url = Url::parse(&url)?;
let mut token_create_url = base_url.join("/admin/settings/tokens")?;
{
let mut query = token_create_url.query_pairs_mut();
query.append_pair("payload", TOKEN_CREATE_PAYLOAD);
};
let confirm = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!(
"Open \"{}\" in your default browser?",
token_create_url.as_str()
))
.interact()?;
if !confirm {
return Err(anyhow!("User cancelled action"));
}
webbrowser::open(token_create_url.as_str())?;
let token: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("API token")
.interact_text()?;
validate_configuration(url, token).await?;
Ok(())
}
pub async fn validate_configuration(url: String, token: String) -> Result<()> {
let base_url = Url::parse(&url)?;
let token_check_url = base_url.join("/api/v1/token")?;
let acl_check = CLIENT
.get(token_check_url)
.bearer_auth(token)
.send()
.await?;
if !acl_check.status().is_success() {
return Err(anyhow!(
"ACL check failed with response code: {}",
acl_check.status()
));
}
let acls: Vec<String> = acl_check.json().await?;
for acl in REQUIRED_ACLS {
if !acls.contains(&acl.to_string()) {
return Err(anyhow!("Token missing {} acl", acl));
}
}
Ok(())
}
#[derive(Serialize, Deserialize)]
pub struct ServerConfiguration {
pub endpoint: String,
pub token: String,
}
+1 -1
View File
@@ -1,2 +1,2 @@
pub mod configure;
pub mod config;
pub mod upload;
+4 -1
View File
@@ -1,7 +1,10 @@
use std::path::Path;
use crate::{
cli::UploadInfo, commands::upload::{s3::S3, uploadable::Uploadable}, config::config::Config, manifest::generate_manifest
cli::UploadInfo,
commands::config::config::Config,
commands::upload::{s3::S3, uploadable::Uploadable},
manifest::generate_manifest,
};
use log::info;
+2 -1
View File
@@ -1,8 +1,9 @@
use crate::{
commands::config::s3::S3Config,
commands::upload::{
speedtest::{SPEEDTEST_PATH, Speedtest},
uploadable::Uploadable,
}, config::s3::S3Config,
},
};
use async_trait::async_trait;
use droplet_rs::manifest::{ChunkData, Manifest};
+6 -1
View File
@@ -11,5 +11,10 @@ pub trait Uploadable {
chunk: &ChunkData,
) -> anyhow::Result<()>;
async fn upload_speedtest(&mut self) -> anyhow::Result<()>;
async fn upload_manifest(&mut self, manifest: Manifest, game_id: &String, version_id: &String) -> anyhow::Result<()>;
async fn upload_manifest(
&mut self,
manifest: Manifest,
game_id: &String,
version_id: &String,
) -> anyhow::Result<()>;
}
-4
View File
@@ -1,4 +0,0 @@
pub trait Configurable {
type Out;
fn configure(self) -> Self::Out;
}
-10
View File
@@ -1,10 +0,0 @@
use clap::Args;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Args, Clone)]
pub struct ServerConfig {
/// Endpoint of the Drop server
url: String,
#[arg(short, long)]
token: String,
}
-1
View File
@@ -4,7 +4,6 @@ use std::env;
use std::fs;
use std::io;
pub fn configure_logging() -> anyhow::Result<()> {
let log_level = env::var("RUST_LOG")
.unwrap_or_else(|_| "info".to_string())
+10 -10
View File
@@ -1,13 +1,13 @@
use crate::config::configure::Configurable;
use crate::commands::config::config_option::ConfigOptionCli;
use crate::commands::config::configure::Configurable;
use crate::{
cli::{Cli, Commands},
commands::config::config::Config,
commands::upload,
config::config::Config,
};
use clap::Parser;
mod cli;
mod commands;
mod config;
mod logging;
mod manifest;
@@ -19,13 +19,13 @@ async fn main() -> anyhow::Result<()> {
let mut config = Config::read();
match &cli.command {
Commands::Configure { name, option } => match option {
config::config::ConfigOptionCli::Server(server_config) => todo!(),
config::config::ConfigOptionCli::S3(s3_config_cli) => config.add_item(
name.clone(),
config::config::ConfigOption::S3(s3_config_cli.clone().configure()),
),
},
Commands::Configure { name, option } => config.add_item(
name.clone(),
match option {
ConfigOptionCli::Server(server_config) => server_config.clone().configure().await?,
ConfigOptionCli::S3(s3_config_cli) => s3_config_cli.clone().configure().await?,
},
),
Commands::Upload(info) => {
upload::interface::upload(info, config).await?;
}
+1 -3
View File
@@ -1,6 +1,4 @@
use std::{
path::Path,
};
use std::path::Path;
use droplet_rs::manifest::{Manifest, generate_manifest_rusty};
use indicatif::{ProgressBar, ProgressStyle};