This commit is contained in:
2026-05-03 16:24:47 +02:00
commit 47c33a3a6c
35 changed files with 7288 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
use config::{Config, ConfigError, File, FileFormat};
use serde::Deserialize;
use std::error::Error;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::Path;
use std::{fmt, fs};
#[derive(Debug)]
pub enum AppConfigError {
Io(std::io::Error),
Config(ConfigError),
}
impl fmt::Display for AppConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => write!(f, "failed to access config file: {err}"),
Self::Config(err) => write!(f, "failed to load config: {err}"),
}
}
}
impl Error for AppConfigError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io(err) => Some(err),
Self::Config(err) => Some(err),
}
}
}
impl From<std::io::Error> for AppConfigError {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl From<ConfigError> for AppConfigError {
fn from(err: ConfigError) -> Self {
Self::Config(err)
}
}
pub const DEFAULT_CONFIG_TOML: &str = r#"[network]
# IP address to bind to
host = "0.0.0.0"
# hostv6 = "::"
# TCP and UDP port can be the same
# HTTP port
tcp_port = 8080
# Voice/Video port
udp_port = 8080
[database]
# DSN for database
# SQLite
url = "sqlite://oxspeak.db"
# PostgreSQL
# url = "postgresql://user:passwd@localhost:5432/db_name"
# MySQL
# url = "mysql://user:passwd@localhost:3306/db_name"
[jwt]
secret = "changeme"
# Duration in seconds
duration = 86400 # 1 day
refresh_duration = 1296000 # 15 days
"#;
#[derive(Debug, Clone, Deserialize)]
pub struct AppConfig {
pub network: NetworkConfig,
pub database: DatabaseConfig,
pub jwt: JwtConfig,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NetworkConfig {
pub host: Ipv4Addr,
pub hostv6: Option<Ipv6Addr>,
pub tcp_port: u16,
pub udp_port: u16,
}
#[derive(Clone, Deserialize)]
pub struct DatabaseConfig {
pub url: String,
}
impl fmt::Debug for DatabaseConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DatabaseConfig")
.field("url", &"<hidden>")
.finish()
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct JwtConfig {
pub secret: String,
pub duration: u64,
pub refresh_duration: u64,
}
impl AppConfig {
pub fn file_exists() -> bool {
Path::new("config.toml").exists()
}
pub fn load() -> Result<Self, AppConfigError> {
let settings = Config::builder()
.add_source(File::new("config.toml", FileFormat::Toml))
.build()?;
Ok(settings.try_deserialize()?)
}
pub fn gen_config() -> Result<(), AppConfigError> {
if !Self::file_exists() {
fs::write("config.toml", DEFAULT_CONFIG_TOML)?;
}
Ok(())
}
pub fn load_or_generate() -> Result<Self, AppConfigError> {
if !Self::file_exists() {
Self::gen_config()?;
tracing::info!(config_path = "config.toml", "Generated");
}
Self::load()
}
}