diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 2196eb4344..e0d9aa7e81 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -70,12 +70,14 @@ import AppLogo from "@/components/icons/AppLogo.vue"; import SqlFormatterSettingsPanel from "./SqlFormatterSettingsPanel.vue"; import type { AppThemeAppearance } from "@/lib/appTheme"; import { useConnectionStore } from "@/stores/connectionStore"; +import { useSavedSqlStore } from "@/stores/savedSqlStore"; import { currentLocale, setLocale, type Locale } from "@/i18n"; import { LOCALE_OPTIONS } from "@/lib/localeOptions"; const { t } = useI18n(); const settingsStore = useSettingsStore(); const connectionStore = useConnectionStore(); +const savedSqlStore = useSavedSqlStore(); const { isDark, themeMode, setThemeMode } = useTheme(); let cachedSystemFonts: string[] | null = null; @@ -882,9 +884,12 @@ const webdavHasSavedPassword = ref(false); const webdavRemotePath = ref(localStorage.getItem("dbx-webdav-remote-path") || "DBX/sync/snapshot.json"); const webdavSyncSecrets = ref(false); const webdavSecretsPassphrase = ref(""); +const webdavAutoUploadEnabled = ref(localStorage.getItem("dbx-webdav-auto-upload-enabled") === "true"); +const webdavAutoUploadIntervalMinutes = ref(Number(localStorage.getItem("dbx-webdav-auto-upload-interval-minutes") || "30")); const webdavBusy = ref<"" | "test" | "upload" | "download">(""); const webdavMessage = ref(""); const webdavError = ref(false); +let webdavAutoUploadTimer: ReturnType | undefined; const webdavReady = computed(() => !!webdavEndpoint.value.trim() && !webdavBusy.value && (!webdavSyncSecrets.value || !!webdavSecretsPassphrase.value.trim())); @@ -906,6 +911,8 @@ function rememberWebDavFields() { localStorage.setItem("dbx-webdav-endpoint", webdavEndpoint.value.trim()); localStorage.setItem("dbx-webdav-username", webdavUsername.value.trim()); localStorage.setItem("dbx-webdav-remote-path", webdavRemotePath.value.trim() || "DBX/sync/snapshot.json"); + localStorage.setItem("dbx-webdav-auto-upload-enabled", String(webdavAutoUploadEnabled.value)); + localStorage.setItem("dbx-webdav-auto-upload-interval-minutes", String(normalizedWebDavAutoUploadInterval())); } function setWebDavResult(message: string, error = false) { @@ -970,6 +977,43 @@ async function uploadWebDavSnapshot() { }); } +function normalizedWebDavAutoUploadInterval() { + const value = Number(webdavAutoUploadIntervalMinutes.value); + if (!Number.isFinite(value)) return 30; + return Math.max(1, Math.min(1440, Math.round(value))); +} + +function scheduleWebDavAutoUpload() { + if (webdavAutoUploadTimer) { + window.clearInterval(webdavAutoUploadTimer); + webdavAutoUploadTimer = undefined; + } + if (!webdavAutoUploadEnabled.value) return; + + const intervalMinutes = normalizedWebDavAutoUploadInterval(); + webdavAutoUploadIntervalMinutes.value = intervalMinutes; + webdavAutoUploadTimer = window.setInterval(() => { + void runWebDavAutoUpload(); + }, intervalMinutes * 60_000); +} + +async function runWebDavAutoUpload() { + if (!webdavEndpoint.value.trim() || webdavBusy.value) return; + webdavBusy.value = "upload"; + webdavMessage.value = ""; + webdavError.value = false; + try { + rememberWebDavFields(); + await applyWebDavPasswordPreference(); + const summary = await webdavSyncUpload(currentWebDavConfig(), settingsStore.editorSettings, webdavSyncSecrets.value ? webdavSecretsPassphrase.value : undefined); + setWebDavResult(t("settings.syncAutoUploadSuccess", { bytes: summary.bytes, path: summary.remotePath })); + } catch (e: any) { + setWebDavResult(e?.message || String(e), true); + } finally { + webdavBusy.value = ""; + } +} + async function downloadWebDavSnapshot() { if (!window.confirm(t("settings.syncDownloadConfirm"))) return; await runWebDavAction("download", async () => { @@ -979,6 +1023,7 @@ async function downloadWebDavSnapshot() { } await settingsStore.updateDesktopSettings(result.desktopSettings); await connectionStore.initFromDisk(); + await savedSqlStore.initFromStorage(); const message = t("settings.syncDownloadSuccess", { bytes: result.summary.bytes, path: result.summary.remotePath, @@ -1031,6 +1076,10 @@ watch([webdavEndpoint, webdavUsername], () => { watch(webdavRememberPassword, (val) => { localStorage.setItem("dbx-webdav-remember-password", String(val)); }); +watch([webdavAutoUploadEnabled, webdavAutoUploadIntervalMinutes], () => { + rememberWebDavFields(); + scheduleWebDavAutoUpload(); +}); watch(activeSettingsTab, (tab) => { if (tab === "mcp" && !mcpStatus.value && !mcpStatusLoading.value) void refreshMcpStatus(); @@ -1043,12 +1092,17 @@ watch(activeSettingsTab, (tab) => { onMounted(() => { void refreshWebDavPasswordStatus(); + scheduleWebDavAutoUpload(); checkLayoutDescTruncation(); checkIconThemeDescTruncation(); initTruncationObservers(); }); onUnmounted(() => { + if (webdavAutoUploadTimer) { + window.clearInterval(webdavAutoUploadTimer); + webdavAutoUploadTimer = undefined; + } cleanupTruncationObservers(); }); @@ -2367,6 +2421,18 @@ watch(

{{ t("settings.syncRemotePathDescription") }}

+
+ +
+ + + {{ t("settings.syncAutoUploadMinutes") }} +
+

{{ t("settings.syncAutoUploadDescription") }}

+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 126db3c619..83790e1993 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2273,7 +2273,7 @@ export default { snippetsAddTitle: "Add Snippet", snippetsEditTitle: "Edit Snippet", syncWebDavTitle: "WebDAV Sync", - syncWebDavDescription: "Upload or restore a DBX snapshot from a WebDAV-compatible storage service.", + syncWebDavDescription: "Upload or restore a DBX snapshot, including saved SQL library entries, from a WebDAV-compatible storage service.", syncEndpoint: "WebDAV URL", syncUsername: "Username", syncPassword: "Password", @@ -2294,6 +2294,11 @@ export default { syncDownload: "Download", syncTestSuccess: "WebDAV target is reachable.", syncUploadSuccess: "Uploaded {bytes} bytes to {path}.", + syncAutoUpload: "Automatically upload snapshots", + syncAutoUploadInterval: "Every", + syncAutoUploadMinutes: "minutes", + syncAutoUploadDescription: "Runs while DBX is open. Downloads still require manual confirmation to avoid replacing local saved SQL unexpectedly.", + syncAutoUploadSuccess: "Auto-uploaded {bytes} bytes to {path}.", syncDownloadSuccess: "Downloaded and applied {bytes} bytes from {path}.", syncSecretsApplied: "Encrypted secrets were restored.", syncSecretsSkipped: "Encrypted secrets were present but were not restored.", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index b8af26e276..fac6003dce 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -1949,7 +1949,7 @@ export default { snippetsAddTitle: "Agregar fragmento", snippetsEditTitle: "Editar fragmento", syncWebDavTitle: "Sincronización WebDAV", - syncWebDavDescription: "Sube o restaura una instantánea de DBX desde almacenamiento compatible con WebDAV.", + syncWebDavDescription: "Sube o restaura una instantánea de DBX, incluida la biblioteca SQL guardada, desde almacenamiento compatible con WebDAV.", syncEndpoint: "URL WebDAV", syncUsername: "Usuario", syncPassword: "Contraseña", @@ -1970,6 +1970,11 @@ export default { syncDownload: "Descargar", syncTestSuccess: "El destino WebDAV está disponible.", syncUploadSuccess: "Se subieron {bytes} bytes a {path}.", + syncAutoUpload: "Subir instantáneas automáticamente", + syncAutoUploadInterval: "Cada", + syncAutoUploadMinutes: "minutos", + syncAutoUploadDescription: "Se ejecuta mientras DBX está abierto. Las descargas aún requieren confirmación manual para evitar reemplazar SQL guardado localmente.", + syncAutoUploadSuccess: "Se subieron automáticamente {bytes} bytes a {path}.", syncDownloadSuccess: "Se descargaron y aplicaron {bytes} bytes desde {path}.", syncSecretsApplied: "Los secretos cifrados fueron restaurados.", syncSecretsSkipped: "Había secretos cifrados, pero no fueron restaurados.", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index a8350d1d8a..3abd5ef259 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -2022,7 +2022,7 @@ export default { snippetsAddTitle: "Aggiungi Snippet", snippetsEditTitle: "Modifica Snippet", syncWebDavTitle: "Sincronizzazione WebDAV", - syncWebDavDescription: "Carica o ripristina uno snapshot di DBX da un servizio di archiviazione compatibile con WebDAV.", + syncWebDavDescription: "Carica o ripristina uno snapshot di DBX, inclusa la libreria SQL salvata, da un servizio di archiviazione compatibile con WebDAV.", syncEndpoint: "URL WebDAV", syncUsername: "Nome utente", syncPassword: "Password", @@ -2043,6 +2043,11 @@ export default { syncDownload: "Scarica", syncTestSuccess: "La destinazione WebDAV è raggiungibile.", syncUploadSuccess: "Caricati {bytes} byte su {path}.", + syncAutoUpload: "Carica automaticamente gli snapshot", + syncAutoUploadInterval: "Ogni", + syncAutoUploadMinutes: "minuti", + syncAutoUploadDescription: "Viene eseguito mentre DBX è aperto. I download richiedono ancora conferma manuale per evitare di sostituire SQL salvati localmente.", + syncAutoUploadSuccess: "Caricati automaticamente {bytes} byte su {path}.", syncDownloadSuccess: "Scaricati e applicati {bytes} byte da {path}.", syncSecretsApplied: "I segreti crittografati sono stati ripristinati.", syncSecretsSkipped: "I segreti crittografati erano presenti ma non sono stati ripristinati.", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 815230e1e9..85fdbe5a96 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -2241,7 +2241,7 @@ export default { snippetsAddTitle: "スニペットを追加", snippetsEditTitle: "スニペットを編集", syncWebDavTitle: "WebDAV同期", - syncWebDavDescription: "WebDAV互換ストレージサービスからDBXスナップショットをアップロードまたは復元します。", + syncWebDavDescription: "保存済みSQLライブラリを含むDBXスナップショットをWebDAV互換ストレージへアップロードまたは復元します。", syncEndpoint: "WebDAV URL", syncUsername: "ユーザー名", syncPassword: "パスワード", @@ -2262,6 +2262,11 @@ export default { syncDownload: "ダウンロード", syncTestSuccess: "WebDAVターゲットに到達可能です。", syncUploadSuccess: "{bytes}バイトを{path}にアップロードしました。", + syncAutoUpload: "スナップショットを自動アップロード", + syncAutoUploadInterval: "間隔", + syncAutoUploadMinutes: "分", + syncAutoUploadDescription: "DBXを開いている間だけ実行されます。ローカルの保存済みSQLを誤って置き換えないよう、ダウンロードは手動確認が必要です。", + syncAutoUploadSuccess: "{bytes}バイトを{path}に自動アップロードしました。", syncDownloadSuccess: "{path}から{bytes}バイトをダウンロードして適用しました。", syncSecretsApplied: "暗号化されたシークレットが復元されました。", syncSecretsSkipped: "暗号化されたシークレットが存在しましたが復元されませんでした。", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index dd48a15111..9f76717b39 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -2033,7 +2033,7 @@ export default { snippetsAddTitle: "Adicionar snippet", snippetsEditTitle: "Editar snippet", syncWebDavTitle: "Sincronização WebDAV", - syncWebDavDescription: "Envie ou restaure um snapshot do DBX a partir de um serviço de armazenamento compatível com WebDAV.", + syncWebDavDescription: "Envie ou restaure um snapshot do DBX, incluindo a biblioteca SQL salva, a partir de um serviço de armazenamento compatível com WebDAV.", syncEndpoint: "URL do WebDAV", syncUsername: "Nome de usuário", syncPassword: "Senha", @@ -2054,6 +2054,11 @@ export default { syncDownload: "Baixar", syncTestSuccess: "O destino WebDAV está acessível.", syncUploadSuccess: "Enviados {bytes} bytes para {path}.", + syncAutoUpload: "Enviar snapshots automaticamente", + syncAutoUploadInterval: "A cada", + syncAutoUploadMinutes: "minutos", + syncAutoUploadDescription: "Executa enquanto o DBX está aberto. Downloads ainda exigem confirmação manual para evitar substituir SQL salvos localmente.", + syncAutoUploadSuccess: "Enviados automaticamente {bytes} bytes para {path}.", syncDownloadSuccess: "Baixados e aplicados {bytes} bytes de {path}.", syncSecretsApplied: "Os segredos criptografados foram restaurados.", syncSecretsSkipped: "Havia segredos criptografados, mas não foram restaurados.", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index ede67772e6..f78729bb8c 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -2297,7 +2297,7 @@ export default { snippetsAddTitle: "添加片段", snippetsEditTitle: "编辑片段", syncWebDavTitle: "WebDAV 同步", - syncWebDavDescription: "将 DBX 快照上传到兼容 WebDAV 的存储服务,或从远端恢复。", + syncWebDavDescription: "将包含 SQL 库的 DBX 快照上传到兼容 WebDAV 的存储服务,或从远端恢复。", syncEndpoint: "WebDAV 地址", syncUsername: "用户名", syncPassword: "密码", @@ -2318,6 +2318,11 @@ export default { syncDownload: "下载", syncTestSuccess: "WebDAV 目标可访问。", syncUploadSuccess: "已上传 {bytes} 字节到 {path}。", + syncAutoUpload: "自动上传快照", + syncAutoUploadInterval: "每", + syncAutoUploadMinutes: "分钟", + syncAutoUploadDescription: "仅在 DBX 打开时运行。下载仍需手动确认,避免意外覆盖本地保存的 SQL。", + syncAutoUploadSuccess: "已自动上传 {bytes} 字节到 {path}。", syncDownloadSuccess: "已从 {path} 下载并应用 {bytes} 字节。", syncSecretsApplied: "已恢复加密敏感信息。", syncSecretsSkipped: "远端包含加密敏感信息,但本次未恢复。", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 3271d7bba8..cfab2ef54b 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -2069,7 +2069,7 @@ export default { snippetsAddTitle: "新增片段", snippetsEditTitle: "編輯片段", syncWebDavTitle: "WebDAV 同步", - syncWebDavDescription: "上傳 DBX 快照到相容 WebDAV 的儲存服務,或從該服務還原快照。", + syncWebDavDescription: "上傳包含 SQL 庫的 DBX 快照到相容 WebDAV 的儲存服務,或從該服務還原快照。", syncEndpoint: "WebDAV 地址", syncUsername: "使用者名稱", syncPassword: "密碼", @@ -2090,6 +2090,11 @@ export default { syncDownload: "下載", syncTestSuccess: "WebDAV 目標可存取。", syncUploadSuccess: "已上傳 {bytes} 位元組到 {path}。", + syncAutoUpload: "自動上傳快照", + syncAutoUploadInterval: "每", + syncAutoUploadMinutes: "分鐘", + syncAutoUploadDescription: "僅在 DBX 開啟時執行。下載仍需手動確認,避免意外覆蓋本機已儲存的 SQL。", + syncAutoUploadSuccess: "已自動上傳 {bytes} 位元組到 {path}。", syncDownloadSuccess: "已從 {path} 下載並套用 {bytes} 位元組。", syncSecretsApplied: "已還原加密敏感資訊。", syncSecretsSkipped: "偵測到加密敏感資訊,但本次未還原。", diff --git a/apps/desktop/src/lib/http.ts b/apps/desktop/src/lib/http.ts index dce983178d..93c62ab68e 100644 --- a/apps/desktop/src/lib/http.ts +++ b/apps/desktop/src/lib/http.ts @@ -962,28 +962,28 @@ export interface WebDavPasswordStatus { hasSavedPassword: boolean; } -export async function webdavSyncTest(_config: WebDavConfig): Promise { - throw new Error("WebDAV sync is only available in the desktop app."); +export async function webdavSyncTest(config: WebDavConfig): Promise { + return post("/api/cloud-sync/webdav/test", { config }); } -export async function webdavPasswordStatus(_config: WebDavConfig): Promise { - return { hasSavedPassword: false }; +export async function webdavPasswordStatus(config: WebDavConfig): Promise { + return post("/api/cloud-sync/webdav/password-status", { config }); } -export async function saveWebdavSavedPassword(_config: WebDavConfig, _password: string): Promise { - throw new Error("WebDAV sync is only available in the desktop app."); +export async function saveWebdavSavedPassword(config: WebDavConfig, password: string): Promise { + return post("/api/cloud-sync/webdav/save-password", { config, password }); } -export async function forgetWebdavSavedPassword(_config: WebDavConfig): Promise { - throw new Error("WebDAV sync is only available in the desktop app."); +export async function forgetWebdavSavedPassword(config: WebDavConfig): Promise { + return post("/api/cloud-sync/webdav/forget-password", { config }); } -export async function webdavSyncUpload(_config: WebDavConfig, _editorSettings?: unknown, _secretsPassphrase?: string): Promise { - throw new Error("WebDAV sync is only available in the desktop app."); +export async function webdavSyncUpload(config: WebDavConfig, editorSettings?: unknown, secretsPassphrase?: string): Promise { + return post("/api/cloud-sync/webdav/upload", { config, editorSettings, secretsPassphrase }); } -export async function webdavSyncDownload(_config: WebDavConfig, _secretsPassphrase?: string): Promise { - throw new Error("WebDAV sync is only available in the desktop app."); +export async function webdavSyncDownload(config: WebDavConfig, secretsPassphrase?: string): Promise { + return post("/api/cloud-sync/webdav/download", { config, secretsPassphrase }); } export async function loadPinnedTreeNodeIds(): Promise { diff --git a/crates/dbx-web/src/main.rs b/crates/dbx-web/src/main.rs index d557d829e5..f2d7733f24 100644 --- a/crates/dbx-web/src/main.rs +++ b/crates/dbx-web/src/main.rs @@ -398,7 +398,14 @@ async fn main() { "/app-settings/pinned-tree-node-ids", get(routes::app_settings::load_pinned_tree_node_ids).post(routes::app_settings::save_pinned_tree_node_ids), ) - .route("/app-settings/config/decrypt", post(routes::app_settings::decrypt_config)); + .route("/app-settings/config/decrypt", post(routes::app_settings::decrypt_config)) + // Cloud sync + .route("/cloud-sync/webdav/test", post(routes::cloud_sync::webdav_sync_test)) + .route("/cloud-sync/webdav/password-status", post(routes::cloud_sync::webdav_password_status)) + .route("/cloud-sync/webdav/save-password", post(routes::cloud_sync::save_webdav_saved_password)) + .route("/cloud-sync/webdav/forget-password", post(routes::cloud_sync::forget_webdav_saved_password)) + .route("/cloud-sync/webdav/upload", post(routes::cloud_sync::webdav_sync_upload)) + .route("/cloud-sync/webdav/download", post(routes::cloud_sync::webdav_sync_download)); let api = add_mq_routes(api) .layer(middleware::from_fn_with_state(web_state.clone(), auth::auth_middleware)) diff --git a/crates/dbx-web/src/routes/cloud_sync.rs b/crates/dbx-web/src/routes/cloud_sync.rs new file mode 100644 index 0000000000..e9cd6b1332 --- /dev/null +++ b/crates/dbx-web/src/routes/cloud_sync.rs @@ -0,0 +1,120 @@ +use std::sync::Arc; + +use axum::extract::State; +use axum::Json; +use dbx_core::cloud_sync::{ + apply_sync_snapshot, build_sync_snapshot, forget_webdav_password, resolve_webdav_password, save_webdav_password, + webdav_saved_password_status, ApplySnapshotOptions, ApplySnapshotSummary, WebDavClient, WebDavConfig, + WebDavPasswordStatus, WebDavSyncSummary, +}; +use dbx_core::storage::DesktopSettings; +use serde::{Deserialize, Serialize}; + +use crate::error::AppError; +use crate::state::WebState; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WebDavDownloadResult { + pub summary: WebDavSyncSummary, + pub editor_settings: Option, + pub desktop_settings: DesktopSettings, + pub apply_summary: ApplySnapshotSummary, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WebDavConfigRequest { + pub config: WebDavConfig, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SaveWebDavPasswordRequest { + pub config: WebDavConfig, + pub password: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WebDavUploadRequest { + pub config: WebDavConfig, + pub editor_settings: Option, + pub secrets_passphrase: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WebDavDownloadRequest { + pub config: WebDavConfig, + pub secrets_passphrase: Option, +} + +pub async fn webdav_sync_test( + State(state): State>, + Json(mut req): Json, +) -> Result, AppError> { + resolve_webdav_password(&state.app.storage, &mut req.config).await.map_err(AppError)?; + WebDavClient::new(req.config).test().await.map_err(AppError)?; + Ok(Json(())) +} + +pub async fn webdav_password_status( + State(state): State>, + Json(req): Json, +) -> Result, AppError> { + webdav_saved_password_status(&state.app.storage, &req.config).await.map(Json).map_err(AppError) +} + +pub async fn save_webdav_saved_password( + State(state): State>, + Json(req): Json, +) -> Result, AppError> { + save_webdav_password(&state.app.storage, &req.config, &req.password).await.map_err(AppError)?; + Ok(Json(())) +} + +pub async fn forget_webdav_saved_password( + State(state): State>, + Json(req): Json, +) -> Result, AppError> { + forget_webdav_password(&state.app.storage, &req.config).await.map_err(AppError)?; + Ok(Json(())) +} + +pub async fn webdav_sync_upload( + State(state): State>, + Json(mut req): Json, +) -> Result, AppError> { + resolve_webdav_password(&state.app.storage, &mut req.config).await.map_err(AppError)?; + let snapshot = build_sync_snapshot( + &state.app.storage, + env!("CARGO_PKG_VERSION"), + req.editor_settings, + req.secrets_passphrase.as_deref(), + ) + .await + .map_err(AppError)?; + WebDavClient::new(req.config).put_snapshot(&snapshot).await.map(Json).map_err(AppError) +} + +pub async fn webdav_sync_download( + State(state): State>, + Json(mut req): Json, +) -> Result, AppError> { + resolve_webdav_password(&state.app.storage, &mut req.config).await.map_err(AppError)?; + let (snapshot, summary) = WebDavClient::new(req.config).get_snapshot().await.map_err(AppError)?; + let apply_summary = apply_sync_snapshot( + &state.app.storage, + &snapshot, + ApplySnapshotOptions { secrets_passphrase: req.secrets_passphrase.as_deref() }, + ) + .await + .map_err(AppError)?; + Ok(Json(WebDavDownloadResult { + summary, + editor_settings: snapshot.editor_settings, + desktop_settings: snapshot.desktop_settings, + apply_summary, + })) +} diff --git a/crates/dbx-web/src/routes/mod.rs b/crates/dbx-web/src/routes/mod.rs index 06dc593a3f..53fbf0450b 100644 --- a/crates/dbx-web/src/routes/mod.rs +++ b/crates/dbx-web/src/routes/mod.rs @@ -1,6 +1,7 @@ pub mod agents; pub mod ai; pub mod app_settings; +pub mod cloud_sync; pub mod connection; pub mod data_compare; pub mod database_export;