2025-10-18 18:19:08 -03:00
|
|
|
use diesel::prelude::*;
|
|
|
|
|
use diesel::sql_types::Text;
|
|
|
|
|
use log::{info, warn};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::sync::{Arc, Mutex};
|
2025-10-06 10:30:17 -03:00
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct AppConfig {
|
2025-10-11 20:02:14 -03:00
|
|
|
pub minio: DriveConfig,
|
2025-10-06 10:30:17 -03:00
|
|
|
pub server: ServerConfig,
|
|
|
|
|
pub database: DatabaseConfig,
|
|
|
|
|
pub database_custom: DatabaseConfig,
|
|
|
|
|
pub email: EmailConfig,
|
|
|
|
|
pub ai: AIConfig,
|
|
|
|
|
pub site_path: String,
|
2025-10-11 20:02:14 -03:00
|
|
|
pub s3_bucket: String,
|
2025-10-18 18:19:08 -03:00
|
|
|
pub stack_path: PathBuf,
|
2025-10-19 11:08:23 -03:00
|
|
|
pub db_conn: Option<Arc<Mutex<PgConnection>>>,
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct DatabaseConfig {
|
|
|
|
|
pub username: String,
|
|
|
|
|
pub password: String,
|
|
|
|
|
pub server: String,
|
|
|
|
|
pub port: u32,
|
|
|
|
|
pub database: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2025-10-11 20:02:14 -03:00
|
|
|
pub struct DriveConfig {
|
2025-10-06 10:30:17 -03:00
|
|
|
pub server: String,
|
|
|
|
|
pub access_key: String,
|
|
|
|
|
pub secret_key: String,
|
|
|
|
|
pub use_ssl: bool,
|
2025-10-15 12:45:15 -03:00
|
|
|
pub org_prefix: String,
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct ServerConfig {
|
|
|
|
|
pub host: String,
|
|
|
|
|
pub port: u16,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct EmailConfig {
|
|
|
|
|
pub from: String,
|
|
|
|
|
pub server: String,
|
|
|
|
|
pub port: u16,
|
|
|
|
|
pub username: String,
|
|
|
|
|
pub password: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct AIConfig {
|
|
|
|
|
pub instance: String,
|
|
|
|
|
pub key: String,
|
|
|
|
|
pub version: String,
|
|
|
|
|
pub endpoint: String,
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-18 18:19:08 -03:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, QueryableByName)]
|
|
|
|
|
pub struct ServerConfigRow {
|
|
|
|
|
#[diesel(sql_type = Text)]
|
|
|
|
|
pub id: String,
|
|
|
|
|
#[diesel(sql_type = Text)]
|
|
|
|
|
pub config_key: String,
|
|
|
|
|
#[diesel(sql_type = Text)]
|
|
|
|
|
pub config_value: String,
|
|
|
|
|
#[diesel(sql_type = Text)]
|
|
|
|
|
pub config_type: String,
|
|
|
|
|
#[diesel(sql_type = diesel::sql_types::Bool)]
|
|
|
|
|
pub is_encrypted: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 10:30:17 -03:00
|
|
|
impl AppConfig {
|
|
|
|
|
pub fn database_url(&self) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
"postgres://{}:{}@{}:{}/{}",
|
|
|
|
|
self.database.username,
|
|
|
|
|
self.database.password,
|
|
|
|
|
self.database.server,
|
|
|
|
|
self.database.port,
|
|
|
|
|
self.database.database
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 19:12:13 -03:00
|
|
|
pub fn database_custom_url(&self) -> String {
|
2025-10-06 10:30:17 -03:00
|
|
|
format!(
|
|
|
|
|
"postgres://{}:{}@{}:{}/{}",
|
|
|
|
|
self.database_custom.username,
|
|
|
|
|
self.database_custom.password,
|
|
|
|
|
self.database_custom.server,
|
|
|
|
|
self.database_custom.port,
|
|
|
|
|
self.database_custom.database
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-18 18:19:08 -03:00
|
|
|
pub fn component_path(&self, component: &str) -> PathBuf {
|
|
|
|
|
self.stack_path.join(component)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn bin_path(&self, component: &str) -> PathBuf {
|
|
|
|
|
self.stack_path.join("bin").join(component)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn data_path(&self, component: &str) -> PathBuf {
|
|
|
|
|
self.stack_path.join("data").join(component)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn config_path(&self, component: &str) -> PathBuf {
|
|
|
|
|
self.stack_path.join("conf").join(component)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn log_path(&self, component: &str) -> PathBuf {
|
|
|
|
|
self.stack_path.join("logs").join(component)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn from_database(conn: &mut PgConnection) -> Self {
|
2025-10-19 11:08:23 -03:00
|
|
|
info!("Loading configuration from database");
|
2025-10-18 18:19:08 -03:00
|
|
|
|
|
|
|
|
let config_map = match Self::load_config_from_db(conn) {
|
|
|
|
|
Ok(map) => {
|
2025-10-19 11:08:23 -03:00
|
|
|
info!("Loaded {} config values from database", map.len());
|
2025-10-18 18:19:08 -03:00
|
|
|
map
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
2025-10-19 11:08:23 -03:00
|
|
|
warn!("Failed to load config from database: {}. Using defaults", e);
|
2025-10-18 18:19:08 -03:00
|
|
|
HashMap::new()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let get_str = |key: &str, default: &str| -> String {
|
|
|
|
|
config_map
|
|
|
|
|
.get(key)
|
|
|
|
|
.map(|v| v.config_value.clone())
|
|
|
|
|
.unwrap_or_else(|| default.to_string())
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let get_u32 = |key: &str, default: u32| -> u32 {
|
|
|
|
|
config_map
|
|
|
|
|
.get(key)
|
|
|
|
|
.and_then(|v| v.config_value.parse().ok())
|
|
|
|
|
.unwrap_or(default)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let get_u16 = |key: &str, default: u16| -> u16 {
|
|
|
|
|
config_map
|
|
|
|
|
.get(key)
|
|
|
|
|
.and_then(|v| v.config_value.parse().ok())
|
|
|
|
|
.unwrap_or(default)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let get_bool = |key: &str, default: bool| -> bool {
|
|
|
|
|
config_map
|
|
|
|
|
.get(key)
|
|
|
|
|
.map(|v| v.config_value.to_lowercase() == "true")
|
|
|
|
|
.unwrap_or(default)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let stack_path = PathBuf::from(get_str("STACK_PATH", "./botserver-stack"));
|
|
|
|
|
|
|
|
|
|
let database = DatabaseConfig {
|
|
|
|
|
username: get_str("TABLES_USERNAME", "botserver"),
|
|
|
|
|
password: get_str("TABLES_PASSWORD", "botserver"),
|
|
|
|
|
server: get_str("TABLES_SERVER", "localhost"),
|
|
|
|
|
port: get_u32("TABLES_PORT", 5432),
|
|
|
|
|
database: get_str("TABLES_DATABASE", "botserver"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let database_custom = DatabaseConfig {
|
|
|
|
|
username: get_str("CUSTOM_USERNAME", "user"),
|
|
|
|
|
password: get_str("CUSTOM_PASSWORD", "pass"),
|
|
|
|
|
server: get_str("CUSTOM_SERVER", "localhost"),
|
|
|
|
|
port: get_u32("CUSTOM_PORT", 5432),
|
|
|
|
|
database: get_str("CUSTOM_DATABASE", "custom"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let minio = DriveConfig {
|
|
|
|
|
server: get_str("DRIVE_SERVER", "localhost:9000"),
|
|
|
|
|
access_key: get_str("DRIVE_ACCESSKEY", "minioadmin"),
|
|
|
|
|
secret_key: get_str("DRIVE_SECRET", "minioadmin"),
|
|
|
|
|
use_ssl: get_bool("DRIVE_USE_SSL", false),
|
|
|
|
|
org_prefix: get_str("DRIVE_ORG_PREFIX", "botserver"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let email = EmailConfig {
|
|
|
|
|
from: get_str("EMAIL_FROM", "noreply@example.com"),
|
|
|
|
|
server: get_str("EMAIL_SERVER", "smtp.example.com"),
|
|
|
|
|
port: get_u16("EMAIL_PORT", 587),
|
|
|
|
|
username: get_str("EMAIL_USER", "user"),
|
|
|
|
|
password: get_str("EMAIL_PASS", "pass"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let ai = AIConfig {
|
|
|
|
|
instance: get_str("AI_INSTANCE", "gpt-4"),
|
|
|
|
|
key: get_str("AI_KEY", ""),
|
|
|
|
|
version: get_str("AI_VERSION", "2023-12-01-preview"),
|
|
|
|
|
endpoint: get_str("AI_ENDPOINT", "https://api.openai.com"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AppConfig {
|
|
|
|
|
minio,
|
|
|
|
|
server: ServerConfig {
|
|
|
|
|
host: get_str("SERVER_HOST", "127.0.0.1"),
|
|
|
|
|
port: get_u16("SERVER_PORT", 8080),
|
|
|
|
|
},
|
|
|
|
|
database,
|
|
|
|
|
database_custom,
|
|
|
|
|
email,
|
|
|
|
|
ai,
|
|
|
|
|
s3_bucket: get_str("DRIVE_BUCKET", "default"),
|
|
|
|
|
site_path: get_str("SITES_ROOT", "./botserver-stack/sites"),
|
|
|
|
|
stack_path,
|
|
|
|
|
db_conn: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 10:30:17 -03:00
|
|
|
pub fn from_env() -> Self {
|
2025-10-19 11:08:23 -03:00
|
|
|
warn!("Loading configuration from environment variables");
|
2025-10-18 18:19:08 -03:00
|
|
|
|
|
|
|
|
let stack_path =
|
|
|
|
|
std::env::var("STACK_PATH").unwrap_or_else(|_| "./botserver-stack".to_string());
|
|
|
|
|
|
2025-10-06 10:30:17 -03:00
|
|
|
let database = DatabaseConfig {
|
2025-10-18 18:19:08 -03:00
|
|
|
username: std::env::var("TABLES_USERNAME").unwrap_or_else(|_| "botserver".to_string()),
|
|
|
|
|
password: std::env::var("TABLES_PASSWORD").unwrap_or_else(|_| "botserver".to_string()),
|
|
|
|
|
server: std::env::var("TABLES_SERVER").unwrap_or_else(|_| "localhost".to_string()),
|
|
|
|
|
port: std::env::var("TABLES_PORT")
|
2025-10-06 10:30:17 -03:00
|
|
|
.ok()
|
|
|
|
|
.and_then(|p| p.parse().ok())
|
|
|
|
|
.unwrap_or(5432),
|
2025-10-18 18:19:08 -03:00
|
|
|
database: std::env::var("TABLES_DATABASE").unwrap_or_else(|_| "botserver".to_string()),
|
2025-10-06 10:30:17 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let database_custom = DatabaseConfig {
|
2025-10-18 18:19:08 -03:00
|
|
|
username: std::env::var("CUSTOM_USERNAME").unwrap_or_else(|_| "user".to_string()),
|
|
|
|
|
password: std::env::var("CUSTOM_PASSWORD").unwrap_or_else(|_| "pass".to_string()),
|
|
|
|
|
server: std::env::var("CUSTOM_SERVER").unwrap_or_else(|_| "localhost".to_string()),
|
|
|
|
|
port: std::env::var("CUSTOM_PORT")
|
2025-10-06 10:30:17 -03:00
|
|
|
.ok()
|
|
|
|
|
.and_then(|p| p.parse().ok())
|
|
|
|
|
.unwrap_or(5432),
|
2025-10-18 18:19:08 -03:00
|
|
|
database: std::env::var("CUSTOM_DATABASE").unwrap_or_else(|_| "custom".to_string()),
|
2025-10-06 10:30:17 -03:00
|
|
|
};
|
|
|
|
|
|
2025-10-11 20:02:14 -03:00
|
|
|
let minio = DriveConfig {
|
2025-10-18 18:19:08 -03:00
|
|
|
server: std::env::var("DRIVE_SERVER").unwrap_or_else(|_| "localhost:9000".to_string()),
|
|
|
|
|
access_key: std::env::var("DRIVE_ACCESSKEY")
|
|
|
|
|
.unwrap_or_else(|_| "minioadmin".to_string()),
|
|
|
|
|
secret_key: std::env::var("DRIVE_SECRET").unwrap_or_else(|_| "minioadmin".to_string()),
|
|
|
|
|
use_ssl: std::env::var("DRIVE_USE_SSL")
|
2025-10-06 10:30:17 -03:00
|
|
|
.unwrap_or_else(|_| "false".to_string())
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap_or(false),
|
2025-10-18 18:19:08 -03:00
|
|
|
org_prefix: std::env::var("DRIVE_ORG_PREFIX")
|
|
|
|
|
.unwrap_or_else(|_| "botserver".to_string()),
|
2025-10-06 10:30:17 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let email = EmailConfig {
|
2025-10-18 18:19:08 -03:00
|
|
|
from: std::env::var("EMAIL_FROM").unwrap_or_else(|_| "noreply@example.com".to_string()),
|
|
|
|
|
server: std::env::var("EMAIL_SERVER")
|
|
|
|
|
.unwrap_or_else(|_| "smtp.example.com".to_string()),
|
|
|
|
|
port: std::env::var("EMAIL_PORT")
|
2025-10-06 19:12:13 -03:00
|
|
|
.unwrap_or_else(|_| "587".to_string())
|
2025-10-06 10:30:17 -03:00
|
|
|
.parse()
|
2025-10-06 19:12:13 -03:00
|
|
|
.unwrap_or(587),
|
2025-10-18 18:19:08 -03:00
|
|
|
username: std::env::var("EMAIL_USER").unwrap_or_else(|_| "user".to_string()),
|
|
|
|
|
password: std::env::var("EMAIL_PASS").unwrap_or_else(|_| "pass".to_string()),
|
2025-10-06 10:30:17 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let ai = AIConfig {
|
2025-10-18 18:19:08 -03:00
|
|
|
instance: std::env::var("AI_INSTANCE").unwrap_or_else(|_| "gpt-4".to_string()),
|
|
|
|
|
key: std::env::var("AI_KEY").unwrap_or_else(|_| "".to_string()),
|
|
|
|
|
version: std::env::var("AI_VERSION")
|
|
|
|
|
.unwrap_or_else(|_| "2023-12-01-preview".to_string()),
|
|
|
|
|
endpoint: std::env::var("AI_ENDPOINT")
|
2025-10-11 20:02:14 -03:00
|
|
|
.unwrap_or_else(|_| "https://api.openai.com".to_string()),
|
2025-10-06 10:30:17 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AppConfig {
|
|
|
|
|
minio,
|
|
|
|
|
server: ServerConfig {
|
2025-10-18 18:19:08 -03:00
|
|
|
host: std::env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
|
|
|
|
|
port: std::env::var("SERVER_PORT")
|
2025-10-06 10:30:17 -03:00
|
|
|
.ok()
|
|
|
|
|
.and_then(|p| p.parse().ok())
|
|
|
|
|
.unwrap_or(8080),
|
|
|
|
|
},
|
|
|
|
|
database,
|
|
|
|
|
database_custom,
|
|
|
|
|
email,
|
|
|
|
|
ai,
|
2025-10-18 18:19:08 -03:00
|
|
|
s3_bucket: std::env::var("DRIVE_BUCKET").unwrap_or_else(|_| "default".to_string()),
|
|
|
|
|
site_path: std::env::var("SITES_ROOT")
|
|
|
|
|
.unwrap_or_else(|_| "./botserver-stack/sites".to_string()),
|
|
|
|
|
stack_path: PathBuf::from(stack_path),
|
|
|
|
|
db_conn: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_config_from_db(
|
|
|
|
|
conn: &mut PgConnection,
|
|
|
|
|
) -> Result<HashMap<String, ServerConfigRow>, diesel::result::Error> {
|
|
|
|
|
let results = diesel::sql_query(
|
|
|
|
|
"SELECT id, config_key, config_value, config_type, is_encrypted
|
|
|
|
|
FROM server_configuration",
|
|
|
|
|
)
|
|
|
|
|
.load::<ServerConfigRow>(conn)?;
|
|
|
|
|
|
|
|
|
|
let mut map = HashMap::new();
|
|
|
|
|
for row in results {
|
|
|
|
|
map.insert(row.config_key.clone(), row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(map)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_config(
|
|
|
|
|
&self,
|
|
|
|
|
conn: &mut PgConnection,
|
|
|
|
|
key: &str,
|
|
|
|
|
value: &str,
|
|
|
|
|
) -> Result<(), diesel::result::Error> {
|
|
|
|
|
diesel::sql_query("SELECT set_config($1, $2)")
|
|
|
|
|
.bind::<Text, _>(key)
|
|
|
|
|
.bind::<Text, _>(value)
|
|
|
|
|
.execute(conn)?;
|
|
|
|
|
|
|
|
|
|
info!("Updated configuration: {} = {}", key, value);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_config(
|
|
|
|
|
&self,
|
|
|
|
|
conn: &mut PgConnection,
|
|
|
|
|
key: &str,
|
|
|
|
|
fallback: Option<&str>,
|
|
|
|
|
) -> Result<String, diesel::result::Error> {
|
|
|
|
|
let fallback_str = fallback.unwrap_or("");
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, QueryableByName)]
|
|
|
|
|
struct ConfigValue {
|
|
|
|
|
#[diesel(sql_type = Text)]
|
|
|
|
|
value: String,
|
|
|
|
|
}
|
2025-10-11 20:02:14 -03:00
|
|
|
|
2025-10-18 18:19:08 -03:00
|
|
|
let result = diesel::sql_query("SELECT get_config($1, $2) as value")
|
|
|
|
|
.bind::<Text, _>(key)
|
|
|
|
|
.bind::<Text, _>(fallback_str)
|
|
|
|
|
.get_result::<ConfigValue>(conn)
|
|
|
|
|
.map(|row| row.value)?;
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct ConfigManager {
|
|
|
|
|
conn: Arc<Mutex<PgConnection>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ConfigManager {
|
|
|
|
|
pub fn new(conn: Arc<Mutex<PgConnection>>) -> Self {
|
|
|
|
|
Self { conn }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn sync_gbot_config(
|
|
|
|
|
&self,
|
|
|
|
|
bot_id: &uuid::Uuid,
|
|
|
|
|
config_path: &str,
|
|
|
|
|
) -> Result<usize, String> {
|
|
|
|
|
use sha2::{Digest, Sha256};
|
|
|
|
|
use std::fs;
|
|
|
|
|
|
|
|
|
|
let content = fs::read_to_string(config_path)
|
|
|
|
|
.map_err(|e| format!("Failed to read config file: {}", e))?;
|
|
|
|
|
|
|
|
|
|
let mut hasher = Sha256::new();
|
|
|
|
|
hasher.update(content.as_bytes());
|
|
|
|
|
let file_hash = format!("{:x}", hasher.finalize());
|
|
|
|
|
|
|
|
|
|
let mut conn = self
|
|
|
|
|
.conn
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| format!("Failed to acquire lock: {}", e))?;
|
|
|
|
|
|
|
|
|
|
#[derive(QueryableByName)]
|
|
|
|
|
struct SyncHash {
|
|
|
|
|
#[diesel(sql_type = Text)]
|
|
|
|
|
file_hash: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let last_hash: Option<String> =
|
|
|
|
|
diesel::sql_query("SELECT file_hash FROM gbot_config_sync WHERE bot_id = $1")
|
|
|
|
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
|
|
|
|
.get_result::<SyncHash>(&mut *conn)
|
|
|
|
|
.optional()
|
|
|
|
|
.map_err(|e| format!("Database error: {}", e))?
|
|
|
|
|
.map(|row| row.file_hash);
|
|
|
|
|
|
|
|
|
|
if last_hash.as_ref() == Some(&file_hash) {
|
|
|
|
|
info!("Config file unchanged for bot {}", bot_id);
|
|
|
|
|
return Ok(0);
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
2025-10-18 18:19:08 -03:00
|
|
|
|
|
|
|
|
let mut updated = 0;
|
|
|
|
|
for line in content.lines().skip(1) {
|
|
|
|
|
let parts: Vec<&str> = line.split(',').collect();
|
|
|
|
|
if parts.len() >= 2 {
|
|
|
|
|
let key = parts[0].trim();
|
|
|
|
|
let value = parts[1].trim();
|
|
|
|
|
|
|
|
|
|
diesel::sql_query(
|
|
|
|
|
"INSERT INTO bot_configuration (id, bot_id, config_key, config_value, config_type)
|
|
|
|
|
VALUES (gen_random_uuid()::text, $1, $2, $3, 'string')
|
|
|
|
|
ON CONFLICT (bot_id, config_key)
|
|
|
|
|
DO UPDATE SET config_value = EXCLUDED.config_value, updated_at = NOW()"
|
|
|
|
|
)
|
|
|
|
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
|
|
|
|
.bind::<diesel::sql_types::Text, _>(key)
|
|
|
|
|
.bind::<diesel::sql_types::Text, _>(value)
|
|
|
|
|
.execute(&mut *conn)
|
|
|
|
|
.map_err(|e| format!("Failed to update config: {}", e))?;
|
|
|
|
|
|
|
|
|
|
updated += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
diesel::sql_query(
|
|
|
|
|
"INSERT INTO gbot_config_sync (id, bot_id, config_file_path, file_hash, sync_count)
|
|
|
|
|
VALUES (gen_random_uuid()::text, $1, $2, $3, 1)
|
|
|
|
|
ON CONFLICT (bot_id)
|
|
|
|
|
DO UPDATE SET last_sync_at = NOW(), file_hash = EXCLUDED.file_hash,
|
|
|
|
|
sync_count = gbot_config_sync.sync_count + 1",
|
|
|
|
|
)
|
|
|
|
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
|
|
|
|
.bind::<diesel::sql_types::Text, _>(config_path)
|
|
|
|
|
.bind::<diesel::sql_types::Text, _>(&file_hash)
|
|
|
|
|
.execute(&mut *conn)
|
|
|
|
|
.map_err(|e| format!("Failed to update sync record: {}", e))?;
|
|
|
|
|
|
2025-10-19 11:08:23 -03:00
|
|
|
info!("Synced {} config values for bot {} from {}", updated, bot_id, config_path);
|
2025-10-18 18:19:08 -03:00
|
|
|
Ok(updated)
|
2025-10-06 10:30:17 -03:00
|
|
|
}
|
2025-10-06 19:12:13 -03:00
|
|
|
}
|