feat: actualizar la gestión de rutas de plantillas y archivos estáticos para mejorar la flexibilidad y la detección de archivos

This commit is contained in:
Shni
2025-10-15 23:32:40 -05:00
parent 9aa2c79be1
commit 585a351785
3 changed files with 140 additions and 55 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "amayo", "name": "amayo",
"version": "2.0.22", "version": "2.1.2",
"description": "", "description": "",
"main": "src/main.ts", "main": "src/main.ts",
"scripts": { "scripts": {

View File

@@ -86,8 +86,28 @@ export const renderTemplate = async (
locals: Record<string, any> = {}, locals: Record<string, any> = {},
statusCode = 200 statusCode = 200
) => { ) => {
const pageFile = path.join(viewsDir, "pages", `${template}.ejs`); const candidatePaths = [
const layoutFile = path.join(viewsDir, "layouts", "layout.ejs"); path.join(viewsDir, "pages", `${template}.ejs`),
// fallback: lib is one level deeper, try ../views
path.join(__dirname, "..", "views", "pages", `${template}.ejs`),
// fallback: project src path
path.join(
process.cwd(),
"src",
"server",
"views",
"pages",
`${template}.ejs`
),
];
const pageFile = (await findFirstExisting(candidatePaths)) || "";
const layoutCandidates = [
path.join(viewsDir, "layouts", "layout.ejs"),
path.join(__dirname, "..", "views", "layouts", "layout.ejs"),
path.join(process.cwd(), "src", "server", "views", "layouts", "layout.ejs"),
];
const layoutFile = (await findFirstExisting(layoutCandidates)) || "";
locals.hideNavbar = locals.hideNavbar =
typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false; typeof locals.hideNavbar !== "undefined" ? locals.hideNavbar : false;
locals.useDashboardNav = locals.useDashboardNav =
@@ -101,20 +121,41 @@ export const renderTemplate = async (
? locals.selectedGuildId ? locals.selectedGuildId
: null; : null;
const pageBody = await ejs.renderFile(pageFile, locals, { async: true }); let pageBody: string;
if (!pageFile) {
// no page template found -> return 404-friendly HTML
pageBody = `<h1>404 - Página no encontrada</h1><p>Template ${template} no disponible.</p>`;
statusCode = 404;
} else {
pageBody = await ejs.renderFile(pageFile, locals, { async: true });
}
const defaultTitle = `${ const defaultTitle = `${
locals.appName ?? pkg.name ?? "Amayo Bot" locals.appName ?? pkg.name ?? "Amayo Bot"
} | Guía Completa`; } | Guía Completa`;
let dashboardNavHtml: string | null = null; let dashboardNavHtml: string | null = null;
try { try {
if (locals.useDashboardNav) { if (locals.useDashboardNav) {
const partialPath = path.join(viewsDir, "partials", "dashboard_nav.ejs"); const partialCandidates = [
path.join(viewsDir, "partials", "dashboard_nav.ejs"),
path.join(__dirname, "..", "views", "partials", "dashboard_nav.ejs"),
path.join(
process.cwd(),
"src",
"server",
"views",
"partials",
"dashboard_nav.ejs"
),
];
const partialPath = (await findFirstExisting(partialCandidates)) || null;
if (partialPath) {
dashboardNavHtml = await ejs.renderFile( dashboardNavHtml = await ejs.renderFile(
partialPath, partialPath,
{ ...locals }, { ...locals },
{ async: true } { async: true }
); );
} }
}
} catch (err) { } catch (err) {
console.warn("Failed rendering dashboard_nav partial:", err); console.warn("Failed rendering dashboard_nav partial:", err);
dashboardNavHtml = null; dashboardNavHtml = null;
@@ -123,26 +164,51 @@ export const renderTemplate = async (
try { try {
const shouldShowNavbar = !locals.hideNavbar && !locals.useDashboardNav; const shouldShowNavbar = !locals.hideNavbar && !locals.useDashboardNav;
if (shouldShowNavbar) { if (shouldShowNavbar) {
const navPath = path.join(viewsDir, "partials", "navbar.ejs"); const navCandidates = [
path.join(viewsDir, "partials", "navbar.ejs"),
path.join(__dirname, "..", "views", "partials", "navbar.ejs"),
path.join(
process.cwd(),
"src",
"server",
"views",
"partials",
"navbar.ejs"
),
];
const navPath = (await findFirstExisting(navCandidates)) || null;
if (navPath) {
navbarHtml = await ejs.renderFile( navbarHtml = await ejs.renderFile(
navPath, navPath,
{ appName: locals.appName ?? pkg.name ?? "Amayo Bot" }, { appName: locals.appName ?? pkg.name ?? "Amayo Bot" },
{ async: true } { async: true }
); );
} }
}
} catch (err) { } catch (err) {
console.warn("Failed rendering navbar partial:", err); console.warn("Failed rendering navbar partial:", err);
navbarHtml = null; navbarHtml = null;
} }
const html = await ejs.renderFile( let html: string;
if (!layoutFile) {
// If layout not available, use the page body directly
console.warn(
"Layout template not found, returning page body directly for:",
template
);
html = pageBody;
} else {
html = await ejs.renderFile(
layoutFile, layoutFile,
{ {
head: null, head: null,
scripts: null, scripts: null,
version: locals.version ?? pkg.version ?? "2.0.0", version: locals.version ?? pkg.version ?? "2.0.0",
djsVersion: djsVersion:
locals.djsVersion ?? pkg?.dependencies?.["discord.js"] ?? "15.0.0-dev", locals.djsVersion ??
pkg?.dependencies?.["discord.js"] ??
"15.0.0-dev",
currentDateHuman: currentDateHuman:
locals.currentDateHuman ?? locals.currentDateHuman ??
new Date().toLocaleDateString("es-ES", { new Date().toLocaleDateString("es-ES", {
@@ -171,6 +237,7 @@ export const renderTemplate = async (
}, },
{ async: true } { async: true }
); );
}
const htmlBuffer = Buffer.from(html, "utf8"); const htmlBuffer = Buffer.from(html, "utf8");
const etag = computeEtag(htmlBuffer); const etag = computeEtag(htmlBuffer);
@@ -214,3 +281,14 @@ export const renderTemplate = async (
res.writeHead(statusCode, applySecurityHeadersForRequest(req, headers)); res.writeHead(statusCode, applySecurityHeadersForRequest(req, headers));
res.end(respBody); res.end(respBody);
}; };
async function findFirstExisting(paths: string[]): Promise<string | null> {
for (const p of paths) {
try {
if (!p) continue;
const st = await fs.stat(p).catch(() => undefined);
if (st && st.isFile()) return p;
} catch {}
}
return null;
}

View File

@@ -12,11 +12,18 @@ import {
} from "node:zlib"; } from "node:zlib";
import path from "node:path"; import path from "node:path";
import ejs from "ejs"; import ejs from "ejs";
import { promises as fs, readFileSync } from "node:fs"; import { promises as fs, readFileSync, existsSync } from "node:fs";
import { prisma } from "../../core/database/prisma"; import { prisma } from "../../core/database/prisma";
const publicDir = path.join(__dirname, "public"); // Prefer project src paths (in case process.cwd differs between environments)
const viewsDir = path.join(__dirname, "views"); const projectPublic = path.join(process.cwd(), "src", "server", "public");
const projectViews = path.join(process.cwd(), "src", "server", "views");
const publicDir = existsSync(projectPublic)
? projectPublic
: path.join(__dirname, "..", "public");
const viewsDir = existsSync(projectViews)
? projectViews
: path.join(__dirname, "..", "views");
export const MIME_TYPES: Record<string, string> = { export const MIME_TYPES: Record<string, string> = {
".html": "text/html; charset=utf-8", ".html": "text/html; charset=utf-8",