use axum::{Json, extract::State}; use serde::{Deserialize, Serialize}; use tranquil_pds::api::error::{ApiError, DbResultExt}; use tranquil_pds::auth::{Admin, Auth}; use tranquil_pds::state::AppState; #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct ServerConfigOutput { pub server_name: String, pub primary_color: Option, pub primary_color_dark: Option, pub secondary_color: Option, pub secondary_color_dark: Option, pub logo_cid: Option, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct UpdateServerConfigRequest { pub server_name: Option, pub primary_color: Option, pub primary_color_dark: Option, pub secondary_color: Option, pub secondary_color_dark: Option, pub logo_cid: Option, } #[derive(Serialize)] pub struct UpdateServerConfigOutput { pub success: bool, } fn is_valid_hex_color(s: &str) -> bool { if s.len() != 7 || !s.starts_with('#') { return false; } s[1..].chars().all(|c| c.is_ascii_hexdigit()) } pub async fn get_server_config( State(state): State, ) -> Result, ApiError> { let keys = &[ "server_name", "primary_color", "primary_color_dark", "secondary_color", "secondary_color_dark", "logo_cid", ]; let rows = state .repos .infra .get_server_configs(keys) .await .log_db_err("fetching server config")?; let config_map: std::collections::HashMap = rows.into_iter().collect(); Ok(Json(ServerConfigOutput { server_name: config_map .get("server_name") .cloned() .unwrap_or_else(|| "Tranquil PDS".to_string()), primary_color: config_map.get("primary_color").cloned(), primary_color_dark: config_map.get("primary_color_dark").cloned(), secondary_color: config_map.get("secondary_color").cloned(), secondary_color_dark: config_map.get("secondary_color_dark").cloned(), logo_cid: config_map.get("logo_cid").cloned(), })) } pub async fn update_server_config( State(state): State, _auth: Auth, Json(req): Json, ) -> Result, ApiError> { if let Some(server_name) = req.server_name { let trimmed = server_name.trim(); if trimmed.is_empty() || trimmed.len() > 100 { return Err(ApiError::InvalidRequest( "Server name must be 1-100 characters".into(), )); } state .repos .infra .upsert_server_config("server_name", trimmed) .await .log_db_err("upserting server_name")?; } if let Some(ref color) = req.primary_color { if color.is_empty() { state .repos .infra .delete_server_config("primary_color") .await .log_db_err("deleting primary_color")?; } else if is_valid_hex_color(color) { state .repos .infra .upsert_server_config("primary_color", color) .await .log_db_err("upserting primary_color")?; } else { return Err(ApiError::InvalidRequest( "Invalid primary color format (expected #RRGGBB)".into(), )); } } if let Some(ref color) = req.primary_color_dark { if color.is_empty() { state .repos .infra .delete_server_config("primary_color_dark") .await .log_db_err("deleting primary_color_dark")?; } else if is_valid_hex_color(color) { state .repos .infra .upsert_server_config("primary_color_dark", color) .await .log_db_err("upserting primary_color_dark")?; } else { return Err(ApiError::InvalidRequest( "Invalid primary dark color format (expected #RRGGBB)".into(), )); } } if let Some(ref color) = req.secondary_color { if color.is_empty() { state .repos .infra .delete_server_config("secondary_color") .await .log_db_err("deleting secondary_color")?; } else if is_valid_hex_color(color) { state .repos .infra .upsert_server_config("secondary_color", color) .await .log_db_err("upserting secondary_color")?; } else { return Err(ApiError::InvalidRequest( "Invalid secondary color format (expected #RRGGBB)".into(), )); } } if let Some(ref color) = req.secondary_color_dark { if color.is_empty() { state .repos .infra .delete_server_config("secondary_color_dark") .await .log_db_err("deleting secondary_color_dark")?; } else if is_valid_hex_color(color) { state .repos .infra .upsert_server_config("secondary_color_dark", color) .await .log_db_err("upserting secondary_color_dark")?; } else { return Err(ApiError::InvalidRequest( "Invalid secondary dark color format (expected #RRGGBB)".into(), )); } } if let Some(ref logo_cid) = req.logo_cid { if logo_cid.is_empty() { state .repos .infra .delete_server_config("logo_cid") .await .log_db_err("deleting logo_cid")?; } else { state .repos .infra .upsert_server_config("logo_cid", logo_cid) .await .log_db_err("upserting logo_cid")?; } } Ok(Json(UpdateServerConfigOutput { success: true })) }