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