diff --git a/src/api/mod.rs b/src/api/mod.rs index 719008c..fe97f9c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -20,6 +20,8 @@ pub mod posts; pub mod rate_limit; /// HTML 消毒器。 pub mod sanitizer; +/// 回收站与站点配置接口。 +pub mod settings; /// URL slug 生成与校验。 pub mod slug; /// 图片上传的 Axum 处理器。 diff --git a/src/api/settings.rs b/src/api/settings.rs new file mode 100644 index 0000000..cdd423d --- /dev/null +++ b/src/api/settings.rs @@ -0,0 +1,105 @@ +//! 回收站配置接口:读取与更新自动清理设置。 +//! +//! 所有接口需要 admin 权限。配置持久化到 settings 键值表。 +//! Dioxus server function,注册在 `/api` 路径下。 + +use dioxus::prelude::*; + +use crate::models::settings::TrashSettings; +#[cfg(feature = "server")] +use crate::api::auth::get_current_admin_user; +#[cfg(feature = "server")] +use crate::api::error::AppError; +#[cfg(feature = "server")] +use crate::db::pool::get_conn; + +/// 读取回收站配置。 +/// +/// settings 表缺失键时回退到默认值,保证向后兼容。 +#[server(GetTrashSettings, "/api")] +pub async fn get_trash_settings() -> Result { + let _user = get_current_admin_user().await?; + + #[cfg(feature = "server")] + { + let client = get_conn().await.map_err(AppError::db_conn)?; + + let enabled: bool = client + .query_opt( + "SELECT value FROM settings WHERE key = 'trash_auto_purge_enabled'", + &[], + ) + .await + .map_err(AppError::query)? + .and_then(|r| r.get::<_, String>("value").parse().ok()) + .unwrap_or_else(|| crate::models::settings::DEFAULT_AUTO_PURGE_ENABLED); + + let days: i32 = client + .query_opt( + "SELECT value FROM settings WHERE key = 'trash_retention_days'", + &[], + ) + .await + .map_err(AppError::query)? + .and_then(|r| r.get::<_, String>("value").parse().ok()) + .unwrap_or_else(|| crate::models::settings::DEFAULT_RETENTION_DAYS); + + Ok(TrashSettings { + auto_purge_enabled: enabled, + retention_days: TrashSettings::clamp_retention(days), + }) + } + + #[cfg(not(feature = "server"))] + { + Ok(TrashSettings::default()) + } +} + +/// 更新回收站配置。 +/// +/// retention_days 会被 clamp 到合法范围后写入。 +#[server(UpdateTrashSettings, "/api")] +pub async fn update_trash_settings( + auto_purge_enabled: bool, + retention_days: i32, +) -> Result { + let _user = get_current_admin_user().await?; + + let retention_days = TrashSettings::clamp_retention(retention_days); + + #[cfg(feature = "server")] + { + let client = get_conn().await.map_err(AppError::db_conn)?; + + // UPSERT 两个键。 + client + .execute( + "INSERT INTO settings (key, value, updated_at) VALUES ('trash_auto_purge_enabled', $1, NOW()) + ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()", + &[&auto_purge_enabled.to_string()], + ) + .await + .map_err(AppError::query)?; + + client + .execute( + "INSERT INTO settings (key, value, updated_at) VALUES ('trash_retention_days', $1, NOW()) + ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()", + &[&retention_days.to_string()], + ) + .await + .map_err(AppError::query)?; + + tracing::info!( + "Trash settings updated: auto_purge={}, retention_days={}", + auto_purge_enabled, + retention_days + ); + } + + Ok(TrashSettings { + auto_purge_enabled, + retention_days, + }) +}