yggdrasil/api/settings/
image_cache.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::ImageCacheSettings;
14#[cfg(feature = "server")]
16use crate::cache::invalidate_image_cache_settings;
17
18#[cfg(feature = "server")]
26pub(crate) async fn seed_image_cache_settings_from_env(
27 client: &tokio_postgres::Client,
28) -> Result<(), AppError> {
29 let mut seeds: Vec<(&'static str, String)> = Vec::new();
30
31 if let Ok(v) = std::env::var("IMAGE_DISK_CACHE_MAX_MB") {
32 match v.trim().parse::<u32>() {
33 Ok(n) => seeds.push((
34 "image_disk_cache_max_mb",
35 crate::models::settings::ImageCacheSettings::clamp_max_mb(n).to_string(),
36 )),
37 Err(_) => tracing::warn!("IMAGE_DISK_CACHE_MAX_MB={v:?} 非法(期望正整数),跳过"),
38 }
39 }
40 if let Ok(v) = std::env::var("IMAGE_DISK_CACHE_MAX_AGE_HOURS") {
41 match v.trim().parse::<u32>() {
42 Ok(n) => seeds.push((
43 "image_disk_cache_max_age_hours",
44 crate::models::settings::ImageCacheSettings::clamp_max_age_hours(n).to_string(),
45 )),
46 Err(_) => {
47 tracing::warn!("IMAGE_DISK_CACHE_MAX_AGE_HOURS={v:?} 非法(期望正整数),跳过")
48 }
49 }
50 }
51
52 for (key, value) in seeds {
53 client
54 .execute(
55 "INSERT INTO settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO NOTHING",
56 &[&key, &value],
57 )
58 .await
59 .map_err(AppError::query)?;
60 tracing::info!("图片缓存配置已从环境变量播种: {key}={value}(仅键缺失时生效)");
61 }
62 Ok(())
63}
64
65#[cfg(feature = "server")]
67pub(crate) async fn load_image_cache_settings(
68 client: &tokio_postgres::Client,
69) -> Result<crate::models::settings::ImageCacheSettings, AppError> {
70 async fn read_key(
71 client: &tokio_postgres::Client,
72 key: &str,
73 ) -> Result<Option<String>, AppError> {
74 let row = client
75 .query_opt("SELECT value FROM settings WHERE key = $1", &[&key])
76 .await
77 .map_err(AppError::query)?;
78 Ok(row.map(|r| r.get::<_, String>("value")))
79 }
80
81 let disk_cache_max_mb = read_key(client, "image_disk_cache_max_mb")
82 .await?
83 .and_then(|v| v.parse().ok())
84 .map(crate::models::settings::ImageCacheSettings::clamp_max_mb)
85 .unwrap_or(crate::models::settings::DEFAULT_IMAGE_DISK_CACHE_MAX_MB);
86 let disk_cache_max_age_hours = read_key(client, "image_disk_cache_max_age_hours")
87 .await?
88 .and_then(|v| v.parse().ok())
89 .map(crate::models::settings::ImageCacheSettings::clamp_max_age_hours)
90 .unwrap_or(crate::models::settings::DEFAULT_IMAGE_DISK_CACHE_MAX_AGE_HOURS);
91
92 Ok(crate::models::settings::ImageCacheSettings {
93 disk_cache_max_mb,
94 disk_cache_max_age_hours,
95 })
96}
97
98#[cfg(feature = "server")]
100pub(crate) async fn runtime_image_cache_settings() -> crate::models::settings::ImageCacheSettings {
101 if let Some(s) = crate::cache::get_image_cache_settings().await {
102 return s;
103 }
104 let fallback = crate::models::settings::ImageCacheSettings::default();
105 if std::env::var("DATABASE_URL").is_err() {
108 return fallback;
109 }
110 match get_conn().await {
111 Ok(client) => match load_image_cache_settings(&client).await {
112 Ok(s) => {
113 crate::cache::set_image_cache_settings(s.clone()).await;
114 s
115 }
116 Err(e) => {
117 tracing::warn!("读取图片缓存配置失败,回退默认值:{e:?}");
118 fallback
119 }
120 },
121 Err(e) => {
122 tracing::warn!("获取连接读取图片缓存配置失败,回退默认值:{e:?}");
123 fallback
124 }
125 }
126}
127
128#[server(GetImageCacheSettings, "/api")]
130pub async fn get_image_cache_settings() -> Result<ImageCacheSettings, ServerFnError> {
131 let _user = get_current_admin_user().await?;
132
133 #[cfg(feature = "server")]
134 {
135 let client = get_conn().await.map_err(AppError::db_conn)?;
136 let s = load_image_cache_settings(&client)
137 .await
138 .map_err(ServerFnError::from)?;
139 crate::cache::set_image_cache_settings(s.clone()).await;
140 Ok(s)
141 }
142
143 #[cfg(not(feature = "server"))]
144 {
145 Ok(ImageCacheSettings::default())
146 }
147}
148
149#[server(UpdateImageCacheSettings, "/api")]
151pub async fn update_image_cache_settings(
152 disk_cache_max_mb: u32,
153 disk_cache_max_age_hours: u32,
154) -> Result<ImageCacheSettings, ServerFnError> {
155 let _user = get_current_admin_user().await?;
156
157 let disk_cache_max_mb = ImageCacheSettings::clamp_max_mb(disk_cache_max_mb);
158 let disk_cache_max_age_hours =
159 ImageCacheSettings::clamp_max_age_hours(disk_cache_max_age_hours);
160
161 #[cfg(feature = "server")]
162 {
163 let client = get_conn().await.map_err(AppError::db_conn)?;
164
165 for (key, value) in [
166 ("image_disk_cache_max_mb", disk_cache_max_mb.to_string()),
167 (
168 "image_disk_cache_max_age_hours",
169 disk_cache_max_age_hours.to_string(),
170 ),
171 ] {
172 client
173 .execute(
174 "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
175 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
176 &[&key, &value],
177 )
178 .await
179 .map_err(AppError::query)?;
180 }
181
182 invalidate_image_cache_settings();
183 tracing::info!(
184 "Image cache settings updated: max_mb={}, max_age_hours={}",
185 disk_cache_max_mb,
186 disk_cache_max_age_hours
187 );
188 }
189
190 Ok(ImageCacheSettings {
191 disk_cache_max_mb,
192 disk_cache_max_age_hours,
193 })
194}