mirror of
https://github.com/R0m1k3/Regisflow.git
synced 2026-10-11 17:29:48 +02:00
Enable automatic data backups to secure user information regularly
Implements automatic database backups every 12 hours using node-cron, retaining only the 10 most recent backups in server/backup-scheduler.ts. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 91318273-c764-4fd4-be04-bdc12c38af32 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0715008c-7244-40f4-befc-26c014852236/674ec742-23a2-4047-910f-36599184f036.jpg
This commit is contained in:
1 parent
546ba12e89
commit
a78d648c01
9 files changed
+594
-5
No files matched your search
@@ -0,0 +1,145 @@
|
||||
import cron from 'node-cron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { storage } from './storage';
|
||||
|
||||
const BACKUP_DIR = path.join(process.cwd(), 'backups');
|
||||
const MAX_BACKUPS = 10;
|
||||
|
||||
// Ensure backup directory exists
|
||||
if (!fs.existsSync(BACKUP_DIR)) {
|
||||
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
export async function createAutomaticBackup() {
|
||||
try {
|
||||
console.log('Creating automatic backup...');
|
||||
|
||||
// Get all data from the database
|
||||
const users = await storage.getAllUsers();
|
||||
const stores = await storage.getAllStores();
|
||||
|
||||
// Get all sales from all stores
|
||||
const allSales = [];
|
||||
for (const store of stores) {
|
||||
const storeSales = await storage.getSalesByStore(store.id);
|
||||
allSales.push(...storeSales);
|
||||
}
|
||||
|
||||
const backupData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
version: "1.0",
|
||||
type: "automatic",
|
||||
data: {
|
||||
users: users.map(user => ({
|
||||
...user,
|
||||
// Don't include the password hash in backup for security
|
||||
password: undefined
|
||||
})),
|
||||
stores,
|
||||
sales: allSales
|
||||
},
|
||||
metadata: {
|
||||
totalUsers: users.length,
|
||||
totalStores: stores.length,
|
||||
totalSales: allSales.length,
|
||||
exportedBy: 'system-auto-backup'
|
||||
}
|
||||
};
|
||||
|
||||
// Create filename with timestamp
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `auto-backup-${timestamp}.json`;
|
||||
const filepath = path.join(BACKUP_DIR, filename);
|
||||
|
||||
// Write backup to file
|
||||
fs.writeFileSync(filepath, JSON.stringify(backupData, null, 2));
|
||||
|
||||
console.log(`✅ Automatic backup created: ${filename}`);
|
||||
console.log(`📊 Backup stats: ${users.length} users, ${stores.length} stores, ${allSales.length} sales`);
|
||||
|
||||
// Clean up old backups
|
||||
await cleanupOldBackups();
|
||||
|
||||
return { success: true, filename, stats: backupData.metadata };
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to create automatic backup:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupOldBackups() {
|
||||
try {
|
||||
// Get all backup files
|
||||
const files = fs.readdirSync(BACKUP_DIR)
|
||||
.filter(file => file.startsWith('auto-backup-') && file.endsWith('.json'))
|
||||
.map(file => ({
|
||||
name: file,
|
||||
path: path.join(BACKUP_DIR, file),
|
||||
stat: fs.statSync(path.join(BACKUP_DIR, file))
|
||||
}))
|
||||
.sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime()); // Sort by modification time, newest first
|
||||
|
||||
// If we have more than MAX_BACKUPS, delete the oldest ones
|
||||
if (files.length > MAX_BACKUPS) {
|
||||
const filesToDelete = files.slice(MAX_BACKUPS);
|
||||
|
||||
for (const file of filesToDelete) {
|
||||
fs.unlinkSync(file.path);
|
||||
console.log(`🗑️ Deleted old backup: ${file.name}`);
|
||||
}
|
||||
|
||||
console.log(`🧹 Cleaned up ${filesToDelete.length} old backup(s), keeping ${MAX_BACKUPS} most recent`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cleaning up old backups:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export function getBackupStats() {
|
||||
try {
|
||||
const files = fs.readdirSync(BACKUP_DIR)
|
||||
.filter(file => file.startsWith('auto-backup-') && file.endsWith('.json'));
|
||||
|
||||
return {
|
||||
totalBackups: files.length,
|
||||
backupDirectory: BACKUP_DIR,
|
||||
maxBackups: MAX_BACKUPS
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
totalBackups: 0,
|
||||
backupDirectory: BACKUP_DIR,
|
||||
maxBackups: MAX_BACKUPS,
|
||||
error: 'Could not read backup directory'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function startBackupScheduler() {
|
||||
// Schedule backup every 12 hours (at 00:00 and 12:00)
|
||||
const task = cron.schedule('0 0,12 * * *', async () => {
|
||||
console.log('\n🕒 Scheduled backup starting...');
|
||||
await createAutomaticBackup();
|
||||
}, {
|
||||
scheduled: false,
|
||||
timezone: "Europe/Paris"
|
||||
});
|
||||
|
||||
// Start the scheduler
|
||||
task.start();
|
||||
|
||||
console.log('📅 Automatic backup scheduler started');
|
||||
console.log('⏰ Backups will run every 12 hours (00:00 and 12:00)');
|
||||
console.log(`📁 Backup directory: ${BACKUP_DIR}`);
|
||||
console.log(`📚 Maximum backups kept: ${MAX_BACKUPS}`);
|
||||
|
||||
// Create an initial backup if none exist
|
||||
const stats = getBackupStats();
|
||||
if (stats.totalBackups === 0) {
|
||||
console.log('🎯 No existing backups found, creating initial backup...');
|
||||
setTimeout(() => createAutomaticBackup(), 5000); // Wait 5 seconds for server to be fully ready
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import express, { type Request, Response, NextFunction } from "express";
|
||||
import { registerRoutes } from "./routes";
|
||||
import { setupVite, serveStatic, log } from "./vite";
|
||||
import { startBackupScheduler } from "./backup-scheduler";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
@@ -66,5 +67,8 @@ app.use((req, res, next) => {
|
||||
reusePort: true,
|
||||
}, () => {
|
||||
log(`serving on port ${port}`);
|
||||
|
||||
// Start the automatic backup scheduler
|
||||
startBackupScheduler();
|
||||
});
|
||||
})();
|
||||
+33
-2
@@ -5,6 +5,7 @@ import session from "express-session";
|
||||
import connectPg from "connect-pg-simple";
|
||||
import { loginSchema, insertUserSchema, insertStoreSchema, insertSaleSchema } from "@shared/schema";
|
||||
import { z } from "zod";
|
||||
import { createAutomaticBackup, getBackupStats } from "./backup-scheduler";
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
@@ -483,13 +484,43 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
message: "Backup imported successfully",
|
||||
imported: {
|
||||
stores: backupData.data.stores.length,
|
||||
users: backupData.data.users.filter(u => u.username !== 'admin').length,
|
||||
users: backupData.data.users.filter((u: any) => u.username !== 'admin').length,
|
||||
sales: backupData.data.sales.length
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Import backup error:', error);
|
||||
res.status(500).json({ error: "Failed to import backup", details: error.message });
|
||||
res.status(500).json({ error: "Failed to import backup", details: error instanceof Error ? error.message : 'Unknown error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Manual backup trigger route
|
||||
app.post('/api/admin/backup/create', requireRole(['admin']), async (req, res) => {
|
||||
try {
|
||||
const result = await createAutomaticBackup();
|
||||
if (result.success) {
|
||||
res.json({
|
||||
message: "Manual backup created successfully",
|
||||
filename: result.filename,
|
||||
stats: result.stats
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({ error: "Failed to create backup", details: result.error });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Manual backup error:', error);
|
||||
res.status(500).json({ error: "Failed to create backup" });
|
||||
}
|
||||
});
|
||||
|
||||
// Backup statistics route
|
||||
app.get('/api/admin/backup/stats', requireRole(['admin']), async (req, res) => {
|
||||
try {
|
||||
const stats = getBackupStats();
|
||||
res.json(stats);
|
||||
} catch (error) {
|
||||
console.error('Backup stats error:', error);
|
||||
res.status(500).json({ error: "Failed to get backup statistics" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user