SPB Git

spb/spinza

Public
2.9 KB · 76 lines typescript
1import Fastify, { type FastifyInstance } from "fastify";2import cookie from "@fastify/cookie";3import { ZodError } from "zod";4import { config } from "./config";5import { ApiError } from "./lib/errors";6import { authPlugin } from "./plugins/auth";7import { authRoutes } from "./routes/auth";8import { userRoutes } from "./routes/user";9import { gameRoutes } from "./routes/games";10import { rewardRoutes } from "./routes/rewards";11import { healthRoutes } from "./routes/health";12import { adminRoutes } from "./routes/admin";13import { refreshSettings } from "./lib/settings";1415const REDACT = ["req.headers.cookie", "req.headers.authorization", "*.password", "*.newPassword", "*.currentPassword", "*.recoveryCode", "*.totp", "*.passwordHash", "*.tokenHash"];1617export async function buildApp(): Promise<FastifyInstance> {18  const app = Fastify({19    logger: {20      level: config.logLevel,21      redact: { paths: REDACT, censor: "[redacted]" },22      transport: config.env === "development" ? { target: "pino-pretty", options: { translateTime: "HH:MM:ss", ignore: "pid,hostname" } } : undefined,23    },24    trustProxy: config.trustProxy,25    bodyLimit: 64 * 1024,26    genReqId: () => Math.random().toString(36).slice(2, 10), // request ids only — never used for game outcomes27  });2829  await app.register(cookie, { secret: config.sessionSecret });30  await app.register(authPlugin);3132  app.addHook("onRequest", async (_req, reply) => {33    reply.header("Cache-Control", "no-store");34    reply.header("X-Content-Type-Options", "nosniff");35  });3637  app.addHook("onReady", async () => {38    await refreshSettings(true);39    setInterval(() => refreshSettings().catch(() => {}), 10_000).unref();40  });4142  app.setErrorHandler((err: Error & { validation?: unknown; statusCode?: number }, req, reply) => {43    if (err instanceof ApiError) {44      reply.code(err.status).send({ error: err.code, message: err.message, details: err.details });45      return;46    }47    if (err instanceof ZodError) {48      reply.code(400).send({ error: "VALIDATION", message: "Invalid request.", details: err.flatten() });49      return;50    }51    if ((err as { validation?: unknown }).validation) {52      reply.code(400).send({ error: "VALIDATION", message: err.message });53      return;54    }55    if ((err as { statusCode?: number }).statusCode === 413) {56      reply.code(413).send({ error: "PAYLOAD_TOO_LARGE", message: "Request too large." });57      return;58    }59    req.log.error({ err }, "unhandled error");60    reply.code(500).send({ error: "INTERNAL", message: "Something went wrong on our side." });61  });6263  app.setNotFoundHandler((_req, reply) => {64    reply.code(404).send({ error: "NOT_FOUND", message: "Route not found." });65  });6667  await app.register(healthRoutes);68  await app.register(authRoutes);69  await app.register(userRoutes);70  await app.register(gameRoutes);71  await app.register(rewardRoutes);72  await app.register(adminRoutes);7374  return app;75}76
Files · 3b4179a710ab1cd5f12ca7cfd253b81674f1521e
Menu

Theme