Files
cg_api_secure-webshare/crates/cgcx-config/src/lib.rs
2026-05-22 02:52:15 +02:00

193 lines
5.5 KiB
Rust

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(())
}
}