initial commit
This commit is contained in:
commit
74d115f9de
|
@ -0,0 +1,13 @@
|
|||
DATABASE_URL=postgres://user:psw@localhost:5432/postgres
|
||||
SESSIONSECRET=Your Secret
|
||||
JWT_SECRET=
|
||||
MAILHOST=
|
||||
MAILUSER=
|
||||
MAILFROM=
|
||||
MAILPASS=
|
||||
PORT=2000
|
||||
TELEBOT=
|
||||
TELECHAT=
|
||||
HOST=
|
||||
WEEKDAY=4
|
||||
ABTEILUNG=KiTu/ABENTEUERSPIELPLATZ/KKT
|
|
@ -0,0 +1,5 @@
|
|||
.env
|
||||
node_modules/
|
||||
logs/*
|
||||
.DS_Store
|
||||
.vscode/
|
|
@ -0,0 +1,79 @@
|
|||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const bcrypt = require("bcrypt");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const crypto = require("crypto");
|
||||
const path = require("path");
|
||||
const moment = require("moment");
|
||||
require("dotenv").config();
|
||||
const log = require("node-file-logger");
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT;
|
||||
|
||||
const options = {
|
||||
timeZone: "europe/Berlin",
|
||||
folderPath: "./logs/",
|
||||
dateBasedFileNaming: true,
|
||||
fileNamePrefix: "DailyLogs_",
|
||||
fileNameExtension: ".log",
|
||||
dateFormat: "YYYY_MM_DD",
|
||||
timeFormat: "H:mm:ss",
|
||||
};
|
||||
log.SetUserOptions(options);
|
||||
|
||||
// Middleware
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.use(express.json());
|
||||
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
|
||||
// Session-Konfiguration
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.SESSIONSECRET,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: { maxAge: 1000 * 60 * 60 * 24 * 2 },
|
||||
})
|
||||
);
|
||||
|
||||
const userRoutes = require("./routes/user");
|
||||
const spieleRoutes = require("./routes/spiele");
|
||||
const featureRoutes = require("./routes/feature");
|
||||
const memberRoutes = require("./routes/member");
|
||||
const trainingRoutes = require("./routes/training");
|
||||
const anwesendRoutes = require("./routes/anwesend");
|
||||
|
||||
app.use("/user", userRoutes);
|
||||
app.use("/spiele", spieleRoutes);
|
||||
app.use("/feature", featureRoutes);
|
||||
app.use(
|
||||
"/:abteilung?/member",
|
||||
(req, res, next) => {
|
||||
req.abteilung = req.params.abteilung; // `abteilung` in req speichern
|
||||
next();
|
||||
},
|
||||
memberRoutes
|
||||
);
|
||||
app.use(
|
||||
"/:abteilung/training",
|
||||
(req, res, next) => {
|
||||
req.abteilung = req.params.abteilung;
|
||||
next();
|
||||
},
|
||||
trainingRoutes
|
||||
);
|
||||
app.use(
|
||||
"/:abteilung/anwesend",
|
||||
(req, res, next) => {
|
||||
req.abteilung = req.params.abteilung;
|
||||
next();
|
||||
},
|
||||
anwesendRoutes
|
||||
);
|
||||
|
||||
const server = app.listen(port, "0.0.0.0", () => {
|
||||
console.log(`Server is running on ${process.env.HOST}:${port}/`);
|
||||
log.Info(`Server is running on ${process.env.HOST}:${port}/`);
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
const { Pool } = require("pg");
|
||||
const bcrypt = require("bcryptjs");
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL + process.env.DBNAME,
|
||||
});
|
||||
|
||||
// Fehlerbehandlung bei der Datenbankverbindung
|
||||
pool.on("error", (err) => {
|
||||
console.error("Fehler in der PostgreSQL-Datenbankverbindung:", err);
|
||||
});
|
||||
|
||||
// Exportiere den Pool für die Nutzung in anderen Dateien
|
||||
module.exports = pool;
|
|
@ -0,0 +1,40 @@
|
|||
const jwt = require("jsonwebtoken");
|
||||
const bcrypt = require("bcryptjs");
|
||||
const pool = require("../db"); // Stelle sicher, dass der DB-Pool importiert wird
|
||||
|
||||
// Authentifizierungs-Middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
const token = req.headers.authorization?.split(" ")[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: "Kein Token vorhanden" });
|
||||
}
|
||||
|
||||
try {
|
||||
// JWT entschlüsseln
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = decoded; // Benutzerinformationen im Request speichern
|
||||
next();
|
||||
} catch (err) {
|
||||
return res.status(401).json({ error: "Ungültiges Token" });
|
||||
}
|
||||
};
|
||||
|
||||
const requireAdmin = async (req, res, next) => {
|
||||
try {
|
||||
const result = await pool.query("SELECT role FROM users WHERE id = $1", [
|
||||
req.user.id,
|
||||
]);
|
||||
|
||||
if (result.rows.length === 0 || result.rows[0].role !== "admin") {
|
||||
return res.status(403).json({ error: "Nicht autorisiert" });
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { requireAuth, requireAdmin };
|
|
@ -0,0 +1,32 @@
|
|||
const nodemailer = require("nodemailer");
|
||||
|
||||
// Email-Konfiguration
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.MAILHOST,
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.MAILUSER,
|
||||
pass: process.env.MAILPASS,
|
||||
},
|
||||
});
|
||||
|
||||
const sendActivationEmail = async (email, username) => {
|
||||
if (!email) return; // Falls keine E-Mail hinterlegt ist, wird nichts gesendet
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.MAILUSER, // Absender-E-Mail
|
||||
to: email,
|
||||
subject: "Dein Konto wurde aktiviert!",
|
||||
text: `Hallo ${username},\n\nDu wurdest soeben freigeschaltet und kannst dich nun unter https://${process.env.HOST} anmelden.\n\nViele Grüße,\nDein Team`,
|
||||
};
|
||||
|
||||
try {
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log(`Aktivierungs-E-Mail an ${email} gesendet.`);
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Senden der E-Mail:", err);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { transporter, sendActivationEmail };
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "training",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"moment": "^2.30.1",
|
||||
"node-file-logger": "^0.9.5",
|
||||
"nodemailer": "^6.9.13",
|
||||
"pg": "^8.11.5",
|
||||
"telebot": "^1.4.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
const express = require("express");
|
||||
const pool = require("../db"); // PostgreSQL-Datenbankverbindung
|
||||
const { requireAuth, requireAdmin } = require("../middleware/auth"); // Auth-Middleware
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// **1. Alle Anwesenheiten eines Trainings abrufen**
|
||||
router.get("/:id", requireAuth, async (req, res) => {
|
||||
const { id } = req.params; // Training-ID
|
||||
const abteilung = req.abteilung; // Abteilung aus Middleware
|
||||
|
||||
if (!abteilung) {
|
||||
return res.status(400).json({ error: "Abteilung ist erforderlich" });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT a.fid_mitglied, m.vorname, m.nachname
|
||||
FROM anwesend a
|
||||
JOIN mitglieder m ON a.fid_mitglied = m.id
|
||||
WHERE a.fid_training = $1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.json([]); // Falls keine Anwesenheiten vorhanden sind
|
||||
}
|
||||
|
||||
return res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **2. Anwesenheit für ein Training setzen**
|
||||
router.post("/:id", requireAuth, async (req, res) => {
|
||||
const { id } = req.params; // Training-ID
|
||||
const { inriege, anw } = req.body; // Arrays mit IDs
|
||||
|
||||
if (!Array.isArray(inriege) || !Array.isArray(anw)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Daten müssen als Arrays übergeben werden" });
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query("BEGIN"); // Transaktion starten
|
||||
|
||||
// **1. Alle aktuellen Einträge für das Training abrufen**
|
||||
const existingEntries = await pool.query(
|
||||
`SELECT fid_mitglied FROM anwesend WHERE fid_training = $1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
const existingSet = new Set(
|
||||
existingEntries.rows.map((row) => row.fid_mitglied)
|
||||
);
|
||||
const anwSet = new Set(anw);
|
||||
|
||||
// **2. Anwesenheit setzen**
|
||||
for (const memberId of inriege) {
|
||||
if (anwSet.has(memberId) && !existingSet.has(memberId)) {
|
||||
// **Fall 1: ID ist in `anw`, aber nicht in der Tabelle → Neu hinzufügen**
|
||||
await pool.query(
|
||||
`INSERT INTO anwesend (fid_mitglied, fid_training) VALUES ($1, $2)`,
|
||||
[memberId, id]
|
||||
);
|
||||
} else if (!anwSet.has(memberId) && existingSet.has(memberId)) {
|
||||
// **Fall 2: ID ist nicht in `anw`, aber in der Tabelle → Löschen**
|
||||
await pool.query(
|
||||
`DELETE FROM anwesend WHERE fid_mitglied = $1 AND fid_training = $2`,
|
||||
[memberId, id]
|
||||
);
|
||||
}
|
||||
// **Fall 3 & 4: Keine Änderung nötig (Eintrag bleibt gleich oder existiert nicht)**
|
||||
}
|
||||
|
||||
await pool.query("COMMIT"); // Transaktion abschließen
|
||||
|
||||
res.json({ message: "Anwesenheit erfolgreich aktualisiert" });
|
||||
} catch (err) {
|
||||
await pool.query("ROLLBACK"); // Falls Fehler, Transaktion rückgängig machen
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,151 @@
|
|||
const express = require("express");
|
||||
const pool = require("../db"); // PostgreSQL-Datenbankverbindung
|
||||
const { requireAuth } = require("../middleware/auth"); // Auth-Middleware
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// **1. Alle Features abrufen (Falls ID gegeben: Nur dieses Feature)**
|
||||
router.get("/:id?", requireAuth, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
if (id) {
|
||||
result = await pool.query("SELECT * FROM features WHERE id = $1", [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Feature nicht gefunden" });
|
||||
}
|
||||
|
||||
return res.json(result.rows[0]);
|
||||
} else {
|
||||
result = await pool.query(
|
||||
"SELECT id, title FROM features ORDER BY datetime DESC"
|
||||
);
|
||||
return res.json(result.rows);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **2. Feature anlegen oder aktualisieren**
|
||||
router.put("/:id?", requireAuth, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { title, body, type, urgency, fid_user, datetime, done } = req.body;
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
if (id) {
|
||||
// **Feature aktualisieren**
|
||||
const featureResult = await pool.query(
|
||||
"SELECT fid_user FROM features WHERE id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (featureResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Feature nicht gefunden" });
|
||||
}
|
||||
|
||||
const featureOwner = featureResult.rows[0].fid_user;
|
||||
|
||||
if (req.user.id !== featureOwner && req.user.id !== 1) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Keine Berechtigung, dieses Feature zu bearbeiten" });
|
||||
}
|
||||
|
||||
if (done !== undefined && req.user.id !== 1) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: 'Nur User mit ID 1 darf das Feld "done" ändern' });
|
||||
}
|
||||
|
||||
if (done !== undefined) {
|
||||
result = await pool.query(
|
||||
"UPDATE features SET done = $1 WHERE id = $2 RETURNING *",
|
||||
[done, id]
|
||||
);
|
||||
} else {
|
||||
if (!title || !body || !type || !urgency ) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Alle Felder sind erforderlich" });
|
||||
}
|
||||
|
||||
result = await pool.query(
|
||||
`UPDATE features
|
||||
SET title = $1, body = $2, type = $3, urgency = $4
|
||||
WHERE id = $5
|
||||
RETURNING *`,
|
||||
[title, body, type, urgency, id]
|
||||
);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: "Feature erfolgreich aktualisiert",
|
||||
feature: result.rows[0],
|
||||
});
|
||||
} else {
|
||||
// **Neues Feature anlegen**
|
||||
if (!title || !body || !type || !urgency || !fid_user || !datetime) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'Alle Felder außer "done" sind erforderlich' });
|
||||
}
|
||||
|
||||
result = await pool.query(
|
||||
`INSERT INTO features (title, body, type, urgency, fid_user, datetime, done)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, false)
|
||||
RETURNING *`,
|
||||
[title, body, type, urgency, fid_user, datetime]
|
||||
);
|
||||
|
||||
return res
|
||||
.status(201)
|
||||
.json({
|
||||
message: "Feature erfolgreich angelegt",
|
||||
feature: result.rows[0],
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **3. Feature löschen (Nur durch anlegenden Benutzer oder User ID 1)**
|
||||
router.delete("/:id", requireAuth, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const featureResult = await pool.query(
|
||||
"SELECT fid_user FROM features WHERE id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (featureResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Feature nicht gefunden" });
|
||||
}
|
||||
|
||||
const featureOwner = featureResult.rows[0].fid_user;
|
||||
|
||||
if (req.user.id !== featureOwner && req.user.id !== 1) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Keine Berechtigung, dieses Feature zu löschen" });
|
||||
}
|
||||
|
||||
await pool.query("DELETE FROM features WHERE id = $1", [id]);
|
||||
|
||||
res.json({ message: "Feature erfolgreich gelöscht" });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,346 @@
|
|||
const express = require("express");
|
||||
const pool = require("../db"); // PostgreSQL-Datenbankverbindung
|
||||
const { requireAuth, requireAdmin } = require("../middleware/auth"); // Auth-Middleware
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/:id?", requireAuth, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const abteilung = req.abteilung;
|
||||
|
||||
try {
|
||||
let query;
|
||||
let values = [];
|
||||
|
||||
if (id) {
|
||||
// **Ein einzelnes Mitglied abrufen**
|
||||
if (abteilung) {
|
||||
// Falls Abteilung gesetzt ist, prüfen ob das Mitglied dazugehört
|
||||
query = `
|
||||
SELECT m.*
|
||||
FROM mitglieder m
|
||||
JOIN abteilungszuordnung dm ON m.id = dm.fid_mitglied
|
||||
WHERE m.id = $1 AND dm.fid_abteilung = $2
|
||||
`;
|
||||
values = [id, abteilung];
|
||||
} else {
|
||||
// Mitglied ohne Abteilungseinschränkung abrufen
|
||||
query = `SELECT * FROM mitglieder WHERE id = $1`;
|
||||
values = [id];
|
||||
}
|
||||
} else if (abteilung) {
|
||||
// **Alle Mitglieder einer Abteilung abrufen**
|
||||
query = `
|
||||
SELECT m.*
|
||||
FROM mitglieder m
|
||||
JOIN abteilungszuordnung dm ON m.id = dm.fid_mitglied
|
||||
WHERE dm.fid_abteilung = $1
|
||||
`;
|
||||
values = [abteilung];
|
||||
} else {
|
||||
// **Alle Mitglieder abrufen**
|
||||
query = `SELECT * FROM mitglieder`;
|
||||
}
|
||||
|
||||
const result = await pool.query(query, values);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Keine Mitglieder gefunden" });
|
||||
}
|
||||
|
||||
return res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **2. Mitglied anlegen oder aktualisieren (Nur für Admins)**
|
||||
router.put("/:id?", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
vorname,
|
||||
nachname,
|
||||
geburtsdatum,
|
||||
adresse,
|
||||
mit_seit,
|
||||
mit_num,
|
||||
helfer,
|
||||
probe,
|
||||
} = req.body;
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
if (id) {
|
||||
// **Mitglied aktualisieren**
|
||||
if (
|
||||
!vorname ||
|
||||
!nachname ||
|
||||
geburtsdatum === undefined ||
|
||||
adresse === undefined ||
|
||||
mit_seit === undefined ||
|
||||
mit_num === undefined ||
|
||||
helfer === undefined ||
|
||||
probe === undefined
|
||||
) {
|
||||
return res.status(400).json({ error: "Alle Felder sind erforderlich" });
|
||||
}
|
||||
|
||||
result = await pool.query(
|
||||
`UPDATE mitglieder
|
||||
SET vorname = $1, nachname = $2, geburtsdatum = $3, adresse = $4,
|
||||
mit_seit = $5, mit_num = $6, helfer = $7, probe = $8
|
||||
WHERE id = $9
|
||||
RETURNING *`,
|
||||
[
|
||||
vorname,
|
||||
nachname,
|
||||
geburtsdatum,
|
||||
adresse,
|
||||
mit_seit,
|
||||
mit_num,
|
||||
helfer,
|
||||
probe,
|
||||
id,
|
||||
]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Mitglied nicht gefunden" });
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: "Mitglied erfolgreich aktualisiert",
|
||||
member: result.rows[0],
|
||||
});
|
||||
} else {
|
||||
// **Neues Mitglied anlegen**
|
||||
if (
|
||||
!vorname ||
|
||||
!nachname ||
|
||||
geburtsdatum === undefined ||
|
||||
adresse === undefined ||
|
||||
mit_seit === undefined ||
|
||||
mit_num === undefined ||
|
||||
helfer === undefined ||
|
||||
probe === undefined
|
||||
) {
|
||||
return res.status(400).json({ error: "Alle Felder sind erforderlich" });
|
||||
}
|
||||
|
||||
result = await pool.query(
|
||||
`INSERT INTO mitglieder (vorname, nachname, geburtsdatum, adresse, mit_seit, mit_num, helfer, probe)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING *`,
|
||||
[
|
||||
vorname,
|
||||
nachname,
|
||||
geburtsdatum,
|
||||
adresse,
|
||||
mit_seit,
|
||||
mit_num,
|
||||
helfer,
|
||||
probe,
|
||||
]
|
||||
);
|
||||
|
||||
return res.status(201).json({
|
||||
message: "Mitglied erfolgreich angelegt",
|
||||
member: result.rows[0],
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **3. Mitglied löschen (Nur für Admins)**
|
||||
router.delete("/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const memberResult = await pool.query(
|
||||
"SELECT id FROM mitglieder WHERE id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (memberResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Mitglied nicht gefunden" });
|
||||
}
|
||||
|
||||
await pool.query("DELETE FROM mitglieder WHERE id = $1", [id]);
|
||||
|
||||
res.json({ message: "Mitglied erfolgreich gelöscht" });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **1. notfallnummern abrufen (Falls ID gegeben: Nur die Nummern eines Mitglieds)**
|
||||
router.get("/phone/:id", requireAuth, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const result = await pool.query(
|
||||
"SELECT * FROM notfallnummern WHERE fid_mitglied = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: "Keine notfallnummern für dieses Mitglied gefunden" });
|
||||
}
|
||||
|
||||
return res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **2. Telefonnummer anlegen oder aktualisieren (Nur für Admins)**
|
||||
router.put("/phone/:id?", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { fid_teilnehmer, name, nummer, verbindung, stand } = req.body;
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
if (id) {
|
||||
// **Telefonnummer aktualisieren**
|
||||
if (!fid_teilnehmer || !nummer) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Telefonnumer und Mitglieds ID sind erforderlich" });
|
||||
}
|
||||
|
||||
result = await pool.query(
|
||||
`UPDATE notfallnummern
|
||||
SET fid_mitglied = $1, name = $2, nummer = $3, verbindung = $4, stand = COALESCE($5, stand)
|
||||
WHERE id = $6
|
||||
RETURNING *`,
|
||||
[
|
||||
fid_teilnehmer,
|
||||
name || null,
|
||||
nummer,
|
||||
verbindung || null,
|
||||
stand || new Date(),
|
||||
id,
|
||||
]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Telefonnummer nicht gefunden" });
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: "Telefonnummer erfolgreich aktualisiert",
|
||||
phone: result.rows[0],
|
||||
});
|
||||
} else {
|
||||
// **Neue Telefonnummer anlegen**
|
||||
if (!fid_teilnehmer || !nummer) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'Alle Felder außer "stand" sind erforderlich' });
|
||||
}
|
||||
|
||||
result = await pool.query(
|
||||
`INSERT INTO notfallnummern (fid_mitglied, name, nummer, verbindung, stand)
|
||||
VALUES ($1, $2, $3, $4, COALESCE($5, NOW()))
|
||||
RETURNING *`,
|
||||
[fid_teilnehmer, name, nummer, verbindung, stand]
|
||||
);
|
||||
|
||||
return res.status(201).json({
|
||||
message: "Telefonnummer erfolgreich angelegt",
|
||||
phone: result.rows[0],
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **3. Telefonnummer löschen (Nur für Admins)**
|
||||
router.delete("/phone/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const phoneResult = await pool.query(
|
||||
"SELECT id FROM notfallnummern WHERE id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (phoneResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Telefonnummer nicht gefunden" });
|
||||
}
|
||||
|
||||
await pool.query("DELETE FROM notfallnummern WHERE id = $1", [id]);
|
||||
|
||||
res.json({ message: "Telefonnummer erfolgreich gelöscht" });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/riege/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const abteilung = req.abteilung;
|
||||
const { neue_riege } = req.body;
|
||||
|
||||
if (!neue_riege) {
|
||||
return res.status(400).json({ error: "Neue Riege ist erforderlich" });
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query("BEGIN"); // Transaktion starten
|
||||
|
||||
// **1. Prüfen, ob das Mitglied bereits in einer Riege dieser Abteilung ist**
|
||||
const aktuelleRiege = await pool.query(
|
||||
`SELECT rz.id, rz.fid_mitglied, rz.fid_riege
|
||||
FROM riegenzuordnung rz
|
||||
JOIN riegen r ON rz.fid_riege = r.id
|
||||
WHERE rz.fid_mitglied = $1 AND r.fid_abteilung = $2 AND rz.bis IS NULL`,
|
||||
[id, abteilung]
|
||||
);
|
||||
if (aktuelleRiege.rows.length > 0) {
|
||||
const alteRiegenId = aktuelleRiege.rows[0].fid_riege;
|
||||
|
||||
// **2. Falls Mitglied bereits in einer Riege ist → Diesen Eintrag beenden**
|
||||
await pool.query(
|
||||
`UPDATE riegenzuordnung
|
||||
SET bis = NOW()
|
||||
WHERE fid_mitglied = $1 AND fid_riege = $2 AND bis IS NULL`,
|
||||
[id, alteRiegenId]
|
||||
);
|
||||
}
|
||||
|
||||
// **3. Neuen Riegen-Eintrag für das Mitglied erstellen**
|
||||
const result = await pool.query(
|
||||
`INSERT INTO riegenzuordnung (fid_mitglied, fid_riege, von)
|
||||
VALUES ($1, $2, NOW())
|
||||
RETURNING *`,
|
||||
[id, neue_riege]
|
||||
);
|
||||
|
||||
await pool.query("COMMIT"); // Transaktion abschließen
|
||||
|
||||
res.json({
|
||||
message: "Mitglied erfolgreich in neue Riege verschoben",
|
||||
riegenzuordnung: result.rows[0],
|
||||
});
|
||||
} catch (err) {
|
||||
await pool.query("ROLLBACK"); // Falls Fehler, Transaktion rückgängig machen
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,119 @@
|
|||
const { requireAuth, requireAdmin } = require("../middleware/auth"); // Falls Middleware in einer extra Datei liegt
|
||||
const express = require("express");
|
||||
const pool = require("../db"); // Stelle sicher, dass dein DB-Pool importiert wird
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Spieleliste
|
||||
router.get("/:id?", async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
if (id) {
|
||||
// Falls eine ID übergeben wurde, geben wir alle Felder des Spiels zurück
|
||||
result = await pool.query("SELECT * FROM spiele WHERE id = $1", [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Spiel nicht gefunden" });
|
||||
}
|
||||
|
||||
return res.json(result.rows[0]);
|
||||
} else {
|
||||
// Falls keine ID übergeben wurde, geben wir nur ID und Name zurück
|
||||
result = await pool.query(
|
||||
"SELECT id, name FROM spiele ORDER BY name ASC"
|
||||
);
|
||||
return res.json(result.rows);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// **2. Spiel löschen (Nur für Admins)**
|
||||
router.delete("/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
// Prüfen, ob das Spiel existiert
|
||||
const spielResult = await pool.query(
|
||||
"SELECT id, name FROM spiele WHERE id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (spielResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Spiel nicht gefunden" });
|
||||
}
|
||||
|
||||
// Spiel löschen
|
||||
await pool.query("DELETE FROM spiele WHERE id = $1", [id]);
|
||||
|
||||
res.json({
|
||||
message: "Spiel erfolgreich gelöscht",
|
||||
spiel: spielResult.rows[0],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/:id?", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name, material, regeln, variationen, dauer, type } = req.body;
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
if (id) {
|
||||
// **Spiel aktualisieren** → Alle Felder müssen übergeben werden
|
||||
if (!name || !material || !regeln || !variationen || !dauer || !type) {
|
||||
return res.status(400).json({ error: "Alle Felder sind erforderlich" });
|
||||
}
|
||||
|
||||
result = await pool.query(
|
||||
`UPDATE spiele
|
||||
SET name = $1, material = $2, regeln = $3, variationen = $4, dauer = $5, type = $6
|
||||
WHERE id = $7
|
||||
RETURNING *`,
|
||||
[name, material, regeln, variationen, dauer, type, id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Spiel nicht gefunden" });
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: "Spiel erfolgreich aktualisiert",
|
||||
spiel: result.rows[0],
|
||||
});
|
||||
} else {
|
||||
// **Neues Spiel anlegen** → Nur `name` ist erforderlich
|
||||
if (!name) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Der Name des Spiels ist erforderlich" });
|
||||
}
|
||||
|
||||
result = await pool.query(
|
||||
`INSERT INTO spiele (name)
|
||||
VALUES ($1)
|
||||
RETURNING *`,
|
||||
[name]
|
||||
);
|
||||
|
||||
return res
|
||||
.status(201)
|
||||
.json({ message: "Spiel erfolgreich angelegt", spiel: result.rows[0] });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,244 @@
|
|||
const express = require("express");
|
||||
const pool = require("../db"); // PostgreSQL-Datenbankverbindung
|
||||
const { requireAuth, requireAdmin } = require("../middleware/auth"); // Auth-Middleware
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// **Trainings abrufen (mit Geräten & Leitern)**
|
||||
router.get("/:year?/:kw?", requireAuth, async (req, res) => {
|
||||
const { year, kw } = req.params;
|
||||
const abteilung = req.abteilung; // Abteilung aus Middleware
|
||||
|
||||
if (!abteilung) {
|
||||
return res.status(400).json({ error: "Abteilung ist erforderlich" });
|
||||
}
|
||||
|
||||
try {
|
||||
let query;
|
||||
let values = [abteilung];
|
||||
|
||||
if (year && kw) {
|
||||
// **Trainings einer bestimmten Woche abrufen**
|
||||
query = `
|
||||
SELECT * FROM trainings
|
||||
WHERE abteilung = $1 AND year = $2 AND kw = $3
|
||||
ORDER BY year DESC, kw DESC
|
||||
`;
|
||||
values.push(year, kw);
|
||||
} else if (year) {
|
||||
// **Alle Trainings eines yeares abrufen**
|
||||
query = `
|
||||
SELECT * FROM trainings
|
||||
WHERE abteilung = $1 AND year = $2
|
||||
ORDER BY kw ASC
|
||||
`;
|
||||
values.push(year);
|
||||
} else {
|
||||
// **Alle Trainings der Abteilung abrufen**
|
||||
query = `
|
||||
SELECT * FROM trainings
|
||||
WHERE abteilung = $1
|
||||
ORDER BY year DESC, kw DESC
|
||||
`;
|
||||
}
|
||||
|
||||
const result = await pool.query(query, values);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Keine Trainings gefunden" });
|
||||
}
|
||||
|
||||
return res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/leiten/:id", requireAuth, async (req, res) => {
|
||||
const { id } = req.params; // Trainings-ID
|
||||
const abteilung = req.abteilung; // Abteilung aus Middleware
|
||||
const { fid_helfer, fid_spiel, typ, action } = req.body; // Daten aus POST-Body
|
||||
|
||||
if (!action || !["new", "update"].includes(action)) {
|
||||
return res.status(400).json({
|
||||
error: 'Es muss "new" oder "update" als action angegeben werden',
|
||||
});
|
||||
}
|
||||
if (!fid_helfer || !typ) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "fid_helfer und typ sind erforderlich" });
|
||||
}
|
||||
if (typ !== "a" && typ !== "s") {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'Typ muss "a" (Aufwärmen) oder "s" (Spiel) sein' });
|
||||
}
|
||||
|
||||
try {
|
||||
if (action === "new") {
|
||||
// **Neuen Eintrag erstellen**
|
||||
const result = await pool.query(
|
||||
`INSERT INTO leiten (fid_training, fid_helfer, fid_spiel, typ)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *`,
|
||||
[id, fid_helfer, fid_spiel || null, typ]
|
||||
);
|
||||
|
||||
return res.status(201).json({
|
||||
message: "Leiten erfolgreich hinzugefügt",
|
||||
leiten: result.rows[0],
|
||||
});
|
||||
} else {
|
||||
// **Vorhandenen Eintrag aktualisieren**
|
||||
const checkLeiten = await pool.query(
|
||||
`SELECT * FROM leiten WHERE fid_training = $1 AND fid_helfer = $2 AND typ = $3`,
|
||||
[id, fid_helfer, typ]
|
||||
);
|
||||
|
||||
if (checkLeiten.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: "Kein passender Leiten-Eintrag gefunden" });
|
||||
}
|
||||
|
||||
const updateResult = await pool.query(
|
||||
`UPDATE leiten
|
||||
SET fid_spiel = $1
|
||||
WHERE fid_training = $2 AND fid_helfer = $3 AND typ = $4
|
||||
RETURNING *`,
|
||||
[fid_spiel || null, id, fid_helfer, typ]
|
||||
);
|
||||
|
||||
return res.json({
|
||||
message: "Leiten erfolgreich aktualisiert",
|
||||
leiten: updateResult.rows[0],
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/new/:jahr/:kw", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { jahr, kw } = req.params;
|
||||
const abteilung = req.abteilung; // Abteilung aus Middleware
|
||||
|
||||
if (!abteilung) {
|
||||
return res.status(400).json({ error: "Abteilung ist erforderlich" });
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query("BEGIN"); // Transaktion starten
|
||||
|
||||
// **1. Prüfen, ob bereits ein Training in dieser Woche existiert**
|
||||
const existingTraining = await pool.query(
|
||||
`SELECT id FROM trainings WHERE year = $1 AND kw = $2 AND abteilung = $3`,
|
||||
[jahr, kw, abteilung]
|
||||
);
|
||||
|
||||
if (existingTraining.rows.length > 0) {
|
||||
await pool.query("ROLLBACK");
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Training für diese Woche existiert bereits" });
|
||||
}
|
||||
|
||||
// **2. Prüfen, ob die Abteilung Riegen hat**
|
||||
const abteilungResult = await pool.query(
|
||||
`SELECT riegen, geraete FROM abteilungen WHERE id = $1`,
|
||||
[abteilung]
|
||||
);
|
||||
|
||||
if (abteilungResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Abteilung nicht gefunden" });
|
||||
}
|
||||
|
||||
const { riegen, geraete } = abteilungResult.rows[0]; // `riegen = true/false`, `geraete` = int[]
|
||||
|
||||
// **3. Neues Training anlegen**
|
||||
const trainingResult = await pool.query(
|
||||
`INSERT INTO trainings (year, kw, abteilung) VALUES ($1, $2, $3) RETURNING id`,
|
||||
[jahr, kw, abteilung]
|
||||
);
|
||||
|
||||
const trainingId = trainingResult.rows[0].id;
|
||||
|
||||
let geraeteplan = [];
|
||||
|
||||
if (riegen) {
|
||||
// **4. Die letzten zwei Trainings abrufen**
|
||||
const lastTrainings = await pool.query(
|
||||
`SELECT g.geraete
|
||||
FROM trainings t
|
||||
JOIN geraeteplan g ON t.id = g.fid_training
|
||||
WHERE t.abteilung = $1
|
||||
ORDER BY t.year DESC, t.kw DESC
|
||||
LIMIT 2`,
|
||||
[abteilung]
|
||||
);
|
||||
|
||||
let lastUsed1 =
|
||||
lastTrainings.rows.length > 0 ? lastTrainings.rows[0].geraete : [];
|
||||
let lastUsed2 =
|
||||
lastTrainings.rows.length > 1 ? lastTrainings.rows[1].geraete : [];
|
||||
|
||||
// **5. Falls eines der letzten beiden Trainings Geräte 11–13 enthielt → Letztes gültiges Training nehmen**
|
||||
if (lastUsed1.some((g) => g >= 11 && g <= 13)) {
|
||||
lastUsed1 = [];
|
||||
}
|
||||
if (lastUsed2.some((g) => g >= 11 && g <= 13)) {
|
||||
lastUsed2 = [];
|
||||
}
|
||||
|
||||
// **6. Bestimmen, ob hochgezählt werden soll**
|
||||
let nextGeraete = [];
|
||||
|
||||
if (
|
||||
lastUsed1.length > 0 &&
|
||||
JSON.stringify(lastUsed1) === JSON.stringify(lastUsed2)
|
||||
) {
|
||||
// Hochzählen, weil die letzten beiden Trainings identisch waren
|
||||
let lastDeviceIndex = geraete.findIndex((g) => g === lastUsed1[0]);
|
||||
|
||||
// Falls letztes Gerät 10 war, dann zurück auf 1
|
||||
let nextDevice = geraete[(lastDeviceIndex + 1) % geraete.length];
|
||||
if (lastUsed1[0] === 10) {
|
||||
nextDevice = 1;
|
||||
}
|
||||
|
||||
nextGeraete = [nextDevice, ...lastUsed1.slice(0, 4)];
|
||||
} else if (lastUsed1.length > 0) {
|
||||
// Letztes Training wiederholen
|
||||
nextGeraete = lastUsed1;
|
||||
} else {
|
||||
// Erstes Training → Starte mit den ersten fünf Geräten
|
||||
nextGeraete = geraete.slice(0, 5);
|
||||
}
|
||||
|
||||
// **7. Geräteplan speichern als int[]**
|
||||
await pool.query(
|
||||
`INSERT INTO geraeteplan (fid_training, geraete) VALUES ($1, $2)`,
|
||||
[trainingId, nextGeraete]
|
||||
);
|
||||
|
||||
geraeteplan = nextGeraete;
|
||||
}
|
||||
|
||||
await pool.query("COMMIT"); // Transaktion abschließen
|
||||
|
||||
res.status(201).json({
|
||||
message: "Training erfolgreich erstellt",
|
||||
training: { id: trainingId, year: jahr, kw, abteilung },
|
||||
geraeteplan,
|
||||
});
|
||||
} catch (err) {
|
||||
await pool.query("ROLLBACK"); // Falls Fehler, Transaktion rückgängig machen
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,341 @@
|
|||
const express = require("express");
|
||||
const pool = require("../db"); // Stelle sicher, dass dein DB-Pool importiert wird
|
||||
const bcrypt = require("bcryptjs");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const crypto = require("crypto");
|
||||
const nodemailer = require("nodemailer");
|
||||
const { requireAuth, requireAdmin } = require("../middleware/auth"); // Falls Middleware in einer extra Datei liegt
|
||||
const { transporter, sendActivationEmail } = require("../middleware/mail");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Registrierung
|
||||
router.post("/register", async (req, res) => {
|
||||
const { username, email, password } = req.body;
|
||||
try {
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
await pool.query(
|
||||
"INSERT INTO users (username, email, password) VALUES ($1, $2, $3)",
|
||||
[username, email, hashedPassword]
|
||||
);
|
||||
const message =
|
||||
"Registrierung erfolgreich. Ein Admin wird dich in kürze freischalten";
|
||||
const mailOptions = {
|
||||
to: "admin@boergmann.it",
|
||||
from: "admin@boergmann.it",
|
||||
subject: "Neue Registrierung",
|
||||
text: `${username} hat sich registriert`,
|
||||
};
|
||||
|
||||
transporter.sendMail(mailOptions, (error) => {
|
||||
if (error) {
|
||||
console.error("Error sending email:", error);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
res.status(201).json({ message: message });
|
||||
} catch (error) {
|
||||
console.error("Error registering user:", error);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// Login
|
||||
router.post("/login", async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
||||
try {
|
||||
const userResult = await pool.query(
|
||||
"SELECT *, CASE WHEN admin_temp IS NOT NULL AND (now() - admin_temp) > interval '22 hours' THEN 'expired' ELSE 'valid' END AS admin_status FROM users WHERE username = $1",
|
||||
[username]
|
||||
);
|
||||
if (userResult.rows.length > 0) {
|
||||
const user = userResult.rows[0];
|
||||
console.log(user);
|
||||
const match = await bcrypt.compare(password, user.password);
|
||||
if (match) {
|
||||
if (user.is_active) {
|
||||
const token = jwt.sign(
|
||||
{ id: user.id, username: user.username, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
{
|
||||
expiresIn: "24h",
|
||||
}
|
||||
);
|
||||
if (user.admin_status === "expired") {
|
||||
await pool.query(
|
||||
"UPDATE users SET role = $1, admin_temp = NULL WHERE id = $2",
|
||||
["user", user.id]
|
||||
);
|
||||
}
|
||||
res.json({ token });
|
||||
} else {
|
||||
return res.status(401).json({ error: "Auf Freischlatung warten" });
|
||||
}
|
||||
} else {
|
||||
const message = "Falsches Passwort";
|
||||
return res.status(401).json({ error: "Ungültige Anmeldedaten" });
|
||||
}
|
||||
} else {
|
||||
const message = "Unbekannter Benutzer";
|
||||
return res.status(401).json({ error: "Ungültige Anmeldedaten" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error logging in:", error);
|
||||
const message = "Error logging in:" + error;
|
||||
return res.status(401).json({ error: "Ungültige Anmeldedaten" });
|
||||
}
|
||||
});
|
||||
|
||||
// Reset initiieren
|
||||
router.post("/reset-password", async (req, res) => {
|
||||
const { email } = req.body;
|
||||
|
||||
if (!email) {
|
||||
return res.status(400).json({ error: "E-Mail-Adresse ist erforderlich" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Prüfen, ob die E-Mail existiert
|
||||
const userResult = await pool.query(
|
||||
"SELECT id, username FROM users WHERE email = $1",
|
||||
[email]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: "Benutzer mit dieser E-Mail nicht gefunden" });
|
||||
}
|
||||
|
||||
const { id, username } = userResult.rows[0];
|
||||
|
||||
// Token generieren (random 32 Byte)
|
||||
const token = crypto.randomBytes(32).toString("hex");
|
||||
const expires = new Date(Date.now() + 3600000); // Token 1 Stunde gültig
|
||||
|
||||
// Token und Ablaufdatum in die DB speichern
|
||||
await pool.query(
|
||||
"UPDATE users SET reset_password_token = $1, reset_password_expires = $2 WHERE id = $3",
|
||||
[token, expires, id]
|
||||
);
|
||||
|
||||
// Mail senden
|
||||
const resetLink = `https://${process.env.HOST}/reset/${token}`;
|
||||
const mailOptions = {
|
||||
from: process.env.MAILUSER,
|
||||
to: email,
|
||||
subject: "Passwort zurücksetzen",
|
||||
text: `Hallo ${username},\n\nKlicke auf den folgenden Link, um dein Passwort zurückzusetzen:\n\n${resetLink}\n\nDer Link ist 1 Stunde gültig.\n\nFalls du das Zurücksetzen nicht beantragt hast, ignoriere diese Nachricht.`,
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
|
||||
res.json({ message: "Passwort-Reset-Link wurde per E-Mail gesendet." });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// Reset Abschließen
|
||||
router.post("/reset/:token", async (req, res) => {
|
||||
const { token } = req.params;
|
||||
const { password } = req.body;
|
||||
|
||||
if (!password) {
|
||||
return res.status(400).json({ error: "Neues Passwort ist erforderlich" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Token in der Datenbank suchen und prüfen, ob es noch gültig ist
|
||||
const result = await pool.query(
|
||||
"SELECT id FROM users WHERE reset_password_token = $1 AND reset_password_expires > NOW()",
|
||||
[token]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Ungültiges oder abgelaufenes Token" });
|
||||
}
|
||||
|
||||
const userId = result.rows[0].id;
|
||||
|
||||
// Passwort hashen
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Passwort speichern und Token-Felder leeren
|
||||
await pool.query(
|
||||
"UPDATE users SET password = $1, reset_password_token = NULL, reset_password_expires = NULL WHERE id = $2",
|
||||
[hashedPassword, userId]
|
||||
);
|
||||
|
||||
res.json({ message: "Passwort erfolgreich zurückgesetzt" });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// Email und/oder Passwort ändern
|
||||
router.post("/update", requireAuth, async (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
try {
|
||||
if (email) {
|
||||
await pool.query("UPDATE users SET email = $1 WHERE id = $2", [
|
||||
email,
|
||||
req.user.id,
|
||||
]);
|
||||
}
|
||||
if (password) {
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
await pool.query("UPDATE users SET password = $1 WHERE id = $2", [
|
||||
hashedPassword,
|
||||
req.user.id,
|
||||
]);
|
||||
}
|
||||
res.json({
|
||||
message: "Benutzerdaten erfolgreich aktualisiert",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating profile:", error);
|
||||
const message = "Error updating profile:" + error;
|
||||
res.status(400).json({ error: "Keine Änderungen vorgenommen" });
|
||||
}
|
||||
});
|
||||
|
||||
// Liste der User mit Rollen und is_active
|
||||
router.get("/admin", requireAuth, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
"SELECT id, username, email, role, is_active FROM users ORDER BY is_active ASC, role DESC"
|
||||
);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/activate/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
// Benutzer-Daten abrufen (inklusive E-Mail und Benutzername)
|
||||
const userResult = await pool.query(
|
||||
"SELECT username, email FROM users WHERE id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Benutzer nicht gefunden" });
|
||||
}
|
||||
|
||||
const { username, email } = userResult.rows[0];
|
||||
|
||||
// Benutzer aktivieren
|
||||
const result = await pool.query(
|
||||
"UPDATE users SET is_active = true WHERE id = $1 RETURNING id, username, is_active",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Benutzer nicht gefunden" });
|
||||
}
|
||||
|
||||
// E-Mail senden (wenn vorhanden)
|
||||
await sendActivationEmail(email, username);
|
||||
|
||||
res.json({
|
||||
message: "Benutzer erfolgreich aktiviert",
|
||||
user: result.rows[0],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
// Prüfen, ob der Benutzer existiert
|
||||
const userResult = await pool.query(
|
||||
"SELECT id, username FROM users WHERE id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Benutzer nicht gefunden" });
|
||||
}
|
||||
|
||||
// Verhindern, dass sich Admins selbst löschen
|
||||
if (req.user.id == id) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Ein Admin kann sich nicht selbst löschen" });
|
||||
}
|
||||
|
||||
// Benutzer löschen
|
||||
await pool.query("DELETE FROM users WHERE id = $1", [id]);
|
||||
|
||||
res.json({
|
||||
message: "Benutzer erfolgreich gelöscht",
|
||||
user: userResult.rows[0],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
// User zu Admins machen
|
||||
router.put("/admin/:id", requireAuth, requireAdmin, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { temporary } = req.body; // erwartet { "temporary": true } oder { "temporary": false }
|
||||
|
||||
if (temporary === undefined) {
|
||||
return res.status(400).json({
|
||||
error: "Es muss angegeben werden, ob der Admin-Zugang temporär sein soll",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
let query, values;
|
||||
|
||||
if (temporary) {
|
||||
query = `
|
||||
UPDATE users
|
||||
SET role = 'admin', admin_temp = NOW() + INTERVAL '48 hours'
|
||||
WHERE id = $1 RETURNING id, username, role, admin_temp
|
||||
`;
|
||||
values = [id];
|
||||
} else {
|
||||
query = `
|
||||
UPDATE users
|
||||
SET role = 'admin', admin_temp = NULL
|
||||
WHERE id = $1 RETURNING id, username, role, admin_temp
|
||||
`;
|
||||
values = [id];
|
||||
}
|
||||
|
||||
const result = await pool.query(query, values);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Benutzer nicht gefunden" });
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: "Benutzer wurde zum Admin gemacht",
|
||||
user: result.rows[0],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Interner Serverfehler" });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
Loading…
Reference in New Issue