Skip to content

Commit 7511e87

Browse files
committed
feat(api): add api route for update rustmail config file
1 parent 1a29f76 commit 7511e87

4 files changed

Lines changed: 170 additions & 0 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use crate::config::{load_config, Config, LanguageConfigExt};
2+
use crate::prelude::types::*;
3+
use axum::extract::State;
4+
use axum::http::StatusCode;
5+
use axum::Json;
6+
use rustmail_types::ConfigResponse;
7+
use std::fs;
8+
use std::sync::Arc;
9+
use tokio::sync::Mutex;
10+
11+
fn mask_secret(secret: &str) -> String {
12+
let len = secret.chars().count();
13+
if len <= 8 {
14+
"*".repeat(len)
15+
} else {
16+
let chars: Vec<char> = secret.chars().collect();
17+
let start: String = chars.iter().take(4).collect();
18+
let end: String = chars.iter().skip(len - 4).collect();
19+
format!("{}...{}", start, end)
20+
}
21+
}
22+
23+
pub async fn handle_get_config(
24+
State(bot_state): State<Arc<Mutex<BotState>>>,
25+
) -> Result<Json<ConfigResponse>, StatusCode> {
26+
let state = bot_state.lock().await;
27+
28+
let config = match &state.config {
29+
Some(c) => c,
30+
None => return Err(StatusCode::INTERNAL_SERVER_ERROR),
31+
};
32+
33+
let mut masked_bot = config.bot.clone();
34+
masked_bot.token = mask_secret(&config.bot.token);
35+
masked_bot.client_secret = mask_secret(&config.bot.client_secret);
36+
37+
let response = ConfigResponse {
38+
bot: masked_bot,
39+
command: config.command.clone(),
40+
thread: config.thread.clone(),
41+
language: config.language.clone(),
42+
error_handling: config.error_handling.clone(),
43+
notifications: config.notifications.clone(),
44+
reminders: config.reminders.clone(),
45+
logs: config.logs.clone(),
46+
};
47+
48+
Ok(Json(response))
49+
}
50+
51+
pub async fn handle_update_config(
52+
State(bot_state): State<Arc<Mutex<BotState>>>,
53+
Json(update): Json<ConfigResponse>,
54+
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
55+
let current_config = {
56+
let state = bot_state.lock().await;
57+
match &state.config {
58+
Some(c) => c.clone(),
59+
None => {
60+
return Err((
61+
StatusCode::INTERNAL_SERVER_ERROR,
62+
"Configuration not loaded".to_string(),
63+
))
64+
}
65+
}
66+
};
67+
68+
let mut new_bot_config = update.bot.clone();
69+
70+
if new_bot_config.token.contains("...") {
71+
new_bot_config.token = current_config.bot.token.clone();
72+
}
73+
74+
if new_bot_config.client_secret.contains("...") {
75+
new_bot_config.client_secret = current_config.bot.client_secret.clone();
76+
}
77+
78+
new_bot_config.ip = current_config.bot.ip.clone();
79+
80+
let new_config = Config {
81+
bot: new_bot_config,
82+
command: update.command,
83+
thread: update.thread,
84+
language: update.language,
85+
error_handling: update.error_handling,
86+
notifications: update.notifications,
87+
reminders: update.reminders,
88+
logs: update.logs,
89+
db_pool: None,
90+
error_handler: None,
91+
thread_locks: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
92+
};
93+
94+
if let Err(e) = validate_config(&new_config) {
95+
return Err((StatusCode::BAD_REQUEST, e));
96+
}
97+
98+
if let Err(e) = save_config_with_backup(&new_config, "config.toml").await {
99+
return Err((StatusCode::INTERNAL_SERVER_ERROR, e));
100+
}
101+
102+
let mut state = bot_state.lock().await;
103+
state.config = load_config("config.toml");
104+
105+
Ok(Json(serde_json::json!({
106+
"success": true,
107+
"message": "Configuration saved successfully. Restart the bot to apply changes."
108+
})))
109+
}
110+
111+
fn validate_config(config: &Config) -> Result<(), String> {
112+
if u64::from_str_radix(&config.thread.user_message_color, 16).is_err() {
113+
return Err("Invalid user message color format (must be hex)".to_string());
114+
}
115+
116+
if u64::from_str_radix(&config.thread.staff_message_color, 16).is_err() {
117+
return Err("Invalid staff message color format (must be hex)".to_string());
118+
}
119+
120+
if u64::from_str_radix(&config.reminders.embed_color, 16).is_err() {
121+
return Err("Invalid reminder embed color format (must be hex)".to_string());
122+
}
123+
124+
config.bot.validate_logs_config()?;
125+
config.bot.validate_features_config()?;
126+
127+
if !config
128+
.language
129+
.is_language_supported(config.language.get_default_language())
130+
{
131+
return Err(format!(
132+
"Default language '{}' is not in supported languages list",
133+
config.language.default_language
134+
));
135+
}
136+
137+
Ok(())
138+
}
139+
140+
async fn save_config_with_backup(config: &Config, path: &str) -> Result<(), String> {
141+
if std::path::Path::new(path).exists() {
142+
let backup_path = format!("{}.backup", path);
143+
fs::copy(path, &backup_path)
144+
.map_err(|e| format!("Failed to create backup: {}", e))?;
145+
}
146+
147+
let config_response = ConfigResponse {
148+
bot: config.bot.clone(),
149+
command: config.command.clone(),
150+
thread: config.thread.clone(),
151+
language: config.language.clone(),
152+
error_handling: config.error_handling.clone(),
153+
notifications: config.notifications.clone(),
154+
reminders: config.reminders.clone(),
155+
logs: config.logs.clone(),
156+
};
157+
158+
let toml_content = toml::to_string_pretty(&config_response)
159+
.map_err(|e| format!("Failed to serialize config: {}", e))?;
160+
161+
fs::write(path, toml_content).map_err(|e| format!("Failed to write config file: {}", e))?;
162+
163+
Ok(())
164+
}

rustmail/src/api/handler/bot/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
pub mod config;
12
pub mod restart;
23
pub mod start;
34
pub mod status;
45
pub mod stop;
56
pub mod tickets;
67

8+
pub use config::*;
79
pub use restart::*;
810
pub use start::*;
911
pub use status::*;

rustmail/src/api/routes/bot.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub fn create_bot_router(bot_state: Arc<Mutex<BotState>>) -> Router<Arc<Mutex<Bo
1212
.route("/restart", post(handle_restart_bot))
1313
.route("/status", get(handle_status_bot))
1414
.route("/tickets", get(handle_tickets_bot))
15+
.route("/config", get(handle_get_config))
16+
.route("/config", axum::routing::put(handle_update_config))
1517
.layer(axum::middleware::from_fn_with_state(
1618
bot_state,
1719
auth_middleware,

rustmail/src/commands/edit/common.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ mod tests {
4949
block_quote: false,
5050
time_to_close_thread: 5,
5151
create_ticket_by_create_channel: false,
52+
close_on_leave: false,
53+
auto_archive_duration: 0,
5254
},
5355
notifications: NotificationsConfig::default(),
5456
logs: LogsConfig::default(),

0 commit comments

Comments
 (0)