Add backup/restore, daily auto-backup and history CSV/JSON export (Phase E3)
- create_backup writes a zip (manifest with sha256 per file, a VACUUM INTO database snapshot, assets, archive, fonts) atomically. restore_backup validates the manifest, rejects tampered files, path traversal, symlinks and newer schemas, checks the staged DB with integrity_check, then stages a pending restore. The swap runs on the next start before the database opens, moves the current data to backups/pre-restore-<ts>/ and rolls back on any failure. - Daily automatic backup (migration M9 auto_backup, default on) keeps the newest 14 and never blocks startup. - Settings Data tab: back up now, restore with confirmation and restart, auto-backup toggle, last backup time. - History exports the filtered rows as CSV (UTF-8 BOM, CRLF, formula-injection guard) or JSON (voiced.history.v1) with CA-friendly tax columns from a new list_invoice_ledger command. - Rust round-trip, rejection, swap-rollback and retention tests; vitest for the export and backup helpers. Adds the zip 8.6.0 and typed-path 0.12.3 crates (pure Rust); package builds must vendor them. PDF output and fingerprint goldens are unchanged. The new UI has not been run in a webview yet.
This commit is contained in:
@@ -12,6 +12,8 @@ import type {
|
||||
} from "./types";
|
||||
import type { LogoAsset } from "./logo";
|
||||
import type { Payment, PaymentInput } from "./payments";
|
||||
import type { BackupStatus, BackupSummary, PendingRestore } from "./backup";
|
||||
import type { LedgerRow } from "./historyExport";
|
||||
import type { FontInspection, ImportMeta, RemoveOutcome, UserFontRow } from "./fontImport";
|
||||
|
||||
export interface ArchiveStatus {
|
||||
@@ -48,6 +50,7 @@ export const api = {
|
||||
cancelInvoice: (id: number, reason: string) =>
|
||||
invoke<Invoice>("cancel_invoice", { id, reason }),
|
||||
listInvoices: () => invoke<InvoiceSummary[]>("list_invoices"),
|
||||
listInvoiceLedger: () => invoke<LedgerRow[]>("list_invoice_ledger"),
|
||||
getInvoice: (id: number) => invoke<Invoice>("get_invoice", { id }),
|
||||
|
||||
recordPayment: (input: PaymentInput) => invoke<Payment>("record_payment", { input }),
|
||||
@@ -102,4 +105,12 @@ export const api = {
|
||||
getLastExportDir: () => invoke<string>("get_last_export_dir"),
|
||||
revealInFolder: (path: string) => invoke<void>("reveal_in_folder", { path }),
|
||||
openFile: (path: string) => invoke<void>("open_file", { path }),
|
||||
|
||||
// Backup and restore. A restore is staged now and applied by the next start (see commands/backup.rs).
|
||||
createBackup: (dest: string) => invoke<BackupSummary>("create_backup", { dest }),
|
||||
restoreBackup: (path: string) => invoke<PendingRestore>("restore_backup", { path }),
|
||||
cancelPendingRestore: () => invoke<void>("cancel_pending_restore"),
|
||||
getBackupStatus: () => invoke<BackupStatus>("get_backup_status"),
|
||||
setAutoBackup: (enabled: boolean) => invoke<boolean>("set_auto_backup", { enabled }),
|
||||
restartApp: () => invoke<void>("restart_app"),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user