import express, { type ErrorRequestHandler } from "express"; import { randomUUID } from "node:crypto"; import { pinoHttp } from "pino-http"; import type { Logger } from "pino"; import type { Config } from "./lib/config.js"; import type { Db } from "./lib/db.js"; import type { Flags } from "./lib/flags.js"; import type { Cache } from "./lib/redis.js"; import { staticRuntimeConfig, type RuntimeConfig, } from "./lib/runtime-config.js"; import { adminRouter } from "./routes/admin.js"; import { healthRouter } from "./routes/health.js"; import { logDemoRouter } from "./routes/log-demo.js"; import { rootRouter } from "./routes/root.js"; import { authRouter } from "./routes/auth.js"; import { createAuth } from "./lib/auth.js"; import { memoryUserStore, pgUserStore, type UserStore, } from "./lib/user-store.js"; import { storiesRouter } from "./routes/stories.js"; import { memoryStore, pgStore, type StoryStore } from "./lib/story-store.js"; export interface Deps { config: Config; logger: Logger; db?: Db; cache?: Cache; flags?: Flags; runtimeConfig?: RuntimeConfig; store?: StoryStore; users?: UserStore; } export function createApp({ config, logger, db, cache, flags, runtimeConfig = staticRuntimeConfig(), store = db ? pgStore(db) : memoryStore(), users = db ? pgUserStore(db) : memoryUserStore(), }: Deps) { const app = express(); app.disable("x-powered-by"); app.use( pinoHttp({ logger, genReqId: (req, res) => { const incoming = req.headers["x-request-id"]; const id = (Array.isArray(incoming) ? incoming[0] : incoming) ?? randomUUID(); res.setHeader("X-Request-Id", id); return id; }, // Keep the logging contract flat and small: req.id/method/url, // res.statusCode, responseTime. serializers: { req: (req) => ({ id: req.id, method: req.method, url: req.url }), res: (res) => ({ statusCode: res.statusCode }), }, autoLogging: { ignore: (req) => req.url === "/healthz" || req.url === "/readyz", }, }), ); app.use(healthRouter({ db, cache })); app.use(rootRouter({ config, db, cache, flags, runtimeConfig })); const auth = createAuth({ config, users, db }); app.use(auth.middleware); app.use( authRouter({ auth, users, registrationOpen: () => runtimeConfig.get().registrationOpen, }), ); app.use(storiesRouter({ store })); app.use(adminRouter({ runtimeConfig })); app.use(logDemoRouter()); // Express identifies error handlers by arity; keep all four args. // eslint-disable-next-line @typescript-eslint/no-unused-vars const onError: ErrorRequestHandler = (err, req, res, _next) => { req.log.error({ err }, "unhandled error"); res.status(500).json({ error: "internal error" }); }; app.use(onError); return app; }