fix: sprechende Dateinamen fuer alle Exporte

Im Downloads-Ordner lagen "certain-finance-export_....json" und
"certain-finance-backup_....json" nebeneinander, ohne dass man sah, welche
Datei sich zurueckladen laesst. Alle Downloads heissen jetzt
certain-finance_<art>_<datum>_<uhrzeit>.<endung>, wobei die Art es sagt:
vollbackup, vollbackup-system, datenuebersicht, buchungen, datenbank. Das
persoenliche Vollbackup traegt zusaetzlich den Benutzernamen.

Den Namen vergibt jetzt allein der Server im Content-Disposition-Kopf; das
Frontend liest ihn von dort. Vorher erfanden beide Seiten ihn getrennt und
kamen auf unterschiedliche Ergebnisse (Bindestrich gegen Unterstrich, UTC
gegen Ortszeit). Zeitstempel ist Ortszeit: wer abends um 20 Uhr exportiert,
soll keine Datei von 18 Uhr finden.

Passend dazu heisst der Knopf "Alle Daten (JSON)" nun "Datenuebersicht
(JSON)", und der Hinweis darueber sagt ausdruecklich, dass sich diese
Dateien nicht zurueckladen lassen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-20 13:13:39 +02:00
co-authored by Claude Opus 5
parent 7fc8763d57
commit e503baa8a6
7 changed files with 94 additions and 27 deletions
+28 -9
View File
@@ -85,8 +85,21 @@ function zeitstempel() {
);
}
/** Datei (mit Sitzungs-Cookie) laden und im Browser als Download anbieten. */
export async function downloadFile(path, filename) {
/** Dateiname aus dem Content-Disposition-Kopf, sonst der Rückfallwert. */
function dateinameAusKopf(resp, rueckfall) {
const kopf = resp.headers.get("content-disposition") || "";
const treffer = /filename="([^"]+)"/.exec(kopf);
return treffer ? treffer[1] : rueckfall;
}
/**
* Datei (mit Sitzungs-Cookie) laden und im Browser als Download anbieten.
*
* Den Namen gibt der Server vor – sonst hiesse dieselbe Datei je nachdem, ob
* man sie über die Oberfläche oder direkt über die API holt, anders. `rueckfall`
* greift nur, wenn der Kopf fehlt.
*/
export async function downloadFile(path, rueckfall) {
const resp = await fetch(`${API_BASE}${path}`, { credentials: "include" });
if (!resp.ok) {
throw new ApiError(`Download fehlgeschlagen (${resp.status})`, resp.status);
@@ -95,7 +108,7 @@ export async function downloadFile(path, filename) {
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.download = dateinameAusKopf(resp, rueckfall);
document.body.appendChild(link);
link.click();
link.remove();
@@ -232,14 +245,20 @@ export const api = {
documentPreview: (formData) => request("/documents/preview", { method: "POST", formData }),
// Backup & Export
downloadDbBackup: () => downloadFile("/backup/db", `certain-finance-${zeitstempel()}.db`),
exportTransactionsCsv: () => downloadFile("/export/transactions.csv", `transaktionen_${zeitstempel()}.csv`),
exportTransactionsJson: () => downloadFile("/export/transactions.json", `transaktionen_${zeitstempel()}.json`),
exportAllJson: () => downloadFile("/export/all.json", `certain-finance-export_${zeitstempel()}.json`),
// Die Dateinamen kommen aus dem Content-Disposition-Kopf (backend/app/routers/
// backup.py, _dateiname). Was hier steht, ist nur der Rückfall.
downloadDbBackup: () =>
downloadFile("/backup/db", `certain-finance_datenbank_${zeitstempel()}.db`),
exportTransactionsCsv: () =>
downloadFile("/export/transactions.csv", `certain-finance_buchungen_${zeitstempel()}.csv`),
exportTransactionsJson: () =>
downloadFile("/export/transactions.json", `certain-finance_buchungen_${zeitstempel()}.json`),
exportAllJson: () =>
downloadFile("/export/all.json", `certain-finance_datenuebersicht_${zeitstempel()}.json`),
exportBackupJson: () =>
downloadFile("/export/backup.json", `certain-finance-backup_${zeitstempel()}.json`),
downloadFile("/export/backup.json", `certain-finance_vollbackup_${zeitstempel()}.json`),
exportSystemBackupJson: () =>
downloadFile("/export/system-backup.json", `certain-finance-system-backup_${zeitstempel()}.json`),
downloadFile("/export/system-backup.json", `certain-finance_vollbackup-system_${zeitstempel()}.json`),
restorePreview: (file) => {
const fd = new FormData();
fd.append("file", file);
+2 -2
View File
@@ -80,10 +80,10 @@
"languageDe": "Deutsch",
"languageEn": "Englisch",
"backup": "Backup & Export",
"exportHint": "Exportiere deine eigenen Daten zum Archivieren oder Weiterverarbeiten.",
"exportHint": "Zum Ansehen und Weiterverarbeiten, etwa in einer Tabellenkalkulation. Diese Dateien lassen sich nicht zurückladen.",
"exportTxnCsv": "Buchungen (CSV)",
"exportTxnJson": "Buchungen (JSON)",
"exportAll": "Alle Daten (JSON)",
"exportAll": "Datenübersicht (JSON)",
"dbBackup": "Komplettes DB-Backup herunterladen",
"dbBackupHint": "Vollständige Datenbank aller Nutzer (nur Admin) – zum Wiederherstellen sichern.",
"keywordReview": "Schlagwörter prüfen",
+2 -2
View File
@@ -80,10 +80,10 @@
"languageDe": "German",
"languageEn": "English",
"backup": "Backup & export",
"exportHint": "Export your own data for archiving or further processing.",
"exportHint": "For reading and further processing, e.g. in a spreadsheet. These files cannot be restored.",
"exportTxnCsv": "Transactions (CSV)",
"exportTxnJson": "Transactions (JSON)",
"exportAll": "All data (JSON)",
"exportAll": "Data overview (JSON)",
"dbBackup": "Download full DB backup",
"dbBackupHint": "Complete database of all users (admin only) — keep it for restore.",
"keywordReview": "Review keywords",
+1 -1
View File
@@ -17,7 +17,7 @@ function codesSichern(codes, benutzer) {
const url = URL.createObjectURL(new Blob([inhalt], { type: "text/plain;charset=utf-8" }));
const link = document.createElement("a");
link.href = url;
link.download = `certain-finance-wiederherstellungscodes_${benutzer}.txt`;
link.download = `certain-finance_wiederherstellungscodes_${benutzer}.txt`;
document.body.appendChild(link);
link.click();
link.remove();