Sidebars eingeführt auf Dashboard und Verwaltung
This commit is contained in:
parent
1aef5a5749
commit
81473882e5
172
app.js
172
app.js
@ -1,9 +1,18 @@
|
||||
require("dotenv").config();
|
||||
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const helmet = require("helmet");
|
||||
const sessionStore = require("./config/session");
|
||||
require("dotenv").config();
|
||||
const mysql = require("mysql2/promise");
|
||||
|
||||
// ✅ Verschlüsselte Config
|
||||
const { configExists, saveConfig } = require("./config-manager");
|
||||
|
||||
// ✅ Reset-Funktionen (Soft-Restart)
|
||||
const db = require("./db");
|
||||
const { getSessionStore, resetSessionStore } = require("./config/session");
|
||||
|
||||
// ✅ Deine Routes (unverändert)
|
||||
const adminRoutes = require("./routes/admin.routes");
|
||||
const dashboardRoutes = require("./routes/dashboard.routes");
|
||||
const patientRoutes = require("./routes/patient.routes");
|
||||
@ -19,20 +28,73 @@ const authRoutes = require("./routes/auth.routes");
|
||||
|
||||
const app = express();
|
||||
|
||||
/* ===============================
|
||||
SETUP HTML
|
||||
================================ */
|
||||
function setupHtml(error = "") {
|
||||
return `
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Praxissoftware Setup</title>
|
||||
<style>
|
||||
body{font-family:Arial;background:#f4f4f4;padding:30px}
|
||||
.card{max-width:520px;margin:auto;background:#fff;padding:22px;border-radius:14px}
|
||||
input{width:100%;padding:10px;margin:6px 0;border:1px solid #ccc;border-radius:8px}
|
||||
button{padding:10px 14px;border:0;border-radius:8px;background:#111;color:#fff;cursor:pointer}
|
||||
.err{color:#b00020;margin:10px 0}
|
||||
.hint{color:#666;font-size:13px;margin-top:12px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2>🔧 Datenbank Einrichtung</h2>
|
||||
${error ? `<div class="err">❌ ${error}</div>` : ""}
|
||||
|
||||
<form method="POST" action="/setup">
|
||||
<label>DB Host</label>
|
||||
<input name="host" placeholder="85.215.63.122" required />
|
||||
|
||||
<label>DB Benutzer</label>
|
||||
<input name="user" placeholder="praxisuser" required />
|
||||
|
||||
<label>DB Passwort</label>
|
||||
<input name="password" type="password" required />
|
||||
|
||||
<label>DB Name</label>
|
||||
<input name="name" placeholder="praxissoftware" required />
|
||||
|
||||
<button type="submit">✅ Speichern</button>
|
||||
</form>
|
||||
|
||||
<div class="hint">
|
||||
Die Daten werden verschlüsselt gespeichert (<b>config.enc</b>).<br/>
|
||||
Danach wirst du automatisch auf die Loginseite weitergeleitet.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
MIDDLEWARE
|
||||
================================ */
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
app.use(helmet());
|
||||
|
||||
// ✅ SessionStore dynamisch (Setup: MemoryStore, normal: MySQLStore)
|
||||
app.use(
|
||||
session({
|
||||
name: "praxis.sid",
|
||||
secret: process.env.SESSION_SECRET,
|
||||
store: sessionStore,
|
||||
store: getSessionStore(),
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const flashMiddleware = require("./middleware/flash.middleware");
|
||||
@ -41,63 +103,91 @@ app.use(flashMiddleware);
|
||||
app.use(express.static("public"));
|
||||
app.use("/uploads", express.static("uploads"));
|
||||
app.set("view engine", "ejs");
|
||||
app.use((req, res, next) => {
|
||||
res.locals.user = req.session.user || null;
|
||||
next();
|
||||
});
|
||||
|
||||
/* ===============================
|
||||
COMPANY SETTINGS
|
||||
SETUP ROUTES
|
||||
================================ */
|
||||
|
||||
// Setup-Seite
|
||||
app.get("/setup", (req, res) => {
|
||||
if (configExists()) return res.redirect("/");
|
||||
return res.status(200).send(setupHtml());
|
||||
});
|
||||
|
||||
// Setup speichern + DB testen + Soft-Restart + Login redirect
|
||||
app.post("/setup", async (req, res) => {
|
||||
try {
|
||||
const { host, user, password, name } = req.body;
|
||||
|
||||
if (!host || !user || !password || !name) {
|
||||
return res.status(400).send(setupHtml("Bitte alle Felder ausfüllen."));
|
||||
}
|
||||
|
||||
// ✅ DB Verbindung testen
|
||||
const conn = await mysql.createConnection({
|
||||
host,
|
||||
user,
|
||||
password,
|
||||
database: name,
|
||||
});
|
||||
|
||||
await conn.query("SELECT 1");
|
||||
await conn.end();
|
||||
|
||||
// ✅ verschlüsselt speichern
|
||||
saveConfig({
|
||||
db: { host, user, password, name },
|
||||
});
|
||||
|
||||
// ✅ Soft-Restart (DB Pool + SessionStore neu laden)
|
||||
if (typeof db.resetPool === "function") {
|
||||
db.resetPool();
|
||||
}
|
||||
resetSessionStore();
|
||||
|
||||
// ✅ automatisch zurück zur Loginseite
|
||||
return res.redirect("/");
|
||||
} catch (err) {
|
||||
return res
|
||||
.status(500)
|
||||
.send(setupHtml("DB Verbindung fehlgeschlagen: " + err.message));
|
||||
}
|
||||
});
|
||||
|
||||
// Wenn keine config.enc → alles außer /setup auf Setup umleiten
|
||||
app.use((req, res, next) => {
|
||||
if (!configExists() && req.path !== "/setup") {
|
||||
return res.redirect("/setup");
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
/* ===============================
|
||||
DEINE LOGIK (unverändert)
|
||||
================================ */
|
||||
|
||||
app.use(companySettingsRoutes);
|
||||
|
||||
/* ===============================
|
||||
AUTH / LOGIN
|
||||
================================ */
|
||||
app.use("/", authRoutes);
|
||||
|
||||
/* ===============================
|
||||
DASHBOARD
|
||||
================================ */
|
||||
app.use("/dashboard", dashboardRoutes);
|
||||
|
||||
/* ===============================
|
||||
ADMIN
|
||||
================================ */
|
||||
app.use("/admin", adminRoutes);
|
||||
|
||||
/* ===============================
|
||||
PATIENTEN
|
||||
================================ */
|
||||
app.use("/patients", patientRoutes);
|
||||
app.use("/patients", patientMedicationRoutes);
|
||||
app.use("/patients", patientServiceRoutes);
|
||||
|
||||
/* ===============================
|
||||
MEDIKAMENTE
|
||||
================================ */
|
||||
app.use("/medications", medicationRoutes);
|
||||
console.log("🧪 /medications Router mounted");
|
||||
|
||||
/* ===============================
|
||||
LEISTUNGEN
|
||||
================================ */
|
||||
app.use("/services", serviceRoutes);
|
||||
|
||||
/* ===============================
|
||||
DATEIEN
|
||||
================================ */
|
||||
app.use("/", patientFileRoutes);
|
||||
|
||||
/* ===============================
|
||||
WARTEZIMMER
|
||||
================================ */
|
||||
app.use("/", waitingRoomRoutes);
|
||||
|
||||
/* ===============================
|
||||
RECHNUNGEN
|
||||
================================ */
|
||||
app.use("/", invoiceRoutes);
|
||||
|
||||
/* ===============================
|
||||
LOGOUT
|
||||
================================ */
|
||||
app.get("/logout", (req, res) => {
|
||||
req.session.destroy(() => res.redirect("/"));
|
||||
});
|
||||
@ -113,7 +203,7 @@ app.use((err, req, res, next) => {
|
||||
/* ===============================
|
||||
SERVER
|
||||
================================ */
|
||||
const PORT = 51777;
|
||||
const PORT = process.env.PORT || 51777;
|
||||
const HOST = "127.0.0.1";
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
|
||||
71
config-manager.js
Normal file
71
config-manager.js
Normal file
@ -0,0 +1,71 @@
|
||||
const fs = require("fs");
|
||||
const crypto = require("crypto");
|
||||
const path = require("path");
|
||||
|
||||
const CONFIG_FILE = path.join(__dirname, "config.enc");
|
||||
|
||||
function getKey() {
|
||||
const key = process.env.CONFIG_KEY;
|
||||
if (!key) throw new Error("CONFIG_KEY fehlt in .env");
|
||||
|
||||
// stabil auf 32 bytes
|
||||
return crypto.createHash("sha256").update(key).digest();
|
||||
}
|
||||
|
||||
function encryptConfig(obj) {
|
||||
const key = getKey();
|
||||
const iv = crypto.randomBytes(12);
|
||||
|
||||
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
||||
const json = JSON.stringify(obj);
|
||||
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(json, "utf8"),
|
||||
cipher.final(),
|
||||
]);
|
||||
const tag = cipher.getAuthTag();
|
||||
|
||||
return Buffer.concat([iv, tag, encrypted]).toString("base64");
|
||||
}
|
||||
|
||||
function decryptConfig(str) {
|
||||
const raw = Buffer.from(str, "base64");
|
||||
|
||||
const iv = raw.subarray(0, 12);
|
||||
const tag = raw.subarray(12, 28);
|
||||
const encrypted = raw.subarray(28);
|
||||
|
||||
const key = getKey();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
||||
decipher.setAuthTag(tag);
|
||||
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(encrypted),
|
||||
decipher.final(),
|
||||
]);
|
||||
return JSON.parse(decrypted.toString("utf8"));
|
||||
}
|
||||
|
||||
function configExists() {
|
||||
return fs.existsSync(CONFIG_FILE);
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
if (!configExists()) return null;
|
||||
const enc = fs.readFileSync(CONFIG_FILE, "utf8").trim();
|
||||
if (!enc) return null;
|
||||
return decryptConfig(enc);
|
||||
}
|
||||
|
||||
function saveConfig(obj) {
|
||||
const enc = encryptConfig(obj);
|
||||
fs.writeFileSync(CONFIG_FILE, enc, "utf8");
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
configExists,
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
};
|
||||
1
config.enc
Normal file
1
config.enc
Normal file
@ -0,0 +1 @@
|
||||
G/kDLEJ/LddnnNnginIGYSM4Ax0g5pJaF0lrdOXke51cz3jSTrZxP7rjTXRlqLcoUJhPaVLvjb/DcyNYB/C339a+PFWyIdWYjSb6G4aPkD8J21yFWDDLpc08bXvoAx2PeE+Fc9v5mJUGDVv2wQoDvkHqIpN8ewrfRZ6+JF3OfQ==
|
||||
@ -1,6 +1,31 @@
|
||||
const MySQLStore = require("express-mysql-session")(require("express-session"));
|
||||
const session = require("express-session");
|
||||
const { configExists } = require("../config-manager");
|
||||
|
||||
let store = null;
|
||||
|
||||
function getSessionStore() {
|
||||
if (store) return store;
|
||||
|
||||
// ✅ Setup-Modus (keine DB)
|
||||
if (!configExists()) {
|
||||
console.log("⚠️ Setup-Modus aktiv → SessionStore = MemoryStore");
|
||||
store = new session.MemoryStore();
|
||||
return store;
|
||||
}
|
||||
|
||||
// ✅ Normalbetrieb (mit DB)
|
||||
const MySQLStore = require("express-mysql-session")(session);
|
||||
const db = require("../db");
|
||||
|
||||
const sessionStore = new MySQLStore({}, db);
|
||||
store = new MySQLStore({}, db);
|
||||
return store;
|
||||
}
|
||||
|
||||
module.exports = sessionStore;
|
||||
function resetSessionStore() {
|
||||
store = null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSessionStore,
|
||||
resetSessionStore,
|
||||
};
|
||||
|
||||
70
db.js
70
db.js
@ -1,24 +1,62 @@
|
||||
const mysql = require("mysql2");
|
||||
const { loadConfig } = require("./config-manager");
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: "85.215.63.122",
|
||||
user: "praxisuser",
|
||||
password: "praxisuser",
|
||||
database: "praxissoftware",
|
||||
let pool = null;
|
||||
|
||||
function initPool() {
|
||||
const config = loadConfig();
|
||||
|
||||
// ✅ Setup-Modus: noch keine config.enc → kein Pool
|
||||
if (!config || !config.db) return null;
|
||||
|
||||
return mysql.createPool({
|
||||
host: config.db.host,
|
||||
user: config.db.user,
|
||||
password: config.db.password,
|
||||
database: config.db.name,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0
|
||||
queueLimit: 0,
|
||||
});
|
||||
|
||||
// Optionaler Test beim Start
|
||||
pool.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
console.error("❌ MySQL Pool Fehler:", err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("✅ MySQL Pool verbunden");
|
||||
connection.release();
|
||||
});
|
||||
|
||||
module.exports = pool;
|
||||
function getPool() {
|
||||
if (!pool) pool = initPool();
|
||||
return pool;
|
||||
}
|
||||
|
||||
function resetPool() {
|
||||
pool = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ Proxy damit alter Code weitergeht:
|
||||
* const db = require("../db");
|
||||
* await db.query(...)
|
||||
*/
|
||||
const dbProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target, prop) {
|
||||
const p = getPool();
|
||||
|
||||
if (!p) {
|
||||
throw new Error(
|
||||
"❌ DB ist noch nicht konfiguriert (config.enc fehlt). Bitte zuerst Setup ausführen: http://127.0.0.1:51777/setup",
|
||||
);
|
||||
}
|
||||
|
||||
const value = p[prop];
|
||||
|
||||
if (typeof value === "function") {
|
||||
return value.bind(p);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = dbProxy;
|
||||
module.exports.getPool = getPool;
|
||||
module.exports.resetPool = resetPool;
|
||||
|
||||
@ -3,30 +3,52 @@ function requireLogin(req, res, next) {
|
||||
return res.redirect("/");
|
||||
}
|
||||
|
||||
// optional, aber sauber
|
||||
req.user = req.session.user;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
// ✅ NEU: Arzt-only (das war früher dein requireAdmin)
|
||||
function requireArzt(req, res, next) {
|
||||
console.log("ARZT CHECK:", req.session.user);
|
||||
|
||||
if (!req.session.user) {
|
||||
return res.redirect("/");
|
||||
}
|
||||
|
||||
if (req.session.user.role !== "arzt") {
|
||||
return res
|
||||
.status(403)
|
||||
.send(
|
||||
"⛔ Kein Zugriff (Arzt erforderlich). Rolle: " + req.session.user.role,
|
||||
);
|
||||
}
|
||||
|
||||
req.user = req.session.user;
|
||||
next();
|
||||
}
|
||||
|
||||
// ✅ NEU: Admin-only
|
||||
function requireAdmin(req, res, next) {
|
||||
console.log("ADMIN CHECK:", req.session.user);
|
||||
|
||||
if (!req.session.user) {
|
||||
return res.send("NICHT EINGELOGGT");
|
||||
return res.redirect("/");
|
||||
}
|
||||
|
||||
if (req.session.user.role !== "arzt") {
|
||||
return res.send("KEIN ARZT: " + req.session.user.role);
|
||||
if (req.session.user.role !== "admin") {
|
||||
return res
|
||||
.status(403)
|
||||
.send(
|
||||
"⛔ Kein Zugriff (Admin erforderlich). Rolle: " + req.session.user.role,
|
||||
);
|
||||
}
|
||||
|
||||
// 🔑 DAS HAT GEFEHLT
|
||||
req.user = req.session.user;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requireLogin,
|
||||
requireAdmin
|
||||
requireArzt,
|
||||
requireAdmin,
|
||||
};
|
||||
|
||||
@ -13,8 +13,11 @@ const {
|
||||
updateUser,
|
||||
} = require("../controllers/admin.controller");
|
||||
|
||||
const { requireAdmin } = require("../middleware/auth.middleware");
|
||||
const { requireArzt, requireAdmin } = require("../middleware/auth.middleware");
|
||||
|
||||
/* ==========================
|
||||
✅ VERWALTUNG (NUR ADMIN)
|
||||
========================== */
|
||||
router.get("/users", requireAdmin, listUsers);
|
||||
router.get("/create-user", requireAdmin, showCreateUser);
|
||||
router.post("/create-user", requireAdmin, postCreateUser);
|
||||
@ -23,7 +26,18 @@ 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.get("/invoices", requireAdmin, showInvoiceOverview);
|
||||
router.post("/users/update/:id", requireAdmin, updateUser);
|
||||
|
||||
/* ✅ Admin-Tools: Datenbankverwaltung */
|
||||
router.get("/database", requireAdmin, (req, res) => {
|
||||
res.render("admin/database", {
|
||||
user: req.session.user,
|
||||
});
|
||||
});
|
||||
|
||||
/* ==========================
|
||||
✅ ABRECHNUNG (NUR ARZT)
|
||||
========================== */
|
||||
router.get("/invoices", requireArzt, showInvoiceOverview);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -1,23 +1,19 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const { requireAdmin } = require("../middleware/auth.middleware");
|
||||
const { requireArzt } = require("../middleware/auth.middleware");
|
||||
const uploadLogo = require("../middleware/uploadLogo");
|
||||
const {
|
||||
getCompanySettings,
|
||||
saveCompanySettings
|
||||
saveCompanySettings,
|
||||
} = require("../controllers/companySettings.controller");
|
||||
|
||||
router.get(
|
||||
"/admin/company-settings",
|
||||
requireAdmin,
|
||||
getCompanySettings
|
||||
);
|
||||
router.get("/admin/company-settings", requireArzt, getCompanySettings);
|
||||
|
||||
router.post(
|
||||
"/admin/company-settings",
|
||||
requireAdmin,
|
||||
requireArzt,
|
||||
uploadLogo.single("logo"), // 🔑 MUSS VOR DEM CONTROLLER KOMMEN
|
||||
saveCompanySettings
|
||||
saveCompanySettings,
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -1,14 +1,8 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const {requireAdmin } = require("../middleware/auth.middleware");
|
||||
const { requireArzt } = require("../middleware/auth.middleware");
|
||||
const { createInvoicePdf } = require("../controllers/invoicePdf.controller");
|
||||
|
||||
|
||||
router.post(
|
||||
"/patients/:id/create-invoice",
|
||||
requireAdmin,
|
||||
createInvoicePdf
|
||||
);
|
||||
router.post("/patients/:id/create-invoice", requireArzt, createInvoicePdf);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
const { requireLogin, requireAdmin } = require("../middleware/auth.middleware");
|
||||
const { requireLogin, requireArzt } = require("../middleware/auth.middleware");
|
||||
|
||||
const {
|
||||
listPatients,
|
||||
@ -31,7 +31,7 @@ router.get("/:id/medications", requireLogin, showPatientMedications);
|
||||
router.post("/waiting-room/:id", requireLogin, moveToWaitingRoom);
|
||||
router.get("/:id/overview", requireLogin, showPatientOverview);
|
||||
router.post("/:id/notes", requireLogin, addPatientNote);
|
||||
router.post("/waiting-room/call/:id", requireAdmin, callFromWaitingRoom);
|
||||
router.post("/waiting-room/call/:id", requireArzt, callFromWaitingRoom);
|
||||
router.post("/:id/discharge", requireLogin, dischargePatient);
|
||||
router.get("/:id/plan", requireLogin, showMedicationPlan);
|
||||
router.post("/deactivate/:id", requireLogin, deactivatePatient);
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
const { requireAdmin } = require("../middleware/auth.middleware");
|
||||
const { requireArzt } = require("../middleware/auth.middleware");
|
||||
const {
|
||||
addMedication,
|
||||
endMedication,
|
||||
deleteMedication
|
||||
deleteMedication,
|
||||
} = require("../controllers/patientMedication.controller");
|
||||
|
||||
router.post("/:id/medications", requireAdmin, addMedication);
|
||||
router.post("/patient-medications/end/:id", requireAdmin, endMedication);
|
||||
router.post("/patient-medications/delete/:id", requireAdmin, deleteMedication);
|
||||
router.post("/:id/medications", requireArzt, addMedication);
|
||||
router.post("/patient-medications/end/:id", requireArzt, endMedication);
|
||||
router.post("/patient-medications/delete/:id", requireArzt, deleteMedication);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
const { requireLogin, requireAdmin } = require("../middleware/auth.middleware");
|
||||
const { requireLogin, requireArzt } = require("../middleware/auth.middleware");
|
||||
const {
|
||||
addPatientService,
|
||||
deletePatientService,
|
||||
@ -10,11 +10,11 @@ const {
|
||||
} = require("../controllers/patientService.controller");
|
||||
|
||||
router.post("/:id/services", requireLogin, addPatientService);
|
||||
router.post("/services/delete/:id", requireAdmin, deletePatientService);
|
||||
router.post("/services/delete/:id", requireArzt, deletePatientService);
|
||||
router.post(
|
||||
"/services/update-price/:id",
|
||||
requireAdmin,
|
||||
updatePatientServicePrice
|
||||
requireArzt,
|
||||
updatePatientServicePrice,
|
||||
);
|
||||
router.post("/services/update-quantity/:id", updatePatientServiceQuantity);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
const { requireLogin, requireAdmin } = require("../middleware/auth.middleware");
|
||||
const { requireLogin, requireArzt } = require("../middleware/auth.middleware");
|
||||
const {
|
||||
listServices,
|
||||
showCreateService,
|
||||
@ -10,17 +10,16 @@ const {
|
||||
toggleService,
|
||||
listOpenServices,
|
||||
showServiceLogs,
|
||||
listServicesAdmin
|
||||
listServicesAdmin,
|
||||
} = require("../controllers/service.controller");
|
||||
|
||||
router.get("/", requireLogin, listServicesAdmin);
|
||||
router.get("/", requireAdmin, listServices);
|
||||
router.get("/create", requireAdmin, showCreateService);
|
||||
router.post("/create", requireAdmin, createService);
|
||||
router.post("/:id/update-price", requireAdmin, updateServicePrice);
|
||||
router.post("/:id/toggle", requireAdmin, toggleService);
|
||||
router.get("/", requireArzt, listServices);
|
||||
router.get("/create", requireArzt, showCreateService);
|
||||
router.post("/create", requireArzt, createService);
|
||||
router.post("/:id/update-price", requireArzt, updateServicePrice);
|
||||
router.post("/:id/toggle", requireArzt, toggleService);
|
||||
router.get("/open", requireLogin, listOpenServices);
|
||||
router.get("/logs", requireAdmin, showServiceLogs);
|
||||
|
||||
router.get("/logs", requireArzt, showServiceLogs);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
172
views/admin/database.ejs
Normal file
172
views/admin/database.ejs
Normal file
@ -0,0 +1,172 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Datenbankverwaltung</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css">
|
||||
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background: #f4f6f9;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
background: #111827;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
color: #cbd5e1;
|
||||
text-decoration: none;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: #1f2937;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar .spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nav-item.locked {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.nav-item.locked:hover {
|
||||
background: transparent;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="layout">
|
||||
|
||||
<!-- ✅ ADMIN SIDEBAR -->
|
||||
<%- include("../partials/admin-sidebar", { user, active: "database" }) %>
|
||||
|
||||
<!-- ✅ MAIN CONTENT -->
|
||||
<div class="main">
|
||||
|
||||
<nav class="navbar navbar-dark bg-dark position-relative px-3 rounded mb-4">
|
||||
<div class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white">
|
||||
<i class="bi bi-hdd-stack fs-4"></i>
|
||||
<span class="fw-semibold fs-5">Datenbankverwaltung</span>
|
||||
</div>
|
||||
|
||||
<div class="ms-auto">
|
||||
<a href="/dashboard" class="btn btn-outline-light btn-sm">⬅️ Dashboard</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<%- include("../partials/flash") %>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
|
||||
<h4 class="mb-3">Datenbank Tools</h4>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<b>Hinweis:</b> Diese Funktionen sind nur für <b>Admins</b> sichtbar und sollten mit Vorsicht benutzt werden.
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">📦 Backup</h5>
|
||||
<p class="text-muted small mb-3">
|
||||
Hier kannst du später ein Datenbank-Backup erstellen.
|
||||
</p>
|
||||
<button class="btn btn-outline-primary" disabled>
|
||||
Backup erstellen (kommt später)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">♻️ Restore</h5>
|
||||
<p class="text-muted small mb-3">
|
||||
Hier kannst du später ein Backup wiederherstellen.
|
||||
</p>
|
||||
<button class="btn btn-outline-danger" disabled>
|
||||
Restore starten (kommt später)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">🔍 Systeminfo</h5>
|
||||
<p class="text-muted small mb-0">
|
||||
In diesem Bereich kannst du später z.B. DB Version, Tabellenstatus, Speicherplatz etc. anzeigen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -14,6 +14,79 @@
|
||||
<script src="/js/services-lock.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background: #f4f6f9;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Sidebar (gleiches Design wie dein Dashboard) */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
background: #111827;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
color: #cbd5e1;
|
||||
text-decoration: none;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: #1f2937;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar .spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nav-item.locked {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.nav-item.locked:hover {
|
||||
background: transparent;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Original Styles */
|
||||
input.form-control { box-shadow: none !important; }
|
||||
|
||||
input.form-control:disabled {
|
||||
@ -37,9 +110,17 @@
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-light">
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-dark bg-dark position-relative px-3">
|
||||
<div class="layout">
|
||||
|
||||
<!-- ✅ ADMIN SIDEBAR -->
|
||||
<%- include("partials/admin-sidebar", { active: "users" }) %>
|
||||
|
||||
<!-- ✅ MAIN -->
|
||||
<div class="main">
|
||||
|
||||
<nav class="navbar navbar-dark bg-dark position-relative px-3 rounded mb-4">
|
||||
<div class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white">
|
||||
<i class="bi bi-shield-lock fs-4"></i>
|
||||
<span class="fw-semibold fs-5">User Verwaltung</span>
|
||||
@ -50,7 +131,7 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="container-fluid">
|
||||
<%- include("partials/flash") %>
|
||||
|
||||
<div class="card shadow">
|
||||
@ -102,7 +183,7 @@
|
||||
|
||||
<tr class="<%= u.active ? '' : 'table-secondary' %>">
|
||||
|
||||
<!-- ✅ UPDATE-FORM (wie medications) -->
|
||||
<!-- ✅ UPDATE-FORM -->
|
||||
<form method="POST" action="/admin/users/update/<%= u.id %>">
|
||||
|
||||
<td><%= u.id %></td>
|
||||
@ -159,6 +240,9 @@
|
||||
<option value="arzt" <%= u.role === "arzt" ? "selected" : "" %>>
|
||||
Arzt
|
||||
</option>
|
||||
<option value="admin" <%= u.role === "admin" ? "selected" : "" %>>
|
||||
Admin
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
@ -210,5 +294,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -102,7 +102,7 @@
|
||||
.waiting-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-rows: repeat(3, 80px);
|
||||
grid-auto-rows: 80px;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
@ -117,7 +117,9 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
overflow: hidden; /* 🔥 DAS fehlt */
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.waiting-slot.occupied {
|
||||
@ -125,22 +127,11 @@
|
||||
background: #eefdf5;
|
||||
}
|
||||
|
||||
.waiting-grid {
|
||||
grid-auto-flow: row dense;
|
||||
}
|
||||
|
||||
.chair-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: contain;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.patient-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px; /* 🔥 Abstand zwischen Name und Datum */
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.waiting-slot.clickable {
|
||||
@ -153,43 +144,21 @@
|
||||
box-shadow: 0 0 0 2px #2563eb;
|
||||
}
|
||||
|
||||
.nav-item.locked {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.nav-item.locked:hover {
|
||||
background: transparent;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layout">
|
||||
<!-- SIDEBAR -->
|
||||
<div class="sidebar">
|
||||
<div class="logo">🩺 Praxis System</div>
|
||||
|
||||
<a href="/patients" class="nav-item active">
|
||||
<i class="bi bi-people"></i> Patienten
|
||||
</a>
|
||||
|
||||
<a href="/medications" class="nav-item">
|
||||
<i class="bi bi-capsule"></i> Medikamente
|
||||
</a>
|
||||
|
||||
<a href="/services/open" class="nav-item">
|
||||
<i class="bi bi-receipt"></i> Offene Leistungen
|
||||
</a>
|
||||
|
||||
<% if (user.role === 'arzt') { %>
|
||||
<a href="/admin/invoices" class="nav-item">
|
||||
<i class="bi bi-cash-coin"></i> Abrechnung
|
||||
</a>
|
||||
|
||||
<a href="/admin/users" class="nav-item">
|
||||
<i class="bi bi-gear"></i> Verwaltung
|
||||
</a>
|
||||
<% } %>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<a href="/logout" class="nav-item">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
<!-- ✅ SIDEBAR ausgelagert -->
|
||||
<%- include("partials/sidebar", { user, active: "patients" }) %>
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<div class="main">
|
||||
@ -201,18 +170,16 @@
|
||||
<%- include("partials/flash") %>
|
||||
|
||||
<!-- =========================
|
||||
WARTEZIMMER MONITOR (dein Original)
|
||||
========================== -->
|
||||
WARTEZIMMER MONITOR
|
||||
========================= -->
|
||||
<div class="waiting-monitor">
|
||||
<h5 class="mb-3">🪑 Wartezimmer-Monitor</h5>
|
||||
|
||||
<div class="waiting-grid">
|
||||
<% const maxSlots = 21;
|
||||
for (let i = 0; i < maxSlots; i++) {
|
||||
const p = waitingPatients && waitingPatients[i];
|
||||
%>
|
||||
<% if (waitingPatients && waitingPatients.length > 0) { %>
|
||||
|
||||
<% waitingPatients.forEach(p => { %>
|
||||
|
||||
<% if (p) { %>
|
||||
<% if (user.role === 'arzt') { %>
|
||||
<a href="/patients/<%= p.id %>/overview" class="waiting-slot occupied clickable">
|
||||
<div class="patient-text">
|
||||
@ -232,13 +199,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
<% }) %>
|
||||
|
||||
<% } else { %>
|
||||
<div class="text-muted">Keine Patienten im Wartezimmer.</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -27,10 +27,6 @@
|
||||
|
||||
<button class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="/register">Registrieren</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
50
views/partials/admin-sidebar.ejs
Normal file
50
views/partials/admin-sidebar.ejs
Normal file
@ -0,0 +1,50 @@
|
||||
<div class="sidebar">
|
||||
<div class="logo">🔐 Admin Bereich</div>
|
||||
|
||||
<%
|
||||
const role = user?.role || null;
|
||||
const isAdmin = role === "admin";
|
||||
|
||||
function hrefIfAllowed(allowed, href) {
|
||||
return allowed ? href : "#";
|
||||
}
|
||||
|
||||
function lockClass(allowed) {
|
||||
return allowed ? "" : "locked";
|
||||
}
|
||||
|
||||
function lockClick(allowed) {
|
||||
return allowed ? "" : 'onclick="return false;"';
|
||||
}
|
||||
%>
|
||||
|
||||
<a
|
||||
href="<%= hrefIfAllowed(isAdmin, '/admin/users') %>"
|
||||
class="nav-item <%= active === 'users' ? 'active' : '' %> <%= lockClass(isAdmin) %>"
|
||||
<%- lockClick(isAdmin) %>
|
||||
title="<%= isAdmin ? '' : 'Nur Admin' %>"
|
||||
>
|
||||
<i class="bi bi-people"></i> Userverwaltung
|
||||
<% if (!isAdmin) { %>
|
||||
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="<%= hrefIfAllowed(isAdmin, '/admin/database') %>"
|
||||
class="nav-item <%= active === 'database' ? 'active' : '' %> <%= lockClass(isAdmin) %>"
|
||||
<%- lockClick(isAdmin) %>
|
||||
title="<%= isAdmin ? '' : 'Nur Admin' %>"
|
||||
>
|
||||
<i class="bi bi-hdd-stack"></i> Datenbankverwaltung
|
||||
<% if (!isAdmin) { %>
|
||||
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<a href="/dashboard" class="nav-item">
|
||||
<i class="bi bi-arrow-left"></i> Dashboard
|
||||
</a>
|
||||
</div>
|
||||
97
views/partials/sidebar.ejs
Normal file
97
views/partials/sidebar.ejs
Normal file
@ -0,0 +1,97 @@
|
||||
<div class="sidebar">
|
||||
<div class="logo">🩺 Praxis System</div>
|
||||
|
||||
<%
|
||||
const role = user?.role || null;
|
||||
|
||||
// ✅ Regeln:
|
||||
// Arztbereich: NUR arzt
|
||||
const canDoctorArea = role === "arzt";
|
||||
|
||||
// Verwaltung: NUR admin
|
||||
const canAdminArea = role === "admin";
|
||||
|
||||
function hrefIfAllowed(allowed, href) {
|
||||
return allowed ? href : "#";
|
||||
}
|
||||
|
||||
function lockClass(allowed) {
|
||||
return allowed ? "" : "locked";
|
||||
}
|
||||
|
||||
function lockClick(allowed) {
|
||||
return allowed ? "" : 'onclick="return false;"';
|
||||
}
|
||||
%>
|
||||
|
||||
<!-- Patienten -->
|
||||
<a
|
||||
href="<%= hrefIfAllowed(canDoctorArea, '/patients') %>"
|
||||
class="nav-item <%= active === 'patients' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
|
||||
<%- lockClick(canDoctorArea) %>
|
||||
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
|
||||
>
|
||||
<i class="bi bi-people"></i> Patienten
|
||||
<% if (!canDoctorArea) { %>
|
||||
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
<!-- Medikamente -->
|
||||
<a
|
||||
href="<%= hrefIfAllowed(canDoctorArea, '/medications') %>"
|
||||
class="nav-item <%= active === 'medications' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
|
||||
<%- lockClick(canDoctorArea) %>
|
||||
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
|
||||
>
|
||||
<i class="bi bi-capsule"></i> Medikamente
|
||||
<% if (!canDoctorArea) { %>
|
||||
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
<!-- Offene Leistungen -->
|
||||
<a
|
||||
href="<%= hrefIfAllowed(canDoctorArea, '/services/open') %>"
|
||||
class="nav-item <%= active === 'services' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
|
||||
<%- lockClick(canDoctorArea) %>
|
||||
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
|
||||
>
|
||||
<i class="bi bi-receipt"></i> Offene Leistungen
|
||||
<% if (!canDoctorArea) { %>
|
||||
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
<!-- Abrechnung -->
|
||||
<a
|
||||
href="<%= hrefIfAllowed(canDoctorArea, '/admin/invoices') %>"
|
||||
class="nav-item <%= active === 'billing' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
|
||||
<%- lockClick(canDoctorArea) %>
|
||||
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
|
||||
>
|
||||
<i class="bi bi-cash-coin"></i> Abrechnung
|
||||
<% if (!canDoctorArea) { %>
|
||||
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
<!-- Verwaltung (nur Admin) -->
|
||||
<a
|
||||
href="<%= hrefIfAllowed(canAdminArea, '/admin/users') %>"
|
||||
class="nav-item <%= active === 'admin' ? 'active' : '' %> <%= lockClass(canAdminArea) %>"
|
||||
<%- lockClick(canAdminArea) %>
|
||||
title="<%= canAdminArea ? '' : 'Nur Admin' %>"
|
||||
>
|
||||
<i class="bi bi-gear"></i> Verwaltung
|
||||
<% if (!canAdminArea) { %>
|
||||
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
|
||||
<% } %>
|
||||
</a>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<a href="/logout" class="nav-item">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
Loading…
Reference in New Issue
Block a user