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    for (key, value) in seeds {
51        client
52            .execute(
53                "INSERT INTO settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO NOTHING",
54                &[&key, &value],
55            )
56            .await
57            .map_err(AppError::query)?;
58        tracing::info!("孤儿素材清理配置已从环境变量播种: {key}={value}(仅键缺失时生效)");
59    }
60    Ok(())
61}
62
63/// 读取孤儿素材清理配置(面板用)。
64///
65/// settings 表缺失键时回退到默认值,保证向后兼容。
66#[server(GetAssetPurgeSettings, "/api")]
67pub async fn get_asset_purge_settings() -> Result<AssetPurgeSettings, ServerFnError> {
68    let _user = get_current_admin_user().await?;
69
70    #[cfg(feature = "server")]
71    {
72        let client = get_conn().await.map_err(AppError::db_conn)?;
73
74        let enabled: bool = client
75            .query_opt(
76                "SELECT value FROM settings WHERE key = 'asset_orphan_purge_enabled'",
77                &[],
78            )
79            .await
80            .map_err(AppError::query)?
81            .and_then(|r| r.get::<_, String>("value").parse().ok())
82            .unwrap_or(crate::models::settings::DEFAULT_ASSET_ORPHAN_PURGE_ENABLED);
83
84        let days: i32 = client
85            .query_opt(
86                "SELECT value FROM settings WHERE key = 'asset_orphan_retention_days'",
87                &[],
88            )
89            .await
90            .map_err(AppError::query)?
91            .and_then(|r| r.get::<_, String>("value").parse().ok())
92            .unwrap_or(crate::models::settings::DEFAULT_ASSET_ORPHAN_RETENTION_DAYS);
93
94        Ok(AssetPurgeSettings {
95            auto_purge_enabled: enabled,
96            retention_days: AssetPurgeSettings::clamp_retention(days),
97        })
98    }
99
100    #[cfg(not(feature = "server"))]
101    {
102        Ok(AssetPurgeSettings::default())
103    }
104}
105
106/// 更新孤儿素材清理配置。
107///
108/// retention_days 会被 clamp 到合法范围后写入。清理任务每天 tick 时读取
109/// 最新值,无需重启。
110#[server(UpdateAssetPurgeSettings, "/api")]
111pub async fn update_asset_purge_settings(
112    auto_purge_enabled: bool,
113    retention_days: i32,
114) -> Result<AssetPurgeSettings, ServerFnError> {
115    let _user = get_current_admin_user().await?;
116
117    let retention_days = AssetPurgeSettings::clamp_retention(retention_days);
118
119    #[cfg(feature = "server")]
120    {
121        let client = get_conn().await.map_err(AppError::db_conn)?;
122
123        for (key, value) in [
124            ("asset_orphan_purge_enabled", auto_purge_enabled.to_string()),
125            ("asset_orphan_retention_days", retention_days.to_string()),
126        ] {
127            client
128                .execute(
129                    "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
130                     ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
131                    &[&key, &value],
132                )
133                .await
134                .map_err(AppError::query)?;
135        }
136
137        tracing::info!(
138            "Asset purge settings updated: auto_purge={}, retention_days={}",
139            auto_purge_enabled,
140            retention_days
141        );
142    }
143
144    Ok(AssetPurgeSettings {
145        auto_purge_enabled,
146        retention_days,
147    })
148}