Praxissofttware/routes/admin.routes.js

383 lines
11 KiB
JavaScript

const express = require("express");
const router = express.Router();
const mysql = require("mysql2/promise");
const fs = require("fs");
const path = require("path");
const { exec } = require("child_process");
const multer = require("multer");
// ✅ Upload Ordner für Restore Dumps
const upload = multer({ dest: path.join(__dirname, "..", "uploads_tmp") });
const {
listUsers,
showCreateUser,
postCreateUser,
changeUserRole,
resetUserPassword,
activateUser,
deactivateUser,
showInvoiceOverview,
updateUser,
} = require("../controllers/admin.controller");
const { requireArzt, requireAdmin } = require("../middleware/auth.middleware");
// ✅ config.enc Manager
const { loadConfig, saveConfig } = require("../config-manager");
// ✅ DB (für resetPool)
const db = require("../db");
/* ==========================
✅ VERWALTUNG (NUR ADMIN)
========================== */
router.get("/users", requireAdmin, listUsers);
router.get("/create-user", requireAdmin, showCreateUser);
router.post("/create-user", requireAdmin, postCreateUser);
router.post("/users/change-role/:id", requireAdmin, changeUserRole);
router.post("/users/reset-password/:id", requireAdmin, resetUserPassword);
router.post("/users/activate/:id", requireAdmin, activateUser);
router.post("/users/deactivate/:id", requireAdmin, deactivateUser);
router.post("/users/update/:id", requireAdmin, updateUser);
/* ==========================
✅ DATENBANKVERWALTUNG (NUR ADMIN)
========================== */
// ✅ Seite anzeigen + aktuelle DB Config aus config.enc anzeigen
router.get("/database", requireAdmin, async (req, res) => {
const cfg = loadConfig();
const backupDir = path.join(__dirname, "..", "backups");
let backupFiles = [];
try {
if (fs.existsSync(backupDir)) {
backupFiles = fs
.readdirSync(backupDir)
.filter((f) => f.toLowerCase().endsWith(".sql"))
.sort()
.reverse(); // ✅ neueste zuerst
}
} catch (err) {
console.error("❌ Backup Ordner Fehler:", err);
}
let systemInfo = null;
try {
if (cfg?.db) {
const conn = await mysql.createConnection({
host: cfg.db.host,
user: cfg.db.user,
password: cfg.db.password,
database: cfg.db.name,
});
// ✅ Version
const [v] = await conn.query("SELECT VERSION() AS version");
// ✅ Anzahl Tabellen
const [tablesCount] = await conn.query(
"SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = ?",
[cfg.db.name],
);
// ✅ DB Größe (Bytes)
const [dbSize] = await conn.query(
`
SELECT IFNULL(SUM(data_length + index_length),0) AS bytes
FROM information_schema.tables
WHERE table_schema = ?
`,
[cfg.db.name],
);
// ✅ Tabellen Details
const [tables] = await conn.query(
`
SELECT
table_name AS name,
table_rows AS row_count,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb
FROM information_schema.tables
WHERE table_schema = ?
ORDER BY (data_length + index_length) DESC
`,
[cfg.db.name],
);
await conn.end();
systemInfo = {
version: v?.[0]?.version || "unbekannt",
tableCount: tablesCount?.[0]?.count || 0,
dbSizeMB:
Math.round(((dbSize?.[0]?.bytes || 0) / 1024 / 1024) * 100) / 100,
tables,
};
}
} catch (err) {
console.error("❌ SYSTEMINFO ERROR:", err);
systemInfo = {
error: err.message,
};
}
res.render("admin/database", {
user: req.session.user,
dbConfig: cfg?.db || null,
testResult: null,
backupFiles,
systemInfo, // ✅ DAS HAT GEFEHLT
});
});
// ✅ Nur testen (ohne speichern)
router.post("/database/test", requireAdmin, async (req, res) => {
try {
const { host, user, password, name } = req.body;
if (!host || !user || !password || !name) {
const cfg = loadConfig();
return res.render("admin/database", {
user: req.session.user,
dbConfig: cfg?.db || null,
testResult: { ok: false, message: "❌ Bitte alle Felder ausfüllen." },
});
}
const conn = await mysql.createConnection({
host,
user,
password,
database: name,
});
await conn.query("SELECT 1");
await conn.end();
return res.render("admin/database", {
user: req.session.user,
dbConfig: { host, user, password, name },
testResult: { ok: true, message: "✅ Verbindung erfolgreich!" },
});
} catch (err) {
console.error("❌ DB TEST ERROR:", err);
return res.render("admin/database", {
user: req.session.user,
dbConfig: req.body,
testResult: {
ok: false,
message: "❌ Verbindung fehlgeschlagen: " + err.message,
},
});
}
});
// ✅ DB Settings speichern + Verbindung testen
router.post("/database", requireAdmin, async (req, res) => {
try {
const { host, user, password, name } = req.body;
if (!host || !user || !password || !name) {
req.flash("error", "❌ Bitte alle Felder ausfüllen.");
return res.redirect("/admin/database");
}
const conn = await mysql.createConnection({
host,
user,
password,
database: name,
});
await conn.query("SELECT 1");
await conn.end();
// ✅ Speichern in config.enc
const current = loadConfig() || {};
current.db = { host, user, password, name };
saveConfig(current);
// ✅ DB Pool resetten (falls vorhanden)
if (typeof db.resetPool === "function") {
db.resetPool();
}
req.flash(
"success",
"✅ DB Einstellungen gespeichert + Verbindung erfolgreich getestet.",
);
return res.redirect("/admin/database");
} catch (err) {
console.error("❌ DB UPDATE ERROR:", err);
req.flash("error", "❌ Verbindung fehlgeschlagen: " + err.message);
return res.redirect("/admin/database");
}
});
/* ==========================
✅ BACKUP (NUR ADMIN)
========================== */
router.post("/database/backup", requireAdmin, (req, res) => {
// ✅ Flash Safe (funktioniert auch ohne req.flash)
function flashSafe(type, msg) {
if (typeof req.flash === "function") {
req.flash(type, msg);
return;
}
req.session.flash = req.session.flash || [];
req.session.flash.push({ type, message: msg });
console.log(`[FLASH-${type}]`, msg);
}
try {
const cfg = loadConfig();
if (!cfg?.db) {
flashSafe("danger", "❌ Keine DB Config gefunden (config.enc fehlt).");
return res.redirect("/admin/database");
}
const { host, user, password, name } = cfg.db;
const backupDir = path.join(__dirname, "..", "backups");
if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir);
const stamp = new Date()
.toISOString()
.replace(/T/, "_")
.replace(/:/g, "-")
.split(".")[0];
const fileName = `${name}_${stamp}.sql`;
const filePath = path.join(backupDir, fileName);
// ✅ mysqldump.exe im Root
const mysqldumpPath = path.join(__dirname, "..", "mysqldump.exe");
// ✅ plugin Ordner im Root (muss existieren)
const pluginDir = path.join(__dirname, "..", "plugin");
if (!fs.existsSync(mysqldumpPath)) {
flashSafe("danger", "❌ mysqldump.exe nicht gefunden: " + mysqldumpPath);
return res.redirect("/admin/database");
}
if (!fs.existsSync(pluginDir)) {
flashSafe("danger", "❌ plugin Ordner nicht gefunden: " + pluginDir);
return res.redirect("/admin/database");
}
const cmd = `"${mysqldumpPath}" --plugin-dir="${pluginDir}" -h ${host} -u ${user} -p${password} ${name} > "${filePath}"`;
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error("❌ BACKUP ERROR:", error);
console.error("STDERR:", stderr);
flashSafe(
"danger",
"❌ Backup fehlgeschlagen: " + (stderr || error.message),
);
return res.redirect("/admin/database");
}
flashSafe("success", `✅ Backup erstellt: ${fileName}`);
return res.redirect("/admin/database");
});
} catch (err) {
console.error("❌ BACKUP ERROR:", err);
flashSafe("danger", "❌ Backup fehlgeschlagen: " + err.message);
return res.redirect("/admin/database");
}
});
/* ==========================
✅ RESTORE (NUR ADMIN)
========================== */
router.post("/database/restore", requireAdmin, (req, res) => {
function flashSafe(type, msg) {
if (typeof req.flash === "function") {
req.flash(type, msg);
return;
}
req.session.flash = req.session.flash || [];
req.session.flash.push({ type, message: msg });
console.log(`[FLASH-${type}]`, msg);
}
try {
const cfg = loadConfig();
if (!cfg?.db) {
flashSafe("danger", "❌ Keine DB Config gefunden (config.enc fehlt).");
return res.redirect("/admin/database");
}
const { host, user, password, name } = cfg.db;
const backupDir = path.join(__dirname, "..", "backups");
const selectedFile = req.body.backupFile;
if (!selectedFile) {
flashSafe("danger", "❌ Bitte ein Backup auswählen.");
return res.redirect("/admin/database");
}
const fullPath = path.join(backupDir, selectedFile);
if (!fs.existsSync(fullPath)) {
flashSafe("danger", "❌ Backup Datei nicht gefunden: " + selectedFile);
return res.redirect("/admin/database");
}
// ✅ mysql.exe im Root
const mysqlPath = path.join(__dirname, "..", "mysql.exe");
const pluginDir = path.join(__dirname, "..", "plugin");
if (!fs.existsSync(mysqlPath)) {
flashSafe("danger", "❌ mysql.exe nicht gefunden im Root: " + mysqlPath);
return res.redirect("/admin/database");
}
const cmd = `"${mysqlPath}" --plugin-dir="${pluginDir}" -h ${host} -u ${user} -p${password} ${name} < "${fullPath}"`;
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error("❌ RESTORE ERROR:", error);
console.error("STDERR:", stderr);
flashSafe(
"danger",
"❌ Restore fehlgeschlagen: " + (stderr || error.message),
);
return res.redirect("/admin/database");
}
flashSafe(
"success",
"✅ Restore erfolgreich abgeschlossen: " + selectedFile,
);
return res.redirect("/admin/database");
});
} catch (err) {
console.error("❌ RESTORE ERROR:", err);
flashSafe("danger", "❌ Restore fehlgeschlagen: " + err.message);
return res.redirect("/admin/database");
}
});
/* ==========================
✅ ABRECHNUNG (NUR ARZT)
========================== */
router.get("/invoices", requireAdmin, showInvoiceOverview);
module.exports = router;