feat: add Network Backup section to Database Management
PC2 can now automatically pull and restore a fresh copy of PC1's database on a daily schedule. Config and API key are stored in local JSON files so they survive database restores. - New networkSyncConfigService: file-based config (network-backup-key.json, network-sync-config.json) that persists through DB restores - New networkSyncService: streams live pg_dump from source PC over HTTP and pipes into psql, then reconnects Prisma and applies missing migrations - 6 new endpoints: get/regenerate API key, serve backup stream (key-auth only), get/save sync config, trigger immediate sync - Hourly cron job that fires only when current hour matches configured syncHour - NetworkBackupManager component: shows this machine's key (show/copy/regen) and receiver config (enable toggle, hour picker 0-23, source URL + key, Save + Sync Now, last sync status) - README setup guide for both PCs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import path from "path";
|
||||
import { storage } from "../storage";
|
||||
import { backupDatabaseToPath } from "../services/databaseBackupService";
|
||||
import { cronJobLogStorage } from "../storage/cron-job-log-storage";
|
||||
import { readSyncConfig, writeSyncConfig } from "../services/networkSyncConfigService";
|
||||
import { runNetworkSync } from "../services/networkSyncService";
|
||||
|
||||
// Local backup folder in the app root (apps/Backend/backups)
|
||||
const LOCAL_BACKUP_DIR = path.resolve(process.cwd(), "backups");
|
||||
@@ -171,4 +173,40 @@ export const startBackupCron = () => {
|
||||
|
||||
console.log("✅ [9 PM] USB backup complete.");
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Every hour — Network sync (runs only when hour matches config)
|
||||
// ============================================================
|
||||
cron.schedule("0 * * * *", async () => {
|
||||
const config = readSyncConfig();
|
||||
if (!config.enabled || !config.sourceUrl || !config.apiKey) return;
|
||||
|
||||
const currentHour = new Date().getHours();
|
||||
if (currentHour !== config.syncHour) return;
|
||||
|
||||
console.log(`🔄 [${config.syncHour}:00] Running network sync from ${config.sourceUrl}...`);
|
||||
|
||||
const admin = await getAdminUser();
|
||||
const startedAt = new Date();
|
||||
const log = await cronJobLogStorage.createJobLog("network-sync", startedAt);
|
||||
|
||||
try {
|
||||
await runNetworkSync(config.sourceUrl, config.apiKey);
|
||||
writeSyncConfig({ lastSyncAt: new Date().toISOString(), lastSyncStatus: "success", lastSyncError: null });
|
||||
await cronJobLogStorage.completeJobLog(log.id, "success", new Date());
|
||||
console.log(`✅ Network sync complete.`);
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
console.error("Network sync failed:", err);
|
||||
writeSyncConfig({ lastSyncAt: new Date().toISOString(), lastSyncStatus: "failed", lastSyncError: errorMessage });
|
||||
await cronJobLogStorage.completeJobLog(log.id, "failed", new Date(), errorMessage);
|
||||
if (admin) {
|
||||
await storage.createNotification(
|
||||
admin.id,
|
||||
"BACKUP",
|
||||
`❌ Network sync failed: ${errorMessage}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user