Checkpoint: Ajout onglet Sauvegarde DB dans ImportSettings : endpoint POST /api/db-backup (mysqldump), GET /api/db-backup/:filename, procédures tRPC backup.list et backup.delete, composant BackupSection avec liste des sauvegardes et téléchargement.
This commit is contained in:
@@ -5,6 +5,9 @@ import net from "net";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import archiver from "archiver";
|
||||
import { exec as execCb } from "child_process";
|
||||
import { promisify } from "util";
|
||||
const execAsync = promisify(execCb);
|
||||
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
||||
import { registerOAuthRoutes } from "./oauth";
|
||||
import { appRouter } from "../routers";
|
||||
@@ -225,6 +228,70 @@ async function startServer() {
|
||||
});
|
||||
|
||||
// ============= WEB IMPORT SOURCES - Endpoint pour script cron externe =============
|
||||
|
||||
// ============= DB BACKUP - Génération et téléchargement dump MySQL =============
|
||||
app.post("/api/db-backup", async (req, res) => {
|
||||
// Vérifier l'auth JWT
|
||||
const { verifyToken } = await import("../auth");
|
||||
const token = req.cookies?.auth_token;
|
||||
if (!token) { res.status(401).json({ error: "Non authentifié" }); return; }
|
||||
const user = verifyToken(token);
|
||||
if (!user || user.role !== "admin") { res.status(403).json({ error: "Accès réservé aux admins" }); return; }
|
||||
|
||||
try {
|
||||
const dbUrl = new URL(process.env.DATABASE_URL || "");
|
||||
const host = dbUrl.hostname;
|
||||
const port = dbUrl.port || "3306";
|
||||
const username = dbUrl.username;
|
||||
const password = dbUrl.password;
|
||||
const database = dbUrl.pathname.slice(1);
|
||||
|
||||
// Créer le dossier backups/
|
||||
const backupDir = path.resolve("backups");
|
||||
if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
||||
const fileName = `backup-${database}-${timestamp}.sql`;
|
||||
const filePath = path.join(backupDir, fileName);
|
||||
|
||||
// Construire la commande mysqldump
|
||||
const sslFlag = dbUrl.searchParams.get("ssl-mode") === "DISABLED" ? "" : "--ssl-mode=REQUIRED";
|
||||
const cmd = `mysqldump ${sslFlag} -h "${host}" -P ${port} -u "${username}" --password="${password}" "${database}" > "${filePath}"`;
|
||||
|
||||
console.log(`[Backup] Generating dump for database ${database}...`);
|
||||
await execAsync(cmd);
|
||||
console.log(`[Backup] Dump saved to ${filePath}`);
|
||||
|
||||
// Retourner le fichier en téléchargement
|
||||
const encodedName = encodeURIComponent(fileName);
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${encodedName}"; filename*=UTF-8''${encodedName}`);
|
||||
res.setHeader("Content-Type", "application/sql");
|
||||
res.sendFile(filePath, (err) => {
|
||||
if (err) console.error("[Backup] Error sending file:", err);
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.error("[Backup] Error:", err.message);
|
||||
res.status(500).json({ error: "Erreur lors de la génération du dump : " + err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Télécharger une sauvegarde existante
|
||||
app.get("/api/db-backup/:filename", async (req, res) => {
|
||||
const { verifyToken } = await import("../auth");
|
||||
const token = req.cookies?.auth_token;
|
||||
if (!token) { res.status(401).json({ error: "Non authentifié" }); return; }
|
||||
const user = verifyToken(token);
|
||||
if (!user || user.role !== "admin") { res.status(403).json({ error: "Accès réservé aux admins" }); return; }
|
||||
|
||||
const fileName = path.basename(req.params.filename);
|
||||
const filePath = path.join(path.resolve("backups"), fileName);
|
||||
if (!fs.existsSync(filePath)) { res.status(404).json({ error: "Fichier introuvable" }); return; }
|
||||
const encodedName = encodeURIComponent(fileName);
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${encodedName}"; filename*=UTF-8''${encodedName}`);
|
||||
res.setHeader("Content-Type", "application/sql");
|
||||
res.sendFile(filePath);
|
||||
});
|
||||
|
||||
app.post("/api/web-import/push-invoice", async (req, res) => {
|
||||
try {
|
||||
const { apiToken, fileName, fileBase64, mimeType } = req.body;
|
||||
|
||||
Reference in New Issue
Block a user