+ Restaurez les données à partir d'un fichier de sauvegarde.
+ Attention : cette action remplacera toutes les données existantes.
+
+
+
+
+
+
+
+ Confirmer la restauration
+
+ Êtes-vous sûr de vouloir restaurer les données depuis un fichier de sauvegarde ?
+
+
+ ⚠️ ATTENTION : Cette action supprimera définitivement toutes les données actuelles
+ (utilisateurs, magasins, ventes) et les remplacera par celles du fichier de sauvegarde.
+
+
+ Cette action est irréversible. Assurez-vous d'avoir une sauvegarde des données actuelles
+ si vous souhaitez pouvoir les récupérer.
+
+
+
+ Annuler
+
+ Confirmer la Restauration
+
+
+
+
+
+
+
+
+ {/* Info Section */}
+
+
+ Informations sur les Sauvegardes
+
+
+
+
+ Format des fichiers : Les sauvegardes sont au format JSON et contiennent toutes les données de l'application.
+
+
+ Contenu inclus : Utilisateurs, magasins, ventes, et toutes les relations entre ces données.
+
+
+ Sécurité : Les mots de passe sont hachés et ne peuvent pas être récupérés depuis une sauvegarde.
+
+
+ Recommandation : Effectuez des sauvegardes régulières et stockez-les en lieu sûr.
+
+
+
+
+
diff --git a/replit.md b/replit.md
index fe1423d..1b9c9d3 100644
--- a/replit.md
+++ b/replit.md
@@ -158,4 +158,12 @@ Preferred communication style: Simple, everyday language.
- ✅ Replaced native browser confirm() dialogs with modern AlertDialog modals
- ✅ Enhanced deletion confirmations in sales history and administration panels
- ✅ Improved visual feedback with detailed confirmation dialogs showing item details
-- ✅ Added proper modal management with shadcn/ui AlertDialog components
\ No newline at end of file
+- ✅ Added proper modal management with shadcn/ui AlertDialog components
+
+### Database Backup & Recovery System (January 9, 2025)
+- ✅ Added complete database backup and restore functionality in administration panel
+- ✅ Export feature creates comprehensive JSON backups with all data (users, stores, sales)
+- ✅ Import feature allows full database restoration with data validation
+- ✅ Automatic ID remapping during import to handle database constraints
+- ✅ Security measures: passwords excluded from backups, admin account protected during restore
+- ✅ User-friendly interface with clear warnings for destructive operations
\ No newline at end of file
diff --git a/server/routes.ts b/server/routes.ts
index 2880a18..ee4a7e1 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -357,6 +357,142 @@ export async function registerRoutes(app: Express): Promise {
}
});
+ // Backup routes
+ app.get('/api/admin/backup/export', requireRole(['admin']), async (req, res) => {
+ try {
+ // 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",
+ 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: req.user.username
+ }
+ };
+
+ res.json(backupData);
+ } catch (error) {
+ console.error('Export backup error:', error);
+ res.status(500).json({ error: "Failed to export backup" });
+ }
+ });
+
+ app.post('/api/admin/backup/import', requireRole(['admin']), async (req, res) => {
+ try {
+ const backupData = req.body;
+
+ // Validate backup structure
+ if (!backupData.data || !backupData.data.users || !backupData.data.stores || !backupData.data.sales) {
+ return res.status(400).json({ error: "Invalid backup file format" });
+ }
+
+ // Clear existing data (in reverse order due to foreign keys)
+ // First delete all sales
+ const existingSales = [];
+ const existingStores = await storage.getAllStores();
+ for (const store of existingStores) {
+ const storeSales = await storage.getSalesByStore(store.id);
+ existingSales.push(...storeSales);
+ }
+
+ for (const sale of existingSales) {
+ await storage.deleteSale(sale.id);
+ }
+
+ // Then delete users (except admin to avoid locking out)
+ const existingUsers = await storage.getAllUsers();
+ for (const user of existingUsers) {
+ if (user.role !== 'administrator' || user.username !== 'admin') {
+ await storage.deleteUser(user.id);
+ }
+ }
+
+ // Then delete stores
+ for (const store of existingStores) {
+ await storage.deleteStore(store.id);
+ }
+
+ // Import new data
+ // First import stores
+ const storeIdMapping = new Map();
+ for (const storeData of backupData.data.stores) {
+ const { id, ...storeWithoutId } = storeData;
+ const newStore = await storage.createStore(storeWithoutId);
+ storeIdMapping.set(id, newStore.id);
+ }
+
+ // Then import users (excluding admin and users with passwords)
+ const userIdMapping = new Map();
+ for (const userData of backupData.data.users) {
+ if (userData.username === 'admin') continue; // Skip admin user
+
+ const { id, password, ...userWithoutIdAndPassword } = userData;
+
+ // Map store ID if it exists
+ if (userData.storeId && storeIdMapping.has(userData.storeId)) {
+ userWithoutIdAndPassword.storeId = storeIdMapping.get(userData.storeId);
+ }
+
+ // Set a default password since we can't restore the original
+ const userWithPassword = {
+ ...userWithoutIdAndPassword,
+ password: 'restored123' // Users will need to change this
+ };
+
+ const newUser = await storage.createUser(userWithPassword);
+ userIdMapping.set(id, newUser.id);
+ }
+
+ // Finally import sales
+ for (const saleData of backupData.data.sales) {
+ const { id, ...saleWithoutId } = saleData;
+
+ // Map store and user IDs
+ if (saleData.storeId && storeIdMapping.has(saleData.storeId)) {
+ saleWithoutId.storeId = storeIdMapping.get(saleData.storeId);
+ }
+ if (saleData.userId && userIdMapping.has(saleData.userId)) {
+ saleWithoutId.userId = userIdMapping.get(saleData.userId);
+ }
+
+ await storage.createSale(saleWithoutId);
+ }
+
+ res.json({
+ message: "Backup imported successfully",
+ imported: {
+ stores: backupData.data.stores.length,
+ users: backupData.data.users.filter(u => 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 });
+ }
+ });
+
const httpServer = createServer(app);
return httpServer;
}