1mod asset_purge;
22mod backup;
23mod image_cache;
24mod image_limit;
25mod rate_limit;
26mod runner;
27mod security;
28mod site;
29mod ssr_cache;
30mod system;
31mod trash;
32mod upload;
33mod webp;
34
35#[cfg(feature = "server")]
36use crate::api::error::AppError;
37#[cfg(feature = "server")]
38use std::collections::HashMap;
39
40#[cfg(feature = "server")]
45pub(crate) async fn insert_env_seeds(
46 client: &tokio_postgres::Client,
47 seeds: Vec<(&'static str, String)>,
48) -> Result<(), AppError> {
49 if seeds.is_empty() {
50 return Ok(());
51 }
52
53 let keys: Vec<&str> = seeds.iter().map(|(key, _)| *key).collect();
54 let values: Vec<&str> = seeds.iter().map(|(_, value)| value.as_str()).collect();
55 client
56 .execute(
57 "INSERT INTO settings (key, value)
58 SELECT * FROM UNNEST($1::text[], $2::text[])
59 ON CONFLICT (key) DO NOTHING",
60 &[&keys, &values],
61 )
62 .await
63 .map_err(AppError::query)?;
64
65 for (key, value) in seeds {
66 tracing::info!("配置已从环境变量播种: {key}={value}(仅键缺失时生效)");
67 }
68 Ok(())
69}
70
71#[cfg(feature = "server")]
77pub(crate) async fn upsert_setting(
78 client: &(impl tokio_postgres::GenericClient + Sync),
79 key: &str,
80 value: &str,
81) -> Result<(), AppError> {
82 client
83 .execute(
84 "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
85 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
86 &[&key, &value],
87 )
88 .await
89 .map_err(AppError::query)?;
90 Ok(())
91}
92
93#[cfg(feature = "server")]
99pub(crate) async fn upsert_settings(
100 client: &(impl tokio_postgres::GenericClient + Sync),
101 pairs: &[(&str, String)],
102) -> Result<(), AppError> {
103 let keys: Vec<&str> = pairs.iter().map(|(key, _)| *key).collect();
104 let values: Vec<&str> = pairs.iter().map(|(_, value)| value.as_str()).collect();
105 client
106 .execute(
107 "INSERT INTO settings (key, value, updated_at)
108 SELECT k, v, NOW() FROM UNNEST($1::text[], $2::text[]) AS t(k, v)
109 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
110 &[&keys, &values],
111 )
112 .await
113 .map_err(AppError::query)?;
114 Ok(())
115}
116
117#[cfg(feature = "server")]
119pub(crate) async fn load_setting_values(
120 client: &tokio_postgres::Client,
121 keys: &[&str],
122) -> Result<HashMap<String, String>, AppError> {
123 if keys.is_empty() {
124 return Ok(HashMap::new());
125 }
126
127 let keys: Vec<&str> = keys.to_vec();
128 let rows = client
129 .query(
130 "SELECT key, value FROM settings WHERE key = ANY($1::text[])",
131 &[&keys],
132 )
133 .await
134 .map_err(AppError::query)?;
135
136 let mut values = HashMap::with_capacity(rows.len());
137 for row in rows {
138 values.insert(row.get::<_, String>("key"), row.get::<_, String>("value"));
139 }
140 Ok(values)
141}
142
143#[cfg(feature = "server")]
149pub(crate) async fn bootstrap_startup_settings(
150 client: &tokio_postgres::Client,
151) -> Result<(), AppError> {
152 let (
153 backup,
154 upload,
155 security,
156 image_cache,
157 asset_purge,
158 rate_limit,
159 webp,
160 image_limit,
161 runner,
162 ssr_cache,
163 ) = tokio::join!(
164 seed_backup_settings_from_env(client),
165 seed_upload_settings_from_env(client),
166 seed_security_settings_from_env(client),
167 seed_image_cache_settings_from_env(client),
168 seed_asset_purge_settings_from_env(client),
169 seed_rate_limit_from_env(client),
170 seed_webp_settings_from_env(client),
171 seed_image_limit_settings_from_env(client),
172 seed_runner_settings_from_env(client),
173 seed_ssr_cache_settings_from_env(client),
174 );
175
176 for (name, result) in [
177 ("backup", backup),
178 ("upload", upload),
179 ("security", security),
180 ("image_cache", image_cache),
181 ("asset_purge", asset_purge),
182 ("rate_limit", rate_limit),
183 ("webp", webp),
184 ("image_limit", image_limit),
185 ("runner", runner),
186 ("ssr_cache", ssr_cache),
187 ] {
188 if let Err(error) = result {
189 tracing::warn!(setting_group = name, error = ?error, "启动设置环境变量播种失败");
190 }
191 }
192
193 let (rate_limit, webp, image_limit, runner, ssr_cache) = tokio::join!(
194 load_rate_limit_settings(client),
195 load_webp_settings(client),
196 load_image_limit_settings(client),
197 load_runner_settings(client),
198 load_ssr_cache_settings(client),
199 );
200
201 let rate_limit = rate_limit.map_err(|error| {
202 tracing::error!(error = ?error, "限流启动配置加载失败,拒绝使用默认值");
203 error
204 })?;
205 let webp = webp.map_err(|error| {
206 tracing::error!(error = ?error, "WebP 启动配置加载失败,拒绝使用默认值");
207 error
208 })?;
209 let image_limit = image_limit.map_err(|error| {
210 tracing::error!(error = ?error, "图片限制启动配置加载失败,拒绝使用默认值");
211 error
212 })?;
213 let runner = runner.map_err(|error| {
214 tracing::error!(error = ?error, "代码运行器启动配置加载失败,拒绝使用默认值");
215 error
216 })?;
217 let ssr_cache = ssr_cache.map_err(|error| {
218 tracing::error!(error = ?error, "SSR 缓存启动配置加载失败,拒绝使用默认值");
219 error
220 })?;
221
222 crate::config::set_rate_limit(rate_limit);
223 crate::config::set_webp(webp);
224 crate::config::set_image_limit(image_limit);
225 crate::config::set_runner(runner);
226 crate::config::set_ssr_cache(ssr_cache);
227 Ok(())
228}
229
230pub use asset_purge::*;
231pub use backup::*;
232pub use image_cache::*;
233pub use image_limit::*;
234pub use rate_limit::*;
235pub use runner::*;
236pub use security::*;
237pub use site::*;
238pub use ssr_cache::*;
239pub use system::*;
240pub use trash::*;
241pub use upload::*;
242pub use webp::*;