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 super::insert_env_seeds(client, seeds).await
137}
138
139#[cfg(feature = "server")]
143pub(crate) async fn load_rate_limit_settings(
144 client: &tokio_postgres::Client,
145) -> Result<RateLimitSettings, AppError> {
146 use crate::models::settings as m;
147 use RateLimitSettings as R;
148
149 let values = super::load_setting_values(
150 client,
151 &[
152 "ratelimit_strict_per_sec",
153 "ratelimit_strict_burst",
154 "ratelimit_upload_per_sec",
155 "ratelimit_upload_burst",
156 "ratelimit_image_per_sec",
157 "ratelimit_image_burst",
158 "ratelimit_comment_per_sec",
159 "ratelimit_comment_burst",
160 "ratelimit_comment_upload_per_sec",
161 "ratelimit_comment_upload_burst",
162 "ratelimit_comment_upload_daily",
163 "ratelimit_code_exec_per_sec",
164 "ratelimit_code_exec_burst",
165 "ratelimit_code_exec_daily",
166 "ratelimit_unknown_per_sec",
167 "ratelimit_unknown_burst",
168 "ratelimit_gc_interval_secs",
169 ],
170 )
171 .await?;
172 let read_clamped = |key: &str, default: u32, clamp: fn(u32) -> u32| {
173 values
174 .get(key)
175 .and_then(|v| v.parse().ok())
176 .map(clamp)
177 .unwrap_or(default)
178 };
179
180 Ok(RateLimitSettings {
181 strict_per_sec: read_clamped(
182 "ratelimit_strict_per_sec",
183 m::DEFAULT_RATE_LIMIT_STRICT_PER_SEC,
184 R::clamp_per_sec,
185 ),
186 strict_burst: read_clamped(
187 "ratelimit_strict_burst",
188 m::DEFAULT_RATE_LIMIT_STRICT_BURST,
189 R::clamp_burst,
190 ),
191 upload_per_sec: read_clamped(
192 "ratelimit_upload_per_sec",
193 m::DEFAULT_RATE_LIMIT_UPLOAD_PER_SEC,
194 R::clamp_per_sec,
195 ),
196 upload_burst: read_clamped(
197 "ratelimit_upload_burst",
198 m::DEFAULT_RATE_LIMIT_UPLOAD_BURST,
199 R::clamp_burst,
200 ),
201 image_per_sec: read_clamped(
202 "ratelimit_image_per_sec",
203 m::DEFAULT_RATE_LIMIT_IMAGE_PER_SEC,
204 R::clamp_per_sec,
205 ),
206 image_burst: read_clamped(
207 "ratelimit_image_burst",
208 m::DEFAULT_RATE_LIMIT_IMAGE_BURST,
209 R::clamp_burst,
210 ),
211 comment_per_sec: read_clamped(
212 "ratelimit_comment_per_sec",
213 m::DEFAULT_RATE_LIMIT_COMMENT_PER_SEC,
214 R::clamp_per_sec,
215 ),
216 comment_burst: read_clamped(
217 "ratelimit_comment_burst",
218 m::DEFAULT_RATE_LIMIT_COMMENT_BURST,
219 R::clamp_burst,
220 ),
221 comment_upload_per_sec: read_clamped(
222 "ratelimit_comment_upload_per_sec",
223 m::DEFAULT_RATE_LIMIT_COMMENT_UPLOAD_PER_SEC,
224 R::clamp_per_sec,
225 ),
226 comment_upload_burst: read_clamped(
227 "ratelimit_comment_upload_burst",
228 m::DEFAULT_RATE_LIMIT_COMMENT_UPLOAD_BURST,
229 R::clamp_burst,
230 ),
231 comment_upload_daily: read_clamped(
232 "ratelimit_comment_upload_daily",
233 m::DEFAULT_RATE_LIMIT_COMMENT_UPLOAD_DAILY,
234 R::clamp_daily,
235 ),
236 code_exec_per_sec: read_clamped(
237 "ratelimit_code_exec_per_sec",
238 m::DEFAULT_RATE_LIMIT_CODE_EXEC_PER_SEC,
239 R::clamp_per_sec,
240 ),
241 code_exec_burst: read_clamped(
242 "ratelimit_code_exec_burst",
243 m::DEFAULT_RATE_LIMIT_CODE_EXEC_BURST,
244 R::clamp_burst,
245 ),
246 code_exec_daily: read_clamped(
247 "ratelimit_code_exec_daily",
248 m::DEFAULT_RATE_LIMIT_CODE_EXEC_DAILY,
249 R::clamp_daily,
250 ),
251 unknown_per_sec: read_clamped(
252 "ratelimit_unknown_per_sec",
253 m::DEFAULT_RATE_LIMIT_UNKNOWN_PER_SEC,
254 R::clamp_per_sec,
255 ),
256 unknown_burst: read_clamped(
257 "ratelimit_unknown_burst",
258 m::DEFAULT_RATE_LIMIT_UNKNOWN_BURST,
259 R::clamp_burst,
260 ),
261 gc_interval_secs: read_clamped(
262 "ratelimit_gc_interval_secs",
263 m::DEFAULT_RATE_LIMIT_GC_INTERVAL_SECS,
264 R::clamp_gc_interval,
265 ),
266 })
267}
268
269#[server(GetRateLimitSettings, "/api")]
271pub async fn get_rate_limit_settings() -> Result<RateLimitSettings, ServerFnError> {
272 let _user = get_current_admin_user().await?;
273
274 #[cfg(feature = "server")]
275 {
276 let client = get_conn().await.map_err(AppError::db_conn)?;
277 let s = load_rate_limit_settings(&client)
278 .await
279 .map_err(ServerFnError::from)?;
280 Ok(s)
281 }
282
283 #[cfg(not(feature = "server"))]
284 {
285 Ok(RateLimitSettings::default())
286 }
287}
288
289#[server(UpdateRateLimitSettings, "/api")]
294pub async fn update_rate_limit_settings(
295 strict_per_sec: u32,
296 strict_burst: u32,
297 upload_per_sec: u32,
298 upload_burst: u32,
299 image_per_sec: u32,
300 image_burst: u32,
301 comment_per_sec: u32,
302 comment_burst: u32,
303 comment_upload_per_sec: u32,
304 comment_upload_burst: u32,
305 comment_upload_daily: u32,
306 code_exec_per_sec: u32,
307 code_exec_burst: u32,
308 code_exec_daily: u32,
309 unknown_per_sec: u32,
310 unknown_burst: u32,
311 gc_interval_secs: u32,
312) -> Result<RateLimitSettings, ServerFnError> {
313 let _user = get_current_admin_user().await?;
314
315 let strict_per_sec = RateLimitSettings::clamp_per_sec(strict_per_sec);
316 let strict_burst = RateLimitSettings::clamp_burst(strict_burst);
317 let upload_per_sec = RateLimitSettings::clamp_per_sec(upload_per_sec);
318 let upload_burst = RateLimitSettings::clamp_burst(upload_burst);
319 let image_per_sec = RateLimitSettings::clamp_per_sec(image_per_sec);
320 let image_burst = RateLimitSettings::clamp_burst(image_burst);
321 let comment_per_sec = RateLimitSettings::clamp_per_sec(comment_per_sec);
322 let comment_burst = RateLimitSettings::clamp_burst(comment_burst);
323 let comment_upload_per_sec = RateLimitSettings::clamp_per_sec(comment_upload_per_sec);
324 let comment_upload_burst = RateLimitSettings::clamp_burst(comment_upload_burst);
325 let comment_upload_daily = RateLimitSettings::clamp_daily(comment_upload_daily);
326 let code_exec_per_sec = RateLimitSettings::clamp_per_sec(code_exec_per_sec);
327 let code_exec_burst = RateLimitSettings::clamp_burst(code_exec_burst);
328 let code_exec_daily = RateLimitSettings::clamp_daily(code_exec_daily);
329 let unknown_per_sec = RateLimitSettings::clamp_per_sec(unknown_per_sec);
330 let unknown_burst = RateLimitSettings::clamp_burst(unknown_burst);
331 let gc_interval_secs = RateLimitSettings::clamp_gc_interval(gc_interval_secs);
332
333 #[cfg(feature = "server")]
334 {
335 let client = get_conn().await.map_err(AppError::db_conn)?;
336
337 for (key, value) in [
338 ("ratelimit_strict_per_sec", strict_per_sec.to_string()),
339 ("ratelimit_strict_burst", strict_burst.to_string()),
340 ("ratelimit_upload_per_sec", upload_per_sec.to_string()),
341 ("ratelimit_upload_burst", upload_burst.to_string()),
342 ("ratelimit_image_per_sec", image_per_sec.to_string()),
343 ("ratelimit_image_burst", image_burst.to_string()),
344 ("ratelimit_comment_per_sec", comment_per_sec.to_string()),
345 ("ratelimit_comment_burst", comment_burst.to_string()),
346 (
347 "ratelimit_comment_upload_per_sec",
348 comment_upload_per_sec.to_string(),
349 ),
350 (
351 "ratelimit_comment_upload_burst",
352 comment_upload_burst.to_string(),
353 ),
354 (
355 "ratelimit_comment_upload_daily",
356 comment_upload_daily.to_string(),
357 ),
358 ("ratelimit_code_exec_per_sec", code_exec_per_sec.to_string()),
359 ("ratelimit_code_exec_burst", code_exec_burst.to_string()),
360 ("ratelimit_code_exec_daily", code_exec_daily.to_string()),
361 ("ratelimit_unknown_per_sec", unknown_per_sec.to_string()),
362 ("ratelimit_unknown_burst", unknown_burst.to_string()),
363 ("ratelimit_gc_interval_secs", gc_interval_secs.to_string()),
364 ] {
365 client
366 .execute(
367 "INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
368 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
369 &[&key, &value],
370 )
371 .await
372 .map_err(AppError::query)?;
373 }
374
375 tracing::info!(
376 "Rate limit settings updated (需重启生效): strict={}/{}, upload={}/{}, \
377 image={}/{}, comment={}/{}, comment_upload={}/{}/{}, code_exec={}/{}/{}, unknown={}/{}, gc={}s",
378 strict_per_sec,
379 strict_burst,
380 upload_per_sec,
381 upload_burst,
382 image_per_sec,
383 image_burst,
384 comment_per_sec,
385 comment_burst,
386 comment_upload_per_sec,
387 comment_upload_burst,
388 comment_upload_daily,
389 code_exec_per_sec,
390 code_exec_burst,
391 code_exec_daily,
392 unknown_per_sec,
393 unknown_burst,
394 gc_interval_secs
395 );
396 }
397
398 Ok(RateLimitSettings {
399 strict_per_sec,
400 strict_burst,
401 upload_per_sec,
402 upload_burst,
403 image_per_sec,
404 image_burst,
405 comment_per_sec,
406 comment_burst,
407 comment_upload_per_sec,
408 comment_upload_burst,
409 comment_upload_daily,
410 code_exec_per_sec,
411 code_exec_burst,
412 code_exec_daily,
413 unknown_per_sec,
414 unknown_burst,
415 gc_interval_secs,
416 })
417}