Skip to main content

yggdrasil/api/settings/
asset_purge.rs

1// 与 posts 模块一致:Dioxus `#[server]` 宏触发 deprecated/unit/too_many_arguments
2// 提示,按项目惯例放行(限流/运行器等配置项天然参数多)。
3#![allow(clippy::unused_unit, deprecated, clippy::too_many_arguments)]
4
5use dioxus::prelude::*;
6
7#[cfg(feature = "server")]
8use crate::api::auth::get_current_admin_user;
9#[cfg(feature = "server")]
10use crate::api::error::AppError;
11#[cfg(feature = "server")]
12use crate::db::pool::get_conn;
13use crate::models::settings::AssetPurgeSettings;
14
15// ============================================================================
16// 孤儿素材自动清理配置(即时生效)
17// ============================================================================
18
19/// 启动时用 `ASSET_ORPHAN_PURGE_*` 环境变量播种孤儿素材清理配置。
20///
21/// 语义与 [`seed_image_cache_settings_from_env`][crate::api::settings::seed_image_cache_settings_from_env] 一致:仅当对应 settings 键
22/// **不存在**时插入(首次部署),之后以面板写入的 DB 值为准,重启不被 env 覆盖。
23/// 单个变量非法只告警跳过,不影响启动。
24#[cfg(feature = "server")]
25pub(crate) async fn seed_asset_purge_settings_from_env(
26    client: &tokio_postgres::Client,
27) -> Result<(), AppError> {
28    let mut seeds: Vec<(&'static str, String)> = Vec::new();
29
30    if let Ok(v) = std::env::var("ASSET_ORPHAN_PURGE_ENABLED") {
31        match v.trim().parse::<bool>() {
32            Ok(b) => seeds.push(("asset_orphan_purge_enabled", b.to_string())),
33            Err(_) => {
34                tracing::warn!("ASSET_ORPHAN_PURGE_ENABLED={v:?} 非法(期望 true/false),跳过")
35            }
36        }
37    }
38    if let Ok(v) = std::env::var("ASSET_ORPHAN_RETENTION_DAYS") {
39        match v.trim().parse::<i32>() {
40            Ok(n) => seeds.push((
41                "asset_orphan_retention_days",
42                crate::models::settings::AssetPurgeSettings::clamp_retention(n).to_string(),
43            )),
44            Err(_) => {
45                tracing::warn!("ASSET_ORPHAN_RETENTION_DAYS={v:?} 非法(期望正整数),跳过")
46            }
47        }
48    }
49
50    super::insert_env_seeds(client, seeds).await
51}
52
53/// 读取孤儿素材清理配置(面板用)。
54///
55/// settings 表缺失键时回退到默认值,保证向后兼容。
56#[server(GetAssetPurgeSettings, "/api")]
57pub async fn get_asset_purge_settings() -> Result<AssetPurgeSettings, ServerFnError> {
58    let _user = get_current_admin_user().await?;
59
60    #[cfg(feature = "server")]
61    {
62        let client = get_conn().await.map_err(AppError::db_conn)?;
63
64        let values = super::load_setting_values(
65            &client,
66            &["asset_orphan_purge_enabled", "asset_orphan_retention_days"],
67        )
68        .await?;
69        let enabled = values
70            .get("asset_orphan_purge_enabled")
71            .and_then(|v| v.parse().ok())
72            .unwrap_or(crate::models::settings::DEFAULT_ASSET_ORPHAN_PURGE_ENABLED);
73        let days = values
74            .get("asset_orphan_retention_days")
75            .and_then(|v| v.parse().ok())
76            .unwrap_or(crate::models::settings::DEFAULT_ASSET_ORPHAN_RETENTION_DAYS);
77
78        Ok(AssetPurgeSettings {
79            auto_purge_enabled: enabled,
80            retention_days: AssetPurgeSettings::clamp_retention(days),
81        })
82    }
83
84    #[cfg(not(feature = "server"))]
85    {
86        Ok(AssetPurgeSettings::default())
87    }
88}
89
90/// 更新孤儿素材清理配置。
91///
92/// retention_days 会被 clamp 到合法范围后写入。清理任务每天 tick 时读取
93/// 最新值,无需重启。
94#[server(UpdateAssetPurgeSettings, "/api")]
95pub async fn update_asset_purge_settings(
96    auto_purge_enabled: bool,
97    retention_days: i32,
98) -> Result<AssetPurgeSettings, ServerFnError> {
99    let _user = get_current_admin_user().await?;
100
101    let retention_days = AssetPurgeSettings::clamp_retention(retention_days);
102
103    #[cfg(feature = "server")]
104    {
105        let client = get_conn().await.map_err(AppError::db_conn)?;
106
107        for (key, value) in [
108            ("asset_orphan_purge_enabled", auto_purge_enabled.to_string()),
109            ("asset_orphan_retention_days", retention_days.to_string()),
110        ] {
111            client
112                .execute(
113                    "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
114                     ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
115                    &[&key, &value],
116                )
117                .await
118                .map_err(AppError::query)?;
119        }
120
121        tracing::info!(
122            "Asset purge settings updated: auto_purge={}, retention_days={}",
123            auto_purge_enabled,
124            retention_days
125        );
126    }
127
128    Ok(AssetPurgeSettings {
129        auto_purge_enabled,
130        retention_days,
131    })
132}