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 blackmarket = require("./routes/blackmarket"); const mineRoute = require("./routes/mine"); const arenaRoutes = require("./routes/arena"); const { registerArenaHandlers } = require("./sockets/arena"); const { registerChatHandlers } = require("./sockets/chat"); const compression = require("compression"); 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); /* ======================== Compression ======================== */ app.use(compression()); /* ======================== Security Middleware ======================== */ app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], scriptSrcAttr: ["'none'"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], fontSrc: ["'self'", "https://fonts.gstatic.com"], imgSrc: ["'self'", "data:"], connectSrc: ["'self'", "ws:", "wss:"], }, }, }), ); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5000, }); app.use(limiter); app.use( session({ secret: process.env.SESSION_SECRET || "dynastyofknights_secret", resave: false, saveUninitialized: false, cookie: { httpOnly: true, secure: process.env.NODE_ENV === "production", maxAge: 1000 * 60 * 60 * 24, }, }), ); /* ======================== 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, 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" }); } }); /* ======================== HUD API ======================== */ app.get("/api/hud", requireLogin, async (req, res) => { const userId = req.session.user.id; try { const [[account]] = await db.query( "SELECT ingame_name FROM accounts WHERE id = ?", [userId], ); const [[currency]] = await db.query( "SELECT silver, gold, gems, wood, stone FROM account_currency WHERE account_id = ?", [userId], ); res.json({ name: account?.ingame_name || "Held", silver: currency?.silver || 0, gold: currency?.gold || 0, gems: currency?.gems || 0, wood: currency?.wood || 0, stone: currency?.stone || 0, }); } 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); app.use("/api/blackmarket", blackmarket); app.use("/api/mine", mineRoute); app.use("/arena", arenaRoutes); /* ======================== 404 Handler ======================== */ app.use((req, res) => { res.status(404).send("Seite nicht gefunden"); }); /* ======================== Socket.io Handler ======================== */ io.on("connection", (socket) => { console.log("Spieler verbunden:", socket.id); registerChatHandlers(io, socket); registerArenaHandlers(io, socket); }); /* ======================== Server Start ======================== */ server.listen(PORT, () => { console.log(`Dynasty of Knights Server läuft auf http://localhost:${PORT}`); });