1#![allow(clippy::unused_unit, deprecated, clippy::too_many_arguments)]
4
5use crate::api::error::ApiError;
6use leptos::prelude::*;
7
8#[cfg(feature = "server")]
9use crate::api::auth::get_current_admin_user;
10#[cfg(feature = "server")]
11use crate::api::error::AppError;
12#[cfg(feature = "server")]
13use crate::db::pool::get_conn;
14use crate::models::settings::BackupSettingsView;
15#[cfg(feature = "server")]
17use crate::models::settings::{BackupSettings, LastBackupRun};
18
19#[cfg(feature = "server")]
28pub(crate) async fn load_backup_settings(
29 client: &tokio_postgres::Client,
30) -> Result<BackupSettings, AppError> {
31 let values = super::load_setting_values(
32 client,
33 &[
34 "backup_auto_enabled",
35 "backup_time_utc",
36 "backup_retention_count",
37 "backup_include_uploads",
38 ],
39 )
40 .await?;
41
42 let auto_enabled = values
43 .get("backup_auto_enabled")
44 .and_then(|v| v.parse().ok())
45 .unwrap_or(crate::models::settings::DEFAULT_BACKUP_AUTO_ENABLED);
46 let time_utc = values
47 .get("backup_time_utc")
48 .map(|v| BackupSettings::normalize_time_utc(v))
49 .unwrap_or_else(|| crate::models::settings::DEFAULT_BACKUP_TIME_UTC.to_string());
50 let retention_count = values
51 .get("backup_retention_count")
52 .and_then(|v| v.parse().ok())
53 .map(BackupSettings::clamp_retention)
54 .unwrap_or(crate::models::settings::DEFAULT_BACKUP_RETENTION_COUNT);
55 let include_uploads = values
56 .get("backup_include_uploads")
57 .and_then(|v| v.parse().ok())
58 .unwrap_or(crate::models::settings::DEFAULT_BACKUP_INCLUDE_UPLOADS);
59
60 Ok(BackupSettings {
61 auto_enabled,
62 time_utc,
63 retention_count,
64 include_uploads,
65 })
66}
67
68#[cfg(feature = "server")]
70async fn load_last_backup_run(
71 client: &tokio_postgres::Client,
72) -> Result<Option<LastBackupRun>, AppError> {
73 let rows = client
74 .query(
75 "SELECT key, value FROM settings WHERE key = ANY(ARRAY[
76 'backup_last_run_at', 'backup_last_run_ok',
77 'backup_last_run_file', 'backup_last_run_error'])",
78 &[],
79 )
80 .await
81 .map_err(AppError::query)?;
82 let get = |k: &str| {
83 rows.iter()
84 .find(|r| r.get::<_, String>("key") == k)
85 .map(|r| r.get::<_, String>("value"))
86 .filter(|v| !v.is_empty())
87 };
88 let (Some(at), Some(ok)) = (get("backup_last_run_at"), get("backup_last_run_ok")) else {
89 return Ok(None);
90 };
91 Ok(Some(LastBackupRun {
92 at,
93 ok: ok.parse().unwrap_or(false),
94 file: get("backup_last_run_file"),
95 error: get("backup_last_run_error"),
96 }))
97}
98
99#[cfg(feature = "server")]
101pub(crate) async fn save_last_backup_run(
102 client: &tokio_postgres::Client,
103 run: &LastBackupRun,
104) -> Result<(), AppError> {
105 let pairs: [(&str, String); 4] = [
106 ("backup_last_run_at", run.at.clone()),
107 ("backup_last_run_ok", run.ok.to_string()),
108 ("backup_last_run_file", run.file.clone().unwrap_or_default()),
109 (
110 "backup_last_run_error",
111 run.error.clone().unwrap_or_default(),
112 ),
113 ];
114 super::upsert_settings(client, &pairs).await?;
115 Ok(())
116}
117
118#[cfg(feature = "server")]
124pub(crate) async fn seed_backup_settings_from_env(
125 client: &tokio_postgres::Client,
126) -> Result<(), AppError> {
127 let mut seeds: Vec<(&'static str, String)> = Vec::new();
128
129 if let Ok(v) = std::env::var("BACKUP_AUTO_ENABLED") {
130 match v.trim().parse::<bool>() {
131 Ok(b) => seeds.push(("backup_auto_enabled", b.to_string())),
132 Err(_) => tracing::warn!("BACKUP_AUTO_ENABLED={v:?} 非法(期望 true/false),跳过"),
133 }
134 }
135 if let Ok(v) = std::env::var("BACKUP_TIME_UTC") {
136 if BackupSettings::is_valid_time_utc(&v) {
137 seeds.push(("backup_time_utc", BackupSettings::normalize_time_utc(&v)));
138 } else {
139 tracing::warn!("BACKUP_TIME_UTC={v:?} 非法(期望 HH:MM,UTC),跳过");
140 }
141 }
142 if let Ok(v) = std::env::var("BACKUP_RETENTION_COUNT") {
143 match v.trim().parse::<i32>() {
144 Ok(n) => seeds.push((
145 "backup_retention_count",
146 BackupSettings::clamp_retention(n).to_string(),
147 )),
148 Err(_) => tracing::warn!("BACKUP_RETENTION_COUNT={v:?} 非法(期望整数),跳过"),
149 }
150 }
151 if let Ok(v) = std::env::var("BACKUP_INCLUDE_UPLOADS") {
152 match v.trim().parse::<bool>() {
153 Ok(b) => seeds.push(("backup_include_uploads", b.to_string())),
154 Err(_) => tracing::warn!("BACKUP_INCLUDE_UPLOADS={v:?} 非法(期望 true/false),跳过"),
155 }
156 }
157
158 super::insert_env_seeds(client, seeds).await
159}
160
161#[cfg(feature = "server")]
163async fn load_backup_settings_view(
164 client: &tokio_postgres::Client,
165) -> Result<BackupSettingsView, AppError> {
166 let settings = load_backup_settings(client).await?;
167 let last_run = load_last_backup_run(client).await?;
168 let next_run_at = if settings.auto_enabled {
169 settings
170 .next_run_after(chrono::Utc::now())
171 .map(|dt| dt.to_rfc3339())
172 } else {
173 None
174 };
175 Ok(BackupSettingsView {
176 settings,
177 last_run,
178 next_run_at,
179 })
180}
181
182#[server(GetBackupSettings, endpoint = "get_backup_settings", input = leptos::server_fn::codec::Json)]
184pub async fn get_backup_settings() -> Result<BackupSettingsView, ApiError> {
185 let _user = get_current_admin_user().await?;
186
187 #[cfg(feature = "server")]
188 {
189 let client = get_conn().await.map_err(AppError::db_conn)?;
190 Ok(load_backup_settings_view(&client).await?)
191 }
192
193 #[cfg(not(feature = "server"))]
194 {
195 Ok(BackupSettingsView {
196 settings: BackupSettings::default(),
197 last_run: None,
198 next_run_at: None,
199 })
200 }
201}
202
203#[server(UpdateBackupSettings, endpoint = "update_backup_settings", input = leptos::server_fn::codec::Json)]
208pub async fn update_backup_settings(
209 auto_enabled: bool,
210 time_utc: String,
211 retention_count: i32,
212 include_uploads: bool,
213) -> Result<BackupSettingsView, ApiError> {
214 let _user = get_current_admin_user().await?;
215
216 let time_utc = BackupSettings::normalize_time_utc(&time_utc);
217 let retention_count = BackupSettings::clamp_retention(retention_count);
218
219 #[cfg(feature = "server")]
220 {
221 let client = get_conn().await.map_err(AppError::db_conn)?;
222 let pairs = [
223 ("backup_auto_enabled", auto_enabled.to_string()),
224 ("backup_time_utc", time_utc.clone()),
225 ("backup_retention_count", retention_count.to_string()),
226 ("backup_include_uploads", include_uploads.to_string()),
227 ];
228 super::upsert_settings(&**client, &pairs).await?;
229 tracing::info!(
230 "Backup settings updated: auto_enabled={}, time_utc={}, retention={}, include_uploads={}",
231 auto_enabled,
232 time_utc,
233 retention_count,
234 include_uploads
235 );
236 crate::tasks::backup::notify_settings_changed();
238 Ok(load_backup_settings_view(&client).await?)
239 }
240
241 #[cfg(not(feature = "server"))]
242 {
243 Ok(BackupSettingsView {
244 settings: BackupSettings {
245 auto_enabled,
246 time_utc,
247 retention_count,
248 include_uploads,
249 },
250 last_run: None,
251 next_run_at: None,
252 })
253 }
254}