SPB Git forge

spb/spinza

Public
8commits 1branches 0releases
1.6 MBsize
maindefault branch
16 days agolast push
TypeScript 97.6% SQL 1.4% JavaScript 0.5%
3.0 KB · 80 lines typescript
Raw Blame History
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 { crashRoutes } from "./routes/crash";14import { arcadeRoutes } from "./routes/arcade";15import { refreshSettings } from "./lib/settings";1617const REDACT = ["req.headers.cookie", "req.headers.authorization", "*.password", "*.newPassword", "*.currentPassword", "*.recoveryCode", "*.totp", "*.passwordHash", "*.tokenHash"];1819export async function buildApp(): Promise<FastifyInstance> {20  const app = Fastify({21    logger: {22      level: config.logLevel,23      redact: { paths: REDACT, censor: "[redacted]" },24      transport: config.env === "development" ? { target: "pino-pretty", options: { translateTime: "HH:MM:ss", ignore: "pid,hostname" } } : undefined,25    },26    trustProxy: config.trustProxy,27    bodyLimit: 64 * 1024,28    genReqId: () => Math.random().toString(36).slice(2, 10), // request ids only — never used for game outcomes29  });3031  await app.register(cookie, { secret: config.sessionSecret });32  await app.register(authPlugin);3334  app.addHook("onRequest", async (_req, reply) => {35    reply.header("Cache-Control", "no-store");36    reply.header("X-Content-Type-Options", "nosniff");37  });3839  app.addHook("onReady", async () => {40    await refreshSettings(true);41    setInterval(() => refreshSettings().catch(() => {}), 10_000).unref();42  });4344  app.setErrorHandler((err: Error & { validation?: unknown; statusCode?: number }, req, reply) => {45    if (err instanceof ApiError) {46      reply.code(err.status).send({ error: err.code, message: err.message, details: err.details });47      return;48    }49    if (err instanceof ZodError) {50      reply.code(400).send({ error: "VALIDATION", message: "Invalid request.", details: err.flatten() });51      return;52    }53    if ((err as { validation?: unknown }).validation) {54      reply.code(400).send({ error: "VALIDATION", message: err.message });55      return;56    }57    if ((err as { statusCode?: number }).statusCode === 413) {58      reply.code(413).send({ error: "PAYLOAD_TOO_LARGE", message: "Request too large." });59      return;60    }61    req.log.error({ err }, "unhandled error");62    reply.code(500).send({ error: "INTERNAL", message: "Something went wrong on our side." });63  });6465  app.setNotFoundHandler((_req, reply) => {66    reply.code(404).send({ error: "NOT_FOUND", message: "Route not found." });67  });6869  await app.register(healthRoutes);70  await app.register(authRoutes);71  await app.register(userRoutes);72  await app.register(gameRoutes);73  await app.register(rewardRoutes);74  await app.register(crashRoutes);75  await app.register(arcadeRoutes);76  await app.register(adminRoutes);7778  return app;79}80