From f415100cdbd28fabd34f50125d7552bfc6b18f0a Mon Sep 17 00:00:00 2001 From: ff Date: Mon, 13 Apr 2026 23:12:41 -0400 Subject: [PATCH] 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 --- apps/Backend/src/cron/backupCheck.ts | 14 +++++++----- .../Backend/src/routes/database-management.ts | 22 ++++++++++++++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/Backend/src/cron/backupCheck.ts b/apps/Backend/src/cron/backupCheck.ts index 8da3525b..c79e3b01 100755 --- a/apps/Backend/src/cron/backupCheck.ts +++ b/apps/Backend/src/cron/backupCheck.ts @@ -111,19 +111,23 @@ export const startBackupCron = () => { return; } - const usbBackupPath = path.join(destination.path, USB_BACKUP_FOLDER_NAME); - - if (!fs.existsSync(usbBackupPath)) { - const errorMessage = `Folder "${USB_BACKUP_FOLDER_NAME}" not found on the drive.`; + 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: folder "${USB_BACKUP_FOLDER_NAME}" not found on the drive. Make sure the USB drive is connected and the folder exists.` + "❌ 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 }); diff --git a/apps/Backend/src/routes/database-management.ts b/apps/Backend/src/routes/database-management.ts index f7fa7568..abff7d94 100755 --- a/apps/Backend/src/routes/database-management.ts +++ b/apps/Backend/src/routes/database-management.ts @@ -339,6 +339,17 @@ router.post("/restore", restoreUpload.single("file"), async (req: Request, res: if (!req.file) return res.status(400).json({ error: "No file provided" }); + // Drop and recreate the public schema so existing tables don't block the restore. + // pg_dump without --clean produces no DROP statements, so restoring into an + // existing schema would silently skip CREATE TABLE / fail on duplicate inserts. + try { + await prisma.$executeRawUnsafe(`DROP SCHEMA public CASCADE`); + await prisma.$executeRawUnsafe(`CREATE SCHEMA public`); + } catch (err: any) { + console.error("Failed to reset schema before restore:", err); + return res.status(500).json({ error: "Failed to reset database schema", details: err.message }); + } + const psql = spawn( "psql", [ @@ -360,11 +371,20 @@ router.post("/restore", restoreUpload.single("file"), async (req: Request, res: res.status(500).json({ error: "Failed to run psql", details: err.message }); }); - psql.on("close", (code) => { + psql.on("close", async (code) => { if (code !== 0) { console.error("psql restore failed:", stderr); return res.status(500).json({ error: "Restore failed", details: stderr }); } + + // Disconnect so Prisma drops its pooled connections and reconnects fresh + // on the next request, avoiding stale prepared statements against the old schema. + try { + await prisma.$disconnect(); + } catch (_) { + // non-fatal — the restore succeeded + } + res.json({ success: true }); });