From feb86628b05fab15d3ae30b88015641f6f88c1f2 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:09:12 +0330 Subject: [PATCH] Import source: backend/src/app Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- backend/src/app.ts | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 backend/src/app.ts diff --git a/backend/src/app.ts b/backend/src/app.ts new file mode 100644 index 0000000..2f1f937 --- /dev/null +++ b/backend/src/app.ts @@ -0,0 +1,120 @@ +import Fastify from "fastify"; +import cors from "@fastify/cors"; +import helmet from "@fastify/helmet"; +import jwt from "@fastify/jwt"; +import { + serializerCompiler, + validatorCompiler, + ZodTypeProvider, +} from "fastify-type-provider-zod"; + +import { env } from "./config/env.js"; +import { prismaPlugin } from "./plugins/prisma.js"; +import { errorHandler } from "./plugins/error-handler.js"; +import { fail } from "./lib/envelope.js"; +import { authRoutes } from "./routes/auth.js"; +import { websiteRoutes } from "./routes/websites.js"; +import { domainRoutes } from "./routes/domains.js"; +import { panelRoutes } from "./routes/panel.js"; +import { moduleRoutes } from "./routes/modules.js"; + +export function buildApp() { + const app = Fastify({ + logger: + env.NODE_ENV === "development" + ? { + transport: { target: "pino-pretty" }, + } + : false, + }).withTypeProvider(); + + // Register JSON schemas compilers for Zod + app.setValidatorCompiler(validatorCompiler); + app.setSerializerCompiler(serializerCompiler); + app.removeContentTypeParser("application/json"); + app.addContentTypeParser( + "application/json", + { parseAs: "string" }, + (_request, body, done) => { + const text = typeof body === "string" ? body : body.toString("utf8"); + if (text.trim() === "") { + done(null, undefined); + return; + } + + try { + done(null, JSON.parse(text)); + } catch (error) { + done(error as Error, undefined); + } + }, + ); + + // Security Plugins + app.register(helmet); + app.register(cors, { origin: true, credentials: true }); + app.register(jwt, { secret: env.JWT_SECRET }); + + // Custom Plugins + app.register(prismaPlugin); + app.setErrorHandler(errorHandler); + + app.get("/api/rate-limited", async (request, reply) => + fail( + request, + reply, + 429, + { + code: "RATE_LIMIT_ERROR", + message: "Too many requests. Please try again later.", + }, + { rateLimit: { limit: 5, remaining: 0, reset: 100 } }, + ), + ); + + app.get("/api/simulate-500", async () => { + throw new Error("Internal server error simulated successfully."); + }); + + app.get("/api/auth/login", async (request, reply) => + fail(request, reply, 405, { + code: "METHOD_NOT_ALLOWED", + message: "GET is not allowed for this endpoint.", + }), + ); + + app.register(authRoutes); + app.register(websiteRoutes); + app.register(domainRoutes); + app.register(panelRoutes); + app.register(moduleRoutes); + + // Health check route + app.get("/health", async (request, reply) => { + try { + // Verify database connection using Prisma + await app.prisma.$queryRaw`SELECT 1`; + return { + status: "ok", + database: "connected", + }; + } catch (err: unknown) { + app.log.error(err); + const message = err instanceof Error ? err.message : String(err); + return reply.status(500).send({ + status: "error", + database: "disconnected", + message, + }); + } + }); + + app.setNotFoundHandler((request, reply) => + fail(request, reply, 404, { + code: "NOT_FOUND", + message: `Route ${request.method} ${request.url} was not found.`, + }), + ); + + return app; +}