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

@@ -11,12 +11,31 @@ const LOCAL_BACKUP_DIR = path.resolve(process.cwd(), "backups");
// Name of the USB backup subfolder the user creates on their drive
const USB_BACKUP_FOLDER_NAME = "USB Backup";
const MAX_BACKUPS = 30;
function ensureLocalBackupDir() {
if (!fs.existsSync(LOCAL_BACKUP_DIR)) {
fs.mkdirSync(LOCAL_BACKUP_DIR, { recursive: true });
}
}
function pruneOldBackups(dir: string) {
try {
const files = fs.readdirSync(dir)
.filter((f) => f.endsWith(".sql") || f.endsWith(".zip"))
.map((f) => ({ name: f, mtime: fs.statSync(path.join(dir, f)).mtimeMs }))
.sort((a, b) => b.mtime - a.mtime);
const toDelete = files.slice(MAX_BACKUPS);
for (const file of toDelete) {
fs.unlinkSync(path.join(dir, file.name));
console.log(`🗑️ Pruned old backup: ${file.name}`);
}
} catch (err) {
console.warn("Failed to prune old backups:", err);
}
}
async function getAdminUser() {
const batchSize = 100;
let offset = 0;
@@ -49,6 +68,7 @@ export const startBackupCron = () => {
const startedAt = new Date();
const log = await cronJobLogStorage.createJobLog("local-backup", startedAt);
await cronJobLogStorage.completeJobLog(log.id, "skipped", new Date());
await storage.deleteNotificationsByType(admin.id, "BACKUP");
return;
}
@@ -58,6 +78,7 @@ export const startBackupCron = () => {
try {
const filename = `dental_backup_${Date.now()}.sql`;
await backupDatabaseToPath({ destinationPath: LOCAL_BACKUP_DIR, filename });
pruneOldBackups(LOCAL_BACKUP_DIR);
await storage.createBackup(admin.id);
await storage.deleteNotificationsByType(admin.id, "BACKUP");
await cronJobLogStorage.completeJobLog(log.id, "success", new Date());
@@ -93,6 +114,7 @@ export const startBackupCron = () => {
const startedAt = new Date();
const log = await cronJobLogStorage.createJobLog("usb-backup", startedAt);
await cronJobLogStorage.completeJobLog(log.id, "skipped", new Date());
await storage.deleteNotificationsByType(admin.id, "BACKUP");
return;
}
@@ -131,6 +153,7 @@ export const startBackupCron = () => {
try {
const filename = `dental_backup_usb_${Date.now()}.sql`;
await backupDatabaseToPath({ destinationPath: usbBackupPath, filename });
pruneOldBackups(usbBackupPath);
await storage.createBackup(admin.id);
await storage.deleteNotificationsByType(admin.id, "BACKUP");
await cronJobLogStorage.completeJobLog(log.id, "success", new Date());