Build & Deploy / deploy (push) Waiting to run
- Absturzsicherheit: Express 4 faengt Fehler aus async-Handlern nicht ab, eine einzige fehlerhafte Anfrage konnte den ganzen Shop beenden - besonders heikel bei capture-order, wo das direkt nach der PayPal-Abbuchung passieren wuerde. Neu: lib/async-wrap.js um alle Handler, zentraler Fehler-Handler in index.js, .catch(next) in der Zugangsschranke, unhandledRejection/uncaughtException-Netz. - Fehlerhaftes JSON lieferte bisher Express' HTML-Fehlerseite mit komplettem Stacktrace und Serverpfaden (weil NODE_ENV nicht gesetzt war). Jetzt saubere JSON-Antwort, NODE_ENV=production in .env.example ergaenzt. - Sicherheits-Header und x-powered-by wie bei der DogFather-Seite. - Falsche Domain .de statt .com korrigiert: astro.config.mjs (betraf alle Canonical-URLs und die komplette Sitemap), robots.txt, Layout.astro sowie den Verwaltungs-Link in jeder Bestellbenachrichtigung (war ein toter Link). - Fehlende Uebersetzung nav.cart in EN/CH/FR ergaenzt: das Warenkorb-Symbol hatte in drei von vier Sprachen keinen Namen fuer Screenreader.
108 lines
4.8 KiB
JavaScript
108 lines
4.8 KiB
JavaScript
/* =====================================================================
|
||
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}`);
|
||
});
|