1use crate::error::AppError;
6use crate::proxy::types::*;
7use rust_decimal::Decimal;
8
9use super::super::{lock_conn, Database};
10
11impl Database {
12 pub async fn get_global_proxy_config(&self) -> Result<GlobalProxyConfig, AppError> {
18 let result = {
20 let conn = lock_conn!(self.conn);
21 conn.query_row(
22 "SELECT proxy_enabled, listen_address, listen_port, enable_logging
23 FROM proxy_config WHERE app_type = 'claude'",
24 [],
25 |row| {
26 Ok(GlobalProxyConfig {
27 proxy_enabled: row.get::<_, i32>(0)? != 0,
28 listen_address: row.get(1)?,
29 listen_port: row.get::<_, i32>(2)? as u16,
30 enable_logging: row.get::<_, i32>(3)? != 0,
31 })
32 },
33 )
34 };
35 match result {
38 Ok(config) => Ok(config),
39 Err(rusqlite::Error::QueryReturnedNoRows) => {
40 self.init_proxy_config_rows().await?;
42 Ok(GlobalProxyConfig {
43 proxy_enabled: false,
44 listen_address: "127.0.0.1".to_string(),
45 listen_port: 15721,
46 enable_logging: true,
47 })
48 }
49 Err(e) => Err(AppError::Database(e.to_string())),
50 }
51 }
52
53 pub async fn update_global_proxy_config(
55 &self,
56 config: GlobalProxyConfig,
57 ) -> Result<(), AppError> {
58 let conn = lock_conn!(self.conn);
59
60 conn.execute(
61 "UPDATE proxy_config SET
62 proxy_enabled = ?1,
63 listen_address = ?2,
64 listen_port = ?3,
65 enable_logging = ?4,
66 updated_at = datetime('now')",
67 rusqlite::params![
68 if config.proxy_enabled { 1 } else { 0 },
69 config.listen_address,
70 config.listen_port as i32,
71 if config.enable_logging { 1 } else { 0 },
72 ],
73 )
74 .map_err(|e| AppError::Database(e.to_string()))?;
75
76 Ok(())
77 }
78
79 pub async fn get_default_cost_multiplier(&self, app_type: &str) -> Result<String, AppError> {
81 let result = {
82 let conn = lock_conn!(self.conn);
83 conn.query_row(
84 "SELECT default_cost_multiplier FROM proxy_config WHERE app_type = ?1",
85 [app_type],
86 |row| row.get(0),
87 )
88 };
89
90 match result {
91 Ok(value) => Ok(value),
92 Err(rusqlite::Error::QueryReturnedNoRows) => {
93 self.init_proxy_config_rows().await?;
94 Ok("1".to_string())
95 }
96 Err(e) => Err(AppError::Database(e.to_string())),
97 }
98 }
99
100 pub async fn set_default_cost_multiplier(
102 &self,
103 app_type: &str,
104 value: &str,
105 ) -> Result<(), AppError> {
106 let trimmed = value.trim();
107 if trimmed.is_empty() {
108 return Err(AppError::localized(
109 "error.multiplierEmpty",
110 "倍率不能为空",
111 "Multiplier cannot be empty",
112 ));
113 }
114 trimmed.parse::<Decimal>().map_err(|e| {
115 AppError::localized(
116 "error.invalidMultiplier",
117 format!("无效倍率: {value} - {e}"),
118 format!("Invalid multiplier: {value} - {e}"),
119 )
120 })?;
121
122 self.ensure_proxy_config_row_exists(app_type)?;
124
125 let conn = lock_conn!(self.conn);
126 conn.execute(
127 "UPDATE proxy_config SET
128 default_cost_multiplier = ?2,
129 updated_at = datetime('now')
130 WHERE app_type = ?1",
131 rusqlite::params![app_type, trimmed],
132 )
133 .map_err(|e| AppError::Database(e.to_string()))?;
134
135 Ok(())
136 }
137
138 pub async fn get_pricing_model_source(&self, app_type: &str) -> Result<String, AppError> {
140 let result = {
141 let conn = lock_conn!(self.conn);
142 conn.query_row(
143 "SELECT pricing_model_source FROM proxy_config WHERE app_type = ?1",
144 [app_type],
145 |row| row.get(0),
146 )
147 };
148
149 match result {
150 Ok(value) => Ok(value),
151 Err(rusqlite::Error::QueryReturnedNoRows) => {
152 self.init_proxy_config_rows().await?;
153 Ok("response".to_string())
154 }
155 Err(e) => Err(AppError::Database(e.to_string())),
156 }
157 }
158
159 pub async fn set_pricing_model_source(
161 &self,
162 app_type: &str,
163 value: &str,
164 ) -> Result<(), AppError> {
165 let trimmed = value.trim();
166 if !matches!(trimmed, "response" | "request") {
167 return Err(AppError::localized(
168 "error.invalidPricingMode",
169 format!("无效计费模式: {value}"),
170 format!("Invalid pricing mode: {value}"),
171 ));
172 }
173
174 self.ensure_proxy_config_row_exists(app_type)?;
176
177 let conn = lock_conn!(self.conn);
178 conn.execute(
179 "UPDATE proxy_config SET
180 pricing_model_source = ?2,
181 updated_at = datetime('now')
182 WHERE app_type = ?1",
183 rusqlite::params![app_type, trimmed],
184 )
185 .map_err(|e| AppError::Database(e.to_string()))?;
186
187 Ok(())
188 }
189
190 pub async fn get_proxy_config_for_app(
192 &self,
193 app_type: &str,
194 ) -> Result<AppProxyConfig, AppError> {
195 let app_type_owned = app_type.to_string();
197 let result = {
198 let conn = lock_conn!(self.conn);
199 conn.query_row(
200 "SELECT app_type, enabled, auto_failover_enabled,
201 max_retries, streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
202 circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
203 circuit_error_rate_threshold, circuit_min_requests
204 FROM proxy_config WHERE app_type = ?1",
205 [app_type],
206 |row| {
207 Ok(AppProxyConfig {
208 app_type: row.get(0)?,
209 enabled: row.get::<_, i32>(1)? != 0,
210 auto_failover_enabled: row.get::<_, i32>(2)? != 0,
211 max_retries: row.get::<_, i32>(3)? as u32,
212 streaming_first_byte_timeout: row.get::<_, i32>(4)? as u32,
213 streaming_idle_timeout: row.get::<_, i32>(5)? as u32,
214 non_streaming_timeout: row.get::<_, i32>(6)? as u32,
215 circuit_failure_threshold: row.get::<_, i32>(7)? as u32,
216 circuit_success_threshold: row.get::<_, i32>(8)? as u32,
217 circuit_timeout_seconds: row.get::<_, i32>(9)? as u32,
218 circuit_error_rate_threshold: row.get(10)?,
219 circuit_min_requests: row.get::<_, i32>(11)? as u32,
220 })
221 },
222 )
223 };
224 match result {
227 Ok(config) => Ok(config),
228 Err(rusqlite::Error::QueryReturnedNoRows) => {
229 self.init_proxy_config_rows().await?;
231 Ok(AppProxyConfig {
232 app_type: app_type_owned,
233 enabled: false,
234 auto_failover_enabled: false,
235 max_retries: 3,
236 streaming_first_byte_timeout: 60,
237 streaming_idle_timeout: 120,
238 non_streaming_timeout: 600,
239 circuit_failure_threshold: 4,
240 circuit_success_threshold: 2,
241 circuit_timeout_seconds: 60,
242 circuit_error_rate_threshold: 0.6,
243 circuit_min_requests: 10,
244 })
245 }
246 Err(e) => Err(AppError::Database(e.to_string())),
247 }
248 }
249
250 pub async fn update_proxy_config_for_app(
252 &self,
253 config: AppProxyConfig,
254 ) -> Result<(), AppError> {
255 let conn = lock_conn!(self.conn);
256
257 conn.execute(
258 "UPDATE proxy_config SET
259 enabled = ?2,
260 auto_failover_enabled = ?3,
261 max_retries = ?4,
262 streaming_first_byte_timeout = ?5,
263 streaming_idle_timeout = ?6,
264 non_streaming_timeout = ?7,
265 circuit_failure_threshold = ?8,
266 circuit_success_threshold = ?9,
267 circuit_timeout_seconds = ?10,
268 circuit_error_rate_threshold = ?11,
269 circuit_min_requests = ?12,
270 updated_at = datetime('now')
271 WHERE app_type = ?1",
272 rusqlite::params![
273 config.app_type,
274 if config.enabled { 1 } else { 0 },
275 if config.auto_failover_enabled { 1 } else { 0 },
276 config.max_retries as i32,
277 config.streaming_first_byte_timeout as i32,
278 config.streaming_idle_timeout as i32,
279 config.non_streaming_timeout as i32,
280 config.circuit_failure_threshold as i32,
281 config.circuit_success_threshold as i32,
282 config.circuit_timeout_seconds as i32,
283 config.circuit_error_rate_threshold,
284 config.circuit_min_requests as i32,
285 ],
286 )
287 .map_err(|e| AppError::Database(e.to_string()))?;
288
289 Ok(())
290 }
291
292 fn ensure_proxy_config_row_exists(&self, app_type: &str) -> Result<(), AppError> {
296 let conn = self
297 .conn
298 .lock()
299 .map_err(|e| AppError::Lock(e.to_string()))?;
300
301 let (retries, fb_timeout, idle_timeout, cb_fail, cb_succ, cb_timeout, cb_rate, cb_min) =
303 match app_type {
304 "claude" => (6, 90, 180, 8, 3, 90, 0.7, 15),
305 "codex" => (3, 60, 120, 4, 2, 60, 0.6, 10),
306 "gemini" => (5, 60, 120, 4, 2, 60, 0.6, 10),
307 _ => (3, 60, 120, 4, 2, 60, 0.6, 10), };
309
310 conn.execute(
311 "INSERT OR IGNORE INTO proxy_config (
312 app_type, max_retries,
313 streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
314 circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
315 circuit_error_rate_threshold, circuit_min_requests
316 ) VALUES (?1, ?2, ?3, ?4, 600, ?5, ?6, ?7, ?8, ?9)",
317 rusqlite::params![
318 app_type,
319 retries,
320 fb_timeout,
321 idle_timeout,
322 cb_fail,
323 cb_succ,
324 cb_timeout,
325 cb_rate,
326 cb_min
327 ],
328 )
329 .map_err(|e| AppError::Database(e.to_string()))?;
330
331 Ok(())
332 }
333
334 async fn init_proxy_config_rows(&self) -> Result<(), AppError> {
338 let conn = lock_conn!(self.conn);
339
340 conn.execute(
343 "INSERT OR IGNORE INTO proxy_config (
344 app_type, max_retries,
345 streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
346 circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
347 circuit_error_rate_threshold, circuit_min_requests
348 ) VALUES ('claude', 6, 90, 180, 600, 8, 3, 90, 0.7, 15)",
349 [],
350 )
351 .map_err(|e| AppError::Database(e.to_string()))?;
352
353 conn.execute(
355 "INSERT OR IGNORE INTO proxy_config (
356 app_type, max_retries,
357 streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
358 circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
359 circuit_error_rate_threshold, circuit_min_requests
360 ) VALUES ('codex', 3, 60, 120, 600, 4, 2, 60, 0.6, 10)",
361 [],
362 )
363 .map_err(|e| AppError::Database(e.to_string()))?;
364
365 conn.execute(
367 "INSERT OR IGNORE INTO proxy_config (
368 app_type, max_retries,
369 streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
370 circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
371 circuit_error_rate_threshold, circuit_min_requests
372 ) VALUES ('gemini', 5, 60, 120, 600, 4, 2, 60, 0.6, 10)",
373 [],
374 )
375 .map_err(|e| AppError::Database(e.to_string()))?;
376
377 Ok(())
378 }
379
380 pub async fn get_proxy_config(&self) -> Result<ProxyConfig, AppError> {
384 let result = {
386 let conn = lock_conn!(self.conn);
387 conn.query_row(
388 "SELECT listen_address, listen_port, max_retries,
389 enable_logging,
390 streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout
391 FROM proxy_config WHERE app_type = 'claude'",
392 [],
393 |row| {
394 Ok(ProxyConfig {
395 listen_address: row.get(0)?,
396 listen_port: row.get::<_, i32>(1)? as u16,
397 max_retries: row.get::<_, i32>(2)? as u8,
398 request_timeout: 600, enable_logging: row.get::<_, i32>(3)? != 0,
400 live_takeover_active: false, streaming_first_byte_timeout: row.get::<_, i32>(4).unwrap_or(60) as u64,
402 streaming_idle_timeout: row.get::<_, i32>(5).unwrap_or(120) as u64,
403 non_streaming_timeout: row.get::<_, i32>(6).unwrap_or(600) as u64,
404 })
405 },
406 )
407 };
408 match result {
411 Ok(config) => Ok(config),
412 Err(rusqlite::Error::QueryReturnedNoRows) => {
413 self.init_proxy_config_rows().await?;
415 Ok(ProxyConfig::default())
416 }
417 Err(e) => Err(AppError::Database(e.to_string())),
418 }
419 }
420
421 pub async fn update_proxy_config(&self, config: ProxyConfig) -> Result<(), AppError> {
423 let conn = lock_conn!(self.conn);
424
425 conn.execute(
427 "UPDATE proxy_config SET
428 listen_address = ?1,
429 listen_port = ?2,
430 max_retries = ?3,
431 enable_logging = ?4,
432 streaming_first_byte_timeout = ?5,
433 streaming_idle_timeout = ?6,
434 non_streaming_timeout = ?7,
435 updated_at = datetime('now')",
436 rusqlite::params![
437 config.listen_address,
438 config.listen_port as i32,
439 config.max_retries as i32,
440 if config.enable_logging { 1 } else { 0 },
441 config.streaming_first_byte_timeout as i32,
442 config.streaming_idle_timeout as i32,
443 config.non_streaming_timeout as i32,
444 ],
445 )
446 .map_err(|e| AppError::Database(e.to_string()))?;
447
448 Ok(())
449 }
450
451 pub async fn set_live_takeover_active(&self, _active: bool) -> Result<(), AppError> {
453 Ok(())
456 }
457
458 pub async fn is_live_takeover_active(&self) -> Result<bool, AppError> {
462 let conn = lock_conn!(self.conn);
463 let count: i64 = conn
464 .query_row(
465 "SELECT COUNT(*) FROM proxy_config WHERE enabled = 1",
466 [],
467 |row| row.get(0),
468 )
469 .map_err(|e| AppError::Database(e.to_string()))?;
470 Ok(count > 0)
471 }
472
473 pub async fn get_provider_health(
477 &self,
478 provider_id: &str,
479 app_type: &str,
480 ) -> Result<ProviderHealth, AppError> {
481 let result = {
482 let conn = lock_conn!(self.conn);
483
484 conn.query_row(
485 "SELECT provider_id, app_type, is_healthy, consecutive_failures,
486 last_success_at, last_failure_at, last_error, updated_at
487 FROM provider_health
488 WHERE provider_id = ?1 AND app_type = ?2",
489 rusqlite::params![provider_id, app_type],
490 |row| {
491 Ok(ProviderHealth {
492 provider_id: row.get(0)?,
493 app_type: row.get(1)?,
494 is_healthy: row.get::<_, i64>(2)? != 0,
495 consecutive_failures: row.get::<_, i64>(3)? as u32,
496 last_success_at: row.get(4)?,
497 last_failure_at: row.get(5)?,
498 last_error: row.get(6)?,
499 updated_at: row.get(7)?,
500 })
501 },
502 )
503 };
504
505 match result {
506 Ok(health) => Ok(health),
507 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(ProviderHealth {
509 provider_id: provider_id.to_string(),
510 app_type: app_type.to_string(),
511 is_healthy: true,
512 consecutive_failures: 0,
513 last_success_at: None,
514 last_failure_at: None,
515 last_error: None,
516 updated_at: chrono::Utc::now().to_rfc3339(),
517 }),
518 Err(e) => Err(AppError::Database(e.to_string())),
519 }
520 }
521
522 pub async fn update_provider_health(
526 &self,
527 provider_id: &str,
528 app_type: &str,
529 success: bool,
530 error_msg: Option<String>,
531 ) -> Result<(), AppError> {
532 self.update_provider_health_with_threshold(provider_id, app_type, success, error_msg, 5)
534 .await
535 }
536
537 pub async fn update_provider_health_with_threshold(
542 &self,
543 provider_id: &str,
544 app_type: &str,
545 success: bool,
546 error_msg: Option<String>,
547 failure_threshold: u32,
548 ) -> Result<(), AppError> {
549 let conn = lock_conn!(self.conn);
550
551 let now = chrono::Utc::now().to_rfc3339();
552
553 let current = conn.query_row(
555 "SELECT consecutive_failures FROM provider_health
556 WHERE provider_id = ?1 AND app_type = ?2",
557 rusqlite::params![provider_id, app_type],
558 |row| Ok(row.get::<_, i64>(0)? as u32),
559 );
560
561 let (is_healthy, consecutive_failures) = if success {
562 (1, 0)
564 } else {
565 let failures = current.unwrap_or(0) + 1;
567 let healthy = if failures >= failure_threshold { 0 } else { 1 };
569 (healthy, failures)
570 };
571
572 let (last_success_at, last_failure_at) = if success {
573 (Some(now.clone()), None)
574 } else {
575 (None, Some(now.clone()))
576 };
577
578 conn.execute(
580 "INSERT OR REPLACE INTO provider_health
581 (provider_id, app_type, is_healthy, consecutive_failures,
582 last_success_at, last_failure_at, last_error, updated_at)
583 VALUES (?1, ?2, ?3, ?4,
584 COALESCE(?5, (SELECT last_success_at FROM provider_health
585 WHERE provider_id = ?1 AND app_type = ?2)),
586 COALESCE(?6, (SELECT last_failure_at FROM provider_health
587 WHERE provider_id = ?1 AND app_type = ?2)),
588 ?7, ?8)",
589 rusqlite::params![
590 provider_id,
591 app_type,
592 is_healthy,
593 consecutive_failures as i64,
594 last_success_at,
595 last_failure_at,
596 error_msg,
597 &now,
598 ],
599 )
600 .map_err(|e| AppError::Database(e.to_string()))?;
601
602 Ok(())
603 }
604
605 pub async fn reset_provider_health(
607 &self,
608 provider_id: &str,
609 app_type: &str,
610 ) -> Result<(), AppError> {
611 let conn = lock_conn!(self.conn);
612
613 conn.execute(
614 "DELETE FROM provider_health WHERE provider_id = ?1 AND app_type = ?2",
615 rusqlite::params![provider_id, app_type],
616 )
617 .map_err(|e| AppError::Database(e.to_string()))?;
618
619 log::debug!("Reset health status for provider {provider_id} (app: {app_type})");
620
621 Ok(())
622 }
623
624 pub async fn clear_provider_health_for_app(&self, app_type: &str) -> Result<(), AppError> {
626 let conn = lock_conn!(self.conn);
627
628 conn.execute(
629 "DELETE FROM provider_health WHERE app_type = ?1",
630 [app_type],
631 )
632 .map_err(|e| AppError::Database(e.to_string()))?;
633
634 log::debug!("Cleared provider health records for app {app_type}");
635 Ok(())
636 }
637
638 pub async fn clear_all_provider_health(&self) -> Result<(), AppError> {
640 let conn = lock_conn!(self.conn);
641
642 conn.execute("DELETE FROM provider_health", [])
643 .map_err(|e| AppError::Database(e.to_string()))?;
644
645 log::debug!("Cleared all provider health records");
646 Ok(())
647 }
648
649 pub async fn get_circuit_breaker_config(
656 &self,
657 ) -> Result<crate::proxy::circuit_breaker::CircuitBreakerConfig, AppError> {
658 let result = {
660 let conn = lock_conn!(self.conn);
661 conn.query_row(
662 "SELECT circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
663 circuit_error_rate_threshold, circuit_min_requests
664 FROM proxy_config WHERE app_type = 'claude'",
665 [],
666 |row| {
667 Ok(crate::proxy::circuit_breaker::CircuitBreakerConfig {
668 failure_threshold: row.get::<_, i32>(0)? as u32,
669 success_threshold: row.get::<_, i32>(1)? as u32,
670 timeout_seconds: row.get::<_, i64>(2)? as u64,
671 error_rate_threshold: row.get(3)?,
672 min_requests: row.get::<_, i32>(4)? as u32,
673 })
674 },
675 )
676 };
677 match result {
680 Ok(config) => Ok(config),
681 Err(rusqlite::Error::QueryReturnedNoRows) => {
682 self.init_proxy_config_rows().await?;
684 Ok(crate::proxy::circuit_breaker::CircuitBreakerConfig::default())
685 }
686 Err(e) => Err(AppError::Database(e.to_string())),
687 }
688 }
689
690 pub async fn update_circuit_breaker_config(
695 &self,
696 config: &crate::proxy::circuit_breaker::CircuitBreakerConfig,
697 ) -> Result<(), AppError> {
698 let conn = lock_conn!(self.conn);
699
700 conn.execute(
702 "UPDATE proxy_config SET
703 circuit_failure_threshold = ?1,
704 circuit_success_threshold = ?2,
705 circuit_timeout_seconds = ?3,
706 circuit_error_rate_threshold = ?4,
707 circuit_min_requests = ?5,
708 updated_at = datetime('now')",
709 rusqlite::params![
710 config.failure_threshold as i32,
711 config.success_threshold as i32,
712 config.timeout_seconds as i64,
713 config.error_rate_threshold,
714 config.min_requests as i32,
715 ],
716 )
717 .map_err(|e| AppError::Database(e.to_string()))?;
718
719 Ok(())
720 }
721
722 pub async fn save_live_backup(
726 &self,
727 app_type: &str,
728 config_json: &str,
729 ) -> Result<(), AppError> {
730 let conn = lock_conn!(self.conn);
731 let now = chrono::Utc::now().to_rfc3339();
732
733 conn.execute(
734 "INSERT OR REPLACE INTO proxy_live_backup (app_type, original_config, backed_up_at)
735 VALUES (?1, ?2, ?3)",
736 rusqlite::params![app_type, config_json, now],
737 )
738 .map_err(|e| AppError::Database(e.to_string()))?;
739
740 log::info!("已备份 {app_type} Live 配置");
741 Ok(())
742 }
743
744 pub async fn has_any_live_backup(&self) -> Result<bool, AppError> {
746 let conn = lock_conn!(self.conn);
747 let count: i64 = conn
748 .query_row("SELECT COUNT(*) FROM proxy_live_backup", [], |row| {
749 row.get(0)
750 })
751 .map_err(|e| AppError::Database(e.to_string()))?;
752 Ok(count > 0)
753 }
754
755 pub async fn get_live_backup(&self, app_type: &str) -> Result<Option<LiveBackup>, AppError> {
757 let conn = lock_conn!(self.conn);
758
759 let result = conn.query_row(
760 "SELECT app_type, original_config, backed_up_at FROM proxy_live_backup WHERE app_type = ?1",
761 rusqlite::params![app_type],
762 |row| {
763 Ok(LiveBackup {
764 app_type: row.get(0)?,
765 original_config: row.get(1)?,
766 backed_up_at: row.get(2)?,
767 })
768 },
769 );
770
771 match result {
772 Ok(backup) => Ok(Some(backup)),
773 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
774 Err(e) => Err(AppError::Database(e.to_string())),
775 }
776 }
777
778 pub async fn delete_live_backup(&self, app_type: &str) -> Result<(), AppError> {
780 let conn = lock_conn!(self.conn);
781
782 conn.execute(
783 "DELETE FROM proxy_live_backup WHERE app_type = ?1",
784 rusqlite::params![app_type],
785 )
786 .map_err(|e| AppError::Database(e.to_string()))?;
787
788 log::info!("已删除 {app_type} Live 配置备份");
789 Ok(())
790 }
791
792 pub async fn delete_all_live_backups(&self) -> Result<(), AppError> {
794 let conn = lock_conn!(self.conn);
795
796 conn.execute("DELETE FROM proxy_live_backup", [])
797 .map_err(|e| AppError::Database(e.to_string()))?;
798
799 log::info!("已删除所有 Live 配置备份");
800 Ok(())
801 }
802
803 pub fn get_proxy_flags_sync(&self, app_type: &str) -> (bool, bool) {
810 let conn = match self.conn.lock() {
811 Ok(c) => c,
812 Err(_) => return (false, false),
813 };
814
815 conn.query_row(
816 "SELECT enabled, auto_failover_enabled FROM proxy_config WHERE app_type = ?1",
817 [app_type],
818 |row| Ok((row.get::<_, i32>(0)? != 0, row.get::<_, i32>(1)? != 0)),
819 )
820 .unwrap_or((false, false))
821 }
822
823 pub fn set_proxy_flags_sync(
827 &self,
828 app_type: &str,
829 enabled: bool,
830 auto_failover_enabled: bool,
831 ) -> Result<(), AppError> {
832 let conn = self
833 .conn
834 .lock()
835 .map_err(|e| AppError::Database(format!("Mutex lock failed: {e}")))?;
836
837 conn.execute(
838 "UPDATE proxy_config SET enabled = ?2, auto_failover_enabled = ?3, updated_at = datetime('now') WHERE app_type = ?1",
839 rusqlite::params![
840 app_type,
841 if enabled { 1 } else { 0 },
842 if auto_failover_enabled { 1 } else { 0 },
843 ],
844 )
845 .map_err(|e| AppError::Database(e.to_string()))?;
846
847 Ok(())
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use crate::database::Database;
854 use crate::error::AppError;
855
856 #[tokio::test]
857 async fn test_default_cost_multiplier_round_trip() -> Result<(), AppError> {
858 let db = Database::memory()?;
859
860 let default = db.get_default_cost_multiplier("claude").await?;
861 assert_eq!(default, "1");
862
863 db.set_default_cost_multiplier("claude", "1.5").await?;
864 let updated = db.get_default_cost_multiplier("claude").await?;
865 assert_eq!(updated, "1.5");
866
867 Ok(())
868 }
869
870 #[tokio::test]
871 async fn test_default_cost_multiplier_validation() -> Result<(), AppError> {
872 let db = Database::memory()?;
873
874 let err = db
875 .set_default_cost_multiplier("claude", "not-a-number")
876 .await
877 .unwrap_err();
878 assert!(matches!(
880 err,
881 AppError::Localized {
882 key: "error.invalidMultiplier",
883 ..
884 }
885 ));
886
887 Ok(())
888 }
889
890 #[tokio::test]
891 async fn test_pricing_model_source_round_trip_and_validation() -> Result<(), AppError> {
892 let db = Database::memory()?;
893
894 let default = db.get_pricing_model_source("claude").await?;
895 assert_eq!(default, "response");
896
897 db.set_pricing_model_source("claude", "request").await?;
898 let updated = db.get_pricing_model_source("claude").await?;
899 assert_eq!(updated, "request");
900
901 let err = db
902 .set_pricing_model_source("claude", "invalid")
903 .await
904 .unwrap_err();
905 assert!(matches!(
907 err,
908 AppError::Localized {
909 key: "error.invalidPricingMode",
910 ..
911 }
912 ));
913
914 Ok(())
915 }
916}