Files
DentalManagement09/apps/Backend/src/cron/backupCheck.ts
T
ffandClaude Sonnet 4.6 f415100cdb fix: fix db restore not applying and auto-create USB backup folder
- Reset public schema before psql restore so existing tables don't
  block CREATE TABLE statements (pg_dump without --clean has no DROPs)
- Disconnect Prisma connection pool after restore so next request
  gets fresh connections against the restored data
- Auto-create the USB backup subfolder if it doesn't exist instead
  of failing; only fail if the destination drive itself is missing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 23:12:41 -04:00

152 lines
5.5 KiB
TypeScript
Executable File

import cron from "node-cron";
import fs from "fs";
import path from "path";
import { storage } from "../storage";
import { backupDatabaseToPath } from "../services/databaseBackupService";
import { cronJobLogStorage } from "../storage/cron-job-log-storage";
// Local backup folder in the app root (apps/Backend/backups)
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";
function ensureLocalBackupDir() {
if (!fs.existsSync(LOCAL_BACKUP_DIR)) {
fs.mkdirSync(LOCAL_BACKUP_DIR, { recursive: true });
}
}
async function getAdminUser() {
const batchSize = 100;
let offset = 0;
while (true) {
const users = await storage.getUsers(batchSize, offset);
if (!users || users.length === 0) break;
const admin = users.find((u) => u.username === "admin");
if (admin) return admin;
offset += batchSize;
}
return null;
}
export const startBackupCron = () => {
// ============================================================
// 8 PM — Local automatic backup to apps/Backend/backups/
// ============================================================
cron.schedule("0 20 * * *", async () => {
console.log("🔄 [8 PM] Running local auto-backup...");
ensureLocalBackupDir();
const admin = await getAdminUser();
if (!admin) {
console.warn("No admin user found, skipping local backup.");
return;
}
if (!admin.autoBackupEnabled) {
console.log("✅ [8 PM] Auto-backup is disabled for admin, skipped.");
const startedAt = new Date();
const log = await cronJobLogStorage.createJobLog("local-backup", startedAt);
await cronJobLogStorage.completeJobLog(log.id, "skipped", new Date());
return;
}
const startedAt = new Date();
const log = await cronJobLogStorage.createJobLog("local-backup", startedAt);
try {
const filename = `dental_backup_${Date.now()}.sql`;
await backupDatabaseToPath({ destinationPath: LOCAL_BACKUP_DIR, filename });
await storage.createBackup(admin.id);
await storage.deleteNotificationsByType(admin.id, "BACKUP");
await cronJobLogStorage.completeJobLog(log.id, "success", new Date());
console.log(`✅ Local backup done → ${filename}`);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.error("Local backup failed:", err);
await cronJobLogStorage.completeJobLog(log.id, "failed", new Date(), errorMessage);
await storage.createNotification(
admin.id,
"BACKUP",
"❌ Automatic backup failed. Please check the server backup folder."
);
}
console.log("✅ [8 PM] Local backup complete.");
});
// ============================================================
// 9 PM — USB backup to the "USB Backup" folder on the drive
// ============================================================
cron.schedule("0 21 * * *", async () => {
console.log("🔄 [9 PM] Running USB backup...");
const admin = await getAdminUser();
if (!admin) {
console.warn("No admin user found, skipping USB backup.");
return;
}
if (!admin.usbBackupEnabled) {
console.log("✅ [9 PM] USB backup is disabled for admin, skipped.");
const startedAt = new Date();
const log = await cronJobLogStorage.createJobLog("usb-backup", startedAt);
await cronJobLogStorage.completeJobLog(log.id, "skipped", new Date());
return;
}
const startedAt = new Date();
const log = await cronJobLogStorage.createJobLog("usb-backup", startedAt);
const destination = await storage.getActiveBackupDestination(admin.id);
if (!destination) {
const errorMessage = "No backup destination configured.";
await cronJobLogStorage.completeJobLog(log.id, "failed", new Date(), errorMessage);
await storage.createNotification(
admin.id,
"BACKUP",
"❌ USB backup failed: no backup destination configured."
);
return;
}
if (!fs.existsSync(destination.path)) {
const errorMessage = "Backup destination drive not found or disconnected.";
await cronJobLogStorage.completeJobLog(log.id, "failed", new Date(), errorMessage);
await storage.createNotification(
admin.id,
"BACKUP",
"❌ USB backup failed: backup drive not found. Make sure the USB drive is connected."
);
return;
}
const usbBackupPath = path.join(destination.path, USB_BACKUP_FOLDER_NAME);
if (!fs.existsSync(usbBackupPath)) {
fs.mkdirSync(usbBackupPath, { recursive: true });
}
try {
const filename = `dental_backup_usb_${Date.now()}.sql`;
await backupDatabaseToPath({ destinationPath: usbBackupPath, filename });
await storage.createBackup(admin.id);
await storage.deleteNotificationsByType(admin.id, "BACKUP");
await cronJobLogStorage.completeJobLog(log.id, "success", new Date());
console.log(`✅ USB backup done → ${usbBackupPath}/${filename}`);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.error("USB backup failed:", err);
await cronJobLogStorage.completeJobLog(log.id, "failed", new Date(), errorMessage);
await storage.createNotification(
admin.id,
"BACKUP",
"❌ USB backup failed. Please check the USB drive and try again."
);
}
console.log("✅ [9 PM] USB backup complete.");
});
};