const express = require("express"); const { Pool } = require("pg"); const os = require("os"); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const cors = require('cors'); const app = express(); const port = process.env.PORT || 3000; const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const JWT_SECRET = "supergeheimes_tg_cms_secret"; // Bodyparser für JSON aktivieren app.use(cors({ origin: ["http://localhost:8080", "http://192.168.50.65:8080"], methods: ["GET", "POST", "PUT", "DELETE"], credentials: true })); app.use(express.json()); app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); // PostgreSQL Verbindung aufbauen const pool = new Pool({ user: "tgadmin", host: "db", // Docker Container Name! database: "tg-cms", password: "secretpass", port: 5432, }); // Testroute API-Check app.get("/api/hello", (req, res) => { res.send("Hello World from TG CMS API! 🚀"); }); // Testroute DB-Check app.get("/api/test-db", async (req, res) => { try { const result = await pool.query("SELECT NOW()"); res.json({ time: result.rows[0].now }); } catch (err) { console.error(err); res.status(500).send("Fehler bei DB-Verbindung"); } }); // Alle News abrufen app.get("/api/news", async (req, res) => { try { const result = await pool.query('SELECT * FROM news ORDER BY created_at DESC'); res.json(result.rows); } catch (err) { console.error(err); res.status(500).send("Fehler beim Abrufen der News"); } }); // Neue News anlegen app.post("/api/news", async (req, res) => { const { title, description, image_url, team } = req.body; if (!title || !description) { return res.status(400).send("Titel und Beschreibung sind Pflichtfelder"); } try { const result = await pool.query( 'INSERT INTO news (title, description, image_url, team) VALUES ($1, $2, $3, $4) RETURNING *', [title, description, image_url, team] ); res.status(201).json(result.rows[0]); } catch (err) { console.error(err); res.status(500).send("Fehler beim Anlegen der News"); } }); // News bearbeiten app.put("/api/news/:id", async (req, res) => { const { id } = req.params; const { title, description, image_url, team } = req.body; try { const result = await pool.query( 'UPDATE news SET title = $1, description = $2, image_url = $3, team = $4 WHERE id = $5 RETURNING *', [title, description, image_url, team, id] ); if (result.rowCount === 0) { return res.status(404).send("News-Eintrag nicht gefunden"); } res.json(result.rows[0]); } catch (err) { console.error(err); res.status(500).send("Fehler beim Aktualisieren der News"); } }); // News löschen app.delete("/api/news/:id", async (req, res) => { const { id } = req.params; try { const result = await pool.query( 'DELETE FROM news WHERE id = $1', [id] ); if (result.rowCount === 0) { return res.status(404).send("News-Eintrag nicht gefunden"); } res.send("News-Eintrag erfolgreich gelöscht"); } catch (err) { console.error(err); res.status(500).send("Fehler beim Löschen der News"); } }); // Neuen Benutzer anlegen app.post("/api/users", async (req, res) => { const { username, password, role, email } = req.body; if (!username || !password || !email) { return res.status(400).send("Username, Email & Passwort sind Pflicht"); } try { // Passwort hashen const hashedPassword = await bcrypt.hash(password, 10); // In DB speichern const result = await pool.query( 'INSERT INTO users (username, password, role, email) VALUES ($1, $2, $3, $4) RETURNING id, username, role, email, created_at', [username, hashedPassword, role ||'user', email] ); res.status(201).json(result.rows[0]); } catch (err) { console.error(err); res.status(500).send("Fehler beim Anlegen des Benutzers"); } }); // Benutzer Login app.post("/api/login", async (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).send("Username und Passwort erforderlich"); } try { const result = await pool.query( 'SELECT * FROM users WHERE username = $1', [username] ); const user = result.rows[0]; if (!user) { return res.status(400).send("Benutzer existiert nicht"); } // Passwort prüfen const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { return res.status(401).send("Passwort falsch"); } await pool.query( 'UPDATE users SET last_logged = NOW() WHERE id = $1', [user.id] ); // JWT Token erzeugen const token = jwt.sign( { id: user.id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: "6h" } ); res.json({ token }); } catch (err) { console.error(err); res.status(500).send("Fehler beim Login"); } }); // Alle Benutzer abrufen app.get("/api/users", async (req, res) => { try { const result = await pool.query( 'SELECT id, username, role, email, created_at FROM users ORDER BY created_at DESC' ); res.json(result.rows); } catch (err) { console.error(err); res.status(500).send("Fehler beim Abrufen der Benutzer"); } }); // Benutzer l�loeschen app.delete("/api/users/:id", async (req, res) => { const { id } = req.params; try { // Prüfen, ob der Benutzer existiert const checkResult = await pool.query('SELECT * FROM users WHERE id = $1', [id]); if (checkResult.rowCount === 0) { return res.status(404).send("Benutzer nicht gefunden"); } // Benutzer löschen await pool.query('DELETE FROM users WHERE id = $1', [id]); res.send("Benutzer erfolgreich gelöscht"); } catch (err) { console.error(err); res.status(500).send("Fehler beim Löschen des Benutzers"); } }); // Benutzer bearbeiten app.put("/api/users/:id", async (req, res) => { const { id } = req.params; const { username, password, role } = req.body; try { // Passwort optional neu setzen let query = 'UPDATE users SET username = $1, role = $2'; let params = [username, role, id]; if (password) { const hashedPassword = await bcrypt.hash(password, 10); query = 'UPDATE users SET username = $1, password = $2, role = $3 WHERE id = $4'; params = [username, hashedPassword, role, id]; } else { query += ' WHERE id = $3'; } const result = await pool.query(query, params); if (result.rowCount === 0) { return res.status(404).send("Benutzer nicht gefunden"); } res.send("Benutzer erfolgreich aktualisiert"); } catch (err) { console.error(err); res.status(500).send("Fehler beim Aktualisieren des Benutzers"); } }); // Speicherort definieren const newsStorage = multer.diskStorage({ destination: (req, file, cb) => { const dir = "./uploads/news"; fs.mkdirSync(dir, { recursive: true }); cb(null, dir); }, filename: (req, file, cb) => { const ext = path.extname(file.originalname); const filename = Date.now() + ext; cb(null, filename); }, }); const playerStorage = multer.diskStorage({ destination: (req, file, cb) => { const dir = "./uploads/players"; fs.mkdirSync(dir, { recursive: true }); cb(null, dir); }, filename: (req, file, cb) => { const ext = path.extname(file.originalname); const filename = Date.now() + ext; cb(null, filename); }, }); const uploadNewsImage = multer({ storage: newsStorage }); const uploadPlayerImage = multer({ storage: playerStorage }); //Neues Bild zu den News hinzufügen app.post("/api/upload-news-image", uploadNewsImage.single("image"), (req, res) => { if (!req.file) { return res.status(400).send("Kein Bild hochgeladen"); } const imageUrl = `/uploads/news/${req.file.filename}`; res.json({ imageUrl }); }); //Teams abfragen app.get("/api/teams", async (req, res) => { try { const result = await pool.query(` SELECT t.id, t.name, t.liga, t.sucht_spieler, t.social_media, t.trainer_name, t.karussell_bilder, -- 👉 Hier angepasst! COUNT(pt.player_id) AS player_count FROM teams t LEFT JOIN player_teams pt ON pt.team_id = t.id GROUP BY t.id, t.name, t.liga, t.sucht_spieler, t.social_media, t.trainer_name, t.karussell_bilder -- 👉 Hier auch ORDER BY t.name ASC `); res.json(result.rows); } catch (err) { console.error("Fehler beim Abrufen der Teams:", err); res.status(500).send("Fehler beim Abrufen der Teams"); } }); // Team aktualisieren app.put("/api/teams/:id", async (req, res) => { const { id } = req.params; const { name, liga, sucht_spieler, social_media, karussell_bilder, trainer_name, trainingszeiten, trainingsort, kontakt_name, kontakt_email, teamfarben, beschreibung, tabellenlink } = req.body; try { const result = await pool.query( `UPDATE teams SET name = $1, liga = $2, sucht_spieler = $3, social_media = $4, karussell_bilder = $5, trainer_name = $6, trainingszeiten = $7, trainingsort = $8, kontakt_name = $9, kontakt_email = $10, teamfarben = $11, beschreibung = $12, tabellenlink = $13 WHERE id = $14 RETURNING *`, [ name, liga, sucht_spieler, social_media, karussell_bilder, trainer_name, trainingszeiten, trainingsort, kontakt_name, kontakt_email, teamfarben, beschreibung, tabellenlink, id ] ); if (result.rowCount === 0) { return res.status(404).send("Team nicht gefunden"); } res.json(result.rows[0]); } catch (err) { console.error("Fehler beim Aktualisieren des Teams:", err); res.status(500).send("Fehler beim Aktualisieren des Teams"); } }); //Teams anlegen app.post("/api/teams", async (req, res) => { const { name, liga, sucht_spieler, social_media, karussell_bilder, trainer_name, trainingszeiten, trainingsort, kontakt_name, kontakt_email, teamfarben, beschreibung, tabellenlink } = req.body; if (!name) return res.status(400).send("Teamname ist erforderlich"); try { const result = await pool.query( `INSERT INTO teams ( name, liga, sucht_spieler, social_media, karussell_bilder, trainer_name, trainingszeiten, trainingsort, kontakt_name, kontakt_email, teamfarben, beschreibung, tabellenlink ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ) RETURNING *`, [ name, liga, sucht_spieler, social_media, karussell_bilder, trainer_name, trainingszeiten, trainingsort, kontakt_name, kontakt_email, teamfarben, beschreibung, tabellenlink ] ); res.status(201).json(result.rows[0]); } catch (err) { console.error("Fehler beim Anlegen des Teams:", err); res.status(500).send("Fehler beim Anlegen des Teams"); } }); //Team loeschen app.delete("/api/teams/:id", async (req, res) => { const { id } = req.params; try { const result = await pool.query("DELETE FROM teams WHERE id = $1", [id]); if (result.rowCount === 0) { return res.status(404).send("Team nicht gefunden"); } res.send("Team gelöscht"); } catch (err) { console.error("Fehler beim Löschen:", err); res.status(500).send("Fehler beim Löschen"); } }); //Spieler anlegen app.post("/api/players", async (req, res) => { const { name, nickname, birthdate, position, jersey_number, favorite_food, image_url, status, team_ids } = req.body; if (!name || !position) { return res.status(400).send("Name und Position sind Pflichtfelder"); } const client = await pool.connect(); try { await client.query("BEGIN"); const result = await client.query( `INSERT INTO players (name, nickname, birthdate, position, jersey_number, favorite_food, image_url, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`, [name, nickname, birthdate, position, jersey_number, favorite_food, image_url, status || "aktiv"] ); const playerId = result.rows[0].id; if (team_ids && team_ids.length > 0) { const teamInsert = team_ids.map((teamId) => `(${playerId}, ${teamId})`).join(","); await client.query(`INSERT INTO player_teams (player_id, team_id) VALUES ${teamInsert}`); } await client.query("COMMIT"); res.status(201).json({ id: playerId }); } catch (err) { await client.query("ROLLBACK"); console.error("Fehler beim Anlegen des Spielers:", err); res.status(500).send("Fehler beim Anlegen des Spielers"); } finally { client.release(); } }); // Einzelnes Team inkl. Metadaten + Spieler laden app.get("/api/teams/:id", async (req, res) => { const { id } = req.params; try { // Team-Daten holen const teamResult = await pool.query( `SELECT id, name, liga, sucht_spieler, social_media, trainer_name, karussell_bilder, trainingszeiten, trainingsort, kontakt_name, kontakt_email, teamfarben, beschreibung, tabellenlink FROM teams WHERE id = $1`, [id] ); if (teamResult.rowCount === 0) { return res.status(404).send("Team nicht gefunden"); } // Spieler für dieses Team holen const playerResult = await pool.query(` SELECT p.id, p.name, p.nickname, p.position, p.jersey_number, p.image_url FROM players p INNER JOIN player_teams pt ON p.id = pt.player_id WHERE pt.team_id = $1 ORDER BY p.name ASC `, [id]); res.json({ ...teamResult.rows[0], players: playerResult.rows, }); } catch (err) { console.error("Fehler beim Laden des Teams:", err); res.status(500).send("Fehler beim Laden des Teams"); } }); //Alle Spieler abrufen app.get("/api/players", async (req, res) => { try { const result = await pool.query("SELECT * FROM players ORDER BY name ASC"); res.json(result.rows); } catch (err) { console.error("Fehler beim Abrufen der Spieler:", err); res.status(500).send("Fehler beim Abrufen der Spieler"); } }); //Upload Player Image app.post("/api/upload-player-image", uploadPlayerImage.single("image"), (req, res) => { if (!req.file) { return res.status(400).send("Kein Bild hochgeladen"); } const imageUrl = `/uploads/players/${req.file.filename}`; res.json({ imageUrl }); }); //Spieler bearbeiten // Spieler aktualisieren app.put("/api/players/:id", async (req, res) => { const { id } = req.params; const { name, nickname, birthdate, position, jersey_number, favorite_food, status, image_url, } = req.body; if (!name || !position) { return res.status(400).send("Name und Position sind Pflichtfelder"); } try { const result = await pool.query( `UPDATE players SET name = $1, nickname = $2, birthdate = $3, position = $4, jersey_number = $5, favorite_food = $6, status = $7, image_url = $8 WHERE id = $9 RETURNING *`, [ name, nickname || null, birthdate || null, position, jersey_number ? Number(jersey_number) : null, favorite_food || null, status || "aktiv", image_url || null, id, ] ); if (result.rowCount === 0) { return res.status(404).send("Spieler nicht gefunden"); } res.json(result.rows[0]); } catch (err) { console.error("Fehler beim Aktualisieren des Spielers:", err); res.status(500).send("Fehler beim Aktualisieren des Spielers"); } }); //Einzelnen Spieler abrufen app.get("/api/players/:id", async (req, res) => { const { id } = req.params; try { const result = await pool.query( "SELECT * FROM players WHERE id = $1", [id] ); if (result.rowCount === 0) { return res.status(404).send("Spieler nicht gefunden"); } const player = result.rows[0]; // 🔥 Team-IDs abfragen const teamResult = await pool.query( "SELECT team_id FROM player_teams WHERE player_id = $1", [id] ); const team_ids = teamResult.rows.map(r => r.team_id); res.json({ ...player, team_ids }); } catch (err) { console.error("Fehler beim Abrufen des Spielers:", err); res.status(500).send("Fehler beim Abrufen des Spielers"); } }); // Spieler aus einem Team entfernen app.delete("/api/teams/:teamId/players/:playerId", async (req, res) => { const { teamId, playerId } = req.params; try { const result = await pool.query( "DELETE FROM player_teams WHERE team_id = $1 AND player_id = $2", [teamId, playerId] ); if (result.rowCount === 0) { return res.status(404).send("Spieler nicht im Team gefunden"); } res.send("Spieler erfolgreich aus dem Team entfernt"); } catch (err) { console.error("Fehler beim Entfernen des Spielers:", err); res.status(500).send("Fehler beim Entfernen des Spielers"); } }); // Existierenden Spieler einem Team hinzufügen app.post("/api/teams/:teamId/players", async (req, res) => { const { teamId } = req.params; const { playerId } = req.body; if (!playerId) { return res.status(400).send("playerId ist erforderlich"); } try { const result = await pool.query( "INSERT INTO player_teams (player_id, team_id) VALUES ($1, $2)", [playerId, teamId] ); res.status(201).send("Spieler erfolgreich dem Team zugeordnet"); } catch (err) { if (err.code === "23505") { return res.status(400).send("Spieler ist bereits im Team"); } console.error("Fehler beim Hinzufügen des Spielers zum Team:", err); res.status(500).send("Fehler beim Hinzufügen des Spielers"); } }); // Alle Teams abrufen app.get("/api/all-teams", async (req, res) => { try { const result = await pool.query("SELECT id, name FROM teams ORDER BY name ASC"); res.json(result.rows); } catch (err) { console.error("Fehler beim Laden der Teams:", err); res.status(500).send("Fehler beim Laden der Teams"); } }); //Spieler einem Team zuweisen app.post("/api/players/:id/assign-team", async (req, res) => { const { id } = req.params; const { team_id } = req.body; if (!team_id) return res.status(400).send("team_id erforderlich"); try { await pool.query("DELETE FROM player_teams WHERE player_id = $1", [id]); await pool.query("INSERT INTO player_teams (player_id, team_id) VALUES ($1, $2)", [id, team_id]); res.send("Teamzugehörigkeit aktualisiert"); } catch (err) { console.error("Fehler beim Aktualisieren der Teamzugehörigkeit:", err); res.status(500).send("Fehler beim Aktualisieren"); } }); //Carousell Bilder für Teams hochladen const multer = require("multer"); const path = require("path"); const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "uploads/carousel"); }, filename: function (req, file, cb) { const uniqueName = Date.now() + "-" + file.originalname; cb(null, uniqueName); }, }); const upload = multer({ storage }); router.post("/api/teams/:id/carousel-upload", upload.single("image"), (req, res) => { if (!req.file) return res.status(400).json({ error: "Keine Datei hochgeladen" }); // z. B. /uploads/carousel/171654543-name.jpg const filePath = `/uploads/carousel/${req.file.filename}`; res.json({ path: filePath }); }); // Server starten app.listen(port, () => { const nets = os.networkInterfaces(); const addresses = []; for (const name of Object.keys(nets)) { for (const net of nets[name]) { if (net.family === 'IPv4' && !net.internal) { addresses.push(net.address); } } } console.log("Backend erreichbar unter:"); addresses.forEach(ip => { console.log(`http://${ip}:${port}`); }); });