feat: improve backup management, settings UI, and Twilio webhooks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-04 00:52:42 -04:00
parent 5689269690
commit 79e20b693d
13 changed files with 468 additions and 545 deletions

View File

@@ -1,54 +1,73 @@
import { spawn } from "child_process";
import fs from "fs";
import path from "path";
import archiver from "archiver";
interface BackupToPathParams {
destinationPath: string;
filename: string;
filename: string; // should end in .zip
}
export async function backupDatabaseToPath({
destinationPath,
filename,
}: BackupToPathParams): Promise<void> {
const path = await import("path");
const fs = await import("fs");
// Verify write access before spawning pg_dump
try {
fs.accessSync(destinationPath, fs.constants.W_OK);
} catch {
throw new Error(
`No write permission to "${destinationPath}". Try running: sudo chmod a+w "${destinationPath}"`
);
}
const outputFile = path.join(destinationPath, filename);
const zipFile = path.join(destinationPath, filename);
const sqlName = filename.replace(/\.zip$/, ".sql");
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(zipFile);
const archive = archiver("zip", { zlib: { level: 6 } });
output.on("close", () => resolve());
archive.on("error", (err) => {
try { if (fs.existsSync(zipFile)) fs.unlinkSync(zipFile); } catch {}
reject(err);
});
archive.pipe(output);
const pgDump = spawn(
"pg_dump",
[
"--no-acl",
"--no-owner",
"-h",
process.env.DB_HOST || "localhost",
"-U",
process.env.DB_USER || "postgres",
"-f",
outputFile,
"-h", process.env.DB_HOST || "localhost",
"-U", process.env.DB_USER || "postgres",
process.env.DB_NAME || "dental_db",
],
{
env: {
...process.env,
PGPASSWORD: process.env.DB_PASSWORD,
},
env: { ...process.env, PGPASSWORD: process.env.DB_PASSWORD },
}
);
let pgError = "";
pgDump.stderr.on("data", (d) => (pgError += d.toString()));
pgDump.on("error", (err) => {
try { if (fs.existsSync(zipFile)) fs.unlinkSync(zipFile); } catch {}
reject(new Error(`Failed to start pg_dump: ${err.message}. Make sure postgresql-client is installed.`));
});
pgDump.on("close", (code) => {
if (code !== 0) {
// 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"));
try { if (fs.existsSync(zipFile)) fs.unlinkSync(zipFile); } catch {}
reject(new Error(pgError.trim() || `pg_dump exited with code ${code}`));
return;
}
resolve();
archive.finalize();
});
// Stream pg_dump stdout directly into the zip as a .sql entry
archive.append(pgDump.stdout, { name: sqlName });
});
}