import { resolve } from "node:path"; import Fastify from "fastify"; import { serializerCompiler, validatorCompiler, type ZodTypeProvider, } from "@fastify/type-provider-zod"; import type { AppConfig } from "./config.js"; import { loadConfig } from "./config.js"; import authPlugin from "./plugins/auth.js"; import { requireAuth } from "./plugins/auth.js"; import corsPlugin from "./plugins/cors.js"; import cfClientPlugin from "./plugins/cf-client.js"; import dbPlugin from "./plugins/db.js"; import errorHandlerPlugin from "./plugins/error-handler.js"; import { authRoutes, healthRoutes } from "./routes/health.js"; import { settingsRoutes } from "./routes/settings.js"; import { fleetRoutes } from "./routes/fleet.js"; export interface BuildAppOptions { config?: AppConfig; memory?: boolean; } export async function buildApp(opts: BuildAppOptions = {}) { const config = opts.config ?? loadConfig(); const app = Fastify({ logger: { level: config.logLevel }, }).withTypeProvider(); app.setValidatorCompiler(validatorCompiler); app.setSerializerCompiler(serializerCompiler); await app.register(import("@fastify/sensible")); await app.register(import("@fastify/helmet"), { contentSecurityPolicy: false, }); await app.register(import("@fastify/rate-limit"), { max: 300, timeWindow: "1 minute", }); await app.register(corsPlugin); await app.register(errorHandlerPlugin); await app.register(dbPlugin, { config, memory: opts.memory }); await app.register(cfClientPlugin, { config }); await app.register(authPlugin, { config }); await app.register(healthRoutes); await app.register(authRoutes, { prefix: "/api/v1" }); await app.register( async (protectedApi) => { protectedApi.addHook("onRequest", requireAuth); await protectedApi.register(settingsRoutes); await protectedApi.register(fleetRoutes); }, { prefix: "/api/v1" }, ); const staticDir = config.staticDir ?? resolve(process.cwd(), "static"); if (config.staticDir !== null) { await app.register(import("@fastify/static"), { root: staticDir, wildcard: false, }); app.setNotFoundHandler(async (_request, reply) => { return reply.sendFile("index.html"); }); } return app; }