const express = require("express") const app = express() const path = require("node:path") const Logger = require("./modules/logger") const logger = Logger.createLogger(__dirname) const loader = require("./modules/loader") const routes = loader.getRecursiveFiles(path.join(__dirname, "routes")) const schemas = loader.getRecursiveFiles(path.join(__dirname, "schemas")) const schemaRegistry = {} app.use(express.json()) app.use(express.urlencoded({ extended: true })) app.set("trust proxy", true) logger.log("Initializing routes", ["WEB", "yellow"]) for (const schemaFile of schemas) { try { const schemaConfig = require(schemaFile) const routePath = loader.computeRoutePath(path.join(__dirname, "schemas"), schemaFile) schemaRegistry[routePath] = schemaConfig logger.log(`${routePath.cyan.bold} schema loaded in memory`, ["WEB", "yellow"]) } catch (error) { logger.error(error.toString(), ["WEB", "yellow"]) } } app.all(/.*/, (req, res, next) => { let currentPath = req.path if (currentPath.length > 1 && currentPath.endsWith("/")) { currentPath = currentPath.slice(0, -1) } const schemaConfig = schemaRegistry[currentPath] if (!schemaConfig || !schemaConfig[req.method]) { return next() } const methodConfig = schemaConfig[req.method] const zodSchema = methodConfig.zod || methodConfig const errorConfig = methodConfig.error || { status: 400, message: "Validation Error" } const dataToValidate = (req.method === "GET" || req.method === "DELETE") ? req.query : req.body const result = zodSchema.safeParse(dataToValidate) if (result.success) { if (req.method === "GET" || req.method === "DELETE") { req.query = result.data } else { req.body = result.data } return next() } const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress logger.warn(`Validation failed for ${req.method.cyan} ${currentPath.cyan.bold} ` + ``.bold, ["WEB", "yellow"]) const response = { success: false, message: errorConfig.message, errors: result.error.issues.map(e => ({ field: e.path.join("."), message: e.message })) } if (methodConfig.error) { const extras = { ...methodConfig.error } delete extras.status delete extras.message Object.assign(response, extras) } return res.status(errorConfig.status || 400).json(response) }) for (const route of routes) { try { const router = require(route) const routePath = loader.computeRoutePath(path.join(__dirname, "routes"), route) if (router.stack) { for (const layer of router.stack) { if (layer.route && layer.route.methods) { const method = Object.keys(layer.route.methods).join(", ").toUpperCase() const subPath = routePath === "/" ? "" : routePath logger.log(`${method.cyan} ${subPath.cyan.bold} route registered`, ["WEB", "yellow"]) } } } app.use(routePath, router) } catch (error) { logger.error(error.toString(), ["WEB", "yellow"]) } } app.listen(process.env.WEB_PORT || 3000, () => { logger.log(`Server listening at port : ${process.env.WEB_PORT || 3000}`, ["WEB", "yellow"]) })