/* ===================================================================== index.js — Haupteinstiegspunkt. Baut den kompletten Express-Server auf: liefert die fertig gebaute Astro-Seite (../dist/) aus UND die API-Routen, genau wie Cloudflare Pages beides in einem Deployment vereint hat. Die Zugangscode-Schranke (middleware/gate.js) läuft vor JEDER Anfrage, exakt wie functions/_middleware.js das vorher bei Cloudflare tat. Umgebungsvariablen kommen aus einer .env-Datei im server/-Ordner (siehe .env.example), geladen über das "dotenv"-Paket direkt beim Start. ===================================================================== */ import "dotenv/config"; import express from "express"; import cookieParser from "cookie-parser"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { existsSync } from "node:fs"; import { initDb } from "./db.js"; import { gateMiddleware } from "./middleware/gate.js"; import { ordersRouter } from "./routes/orders.js"; import { bestsellerRouter } from "./routes/bestseller.js"; import { paypalRouter } from "./routes/paypal.js"; import { authRouter } from "./routes/auth.js"; import { accountRouter } from "./routes/account.js"; import { verwaltungRouter } from "./routes/verwaltung.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); const DIST_DIR = join(__dirname, "..", "dist"); const PORT = Number(process.env.PORT || 4000); initDb(); const app = express(); // Läuft hinter Caddy (Reverse Proxy) — nötig, damit req.protocol/req.get("host") und // "secure"-Cookies korrekt funktionieren, statt anzunehmen, die Verbindung sei unverschlüsselt. app.set("trust proxy", 1); app.disable("x-powered-by"); // verrät sonst unnötig den eingesetzten Technik-Stack /* Sicherheits-Header, ergänzt 05.08.2026 (bei Cloudflare Pages kamen die teils von der Plattform; auf dem eigenen Server muss der Server sie selbst setzen). `payment=(self)` bleibt erlaubt, damit die PayPal-Zahlung im eigenen Fenster weiter funktioniert. */ app.use((req, res, next) => { res.setHeader("X-Content-Type-Options", "nosniff"); res.setHeader("X-Frame-Options", "SAMEORIGIN"); res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=(self)"); res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); next(); }); app.use(cookieParser()); app.use(express.json({ limit: "2mb" })); // Zugangscode-Schranke vor allem anderen — exakt wie functions/_middleware.js bei Cloudflare. app.use(gateMiddleware); app.use(ordersRouter); app.use(bestsellerRouter); app.use(paypalRouter); app.use(authRouter); app.use(accountRouter); app.use(verwaltungRouter); // Statische Auslieferung der fertig gebauten Astro-Seite (nach "npm run build" in ../). if (existsSync(DIST_DIR)) { app.use(express.static(DIST_DIR, { extensions: ["html"] })); } else { console.warn(`⚠️ ${DIST_DIR} existiert nicht — "npm run build" im Astro-Projekt (../) noch nicht ausgeführt.`); } // 404 für alles, was weder Datei noch bekannte Route ist. app.use((req, res) => { res.status(404).sendFile(join(DIST_DIR, "404.html"), (err) => { if (err) res.status(404).send("Nicht gefunden."); }); }); /* ---------- Fehler-Handler (muss NACH allen Routen stehen), ergänzt 05.08.2026 ---------- Express 4 fängt Fehler aus async-Handlern nicht selbst ab. Ohne diesen Handler landete kaputtes JSON in Express' Standard-Fehlerseite — inklusive komplettem Stacktrace mit Serverpfaden, solange NODE_ENV nicht auf "production" steht. Jetzt kommt in jedem Fall eine saubere JSON-Antwort. */ // eslint-disable-next-line no-unused-vars app.use((err, req, res, next) => { if (err?.type === "entity.parse.failed") { return res.status(400).json({ ok: false, error: "Ungültige Anfrage." }); } if (err?.type === "entity.too.large") { return res.status(413).json({ ok: false, error: "Die Anfrage ist zu groß." }); } console.error(`[fehler] ${req.method} ${req.originalUrl}:`, err?.stack || err); if (res.headersSent) return; if (req.path.startsWith("/api/")) { return res.status(500).json({ ok: false, error: "Interner Fehler." }); } return res.status(500).send("Es ist ein Fehler aufgetreten. Bitte später erneut versuchen."); }); /* Letztes Netz: Ein unbehandelter Fehler darf den Shop nicht offline nehmen — besonders wichtig direkt nach einer PayPal-Abbuchung, wo ein Absturz die Kundin ohne Rückmeldung zurücklassen würde, obwohl das Geld bereits eingezogen ist. */ process.on("unhandledRejection", (grund) => console.error("[unhandledRejection]", grund)); process.on("uncaughtException", (fehler) => console.error("[uncaughtException]", fehler?.stack || fehler)); app.listen(PORT, "127.0.0.1", () => { console.log(`Van's DIY & Bastelbedarf – Server läuft auf http://127.0.0.1:${PORT}`); });