Initial commit
This commit is contained in:
11
crates/cgcx-config/Cargo.toml
Normal file
11
crates/cgcx-config/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "cgcx-config"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cgcx-core = { path = "../cgcx-core" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
config = "0.14"
|
||||
tokio = { version = "1", features = ["fs", "sync", "time"] }
|
||||
tracing = "0.1"
|
||||
192
crates/cgcx-config/src/lib.rs
Normal file
192
crates/cgcx-config/src/lib.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub content: ContentConfig,
|
||||
pub crypto: CryptoConfig,
|
||||
pub telegram: TelegramConfig,
|
||||
pub groups: GroupsConfig,
|
||||
pub storage: StorageConfig,
|
||||
pub upload_limits: UploadLimits,
|
||||
pub server: ServerConfig,
|
||||
pub rate_limiting: RateLimitConfig,
|
||||
pub logging: LoggingConfig,
|
||||
pub frontend: FrontendConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ContentConfig {
|
||||
pub keep_content: bool,
|
||||
pub share_mode: ShareMode,
|
||||
pub default_allow_download: bool,
|
||||
#[serde(default)]
|
||||
pub default_max_views: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ShareMode {
|
||||
B,
|
||||
W,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CryptoConfig {
|
||||
pub aes_master_key_source: KeySource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum KeySource {
|
||||
File { path: PathBuf },
|
||||
Env { var: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TelegramConfig {
|
||||
pub bot_token: String,
|
||||
pub api_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct GroupsConfig {
|
||||
pub admin_group_ids: Vec<i64>,
|
||||
pub review_group_ids: Vec<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct StorageConfig {
|
||||
pub paths: StoragePaths,
|
||||
pub chunk_size_bytes: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct StoragePaths {
|
||||
pub media: PathBuf,
|
||||
pub documents: PathBuf,
|
||||
pub text: PathBuf,
|
||||
pub temp: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct UploadLimits {
|
||||
pub max_batch_size: usize,
|
||||
pub max_file_size_bytes: u64,
|
||||
pub max_total_batch_bytes: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ServerConfig {
|
||||
pub base_url: String,
|
||||
pub bind_address: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RateLimitConfig {
|
||||
pub requests_per_minute: u32,
|
||||
pub burst: u32,
|
||||
pub password_attempts_per_minute: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct LoggingConfig {
|
||||
pub level: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct FrontendConfig {
|
||||
pub behavior_toggles: HashMap<String, bool>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Self, cgcx_core::CgcxError> {
|
||||
let s = config::Config::builder()
|
||||
.add_source(config::File::with_name("config/default"))
|
||||
.add_source(config::File::with_name("config/local").required(false))
|
||||
.add_source(
|
||||
config::Environment::with_prefix("CGCX")
|
||||
.separator("__")
|
||||
.try_parsing(true)
|
||||
.list_separator(","),
|
||||
)
|
||||
.build()
|
||||
.map_err(|e| cgcx_core::CgcxError::Config(e.to_string()))?;
|
||||
let cfg: Config = s.try_deserialize()
|
||||
.map_err(|e| cgcx_core::CgcxError::Config(e.to_string()))?;
|
||||
cfg.validate()?;
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), cgcx_core::CgcxError> {
|
||||
let chunk = self.storage.chunk_size_bytes;
|
||||
const MIN: usize = 8 * 1024 * 1024;
|
||||
const MAX: usize = 256 * 1024 * 1024;
|
||||
if chunk < MIN || chunk > MAX {
|
||||
return Err(cgcx_core::CgcxError::Config(format!(
|
||||
"chunk_size_bytes must be between {} and {}, got {}",
|
||||
MIN, MAX, chunk
|
||||
)));
|
||||
}
|
||||
|
||||
if self.telegram.bot_token.is_empty() || self.telegram.bot_token == "BOT_TOKEN_PLACEHOLDER" {
|
||||
return Err(cgcx_core::CgcxError::Config(
|
||||
"telegram.bot_token must be set to a valid bot token".into()
|
||||
));
|
||||
}
|
||||
|
||||
if self.server.port == 0 {
|
||||
return Err(cgcx_core::CgcxError::Config(
|
||||
"server.port must be > 0".into()
|
||||
));
|
||||
}
|
||||
|
||||
if self.server.base_url.is_empty() {
|
||||
return Err(cgcx_core::CgcxError::Config(
|
||||
"server.base_url must be set".into()
|
||||
));
|
||||
}
|
||||
|
||||
if self.upload_limits.max_batch_size == 0
|
||||
|| self.upload_limits.max_file_size_bytes == 0
|
||||
|| self.upload_limits.max_total_batch_bytes == 0
|
||||
{
|
||||
return Err(cgcx_core::CgcxError::Config(
|
||||
"upload_limits must all be > 0".into()
|
||||
));
|
||||
}
|
||||
|
||||
if self.rate_limiting.requests_per_minute == 0
|
||||
|| self.rate_limiting.burst == 0
|
||||
|| self.rate_limiting.password_attempts_per_minute == 0
|
||||
{
|
||||
return Err(cgcx_core::CgcxError::Config(
|
||||
"rate_limiting values must all be > 0".into()
|
||||
));
|
||||
}
|
||||
|
||||
if self.logging.level.is_empty() {
|
||||
return Err(cgcx_core::CgcxError::Config(
|
||||
"logging.level must be set".into()
|
||||
));
|
||||
}
|
||||
|
||||
match &self.crypto.aes_master_key_source {
|
||||
KeySource::File { path } if path.as_os_str().is_empty() => {
|
||||
return Err(cgcx_core::CgcxError::Config(
|
||||
"crypto.aes_master_key_source.file path must not be empty".into()
|
||||
));
|
||||
}
|
||||
KeySource::Env { var } if var.is_empty() => {
|
||||
return Err(cgcx_core::CgcxError::Config(
|
||||
"crypto.aes_master_key_source.env var must not be empty".into()
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user