mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-22 22:12:06 +00:00
feat(sync): support webdav sync on web
This commit is contained in:
@@ -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<typeof window.setInterval> | 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(
|
||||
<Input id="webdav-remote-path" v-model="webdavRemotePath" autocomplete="off" />
|
||||
<p class="text-xs text-muted-foreground">{{ t("settings.syncRemotePathDescription") }}</p>
|
||||
</div>
|
||||
<div class="space-y-2 md:col-span-2 rounded-md border bg-muted/20 px-3 py-3">
|
||||
<label class="flex items-center gap-2 text-xs">
|
||||
<input v-model="webdavAutoUploadEnabled" type="checkbox" class="h-4 w-4 shrink-0 accent-primary" />
|
||||
<span class="font-medium">{{ t("settings.syncAutoUpload") }}</span>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<Label for="webdav-auto-upload-interval" class="text-xs text-muted-foreground">{{ t("settings.syncAutoUploadInterval") }}</Label>
|
||||
<Input id="webdav-auto-upload-interval" v-model.number="webdavAutoUploadIntervalMinutes" type="number" min="1" max="1440" step="1" class="h-7 w-24 text-xs" :disabled="!webdavAutoUploadEnabled" />
|
||||
<span class="text-xs text-muted-foreground">{{ t("settings.syncAutoUploadMinutes") }}</span>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">{{ t("settings.syncAutoUploadDescription") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border bg-muted/20 px-3 py-2 text-xs text-muted-foreground">
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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: "暗号化されたシークレットが存在しましたが復元されませんでした。",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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: "远端包含加密敏感信息,但本次未恢复。",
|
||||
|
||||
@@ -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: "偵測到加密敏感資訊,但本次未還原。",
|
||||
|
||||
@@ -962,28 +962,28 @@ export interface WebDavPasswordStatus {
|
||||
hasSavedPassword: boolean;
|
||||
}
|
||||
|
||||
export async function webdavSyncTest(_config: WebDavConfig): Promise<void> {
|
||||
throw new Error("WebDAV sync is only available in the desktop app.");
|
||||
export async function webdavSyncTest(config: WebDavConfig): Promise<void> {
|
||||
return post("/api/cloud-sync/webdav/test", { config });
|
||||
}
|
||||
|
||||
export async function webdavPasswordStatus(_config: WebDavConfig): Promise<WebDavPasswordStatus> {
|
||||
return { hasSavedPassword: false };
|
||||
export async function webdavPasswordStatus(config: WebDavConfig): Promise<WebDavPasswordStatus> {
|
||||
return post("/api/cloud-sync/webdav/password-status", { config });
|
||||
}
|
||||
|
||||
export async function saveWebdavSavedPassword(_config: WebDavConfig, _password: string): Promise<void> {
|
||||
throw new Error("WebDAV sync is only available in the desktop app.");
|
||||
export async function saveWebdavSavedPassword(config: WebDavConfig, password: string): Promise<void> {
|
||||
return post("/api/cloud-sync/webdav/save-password", { config, password });
|
||||
}
|
||||
|
||||
export async function forgetWebdavSavedPassword(_config: WebDavConfig): Promise<void> {
|
||||
throw new Error("WebDAV sync is only available in the desktop app.");
|
||||
export async function forgetWebdavSavedPassword(config: WebDavConfig): Promise<void> {
|
||||
return post("/api/cloud-sync/webdav/forget-password", { config });
|
||||
}
|
||||
|
||||
export async function webdavSyncUpload(_config: WebDavConfig, _editorSettings?: unknown, _secretsPassphrase?: string): Promise<WebDavSyncSummary> {
|
||||
throw new Error("WebDAV sync is only available in the desktop app.");
|
||||
export async function webdavSyncUpload(config: WebDavConfig, editorSettings?: unknown, secretsPassphrase?: string): Promise<WebDavSyncSummary> {
|
||||
return post("/api/cloud-sync/webdav/upload", { config, editorSettings, secretsPassphrase });
|
||||
}
|
||||
|
||||
export async function webdavSyncDownload(_config: WebDavConfig, _secretsPassphrase?: string): Promise<WebDavDownloadResult> {
|
||||
throw new Error("WebDAV sync is only available in the desktop app.");
|
||||
export async function webdavSyncDownload(config: WebDavConfig, secretsPassphrase?: string): Promise<WebDavDownloadResult> {
|
||||
return post("/api/cloud-sync/webdav/download", { config, secretsPassphrase });
|
||||
}
|
||||
|
||||
export async function loadPinnedTreeNodeIds(): Promise<string[]> {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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<serde_json::Value>,
|
||||
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<serde_json::Value>,
|
||||
pub secrets_passphrase: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WebDavDownloadRequest {
|
||||
pub config: WebDavConfig,
|
||||
pub secrets_passphrase: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn webdav_sync_test(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Json(mut req): Json<WebDavConfigRequest>,
|
||||
) -> Result<Json<()>, 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<Arc<WebState>>,
|
||||
Json(req): Json<WebDavConfigRequest>,
|
||||
) -> Result<Json<WebDavPasswordStatus>, 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<Arc<WebState>>,
|
||||
Json(req): Json<SaveWebDavPasswordRequest>,
|
||||
) -> Result<Json<()>, 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<Arc<WebState>>,
|
||||
Json(req): Json<WebDavConfigRequest>,
|
||||
) -> Result<Json<()>, AppError> {
|
||||
forget_webdav_password(&state.app.storage, &req.config).await.map_err(AppError)?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn webdav_sync_upload(
|
||||
State(state): State<Arc<WebState>>,
|
||||
Json(mut req): Json<WebDavUploadRequest>,
|
||||
) -> Result<Json<WebDavSyncSummary>, 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<Arc<WebState>>,
|
||||
Json(mut req): Json<WebDavDownloadRequest>,
|
||||
) -> Result<Json<WebDavDownloadResult>, 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,
|
||||
}))
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user