125 lines
3.9 KiB
TypeScript
Executable File
125 lines
3.9 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";
|
|
|
|
// 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.");
|
|
return;
|
|
}
|
|
|
|
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");
|
|
console.log(`✅ Local backup done → ${filename}`);
|
|
} catch (err) {
|
|
console.error("Local backup failed:", err);
|
|
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.");
|
|
return;
|
|
}
|
|
|
|
const destination = await storage.getActiveBackupDestination(admin.id);
|
|
if (!destination) {
|
|
await storage.createNotification(
|
|
admin.id,
|
|
"BACKUP",
|
|
"❌ USB backup failed: no backup destination configured."
|
|
);
|
|
return;
|
|
}
|
|
|
|
const usbBackupPath = path.join(destination.path, USB_BACKUP_FOLDER_NAME);
|
|
|
|
if (!fs.existsSync(usbBackupPath)) {
|
|
await storage.createNotification(
|
|
admin.id,
|
|
"BACKUP",
|
|
`❌ USB backup failed: folder "${USB_BACKUP_FOLDER_NAME}" not found on the drive. Make sure the USB drive is connected and the folder exists.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
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");
|
|
console.log(`✅ USB backup done → ${usbBackupPath}/${filename}`);
|
|
} catch (err) {
|
|
console.error("USB backup failed:", err);
|
|
await storage.createNotification(
|
|
admin.id,
|
|
"BACKUP",
|
|
"❌ USB backup failed. Please check the USB drive and try again."
|
|
);
|
|
}
|
|
|
|
console.log("✅ [9 PM] USB backup complete.");
|
|
});
|
|
};
|