const express = require("express"); const session = require("express-session"); const bcrypt = require("bcrypt"); const crypto = require("crypto"); const nodemailer = require("nodemailer"); const { Pool } = require("pg"); 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 TeleBot = require('telebot'); const bot = new TeleBot(process.env.TELEBOT); telebotChatID=process.env.TELECHAT; bot.on('text', (msg) => { if (msg.from.id==telebotChatID) { msg.reply.text(msg.text); } else { msg.reply.text("Entschuldige, " + msg.from.username + "\nIch darf nicht mit fremden reden."); log.Info("Telebot-nachricht: " + msg.from.username + " - " + msg.text); } }); bot.start(); */ 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.set("view engine", "ejs"); 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 }, }) ); // Authentifizierungs-Middleware const requireAuth = (req, res, next) => { if (!req.session.userId) { return res.redirect("/login"); } next(); }; const requireAdmin = (req, res, next) => { if (req.session.role !== "admin") { return res.status(403).send("Access denied"); } next(); }; // Email-Konfiguration const transporter = nodemailer.createTransport({ host: process.env.MAILHOST, port: 465, secure: true, auth: { user: process.env.MAILUSER, pass: process.env.MAILPASS, }, }); // Datenbankverbindung const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); // Altersberechnung const calculateAge = (birthdate) => { const today = new Date(); const birthDate = new Date(birthdate); let age = today.getFullYear() - birthDate.getFullYear(); const m = today.getMonth() - birthDate.getMonth(); if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) { age--; } return age; }; const isBirthday = (birthdate, date, previousTraining) => { let adjustedBirthdate = new Date(birthdate); adjustedBirthdate.setFullYear(date.getFullYear()); if (adjustedBirthdate.getMonth() == 10) { console.log("checking:", adjustedBirthdate, date, previousTraining); } if (adjustedBirthdate.getTime() === date.getTime()) { console.log("birthday"); return 2; } if ( adjustedBirthdate.getTime() >= previousTraining.getTime() && adjustedBirthdate.getTime() <= date.getTime() ) { console.log("had birthday"); return 1; } return 0; }; // Datumsformatierung const formatDate = (date) => { const d = new Date(date); const day = String(d.getDate()).padStart(2, "0"); const month = String(d.getMonth() + 1).padStart(2, "0"); const year = d.getFullYear(); return `${day}.${month}.${year}`; }; // Funktion zum Laden des aktuellen oder letzten Trainings const getTraining = async (kw, jahr) => { const result = await pool.query( ` SELECT tr.*, g1.name AS geraet_riege_1_name, g2.name AS geraet_riege_2_name, g3.name AS geraet_riege_3_name, g4.name AS geraet_riege_4_name, g5.name AS geraet_riege_5_name, t1.name AS aufwaermleiter_name, t2.name AS spielleiter_name, sp1.name AS aufwaermen_name, sp2.name AS spiel_name FROM trainings tr LEFT JOIN geraete g1 ON tr.geraet_riege_1 = g1.id LEFT JOIN geraete g2 ON tr.geraet_riege_2 = g2.id LEFT JOIN geraete g3 ON tr.geraet_riege_3 = g3.id LEFT JOIN geraete g4 ON tr.geraet_riege_4 = g4.id LEFT JOIN geraete g5 ON tr.geraet_riege_5 = g5.id LEFT JOIN teilnehmende t1 ON tr.aufwaermleiter = t1.id LEFT JOIN teilnehmende t2 ON tr.spielleiter = t2.id LEFT JOIN spiele sp1 ON tr.aufwaermen = sp1.id LEFT JOIN spiele sp2 ON tr.spiel = sp2.id WHERE tr.kw <= $1 AND tr.jahr = $2 ORDER BY tr.kw DESC LIMIT 1 `, [kw, jahr] ); return result.rows[0]; }; // Funktion zum Laden aller Spiele const getAllSpiele = async () => { const result = await pool.query(` SELECT * FROM spiele ORDER BY name ASC; `); return result.rows; }; // Funktion zum Hinzufügen eines neuen Spiels, falls es noch nicht existiert const addNewSpiel = async (spielName) => { try { const result = await pool.query( "INSERT INTO spiele (name) VALUES ($1) RETURNING id", [spielName] ); return result.rows[0].id; } catch (error) { console.error("Error adding new spiel:", error); throw error; } }; function getdayOfWeek(week, year) { const weekday = parseInt(process.env.WEEKDAY, 10); const firstDayOfYear = new Date(year, 0, 1); const dayOfWeek = firstDayOfYear.getDay(); const daysUntilFirstThursday = dayOfWeek <= weekday ? weekday - dayOfWeek : 11 - dayOfWeek; const daysUntilThursdayOfWeek = (week - 1) * 7 + daysUntilFirstThursday; const thursdayOfWeek = new Date(year, 0, 1 + daysUntilThursdayOfWeek); return thursdayOfWeek; } // Funktion zum Laden der vier Leute, die am längsten nicht Aufwärmen oder Spiel geleitet haben const getCandidatesForAufwaermleiter = async () => { const result = await pool.query(` SELECT t.id, t.name, COALESCE(EXTRACT(EPOCH FROM (NOW() - MAX(tr.datum))) / 604800, EXTRACT(EPOCH FROM (NOW() - '1970-01-01'::date)) / 604800) AS weeks_since_last FROM teilnehmende t LEFT JOIN trainings tr ON t.id = tr.aufwaermleiter WHERE t.helfer = true GROUP BY t.id ORDER BY weeks_since_last DESC; `); return result.rows; }; const putInRiege = async (riege, teilnehmerID) => { try { if (riege == 0) { const resultRiege = await pool.query( "DELETE FROM riegen WHERE fremdid_teilnehmende = $1;", [teilnehmerID] ); } else { const resultRiege = await pool.query( "SELECT * FROM riegen WHERE fremdid_teilnehmende = $1;", [teilnehmerID] ); if (resultRiege.rows.length > 0) { const resultRiege = await pool.query( "UPDATE riegen SET riegennummer = $1 WHERE fremdid_teilnehmende = $2", [riege, teilnehmerID] ); log.Info("Mitglied " + teilnehmerID + " ist jetzt in Riege " + riege); } else { const resultRiege = await pool.query( "INSERT INTO riegen (fremdid_teilnehmende, riegennummer) VALUES ($1, $2)", [teilnehmerID, riege] ); log.Info( "Mitglied " + teilnehmerID + " neu in Riege " + riege + " eingefügt." ); } } } catch (error) { console.error("Error adding new spiel:", error); throw error; } }; // Funktion zum Laden der vier Leute, die am längsten nicht Spiel geleitet haben const getCandidatesForSpielleiter = async () => { const result = await pool.query(` SELECT t.id, t.name, COALESCE(EXTRACT(EPOCH FROM (NOW() - MAX(tr.datum))) / 604800, EXTRACT(EPOCH FROM (NOW() - '1970-01-01'::date)) / 604800) AS weeks_since_last FROM teilnehmende t LEFT JOIN trainings tr ON t.id = tr.spielleiter WHERE t.helfer = true GROUP BY t.id ORDER BY weeks_since_last DESC; `); return result.rows; }; // Funktion zum Laden der Riegenteilnehmer absteigend nach Alter sortiert const getRiegenMitgliederSortedByAge = async () => { const result = await pool.query(` SELECT r.riegennummer, t.name, t.alter FROM riegen r JOIN teilnehmende t ON r.fremdid_teilnehmende = t.id ORDER BY r.riegennummer, t.alter DESC `); return result.rows; }; const getAnwesenheit = function (id, anwesend) { for (let item of anwesend) { if (item.fid_teilnehmer === id) { return true; } } return false; }; // Registrierung app.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); const message = "Error sending Mail:" + error; res.render("error", { session: req.session, message }); } }); res.render("error", { session: req.session, message }); } catch (error) { console.error("Error registering user:", error); const message = "Error registering user:" + error; res.render("error", { session: req.session, message }); } }); // Login app.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]; const match = await bcrypt.compare(password, user.password); if (match) { if (user.is_active) { req.session.userId = user.id; req.session.userName = user.username; req.session.activeRiege = 1; req.session.activeTab = "geraete"; req.session.message = [(title = ""), (message = ""), (type = "none")]; if (user.admin_status === "expired") { await pool.query( "UPDATE users SET role = $1, admin_temp = NULL WHERE id = $2", ["user", user.id] ); req.session.role = "user"; } else { req.session.role = user.role; } res.redirect("/training"); } else { res.redirect("/freischaltung"); } } else { const message = "Falsches Passwort"; res.render("login", { session: req.session, username, message }); } } else { const message = "Unbekannter Benutzer"; res.render("login", { session: req.session, username, message }); } } catch (error) { console.error("Error logging in:", error); const message = "Error logging in:" + error; res.render("error", { session: req.session, message }); } }); //Wird angezeigt, wenn ein nicht freigeschalteter User sich anmelden will. app.get("/freischaltung", async (req, res) => { res.render("freischaltung", { session: req.session }); }); // Logout app.get("/logout", (req, res) => { req.session.destroy((err) => { if (err) { return res.status(500).send("Internal Server Error"); } res.redirect("/"); }); }); // Benutzer freischalten (nur Admin) app.post("/userrights", requireAuth, requireAdmin, async (req, res) => { const { userId, type } = req.body; try { if (type === "activate") { await pool.query("UPDATE users SET is_active = TRUE WHERE id = $1", [ userId, ]); const userResult = await pool.query("SELECT * FROM users WHERE id = $1", [ userId, ]); if (userResult.rows.length > 0) { if (userResult.rows[0].email) { const mailOptions = { to: userResult.rows[0].email, from: "admin@boergmann.it", subject: "Freischaltung", text: `Hallo ${userResult.rows[0].username}, du wurdest soeben freigeschaltet.`, }; transporter.sendMail(mailOptions, (error) => { if (error) { console.error("Error sending email:", error); const message = "Error sending Mail:" + error; res.render("error", { session: req.session, message }); } }); } } } else if (type === "admin") { await pool.query("UPDATE users SET role = $1 WHERE id = $2", [ "admin", userId, ]); } else if (type === "admint") { await pool.query( "UPDATE users SET role = $1, admin_temp = $2 WHERE id = $3", ["admin", moment().toDate(), userId] ); } else if (type === "delete") { await pool.query("DELETE FROM users WHERE id = $1", [userId]); } res.redirect("/admin"); } catch (error) { console.error("Error activating user:", error); const message = "Error activating user:" + error; res.render("error", { session: req.session, message }); } }); // Passwort-Zurücksetzung anfordern app.post("/send-password", async (req, res) => { const { email } = req.body; try { const userResult = await pool.query( "SELECT * FROM users WHERE email = $1", [email] ); if (userResult.rows.length > 0) { const user = userResult.rows[0]; const token = crypto.randomBytes(20).toString("hex"); const resetLink = `http://tkd.boergmann.it/reset-password/${token}`; await pool.query( "UPDATE users SET reset_password_token = $1, reset_password_expires = $2 WHERE id = $3", [token, (selectedDate = moment().add(1, "d").toDate()), user.id] ); const mailOptions = { to: user.email, from: "admin@boergmann.it", subject: "Password Reset", text: `Click the following link to reset your password: ${resetLink}`, }; transporter.sendMail(mailOptions, (err) => { if (err) { console.error("Error sending email:", err); const message = "Error sending Mail:" + error; res.render("error", { session: req.session, message }); } else { const message = "Password reset link sent"; res.render("error", { session: req.session, message }); } }); } else { const message = "Email not found"; res.render("error", { session: req.session, message }); } } catch (error) { console.error("Error in forgot-password:", error); const message = "Error in forgot-password"; res.render("error", { session: req.session, message }); } }); app.get("/forgot-password", async (req, res) => { res.render("forgot-password", { session: req.session }); }); // Passwort zurücksetzen app.get("/reset-password/:token", async (req, res) => { const { token } = req.params; try { // const userResult = await pool.query('SELECT * FROM users WHERE reset_password_token = $1 AND reset_password_expires > $2', [token, Date.now()]); const userResult = await pool.query( "SELECT *, CASE WHEN reset_password_token IS NOT NULL AND (now() - reset_password_expires) > interval '22 hours' THEN 'expired' ELSE 'valid' END AS reset_status FROM users WHERE reset_password_token = $1", [token] ); if (userResult.rows.length > 0) { res.render("reset-password", { session: req.session, token }); // Stelle sicher, dass es eine reset-password.ejs gibt } else { const message = "Token ungültig oder abgelaufen"; res.render("error", { session: req.session, message }); } } catch (error) { console.error("Error in reset-password:", error); const message = "Error in reset-password"; res.render("error", { session: req.session, message }); } }); app.post("/reset-password/:token", async (req, res) => { const { token } = req.params; const { password } = req.body; try { const userResult = await pool.query( "SELECT *, CASE WHEN reset_password_token IS NOT NULL AND (now() - reset_password_expires) > interval '22 hours' THEN 'expired' ELSE 'valid' END AS reset_status FROM users WHERE reset_password_token = $1", [token] ); if (userResult.rows.length > 0) { const user = userResult.rows[0]; const hashedPassword = await bcrypt.hash(password, 10); await pool.query( "UPDATE users SET password = $1, reset_password_token = NULL, reset_password_expires = NULL WHERE id = $2", [hashedPassword, user.id] ); res.redirect("/login"); } else { const message = "Token ungültig oder abgelaufen"; res.render("error", { session: req.session, message }); } } catch (error) { console.error("Error in reset-password:", error); const message = "Error in reset-password"; res.render("error", { session: req.session, message }); } }); // Profilseite app.get("/profile", requireAuth, (req, res) => { res.render("profile", { session: req.session }); // Stelle sicher, dass es eine profile.ejs gibt }); app.post("/profile", 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.session.userId, ]); } if (password) { const hashedPassword = await bcrypt.hash(password, 10); await pool.query("UPDATE users SET password = $1 WHERE id = $2", [ hashedPassword, req.session.userId, ]); } res.redirect("/profile"); } catch (error) { console.error("Error updating profile:", error); const message = "Error updating profile:" + error; res.render("error", { session: req.session, message }); } }); app.post("/update-training", requireAuth, async (req, res) => { const { trainingId, type, spielName } = req.body; req.session.activeTab = "spiel"; let spielId; const spiel = await pool.query(`SELECT * FROM spiele WHERE name = $1`, [ spielName, ]); if (spiel.rows.length > 0) { spielId = spiel.rows[0].id; } else { const newSpiel = await pool.query( "INSERT INTO spiele (name) VALUES ($1) RETURNING id", [spielName] ); spielId = newSpiel.rows[0].id; req.session.message = [ (title = "Neues Spiel"), (body = "Das Spiel ${ spielName } wurde angelegt."), ]; } try { if (type === "spiel") { await pool.query("UPDATE trainings SET spiel = $1 WHERE id = $2", [ spielId, trainingId, ]); } else if (type === "aufwaermen") { await pool.query("UPDATE trainings SET aufwaermen = $1 WHERE id = $2", [ spielId, trainingId, ]); } res.redirect("/training"); } catch (error) { console.error(error); const message = "Error:" + error; res.render("error", { session: req.session, message }); } }); app.post("/update-leader", requireAuth, async (req, res) => { const { trainingId, type, leaderId } = req.body; req.session.activeTab = "spiel"; try { if (type === "aufwaermleiter") { await pool.query( "UPDATE trainings SET aufwaermleiter = $1 WHERE id = $2", [leaderId, trainingId] ); } else if (type === "spielleiter") { await pool.query("UPDATE trainings SET spielleiter = $1 WHERE id = $2", [ leaderId, trainingId, ]); } res.redirect("/training"); } catch (error) { console.error(error); const message = "Error:" + error; res.render("error", { session: req.session, message }); } }); // Admin-Seite app.get("/admin", requireAuth, requireAdmin, async (req, res) => { const usersResult = await pool.query("SELECT * FROM users"); res.render("admin", { users: usersResult.rows, session: req.session }); // Stelle sicher, dass es eine admin.ejs gibt }); app.post("/new-member", requireAuth, requireAdmin, async (req, res) => { var { from, vorname, nachname, geburt, riege, adresse, probe, from, training, } = req.body; var geb = "01.01.2024"; const name = vorname + " " + nachname; try { if (geburt) { geb = geburt; } const teilnehmerID = await pool.query( "INSERT INTO teilnehmende (name, geburtsdatum, adresse, vorname, nachname, probe) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", [name, geb, adresse, vorname, nachname, probe ? true : false] ); if (from === "admin") { selectedKW = moment().isoWeek(); selectedYear = moment().year(); const trainingresult = await getTraining(selectedKW, selectedYear); training = trainingresult.id; } await pool.query( "INSERT INTO anwesend (fid_teilnehmer, fid_training) VALUES ($1, $2)", [teilnehmerID.rows[0].id, training] ); log.Info( "Mitglied " + name + " durch " + req.session.userName + " angelegt" ); putInRiege(riege, teilnehmerID.rows[0].id); req.session.activeRiege = riege; req.session.activeTab = "anwesend"; res.redirect("/training"); } catch (error) { const message = "Error:" + error; res.render("error", { session: req.session, message }); } }); // Teilnehmer_innen app.get("/training", requireAuth, async (req, res) => { try { let dateParam = req.query.date; let kwParam = req.query.kw; let jahrParam = req.query.jahr; let selectedDate; let selectedKW; let selectetYear; if (kwParam) { selectedKW = kwParam; } else { selectedKW = moment().isoWeek(); } if (jahrParam) { selectedYear = jahrParam; } else { selectedYear = moment().year(); } const training = await getTraining(selectedKW, selectedYear); const anwesendResult = await pool.query( "SELECT * FROM anwesend WHERE fid_training = $1", [training.id] ); anwesend = anwesendResult.rows; const anzahl = anwesend.length; const trainingsResult = await pool.query( "SELECT kw, jahr FROM trainings ORDER BY kw ASC" ); const trainingsDates = trainingsResult.rows.map((tr) => ({ kw: tr.kw, datum: formatDate(getdayOfWeek(tr.kw, tr.jahr)), })); // Vorheriges Training ermitteln const previousTrainingResult = await pool.query( "SELECT * FROM trainings WHERE id < $1 ORDER BY id DESC LIMIT 1", [training.id] ); const nextTrainingResult = await pool.query( "SELECT * FROM trainings WHERE id > $1 ORDER BY id ASC LIMIT 1", [training.id] ); const previousTraining = previousTrainingResult.rows.length > 0 ? previousTrainingResult.rows[0] : null; const nextTraining = nextTrainingResult.rows.length > 0 ? nextTrainingResult.rows[0] : null; // Abrufen der Riegendaten einschließlich der Teilnehmer und deren Altersberechnung const result = await pool.query(` SELECT r.riegennummer, t.id, t.name, t.vorname, t.nachname, t.geburtsdatum, t.probe, r.helfer, COUNT(a.fid_teilnehmer) AS anwesenheit FROM riegen r JOIN teilnehmende t ON r.fremdID_Teilnehmende = t.id LEFT JOIN anwesend a ON t.id = a.fid_teilnehmer GROUP BY r.riegennummer, t.id, t.name, t.vorname, t.nachname, t.geburtsdatum, r.helfer ORDER BY r.riegennummer, t.geburtsdatum ASC; `); // Gruppieren der Riegenteilnehmer nach Riegennummer const riegen = {}; result.rows.forEach((row) => { const age = calculateAge(row.geburtsdatum); const hasBirthday = isBirthday( row.geburtsdatum, training.datum, previousTraining.datum ); const tnAnwesend = getAnwesenheit(row.id, anwesend); if (!riegen[row.riegennummer]) { riegen[row.riegennummer] = []; } riegen[row.riegennummer].push({ id: row.id, name: row.name, vorname: row.vorname, nachname: row.nachname, geb: row.geburtsdatum, hasBirthday: hasBirthday, age: age, helfer: row.helfer, probe: row.probe, anwesenheit: row.anwesenheit, anwesend: tnAnwesend, anzahl, }); }); if (training) { training.datum = getdayOfWeek(training.kw, training.jahr); } const aufwaermleiterCandidates = await getCandidatesForAufwaermleiter(); const spielleiterCandidates = await getCandidatesForSpielleiter(); const spielCandidates = await getAllSpiele(); const aufwaermenCandidates = await getAllSpiele(); const abteilung = process.env.ABTEILUNG; let view; if (abteilung == "KiTu") { view = "trainings_riegen"; } else { view = "trainings"; } res.render(view, { training, trainingsDates, selectedKW: training.kw, aufwaermleiterCandidates, spielleiterCandidates, aufwaermenCandidates, // Übergeben der Kandidaten für Aufwärmleiter spielCandidates, // Übergeben der Spiele session: req.session, previousTraining, nextTraining, riegen, activeRiege: req.session.activeRiege, activeTab: req.session.activeTab, moment, }); } catch (error) { console.error(error); const message = "Error:" + error; res.render("error", { session: req.session, message }); } }); app.get("/riege", requireAuth, async (req, res) => { try { // Abrufen der Riegendaten einschließlich der Teilnehmer und deren Altersberechnung const result = await pool.query(` SELECT r.riegennummer, t.id, t.name, t.vorname, t.nachname, t.geburtsdatum, t.probe, r.helfer, COUNT(a.fid_teilnehmer) AS anwesenheit FROM riegen r JOIN teilnehmende t ON r.fremdID_Teilnehmende = t.id LEFT JOIN anwesend a ON t.id = a.fid_teilnehmer GROUP BY r.riegennummer, t.id, t.name, t.vorname, t.nachname, t.geburtsdatum, r.helfer ORDER BY r.riegennummer, t.geburtsdatum ASC; `); // Gruppieren der Riegenteilnehmer nach Riegennummer const date = new Date(); let previous = new Date(date); previous.setDate(date.getDate() - 7); const riegen = {}; result.rows.forEach((row) => { const age = calculateAge(row.geburtsdatum); if (!riegen[row.riegennummer]) { riegen[row.riegennummer] = []; } riegen[row.riegennummer].push({ id: row.id, name: row.name, vorname: row.vorname, nachname: row.nachname, hasBirthday: isBirthday(row.geburtsdatum, date, previous), age: age, helfer: row.helfer, probe: row.probe, anwesenheit: row.anwesenheit, }); }); res.render("riegen", { riegen: riegen, session: req.session }); } catch (error) { console.error("Error fetching riegen:", error); req.session.message = ["Error", error, "error"]; res.render("error", { session: req.session }); } }); app.get("/teilnehmer", requireAuth, requireAdmin, async (req, res) => { try { const teilnehmendeResult = await pool.query( "SELECT m.*, COUNT(a.fid_teilnehmer) AS anwesenheit FROM teilnehmende m LEFT JOIN anwesend a ON m.id = a.fid_teilnehmer GROUP BY m.id ORDER BY helfer DESC, vorname ASC" ); const date = new Date(); let previous = new Date(date); previous.setDate(date.getDate() - 7); const teilnehmende = teilnehmendeResult.rows.map((t) => ({ ...t, age: calculateAge(t.geburtsdatum), hasBirthday: isBirthday(t.geburtsdatum, date, previous), })); res.render("teilnehmer", { teilnehmende, session: req.session }); } catch (error) { console.error(error); req.session.message = ["Error", error, "error"]; res.render("error", { session: req.session }); } }); app.get("/mitglied/:id", requireAuth, requireAdmin, async (req, res) => { const { id } = req.params; try { var riege = 0; const userResult = await pool.query( "SELECT * FROM teilnehmende WHERE id = $1", [id] ); const riegeResult = await pool.query( "SELECT * FROM riegen WHERE fremdid_teilnehmende = $1", [id] ); if (riegeResult.rows.length > 0) { riege = riegeResult.rows[0].riegennummer; } req.session.activeRiege = riege; const anwesendResult = await pool.query( "SELECT * FROM anwesend WHERE fid_teilnehmer = $1", [id] ); const anwesend = anwesendResult.rows.length; const numberResult = await pool.query( "SELECT telefonnummern.*, telverbindung.bezeichnung FROM telefonnummern JOIN telverbindung ON telefonnummern.verbindung = telverbindung.id WHERE telefonnummern.fid_teilnehmer = $1", [id] ); const numbers = numberResult.rows; if (userResult.rows.length > 0) { const mitglied = userResult.rows.map((t) => ({ ...t, age: calculateAge(t.geburtsdatum), })); res.render("mitglied", { id, mitglied: mitglied[0], numbers, riege, anwesend, session: req.session, }); } else { req.session.message = ["Error", "Mitglied existiert nicht", "error"]; res.redirect("/teilnehmer"); } } catch (error) { console.error("Error in Mitglied:", error); req.session.message = ["Error", "Mitglied existiert nicht", "error"]; res.redirect("/teilnehmer"); } }); app.post("/set-riege", requireAuth, async (req, res) => { const { riege, id } = req.body; putInRiege(riege, id); res.redirect("/riege"); }); app.post("/update-mitglied", requireAdmin, async (req, res) => { const { type, id, geburt, telid, adresse, name, verbindung, nummer, vorname, nachname, probe, helfer, } = req.body; try { if (type == "tel") { await pool.query( "INSERT INTO telefonnummern (fid_teilnehmer, name, verbindung, nummer, stand) VALUES ($1, $2, $3, $4, $5)", [id, name, verbindung, nummer, moment().toDate()] ); } else if (type == "tel-delete") { await pool.query("DELETE FROM telefonnummern WHERE id = $1", [telid]); } else if (type == "adresse") { await pool.query( "UPDATE teilnehmende SET vorname = $1, nachname = $2, geburtsdatum = $3, adresse = $4, probe = $5, helfer = $6 WHERE id = $7", [ vorname, nachname, geburt, adresse, probe ? true : false, helfer ? true : false, id, ] ); } res.redirect("/mitglied/" + id); } catch (error) { console.error(error); req.session.message = ["Error", error, "error"]; res.redirect("/mitglied/" + id); } }); // Login und Registrierung anzeigen app.get("/login", (req, res) => { req.session.message = ["", "", "none"]; res.render("login", { session: req.session }); // Stelle sicher, dass es eine login.ejs gibt }); // Registrierung app.get("/register", (req, res) => { res.render("register", { session: req.session }); // Stelle sicher, dass es eine register.ejs gibt }); // Spieleliste app.get("/spiele", async (req, res) => { try { const spieleResult = await pool.query( "SELECT * FROM spiele ORDER BY name ASC;" ); const spiele = spieleResult.rows; res.render("spiele", { spiele, session: req.session }); } catch (error) { console.error("Error in Mitglied:", error); req.session.message = ["Error", error, "error"]; res.render("spiele", { spiele, session: req.session }); } }); app.post("/delete-spiel", requireAdmin, async (req, res) => { const { spielId } = req.body; await pool.query("DELETE FROM spiele WHERE id = $1", [spielId]); res.redirect("/spiele"); }); // Gerenderte Seite für gewähltes Spiel app.get("/spiel/:id", async (req, res) => { const { id } = req.params; try { const spieleResult = await pool.query( "SELECT * FROM spiele WHERE id = $1", [id] ); if (spieleResult.rows.length > 0) { const spiel = spieleResult.rows[0]; res.render("spiel", { spiel, session: req.session }); } else { req.session.message = ["Error", "Spiel existiert nicht", "error"]; res.redirect("/spiele"); } } catch (error) { console.error("Error in Spiele:", error); req.session.message = ["Error", "Spiel existiert nicht", "error"]; res.redirect("/spiele"); } }); // Postseite für Änderungen app.post("/edit-spiel", requireAuth, async (req, res) => { const { material, dauer, regeln, variationen, type, id } = req.body; try { const queryResult = await pool.query( "UPDATE spiele set material = $1, regeln = $2, dauer = $3, variationen = $4, type = $5 Where id = $6 ", [material, regeln, dauer, variationen, type, id] ); req.session.message = ["Erfolg", "Das Spiel wurde gespeichert", "success"]; res.redirect("/spiel/" + id); } catch (error) { console.error("Error in edit Spiel:", error); req.session.message = ["Error", error, "error"]; res.redirect("/spiel/" + id); } }); // Startseite app.get("/", (req, res) => { req.session.message = ["", "", "none"]; res.render("index", { session: req.session }); }); // Changelog app.get("/changelog", async (req, res) => { try { const changeResult = await pool.query( "SELECT * FROM changelog ORDER BY datetime DESC;" ); const changes = changeResult.rows; req.session.message = ["Erfolg", "Log gespeichert", "success"]; res.render("changelog", { changes, session: req.session }); } catch (error) { console.error("Error:", error); req.session.message = ["Error", error, "error"]; res.redirect("/changelog"); } }); app.post("/changelog", requireAdmin, async (req, res) => { const { title, body } = req.body; try { await pool.query("INSERT INTO changelog (title, body) VALUES ($1, $2);", [ title, body, ]); const changeResult = await pool.query( "SELECT * FROM changelog ORDER BY datetime DESC;" ); const changes = changeResult.rows; res.render("changelog", { changes, session: req.session }); } catch (error) { console.error("Error:", error); req.session.message = ["Error", error, "error"]; res.render("changelog", { changes, session: req.session }); } }); app.post("/anwesend", requireAuth, async (req, res) => { var { anw, inriege, trainingId, riege } = req.body; req.session.activeTab = "anwesend"; try { for (const mitgliedId of inriege) { const Idresult = await pool.query( "SELECT *From anwesend WHERE fid_teilnehmer = $1 AND fid_training=$2", [mitgliedId, trainingId] ); if (Idresult.rows.length > 0) { if (!anw.includes(mitgliedId)) { await pool.query( "Delete FROM anwesend WHERE fid_teilnehmer = $1 AND fid_training=$2", [mitgliedId, trainingId] ); } } } if (!Array.isArray(anw)) { anw = [anw]; } for (const teilnehmer of anw) { const Tnresult = await pool.query( "SELECT *From anwesend WHERE fid_teilnehmer = $1 AND fid_training=$2", [teilnehmer, trainingId] ); if (Tnresult.rows.length === 0) { await pool.query( "INSERT INTO anwesend (fid_teilnehmer, fid_training) VALUES ($1, $2)", [teilnehmer, trainingId] ); } } req.session.activeRiege = riege; req.session.activeTab = "anwesend"; req.session.message = [ (title = "Erfolg"), (body = "Die Anwesenheit wurde gespeichert"), (type = "success"), ]; res.redirect("/training"); } catch (error) { console.error("Error:", error); req.session.message = ["Error", error, "error"]; res.redirect("/training"); } }); app.post("/deleteMember", requireAuth, async (req, res) => { const { id } = req.body; try { await pool.query("DELETE from teilnehmende WHERE id = $1", [id]); } catch (error) { console.error("Error:", error); } res.redirect("/teilnehmer"); }); app.get("/feature", requireAuth, async (req, res) => { try { const featureResult = await pool.query( "SELECT * FROM features ORDER BY datetime DESC;" ); const features = featureResult.rows; res.render("feature", { features, session: req.session }); } catch (error) { console.error("Error:", error); req.session.message = ["Error", error, "error"]; res.redirect("/feature"); } }); app.post("/feature", requireAdmin, async (req, res) => { const { title, body, type, urgency, user } = req.body; const userResult = await pool.query( "SELECT id FROM users WHERE username = $1", [user] ); try { await pool.query( "INSERT INTO features (title, body, type, urgency, fid_user) VALUES ($1, $2, $3, $4, $5);", [title, body, type, urgency, userResult.rows[0].id] ); const featureResult = await pool.query( "SELECT * FROM features ORDER BY datetime DESC;" ); const features = featureResult.rows; req.session.message = ["Erfolg", "Feature-request gespeichert", "success"]; res.render("feature", { features, session: req.session }); } catch (error) { console.error("Error:", error); req.session.message = ["Error", error, "error"]; res.render("feature", { features, session: req.session }); } }); const server = app.listen(port, "0.0.0.0", () => { log.Info(`Server is running on ${process.env.HOST}:${port}/`); });