1#![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::RateLimitSettings;
14
15#[cfg(feature = "server")]
29pub(crate) async fn seed_rate_limit_from_env(
30 client: &tokio_postgres::Client,
31) -> Result<(), AppError> {
32 type EnvField = (&'static str, &'static str, fn(u32) -> u32);
34
35 use crate::models::settings::RateLimitSettings as R;
36
37 let mut seeds: Vec<(&'static str, String)> = Vec::new();
38
39 let fields: &[EnvField] = &[
40 (
41 "RATE_LIMIT_STRICT_PER_SEC",
42 "ratelimit_strict_per_sec",
43 R::clamp_per_sec,
44 ),
45 (
46 "RATE_LIMIT_STRICT_BURST",
47 "ratelimit_strict_burst",
48 R::clamp_burst,
49 ),
50 (
51 "RATE_LIMIT_UPLOAD_PER_SEC",
52 "ratelimit_upload_per_sec",
53 R::clamp_per_sec,
54 ),
55 (
56 "RATE_LIMIT_UPLOAD_BURST",
57 "ratelimit_upload_burst",
58 R::clamp_burst,
59 ),
60 (
61 "RATE_LIMIT_IMAGE_PER_SEC",
62 "ratelimit_image_per_sec",
63 R::clamp_per_sec,
64 ),
65 (
66 "RATE_LIMIT_IMAGE_BURST",
67 "ratelimit_image_burst",
68 R::clamp_burst,
69 ),
70 (
71 "RATE_LIMIT_COMMENT_PER_SEC",
72 "ratelimit_comment_per_sec",
73 R::clamp_per_sec,
74 ),
75 (
76 "RATE_LIMIT_COMMENT_BURST",
77 "ratelimit_comment_burst",
78 R::clamp_burst,
79 ),
80 (
81 "RATE_LIMIT_COMMENT_UPLOAD_PER_SEC",
82 "ratelimit_comment_upload_per_sec",
83 R::clamp_per_sec,
84 ),
85 (
86 "RATE_LIMIT_COMMENT_UPLOAD_BURST",
87 "ratelimit_comment_upload_burst",
88 R::clamp_burst,
89 ),
90 (
91 "RATE_LIMIT_COMMENT_UPLOAD_DAILY",
92 "ratelimit_comment_upload_daily",
93 R::clamp_daily,
94 ),
95 (
96 "RATE_LIMIT_CODE_EXEC_PER_SEC",
97 "ratelimit_code_exec_per_sec",
98 R::clamp_per_sec,
99 ),
100 (
101 "RATE_LIMIT_CODE_EXEC_BURST",
102 "ratelimit_code_exec_burst",
103 R::clamp_burst,
104 ),
105 (
106 "RATE_LIMIT_CODE_EXEC_DAILY",
107 "ratelimit_code_exec_daily",
108 R::clamp_daily,
109 ),
110 (
111 "RATE_LIMIT_UNKNOWN_PER_SEC",
112 "ratelimit_unknown_per_sec",
113 R::clamp_per_sec,
114 ),
115 (
116 "RATE_LIMIT_UNKNOWN_BURST",
117 "ratelimit_unknown_burst",
118 R::clamp_burst,
119 ),
120 (
121 "RATE_LIMIT_GC_INTERVAL_SECS",
122 "ratelimit_gc_interval_secs",
123 R::clamp_gc_interval,
124 ),
125 ];
126
127 for &(env_key, db_key, clamp) in fields {
128 if let Ok(v) = std::env::var(env_key) {
129 match v.trim().parse::<u32>() {
130 Ok(n) => seeds.push((db_key, clamp(n).to_string())),
131 Err(_) => tracing::warn!("{env_key}={v:?} 非法(期望正整数),跳过"),
132 }
133 }
134 }
135
136 for (key, value) in seeds {
137 client
138 .execute(
139 "INSERT INTO settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO NOTHING",
140 &[&key, &value],
141 )
142 .await
143 .map_err(AppError::query)?;
144 tracing::info!("限流配置已从环境变量播种: {key}={value}(仅键缺失时生效)");
145 }
146 Ok(())
147}
148
149#[cfg(feature = "server")]
154pub(crate) async fn load_rate_limit_settings(
155 client: &tokio_postgres::Client,
156) -> Result<RateLimitSettings, AppError> {
157 use crate::models::settings as m;
158 use RateLimitSettings as R;
159
160 async fn read_clamped(
161 client: &tokio_postgres::Client,
162 key: &str,
163 default: u32,
164 clamp: fn(u32) -> u32,
165 ) -> Result<u32, AppError> {
166 let row = client
167 .query_opt("SELECT value FROM settings WHERE key = $1", &[&key])
168 .await
169 .map_err(AppError::query)?;
170 Ok(row
171 .map(|r| r.get::<_, String>("value"))
172 .and_then(|v| v.parse().ok())
173 .map(clamp)
174 .unwrap_or(default))
175 }
176
177 Ok(RateLimitSettings {
178 strict_per_sec: read_clamped(
179 client,
180 "ratelimit_strict_per_sec",
181 m::DEFAULT_RATE_LIMIT_STRICT_PER_SEC,
182 R::clamp_per_sec,
183 )
184 .await?,
185 strict_burst: read_clamped(
186 client,
187 "ratelimit_strict_burst",
188 m::DEFAULT_RATE_LIMIT_STRICT_BURST,
189 R::clamp_burst,
190 )
191 .await?,
192 upload_per_sec: read_clamped(
193 client,
194 "ratelimit_upload_per_sec",
195 m::DEFAULT_RATE_LIMIT_UPLOAD_PER_SEC,
196 R::clamp_per_sec,
197 )
198 .await?,
199 upload_burst: read_clamped(
200 client,
201 "ratelimit_upload_burst",
202 m::DEFAULT_RATE_LIMIT_UPLOAD_BURST,
203 R::clamp_burst,
204 )
205 .await?,
206 image_per_sec: read_clamped(
207 client,
208 "ratelimit_image_per_sec",
209 m::DEFAULT_RATE_LIMIT_IMAGE_PER_SEC,
210 R::clamp_per_sec,
211 )
212 .await?,
213 image_burst: read_clamped(
214 client,
215 "ratelimit_image_burst",
216 m::DEFAULT_RATE_LIMIT_IMAGE_BURST,
217 R::clamp_burst,
218 )
219 .await?,
220 comment_per_sec: read_clamped(
221 client,
222 "ratelimit_comment_per_sec",
223 m::DEFAULT_RATE_LIMIT_COMMENT_PER_SEC,
224 R::clamp_per_sec,
225 )
226 .await?,
227 comment_burst: read_clamped(
228 client,
229 "ratelimit_comment_burst",
230 m::DEFAULT_RATE_LIMIT_COMMENT_BURST,
231 R::clamp_burst,
232 )
233 .await?,
234 comment_upload_per_sec: read_clamped(
235 client,
236 "ratelimit_comment_upload_per_sec",
237 m::DEFAULT_RATE_LIMIT_COMMENT_UPLOAD_PER_SEC,
238 R::clamp_per_sec,
239 )
240 .await?,
241 comment_upload_burst: read_clamped(
242 client,
243 "ratelimit_comment_upload_burst",
244 m::DEFAULT_RATE_LIMIT_COMMENT_UPLOAD_BURST,
245 R::clamp_burst,
246 )
247 .await?,
248 comment_upload_daily: read_clamped(
249 client,
250 "ratelimit_comment_upload_daily",
251 m::DEFAULT_RATE_LIMIT_COMMENT_UPLOAD_DAILY,
252 R::clamp_daily,
253 )
254 .await?,
255 code_exec_per_sec: read_clamped(
256 client,
257 "ratelimit_code_exec_per_sec",
258 m::DEFAULT_RATE_LIMIT_CODE_EXEC_PER_SEC,
259 R::clamp_per_sec,
260 )
261 .await?,
262 code_exec_burst: read_clamped(
263 client,
264 "ratelimit_code_exec_burst",
265 m::DEFAULT_RATE_LIMIT_CODE_EXEC_BURST,
266 R::clamp_burst,
267 )
268 .await?,
269 code_exec_daily: read_clamped(
270 client,
271 "ratelimit_code_exec_daily",
272 m::DEFAULT_RATE_LIMIT_CODE_EXEC_DAILY,
273 R::clamp_daily,
274 )
275 .await?,
276 unknown_per_sec: read_clamped(
277 client,
278 "ratelimit_unknown_per_sec",
279 m::DEFAULT_RATE_LIMIT_UNKNOWN_PER_SEC,
280 R::clamp_per_sec,
281 )
282 .await?,
283 unknown_burst: read_clamped(
284 client,
285 "ratelimit_unknown_burst",
286 m::DEFAULT_RATE_LIMIT_UNKNOWN_BURST,
287 R::clamp_burst,
288 )
289 .await?,
290 gc_interval_secs: read_clamped(
291 client,
292 "ratelimit_gc_interval_secs",
293 m::DEFAULT_RATE_LIMIT_GC_INTERVAL_SECS,
294 R::clamp_gc_interval,
295 )
296 .await?,
297 })
298}
299
300#[server(GetRateLimitSettings, "/api")]
302pub async fn get_rate_limit_settings() -> Result<RateLimitSettings, ServerFnError> {
303 let _user = get_current_admin_user().await?;
304
305 #[cfg(feature = "server")]
306 {
307 let client = get_conn().await.map_err(AppError::db_conn)?;
308 let s = load_rate_limit_settings(&client)
309 .await
310 .map_err(ServerFnError::from)?;
311 Ok(s)
312 }
313
314 #[cfg(not(feature = "server"))]
315 {
316 Ok(RateLimitSettings::default())
317 }
318}
319
320#[server(UpdateRateLimitSettings, "/api")]
325pub async fn update_rate_limit_settings(
326 strict_per_sec: u32,
327 strict_burst: u32,
328 upload_per_sec: u32,
329 upload_burst: u32,
330 image_per_sec: u32,
331 image_burst: u32,
332 comment_per_sec: u32,
333 comment_burst: u32,
334 comment_upload_per_sec: u32,
335 comment_upload_burst: u32,
336 comment_upload_daily: u32,
337 code_exec_per_sec: u32,
338 code_exec_burst: u32,
339 code_exec_daily: u32,
340 unknown_per_sec: u32,
341 unknown_burst: u32,
342 gc_interval_secs: u32,
343) -> Result<RateLimitSettings, ServerFnError> {
344 let _user = get_current_admin_user().await?;
345
346 let strict_per_sec = RateLimitSettings::clamp_per_sec(strict_per_sec);
347 let strict_burst = RateLimitSettings::clamp_burst(strict_burst);
348 let upload_per_sec = RateLimitSettings::clamp_per_sec(upload_per_sec);
349 let upload_burst = RateLimitSettings::clamp_burst(upload_burst);
350 let image_per_sec = RateLimitSettings::clamp_per_sec(image_per_sec);
351 let image_burst = RateLimitSettings::clamp_burst(image_burst);
352 let comment_per_sec = RateLimitSettings::clamp_per_sec(comment_per_sec);
353 let comment_burst = RateLimitSettings::clamp_burst(comment_burst);
354 let comment_upload_per_sec = RateLimitSettings::clamp_per_sec(comment_upload_per_sec);
355 let comment_upload_burst = RateLimitSettings::clamp_burst(comment_upload_burst);
356 let comment_upload_daily = RateLimitSettings::clamp_daily(comment_upload_daily);
357 let code_exec_per_sec = RateLimitSettings::clamp_per_sec(code_exec_per_sec);
358 let code_exec_burst = RateLimitSettings::clamp_burst(code_exec_burst);
359 let code_exec_daily = RateLimitSettings::clamp_daily(code_exec_daily);
360 let unknown_per_sec = RateLimitSettings::clamp_per_sec(unknown_per_sec);
361 let unknown_burst = RateLimitSettings::clamp_burst(unknown_burst);
362 let gc_interval_secs = RateLimitSettings::clamp_gc_interval(gc_interval_secs);
363
364 #[cfg(feature = "server")]
365 {
366 let client = get_conn().await.map_err(AppError::db_conn)?;
367
368 for (key, value) in [
369 ("ratelimit_strict_per_sec", strict_per_sec.to_string()),
370 ("ratelimit_strict_burst", strict_burst.to_string()),
371 ("ratelimit_upload_per_sec", upload_per_sec.to_string()),
372 ("ratelimit_upload_burst", upload_burst.to_string()),
373 ("ratelimit_image_per_sec", image_per_sec.to_string()),
374 ("ratelimit_image_burst", image_burst.to_string()),
375 ("ratelimit_comment_per_sec", comment_per_sec.to_string()),
376 ("ratelimit_comment_burst", comment_burst.to_string()),
377 (
378 "ratelimit_comment_upload_per_sec",
379 comment_upload_per_sec.to_string(),
380 ),
381 (
382 "ratelimit_comment_upload_burst",
383 comment_upload_burst.to_string(),
384 ),
385 (
386 "ratelimit_comment_upload_daily",
387 comment_upload_daily.to_string(),
388 ),
389 ("ratelimit_code_exec_per_sec", code_exec_per_sec.to_string()),
390 ("ratelimit_code_exec_burst", code_exec_burst.to_string()),
391 ("ratelimit_code_exec_daily", code_exec_daily.to_string()),
392 ("ratelimit_unknown_per_sec", unknown_per_sec.to_string()),
393 ("ratelimit_unknown_burst", unknown_burst.to_string()),
394 ("ratelimit_gc_interval_secs", gc_interval_secs.to_string()),
395 ] {
396 client
397 .execute(
398 "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
399 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
400 &[&key, &value],
401 )
402 .await
403 .map_err(AppError::query)?;
404 }
405
406 tracing::info!(
407 "Rate limit settings updated (需重启生效): strict={}/{}, upload={}/{}, \
408 image={}/{}, comment={}/{}, comment_upload={}/{}/{}, code_exec={}/{}/{}, unknown={}/{}, gc={}s",
409 strict_per_sec,
410 strict_burst,
411 upload_per_sec,
412 upload_burst,
413 image_per_sec,
414 image_burst,
415 comment_per_sec,
416 comment_burst,
417 comment_upload_per_sec,
418 comment_upload_burst,
419 comment_upload_daily,
420 code_exec_per_sec,
421 code_exec_burst,
422 code_exec_daily,
423 unknown_per_sec,
424 unknown_burst,
425 gc_interval_secs
426 );
427 }
428
429 Ok(RateLimitSettings {
430 strict_per_sec,
431 strict_burst,
432 upload_per_sec,
433 upload_burst,
434 image_per_sec,
435 image_burst,
436 comment_per_sec,
437 comment_burst,
438 comment_upload_per_sec,
439 comment_upload_burst,
440 comment_upload_daily,
441 code_exec_per_sec,
442 code_exec_burst,
443 code_exec_daily,
444 unknown_per_sec,
445 unknown_burst,
446 gc_interval_secs,
447 })
448}