feat: change backup format to plain SQL, admin-only cron, backup now button, import restore UI

This commit is contained in:
ff
2026-04-11 23:37:43 -04:00
parent 4025ca45e0
commit 66a3d271f1
6 changed files with 329 additions and 269 deletions

View File

@@ -1,14 +1,4 @@
import { spawn } from "child_process";
import fs from "fs";
import os from "os";
import path from "path";
import archiver from "archiver";
function safeRmDir(dir: string) {
try {
fs.rmSync(dir, { recursive: true, force: true });
} catch {}
}
interface BackupToPathParams {
destinationPath: string;
@@ -19,24 +9,24 @@ export async function backupDatabaseToPath({
destinationPath,
filename,
}: BackupToPathParams): Promise<void> {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "dental_backup_"));
const path = await import("path");
const fs = await import("fs");
const outputFile = path.join(destinationPath, filename);
return new Promise((resolve, reject) => {
const pgDump = spawn(
"pg_dump",
[
"-Fd",
"-j",
"4",
"--no-acl",
"--no-owner",
"-h",
process.env.DB_HOST || "localhost",
"-U",
process.env.DB_USER || "postgres",
process.env.DB_NAME || "dental_db",
"-f",
tmpDir,
outputFile,
process.env.DB_NAME || "dental_db",
],
{
env: {
@@ -50,36 +40,15 @@ export async function backupDatabaseToPath({
pgDump.stderr.on("data", (d) => (pgError += d.toString()));
pgDump.on("close", async (code) => {
pgDump.on("close", (code) => {
if (code !== 0) {
safeRmDir(tmpDir);
// clean up partial file if it was created
try {
if (fs.existsSync(outputFile)) fs.unlinkSync(outputFile);
} catch {}
return reject(new Error(pgError || "pg_dump failed"));
}
const outputFile = path.join(destinationPath, filename);
const outputStream = fs.createWriteStream(outputFile);
const archive = archiver("zip");
outputStream.on("error", (err) => {
safeRmDir(tmpDir);
reject(err);
});
archive.on("error", (err) => {
safeRmDir(tmpDir);
reject(err);
});
archive.pipe(outputStream);
archive.directory(tmpDir + path.sep, false);
archive.finalize();
archive.on("end", () => {
safeRmDir(tmpDir);
resolve();
});
resolve();
});
});
}