yggdrasil/api/settings/
image_limit.rs1#![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::ImageLimitSettings;
14
15#[cfg(feature = "server")]
22pub(crate) async fn seed_image_limit_settings_from_env(
23 client: &tokio_postgres::Client,
24) -> Result<(), AppError> {
25 use crate::models::settings as m;
26
27 let mut seeds: Vec<(&'static str, String)> = Vec::new();
28
29 if let Ok(v) = std::env::var("MAX_IMAGE_DIMENSION") {
30 match v.trim().parse::<u32>() {
31 Ok(n) => seeds.push((
32 "image_max_dimension",
33 m::ImageLimitSettings::clamp_max_dimension(n).to_string(),
34 )),
35 Err(_) => tracing::warn!("MAX_IMAGE_DIMENSION={v:?} 非法(期望正整数),跳过"),
36 }
37 }
38 if let Ok(v) = std::env::var("MAX_IMAGE_PIXELS") {
39 match v.trim().parse::<u64>() {
40 Ok(n) => seeds.push((
41 "image_max_pixels",
42 m::ImageLimitSettings::clamp_max_pixels(n).to_string(),
43 )),
44 Err(_) => tracing::warn!("MAX_IMAGE_PIXELS={v:?} 非法(期望正整数),跳过"),
45 }
46 }
47 if let Ok(v) = std::env::var("IMAGE_DIMENSIONS_CACHE_TTL_SECS") {
48 match v.trim().parse::<u64>() {
49 Ok(n) => seeds.push((
50 "image_dimensions_cache_ttl_secs",
51 m::ImageLimitSettings::clamp_dimensions_cache_ttl_secs(n).to_string(),
52 )),
53 Err(_) => {
54 tracing::warn!("IMAGE_DIMENSIONS_CACHE_TTL_SECS={v:?} 非法(期望正整数),跳过")
55 }
56 }
57 }
58
59 for (key, value) in seeds {
60 client
61 .execute(
62 "INSERT INTO settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO NOTHING",
63 &[&key, &value],
64 )
65 .await
66 .map_err(AppError::query)?;
67 tracing::info!("图片尺寸限制配置已从环境变量播种: {key}={value}(仅键缺失时生效)");
68 }
69 Ok(())
70}
71
72#[cfg(feature = "server")]
74pub(crate) async fn load_image_limit_settings(
75 client: &tokio_postgres::Client,
76) -> Result<ImageLimitSettings, AppError> {
77 use crate::models::settings as m;
78
79 async fn read_key(
80 client: &tokio_postgres::Client,
81 key: &str,
82 ) -> Result<Option<String>, AppError> {
83 let row = client
84 .query_opt("SELECT value FROM settings WHERE key = $1", &[&key])
85 .await
86 .map_err(AppError::query)?;
87 Ok(row.map(|r| r.get::<_, String>("value")))
88 }
89
90 let max_dimension = read_key(client, "image_max_dimension")
91 .await?
92 .and_then(|v| v.parse().ok())
93 .map(m::ImageLimitSettings::clamp_max_dimension)
94 .unwrap_or(m::DEFAULT_IMAGE_MAX_DIMENSION);
95 let max_pixels = read_key(client, "image_max_pixels")
96 .await?
97 .and_then(|v| v.parse().ok())
98 .map(m::ImageLimitSettings::clamp_max_pixels)
99 .unwrap_or(m::DEFAULT_IMAGE_MAX_PIXELS);
100 let dimensions_cache_ttl_secs = read_key(client, "image_dimensions_cache_ttl_secs")
101 .await?
102 .and_then(|v| v.parse().ok())
103 .map(m::ImageLimitSettings::clamp_dimensions_cache_ttl_secs)
104 .unwrap_or(m::DEFAULT_IMAGE_DIMENSIONS_CACHE_TTL_SECS);
105
106 Ok(ImageLimitSettings {
107 max_dimension,
108 max_pixels,
109 dimensions_cache_ttl_secs,
110 })
111}
112
113#[server(GetImageLimitSettings, "/api")]
115pub async fn get_image_limit_settings() -> Result<ImageLimitSettings, ServerFnError> {
116 let _user = get_current_admin_user().await?;
117
118 #[cfg(feature = "server")]
119 {
120 let client = get_conn().await.map_err(AppError::db_conn)?;
121 let s = load_image_limit_settings(&client)
122 .await
123 .map_err(ServerFnError::from)?;
124 Ok(s)
125 }
126
127 #[cfg(not(feature = "server"))]
128 {
129 Ok(ImageLimitSettings::default())
130 }
131}
132
133#[server(UpdateImageLimitSettings, "/api")]
137pub async fn update_image_limit_settings(
138 max_dimension: u32,
139 max_pixels: u64,
140 dimensions_cache_ttl_secs: u64,
141) -> Result<ImageLimitSettings, ServerFnError> {
142 let _user = get_current_admin_user().await?;
143
144 let max_dimension = ImageLimitSettings::clamp_max_dimension(max_dimension);
145 let max_pixels = ImageLimitSettings::clamp_max_pixels(max_pixels);
146 let dimensions_cache_ttl_secs =
147 ImageLimitSettings::clamp_dimensions_cache_ttl_secs(dimensions_cache_ttl_secs);
148
149 #[cfg(feature = "server")]
150 {
151 let client = get_conn().await.map_err(AppError::db_conn)?;
152
153 for (key, value) in [
154 ("image_max_dimension", max_dimension.to_string()),
155 ("image_max_pixels", max_pixels.to_string()),
156 (
157 "image_dimensions_cache_ttl_secs",
158 dimensions_cache_ttl_secs.to_string(),
159 ),
160 ] {
161 client
162 .execute(
163 "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
164 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
165 &[&key, &value],
166 )
167 .await
168 .map_err(AppError::query)?;
169 }
170
171 tracing::info!(
172 "Image limit settings updated (需重启生效): max_dimension={}, max_pixels={}, ttl={}s",
173 max_dimension,
174 max_pixels,
175 dimensions_cache_ttl_secs
176 );
177 }
178
179 Ok(ImageLimitSettings {
180 max_dimension,
181 max_pixels,
182 dimensions_cache_ttl_secs,
183 })
184}