0c6a35e841
Build & Push Docker Image / build (push) Successful in 8m33s
- Invalidate categories/features queries after creating/editing tools so new categories appear immediately in search, browse dropdown and tool form - Always refetch categories/features when the combobox/suggestion inputs mount - Return JSON 404 for unmatched /api routes instead of the SPA index.html - Read the manually confirmed 'better tool' from the recommendation notes instead of using the min tool id in the redundancy dashboard - Require admin for cost/relation update+delete endpoints - Stop exposing the voter token in the ratings list response - Fix parseInt type error on user id params (Express 5 params typing)
79 lines
1.9 KiB
TypeScript
79 lines
1.9 KiB
TypeScript
import express, { type Express } from "express";
|
|
import cors from "cors";
|
|
import { existsSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import pinoHttp from "pino-http";
|
|
import session from "express-session";
|
|
import ConnectPgSimple from "connect-pg-simple";
|
|
import router from "./routes";
|
|
import { logger } from "./lib/logger";
|
|
import "./types/session.d.ts";
|
|
|
|
const PgStore = ConnectPgSimple(session);
|
|
|
|
const app: Express = express();
|
|
|
|
app.set("trust proxy", 1);
|
|
|
|
app.use(
|
|
pinoHttp({
|
|
logger,
|
|
serializers: {
|
|
req(req) {
|
|
return {
|
|
id: req.id,
|
|
method: req.method,
|
|
url: req.url?.split("?")[0],
|
|
};
|
|
},
|
|
res(res) {
|
|
return {
|
|
statusCode: res.statusCode,
|
|
};
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
app.use(cors({ origin: true, credentials: true }));
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
app.use(
|
|
session({
|
|
store: new PgStore({
|
|
conString: process.env.DATABASE_URL,
|
|
tableName: "sessions",
|
|
createTableIfMissing: true,
|
|
}),
|
|
secret: process.env.SESSION_SECRET || "dev-secret-change-in-production",
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: {
|
|
secure: process.env.NODE_ENV === "production",
|
|
httpOnly: true,
|
|
maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
|
|
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
|
|
},
|
|
}),
|
|
);
|
|
|
|
app.use("/api", router);
|
|
|
|
// Any unmatched /api route should return a JSON 404 instead of falling
|
|
// through to the SPA catch-all below.
|
|
app.use("/api", (_req, res) => {
|
|
res.status(404).json({ error: "Not found" });
|
|
});
|
|
|
|
const staticDir = process.env.STATIC_DIR;
|
|
if (staticDir && existsSync(staticDir)) {
|
|
app.use(express.static(staticDir));
|
|
app.get("/*any", (_req, res) => {
|
|
res.sendFile(resolve(staticDir, "index.html"));
|
|
});
|
|
logger.info({ staticDir }, "Serving static files");
|
|
}
|
|
|
|
export default app;
|