Base-REST-API/server.js
2025-12-16 03:43:54 +01:00

101 lines
3.3 KiB
JavaScript

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} ` + `<IP:${ip}>`.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"])
})