diff --git a/src/app.js b/src/app.js index 38fa3ea..8a887ff 100644 --- a/src/app.js +++ b/src/app.js @@ -4,15 +4,14 @@ const express = require('express'); const fs = require('node:fs'); const { join } = require('node:path'); const morgan = require('morgan'); -require("./utils/instrument"); +require('./utils/instrument'); const { statusCodeHandler } = require(join(__dirname, 'utils', 'status-code-handler')); const errorHandler = require(join(__dirname, 'utils', 'api', 'error-handler')); +const { createReverseProxy } = require(join(__dirname, 'utils', 'reverse-proxy')); const app = express(); -app.use(express.json()); // <--- ¡Esto es obligatorio para JSON! - const port = process.env.PORT || 3000; global.__basedir = __dirname; @@ -21,9 +20,9 @@ app.use(morgan('dev', { skip: (req, res) => res.statusCode < 400, })); -app.use(errorHandler) +app.use(errorHandler); -//Replace the X-Powered-By header with our own +// Replace the X-Powered-By header with our own app.use((req, res, next) => { res.setHeader('X-Powered-By', 'Any Dev Code'); res.setHeader('X-Developer', 'MDCDEV'); @@ -34,7 +33,7 @@ app.use((req, res, next) => { next(); }); -//Allow CORS +// Allow CORS app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); @@ -43,46 +42,51 @@ app.use((req, res, next) => { next(); }); +app.use('/v2/peruserver', createReverseProxy({ + targetOrigin: 'https://api-data.peruserver.de', +})); + +app.use(express.json()); + // Rutas deshabilitadas -const DISABLED_ROUTES = process.env.DISABLED_ROUTES +const DISABLED_ROUTES = process.env.DISABLED_ROUTES ? process.env.DISABLED_ROUTES.split(',').map(r => r.trim()) : []; //We have a folder called "routes", and inside that folder we have a folders called "v1", "v2", "v3" and more, inside those folders we have a folder called "users", "guilds" and more, inside those folders we have a file called "@me.js", "index.js" and more //This is how we require all the files in the "routes" folder //The route is the path to the file, and the file is the file that we require -function requireRoutes(path, fullpath = "") { +function requireRoutes(path, fullpath = '') { fs.readdirSync(join(__dirname, path)).forEach(file => { - if (file.endsWith(".js")) { - let routePath = ""; - if (file === "index.js") { - routePath = `${fullpath.replace("~", ":")}`; + if (file.endsWith('.js')) { + let routePath = ''; + if (file === 'index.js') { + routePath = `${fullpath.replace('~', ':')}`; } else { - routePath = `${fullpath.replace(".js", ).replace("~", ":")}/${file.replace(".js", "")}`; + routePath = `${fullpath.replace('.js', '').replace('~', ':')}/${file.replace('.js', '')}`; } - - // Verificar si la ruta está deshabilitada + + // Verificar si la ruta esta deshabilitada const isDisabled = DISABLED_ROUTES.some(disabled => routePath.includes(disabled)); - + if (isDisabled) { - console.log(`⛔ Ruta deshabilitada (saltada): ${routePath}`); + console.log(`Ruta deshabilitada (saltada): ${routePath}`); } else { - if (file === "index.js") { - app.use(`${fullpath.replace("~", ":")}`, require(join(__dirname, path, file))); - console.log(`✅ Ruta cargada: ${fullpath.replace("~", ":")}`); + if (file === 'index.js') { + app.use(`${fullpath.replace('~', ':')}`, require(join(__dirname, path, file))); + console.log(`Ruta cargada: ${fullpath.replace('~', ':')}`); } else { - app.use(`${fullpath.replace(".js", ).replace("~", ":")}/${file.replace(".js", "")}`, require(join(__dirname, path, file))); - console.log(`✅ Ruta cargada: ${fullpath.replace(".js", ).replace("~", ":")}/${file.replace(".js", "")}`); + app.use(`${fullpath.replace('.js', '').replace('~', ':')}/${file.replace('.js', '')}`, require(join(__dirname, path, file))); + console.log(`Ruta cargada: ${fullpath.replace('.js', '').replace('~', ':')}/${file.replace('.js', '')}`); } } } else { requireRoutes(join(path, file), `${fullpath}/${file}`); } - }); } -requireRoutes("routes"); +requireRoutes('routes'); app.get('/', (req, res) => res.json({ message: 'Welcome to the MDCDEV API', documentation: 'https://docs.api.mdcdev.me' })); @@ -90,7 +94,6 @@ app.all('*', (req, res) => { statusCodeHandler({ statusCode: 10005 }, res); }); - app.listen(port, () => console.log(`API listening on port ${port}!`)); process.on('unhandledRejection', (reason, promise) => { @@ -99,4 +102,4 @@ process.on('unhandledRejection', (reason, promise) => { }); process.on('uncaughtException', (err, origin) => { console.error(`Caught exception: ${err}\nException origin: ${origin}`); -}); \ No newline at end of file +}); diff --git a/src/utils/reverse-proxy.js b/src/utils/reverse-proxy.js new file mode 100644 index 0000000..1241e04 --- /dev/null +++ b/src/utils/reverse-proxy.js @@ -0,0 +1,85 @@ +const http = require('node:http'); +const https = require('node:https'); +const { URL } = require('node:url'); + +function appendForwardedFor(existingForwardedFor, clientIp) { + if (!clientIp) { + return existingForwardedFor; + } + + return existingForwardedFor + ? `${existingForwardedFor}, ${clientIp}` + : clientIp; +} + +function rewriteLocationHeader(location, targetOrigin, requestOrigin) { + if (!location || !location.startsWith(targetOrigin)) { + return location; + } + + return `${requestOrigin}${location.slice(targetOrigin.length)}`; +} + +function createReverseProxy({ targetOrigin, timeoutMs = 30000 }) { + const normalizedTargetOrigin = targetOrigin.replace(/\/+$/, ''); + const transport = normalizedTargetOrigin.startsWith('https://') ? https : http; + + return (req, res) => { + const upstreamUrl = new URL(req.originalUrl, `${normalizedTargetOrigin}/`); + const requestOrigin = `${req.protocol}://${req.get('host')}`; + const headers = { ...req.headers }; + + delete headers.host; + delete headers.connection; + + headers['x-forwarded-host'] = req.get('host'); + headers['x-forwarded-proto'] = req.protocol; + headers['x-forwarded-for'] = appendForwardedFor(req.headers['x-forwarded-for'], req.ip); + + const proxyReq = transport.request( + upstreamUrl, + { + method: req.method, + headers, + timeout: timeoutMs, + }, + (proxyRes) => { + const responseHeaders = { ...proxyRes.headers }; + + if (responseHeaders.location) { + responseHeaders.location = rewriteLocationHeader( + responseHeaders.location, + normalizedTargetOrigin, + requestOrigin + ); + } + + res.writeHead(proxyRes.statusCode || 502, responseHeaders); + proxyRes.pipe(res); + } + ); + + proxyReq.on('timeout', () => { + proxyReq.destroy(new Error('Upstream timeout')); + }); + + proxyReq.on('error', (error) => { + if (res.headersSent) { + res.end(); + return; + } + + res.status(502).json({ + error: 'bad_gateway', + message: 'No se pudo contactar al upstream de PeruServer.', + details: error.message, + }); + }); + + req.pipe(proxyReq); + }; +} + +module.exports = { + createReverseProxy, +};