require("dotenv").config(); const express = require("express"); const path = require("path"); const helmet = require("helmet"); const rateLimit = require("express-rate-limit"); const http = require("http"); const { Server } = require("socket.io"); const db = require("./database/database"); const serverRoutes = require("./routes/servers"); const registerRoutes = require("./routes/register"); const verifyRoutes = require("./routes/verify"); const characterRoutes = require("./routes/character"); const session = require("express-session"); const loginRoutes = require("./routes/login"); const launcherRoutes = require("./routes/launcher"); const buildingRoutes = require("./routes/buildings"); const inventory = require("./routes/inventory"); const avatar = require("./routes/avatar"); const equip = require("./routes/equip"); const equipment = require("./routes/equipment"); const app = express(); app.set("trust proxy", 1); const PORT = process.env.PORT || 3000; /* ======================== Chatserver ======================== */ const server = http.createServer(app); const io = new Server(server); /* ======================== Security Middleware ======================== */ app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:"], connectSrc: ["'self'", "ws:", "wss:"], }, }, }), ); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, }); app.use(limiter); app.use( session({ secret: "dynastyofknights_secret", resave: false, saveUninitialized: false, }), ); /* ======================== Express Settings ======================== */ app.set("view engine", "ejs"); app.set("views", path.join(__dirname, "views")); /* ======================== Login Middleware ======================== */ function requireLogin(req, res, next) { if (!req.session.user) { return res.status(401).json({ error: "Nicht eingeloggt" }); } next(); } /* ======================== Route für Ajax für Gebäude ======================== */ app.get("/api/building/:id", requireLogin, async (req, res) => { const buildingId = req.params.id; const userId = req.session.user.id; try { const [userBuilding] = await db.query( "SELECT level, points FROM user_buildings WHERE user_id=? AND building_id=?", [userId, buildingId], ); let building; if (!userBuilding.length) { await db.query( "INSERT INTO user_buildings (user_id,building_id,level,points) VALUES (?,?,1,0)", [userId, buildingId], ); building = { level: 1, points: 0 }; } else { building = userBuilding[0]; } const [nextLevel] = await db.query( "SELECT required_points, wood, stone, gold FROM building_levels WHERE building_id=? AND level=?", [buildingId, building.level + 1], ); const [info] = await db.query( "SELECT name,description,history FROM buildings WHERE id=?", [buildingId], ); const buildingInfo = info[0] || {}; res.json({ name: buildingInfo.name || "Gebäude", type: buildingId, // 👈 DAS HINZUFÜGEN level: building.level, points: building.points, nextLevelPoints: nextLevel[0]?.required_points || null, description: info[0].description, history: info[0].history, upgradeCost: `${nextLevel[0]?.wood} Holz, ${nextLevel[0]?.stone} Stein, ${nextLevel[0]?.gold} Gold`, }); } catch (err) { console.error(err); res.status(500).json({ error: "DB Fehler" }); } }); app.get("/api/buildings", requireLogin, async (req, res) => { const userId = req.session.user.id; try { const [rows] = await db.query( ` SELECT b.id, b.name, b.description, b.history, ub.level, ub.points FROM buildings b LEFT JOIN user_buildings ub ON ub.building_id = b.id AND ub.user_id = ? `, [userId], ); res.json(rows); } catch (err) { console.error(err); res.status(500).json({ error: "DB Fehler" }); } }); /* ======================== Body Parser ======================== */ app.use(express.json()); app.use(express.urlencoded({ extended: true })); /* ======================== Static Files ======================== */ app.use(express.static(path.join(__dirname, "public"))); /* ======================== Routes ======================== */ app.use("/", serverRoutes); app.use("/register", registerRoutes); app.use("/verify", verifyRoutes); app.use("/create-character", characterRoutes); app.use("/login", loginRoutes); app.use("/launcher", launcherRoutes); app.use("/", buildingRoutes); app.use("/api/inventory", inventory); app.use("/api/avatar", avatar); app.use("/api/equip", equip); app.use("/api/equipment", equipment); /* ======================== 404 Handler ======================== */ app.use((req, res) => { res.status(404).send("Seite nicht gefunden"); }); /* ======================== Webseite beschleunigen ======================== */ const compression = require("compression"); app.use(compression()); /* ======================== Chat System ======================== */ let onlineUsers = {}; io.on("connection", (socket) => { console.log("Spieler verbunden"); socket.on("register", async (username) => { const [rows] = await db.query( "SELECT ingame_name FROM accounts WHERE username = ?", [username], ); if (!rows.length) return; const ingameName = rows[0].ingame_name; socket.user = ingameName; onlineUsers[ingameName] = socket.id; io.emit("onlineUsers", Object.keys(onlineUsers)); }); socket.on("disconnect", () => { if (socket.user) { delete onlineUsers[socket.user]; io.emit("onlineUsers", Object.keys(onlineUsers)); } }); socket.on("chatMessage", (data) => { if (data.channel === "global") { io.emit("chatMessage", { user: socket.user, message: data.message, channel: "global", }); } if (data.channel === "guild") { io.to("guild_" + data.guild).emit("chatMessage", { user: socket.user, message: data.message, channel: "guild", }); } }); socket.on("whisper", (data) => { const targetSocket = onlineUsers[data.to]; if (!targetSocket) { socket.emit("systemMessage", { message: data.to + " ist offline", }); return; } io.to(targetSocket).emit("chatMessage", { user: socket.user, message: data.message, channel: "private", }); socket.emit("chatMessage", { user: "(an " + data.to + ")", message: data.message, channel: "private", }); }); socket.on("privateMessage", (data) => { const target = onlineUsers[data.to]; if (target) { io.to(target).emit("chatMessage", { user: socket.user, message: data.message, channel: "private", }); } }); }); /* ======================== Server Start ======================== */ server.listen(PORT, () => { console.log(`Dynasty of Knights Server läuft auf http://localhost:${PORT}`); });