Skip to main content

yggdrasil/api/settings/
upload.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::UploadSettings;
14
15// ============================================================================
16// 素材上传配置
17// ============================================================================
18
19/// 启动时用 `UPLOAD_CONCURRENCY` 环境变量播种素材上传并发数。
20///
21/// 语义与 [`seed_backup_settings_from_env`][crate::api::settings::seed_backup_settings_from_env] 一致:仅当 settings 键**不存在**时
22/// 插入(首次部署),之后以「站点配置」面板写入的 DB 值为准,重启不被 env 覆盖。
23/// 非法值只告警跳过,不影响启动。
24#[cfg(feature = "server")]
25pub(crate) async fn seed_upload_settings_from_env(
26    client: &tokio_postgres::Client,
27) -> Result<(), AppError> {
28    let Ok(v) = std::env::var("UPLOAD_CONCURRENCY") else {
29        return Ok(());
30    };
31    let value = match v.trim().parse::<i32>() {
32        Ok(n) => UploadSettings::clamp_concurrency(n),
33        Err(_) => {
34            tracing::warn!("UPLOAD_CONCURRENCY={v:?} 非法(期望整数),跳过");
35            return Ok(());
36        }
37    };
38    client
39        .execute(
40            "INSERT INTO settings (key, value) VALUES ('upload_concurrency', $1) ON CONFLICT (key) DO NOTHING",
41            &[&value.to_string()],
42        )
43        .await
44        .map_err(AppError::query)?;
45    tracing::info!("素材上传配置已从环境变量播种: upload_concurrency={value}(仅键缺失时生效)");
46    Ok(())
47}
48
49/// 读取素材上传配置(上传弹窗并发数)。
50///
51/// settings 表缺失键时回退到默认值,保证向后兼容。
52#[server(GetUploadSettings, "/api")]
53pub async fn get_upload_settings() -> Result<UploadSettings, ServerFnError> {
54    let _user = get_current_admin_user().await?;
55
56    #[cfg(feature = "server")]
57    {
58        let client = get_conn().await.map_err(AppError::db_conn)?;
59
60        let concurrency: i32 = client
61            .query_opt(
62                "SELECT value FROM settings WHERE key = 'upload_concurrency'",
63                &[],
64            )
65            .await
66            .map_err(AppError::query)?
67            .and_then(|r| r.get::<_, String>("value").parse().ok())
68            .map(UploadSettings::clamp_concurrency)
69            .unwrap_or(crate::models::settings::DEFAULT_UPLOAD_CONCURRENCY);
70
71        Ok(UploadSettings { concurrency })
72    }
73
74    #[cfg(not(feature = "server"))]
75    {
76        Ok(UploadSettings::default())
77    }
78}
79
80/// 更新素材上传配置。
81///
82/// concurrency 会被 clamp 到合法范围后写入。
83#[server(UpdateUploadSettings, "/api")]
84pub async fn update_upload_settings(concurrency: i32) -> Result<UploadSettings, ServerFnError> {
85    let _user = get_current_admin_user().await?;
86
87    let concurrency = UploadSettings::clamp_concurrency(concurrency);
88
89    #[cfg(feature = "server")]
90    {
91        let client = get_conn().await.map_err(AppError::db_conn)?;
92
93        client
94            .execute(
95                "INSERT INTO settings (key, value, updated_at) VALUES ('upload_concurrency', $1, NOW())
96                 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
97                &[&concurrency.to_string()],
98            )
99            .await
100            .map_err(AppError::query)?;
101
102        tracing::info!("Upload settings updated: concurrency={}", concurrency);
103    }
104
105    Ok(UploadSettings { concurrency })
106}