feat(backup): download the full backup as ZIP
GET /export?format=zip streams the JSON backup compressed (plain JSON stays the API default). The MindSift import recognises the backup ZIP next to the plain JSON and the Markdown archive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1601a6a2d5
commit
1df32ece92
7 files changed
+122
-25
No files matched your search
@@ -375,12 +375,16 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 10000);
|
||||
};
|
||||
|
||||
const [exportingBackup, setExportingBackup] = useState(false);
|
||||
const handleExportData = async () => {
|
||||
const data = await api.exportAll();
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
||||
type: 'application/json;charset=utf-8',
|
||||
});
|
||||
saveBlob(blob, `mindsift-backup-${new Date().toISOString().slice(0, 10)}.json`);
|
||||
setExportingBackup(true);
|
||||
try {
|
||||
saveBlob(await api.exportBackupZip(), `mindsift-backup-${new Date().toISOString().slice(0, 10)}.zip`);
|
||||
} catch (err: any) {
|
||||
setVaultError(err.message || 'Backup fehlgeschlagen');
|
||||
} finally {
|
||||
setExportingBackup(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [exportingVault, setExportingVault] = useState(false);
|
||||
@@ -1238,18 +1242,19 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
{/* Full Data Backup */}
|
||||
<div className="pt-4 border-t border-border flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="font-bold text-main">Vollständiger Daten-Export (JSON Backup)</h3>
|
||||
<h3 className="font-bold text-main">Vollständiges Backup (.zip)</h3>
|
||||
<p className="text-muted text-[11px]">
|
||||
Lädt alle deine Gedanken, Aufgaben, Markdown-Dokumente und Listen als portable
|
||||
JSON-Datei herunter.
|
||||
Alle deine Gedanken, Aufgaben, Notizen und Listen als JSON, gepackt in eine ZIP-Datei. Über
|
||||
„MindSift-Daten importieren“ lässt es sich vollständig zurückspielen.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleExportData}
|
||||
className="btn-action h-9 px-4 text-xs flex items-center gap-2 flex-shrink-0"
|
||||
disabled={exportingBackup}
|
||||
className="btn-action h-11 sm:h-9 px-4 text-xs flex items-center justify-center gap-2 flex-shrink-0 disabled:opacity-50"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5 text-accent" />
|
||||
<span>Backup herunterladen</span>
|
||||
<span>{exportingBackup ? 'Wird erstellt…' : 'Backup herunterladen'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
+14
-7
@@ -135,6 +135,14 @@ export const setCurrentSharingMode = (mode: SharingMode) => {
|
||||
};
|
||||
export const isSharingDisabled = () => currentSharingMode === 'private';
|
||||
|
||||
// Authenticated binary download (archives); no JSON parsing and no request timeout
|
||||
async function downloadBlob(path: string): Promise<Blob> {
|
||||
const token = getToken();
|
||||
const res = await fetch(path, { headers: token ? { Authorization: `Bearer ${token}` } : {}, cache: 'no-store' });
|
||||
if (!res.ok) throw new Error(`Download fehlgeschlagen (HTTP ${res.status})`);
|
||||
return res.blob();
|
||||
}
|
||||
|
||||
export const api = {
|
||||
// --- AUTH, FIRST-BOOT SETUP & SETTINGS ---
|
||||
async getAuthStatus(): Promise<AuthStatus> {
|
||||
@@ -1093,15 +1101,14 @@ export const api = {
|
||||
return data as ImportSummary;
|
||||
},
|
||||
|
||||
// Full backup (all data as JSON) packed into a ZIP; restorable via importData('mindsift', …)
|
||||
async exportBackupZip(): Promise<Blob> {
|
||||
return downloadBlob('/api/v1/export?format=zip');
|
||||
},
|
||||
|
||||
// Obsidian-compatible ZIP of the workspace; binary and possibly large, so no JSON helper and no timeout
|
||||
async exportMarkdownVault(): Promise<Blob> {
|
||||
const token = getToken();
|
||||
const res = await fetch('/api/v1/export/markdown', {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!res.ok) throw new Error(`Export fehlgeschlagen (HTTP ${res.status})`);
|
||||
return res.blob();
|
||||
return downloadBlob('/api/v1/export/markdown');
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user