Skip to main content

yggdrasil/api/settings/
webp.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::WebpSettings;
14
15// ============================================================================
16// WebP 编码配置(需重启生效)
17// ============================================================================
18
19/// 启动时用 `WEBP_QUALITY` / `WEBP_METHOD` 环境变量播种 WebP 编码配置。
20///
21/// 语义与 [`seed_security_settings_from_env`][crate::api::settings::seed_security_settings_from_env] 一致:仅当对应 settings 键
22/// **不存在**时插入(首次部署),之后以面板写入的 DB 值为准。单个变量非法只
23/// 告警跳过。这些值在进程启动时烘焙进 LazyLock,改 DB 值后需重启生效。
24#[cfg(feature = "server")]
25pub(crate) async fn seed_webp_settings_from_env(
26    client: &tokio_postgres::Client,
27) -> Result<(), AppError> {
28    use crate::models::settings as m;
29
30    let mut seeds: Vec<(&'static str, String)> = Vec::new();
31
32    if let Ok(v) = std::env::var("WEBP_QUALITY") {
33        match v.trim().parse::<f32>() {
34            Ok(q) => seeds.push((
35                "webp_quality",
36                m::WebpSettings::clamp_quality(q).to_string(),
37            )),
38            Err(_) => tracing::warn!("WEBP_QUALITY={v:?} 非法(期望浮点数),跳过"),
39        }
40    }
41    if let Ok(v) = std::env::var("WEBP_METHOD") {
42        match v.trim().parse::<u32>() {
43            Ok(n) => seeds.push(("webp_method", m::WebpSettings::clamp_method(n).to_string())),
44            Err(_) => tracing::warn!("WEBP_METHOD={v:?} 非法(期望非负整数),跳过"),
45        }
46    }
47
48    for (key, value) in seeds {
49        client
50            .execute(
51                "INSERT INTO settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO NOTHING",
52                &[&key, &value],
53            )
54            .await
55            .map_err(AppError::query)?;
56        tracing::info!("WebP 配置已从环境变量播种: {key}={value}(仅键缺失时生效)");
57    }
58    Ok(())
59}
60
61/// 从 settings 表读取 WebP 配置(缺键回退默认值)。
62///
63/// 启动时由 main.rs 调用,将结果写入 `config::WEBP_CFG`,供 infra/webp.rs 的 LazyLock
64/// 在首次编码时读取。
65#[cfg(feature = "server")]
66pub(crate) async fn load_webp_settings(
67    client: &tokio_postgres::Client,
68) -> Result<WebpSettings, AppError> {
69    use crate::models::settings as m;
70
71    async fn read_key(
72        client: &tokio_postgres::Client,
73        key: &str,
74    ) -> Result<Option<String>, AppError> {
75        let row = client
76            .query_opt("SELECT value FROM settings WHERE key = $1", &[&key])
77            .await
78            .map_err(AppError::query)?;
79        Ok(row.map(|r| r.get::<_, String>("value")))
80    }
81
82    let quality = read_key(client, "webp_quality")
83        .await?
84        .and_then(|v| v.parse().ok())
85        .map(m::WebpSettings::clamp_quality)
86        .unwrap_or(m::DEFAULT_WEBP_QUALITY);
87    let method = read_key(client, "webp_method")
88        .await?
89        .and_then(|v| v.parse().ok())
90        .map(m::WebpSettings::clamp_method)
91        .unwrap_or(m::DEFAULT_WEBP_METHOD);
92
93    Ok(WebpSettings { quality, method })
94}
95
96/// 读取 WebP 配置(面板用)。
97#[server(GetWebpSettings, "/api")]
98pub async fn get_webp_settings() -> Result<WebpSettings, ServerFnError> {
99    let _user = get_current_admin_user().await?;
100
101    #[cfg(feature = "server")]
102    {
103        let client = get_conn().await.map_err(AppError::db_conn)?;
104        let s = load_webp_settings(&client)
105            .await
106            .map_err(ServerFnError::from)?;
107        Ok(s)
108    }
109
110    #[cfg(not(feature = "server"))]
111    {
112        Ok(WebpSettings::default())
113    }
114}
115
116/// 更新 WebP 配置。
117///
118/// 字段会被 clamp 后写入 DB。由于配置烘焙进 LazyLock 静态量,修改后需**重启进程**
119/// 生效——不做运行时缓存失效。
120#[server(UpdateWebpSettings, "/api")]
121pub async fn update_webp_settings(
122    quality: f32,
123    method: u32,
124) -> Result<WebpSettings, ServerFnError> {
125    let _user = get_current_admin_user().await?;
126
127    let quality = WebpSettings::clamp_quality(quality);
128    let method = WebpSettings::clamp_method(method);
129
130    #[cfg(feature = "server")]
131    {
132        let client = get_conn().await.map_err(AppError::db_conn)?;
133
134        for (key, value) in [
135            ("webp_quality", quality.to_string()),
136            ("webp_method", method.to_string()),
137        ] {
138            client
139                .execute(
140                    "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
141                     ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
142                    &[&key, &value],
143                )
144                .await
145                .map_err(AppError::query)?;
146        }
147
148        tracing::info!(
149            "WebP settings updated (需重启生效): quality={}, method={}",
150            quality,
151            method
152        );
153    }
154
155    Ok(WebpSettings { quality, method })
156}