dde53319e5
- New custom server.js (replaces react-router-serve): ISO timestamps on all console.* output and on access logs, and skip successful /healthz polls so real traffic stays visible. - New ProcessedWebhook table + dedupe helper keyed on X-Shopify-Webhook-Id; stops Shopify retries from triggering a second invoice email when the original delivery exceeded the 5s ack timeout. - orders/create + orders/fulfilled now respond 200 immediately and run the PDF/email work in the background so we stay under that timeout. - pickLanguage(): non-German locales (it, fr, es, ...) now default to English instead of falling back to German. Empty/unknown still maps to 'de' so the per-shop defaultLanguage chain keeps working. - Tests for pickLanguage and dedupe via node --test + tsx.
82 lines
2.6 KiB
JavaScript
82 lines
2.6 KiB
JavaScript
// Custom production server for the React Router build.
|
|
//
|
|
// Replaces `react-router-serve` so we can:
|
|
// - prefix every console line with an ISO timestamp,
|
|
// - use a richer morgan format (with timestamp + content-length),
|
|
// - skip access logs for successful /healthz probes (they would otherwise
|
|
// drown out everything useful — Docker/Caddy poll them every couple of
|
|
// seconds).
|
|
//
|
|
// Behaviour is otherwise intentionally identical to `@react-router/serve`'s
|
|
// CLI (compression, /assets immutable cache, public/ static, SIGTERM/SIGINT
|
|
// handling).
|
|
|
|
import { createRequestHandler } from "@react-router/express";
|
|
import compression from "compression";
|
|
import express from "express";
|
|
import morgan from "morgan";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Console timestamps — patch BEFORE anything else logs.
|
|
// ---------------------------------------------------------------------------
|
|
const ts = () => `[${new Date().toISOString()}]`;
|
|
for (const level of ["log", "info", "warn", "error", "debug"]) {
|
|
const original = console[level].bind(console);
|
|
console[level] = (...args) => original(ts(), ...args);
|
|
}
|
|
|
|
const PORT = Number(process.env.PORT || 3000);
|
|
const HOST = process.env.HOST;
|
|
|
|
const buildModule = await import("./build/server/index.js");
|
|
const build = buildModule.default ?? buildModule;
|
|
|
|
const app = express();
|
|
app.disable("x-powered-by");
|
|
app.use(compression());
|
|
|
|
// Static assets emitted by the React Router build.
|
|
app.use(
|
|
"/assets",
|
|
express.static("build/client/assets", { immutable: true, maxAge: "1y" }),
|
|
);
|
|
app.use(express.static("build/client", { maxAge: "1h" }));
|
|
app.use(express.static("public", { maxAge: "1h" }));
|
|
|
|
// Access log: ISO timestamp + standard request info; suppress healthy
|
|
// /healthz polls so real traffic stays visible.
|
|
morgan.token("isotime", () => new Date().toISOString());
|
|
app.use(
|
|
morgan(
|
|
":isotime :method :url :status :res[content-length] - :response-time ms",
|
|
{
|
|
skip: (req, res) => req.url === "/healthz" && res.statusCode < 400,
|
|
},
|
|
),
|
|
);
|
|
|
|
app.all(
|
|
"*",
|
|
createRequestHandler({ build, mode: process.env.NODE_ENV }),
|
|
);
|
|
|
|
const onListen = () => {
|
|
console.log(
|
|
`[server] listening on http://${HOST ?? "localhost"}:${PORT}`,
|
|
);
|
|
};
|
|
|
|
const server = HOST
|
|
? app.listen(PORT, HOST, onListen)
|
|
: app.listen(PORT, onListen);
|
|
|
|
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
process.once(signal, () => {
|
|
console.log(`[server] received ${signal}, shutting down`);
|
|
server.close((err) => {
|
|
if (err) console.error("[server] close error:", err);
|
|
process.exit(0);
|
|
});
|
|
});
|
|
}
|